fix: client side refresh for component
This commit is contained in:
		
							parent
							
								
									4c715b815a
								
							
						
					
					
						commit
						ea675fcd4c
					
				@ -13,10 +13,7 @@ export async function CodeBlock(props: {
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="relative w-full text-sm">
 | 
			
		||||
      {/* max-w-[calc(32rem-1.5rem-1.5rem)] */}
 | 
			
		||||
      {/* <div className=""> */}
 | 
			
		||||
      <p
 | 
			
		||||
        // tabIndex={1}
 | 
			
		||||
        className="[&>pre]:p-4 rounded-lg max-h-96 overflow-auto w-full"
 | 
			
		||||
        style={{
 | 
			
		||||
          overflowWrap: "break-word",
 | 
			
		||||
@ -28,7 +25,6 @@ export async function CodeBlock(props: {
 | 
			
		||||
          }),
 | 
			
		||||
        }}
 | 
			
		||||
      />
 | 
			
		||||
      {/* </div> */}
 | 
			
		||||
      <CopyButton className="absolute right-2 top-2" text={props.code} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										35
									
								
								web/src/components/CodeBlockClient.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								web/src/components/CodeBlockClient.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { CopyButton } from "@/components/CopyButton";
 | 
			
		||||
import type { StringLiteralUnion } from "shikiji";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import { highlight } from "../server/highlight";
 | 
			
		||||
 | 
			
		||||
export function CodeBlockClient({
 | 
			
		||||
  code,
 | 
			
		||||
  lang,
 | 
			
		||||
}: {
 | 
			
		||||
  code: string;
 | 
			
		||||
  lang: StringLiteralUnion<string>;
 | 
			
		||||
}) {
 | 
			
		||||
  const { data } = useSWR(code, async () => {
 | 
			
		||||
    return highlight(code.trim(), lang);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="relative w-full text-sm">
 | 
			
		||||
      {data && (
 | 
			
		||||
        <p
 | 
			
		||||
          className="[&>pre]:p-4 rounded-lg max-h-96 overflow-auto w-full"
 | 
			
		||||
          style={{
 | 
			
		||||
            overflowWrap: "break-word",
 | 
			
		||||
          }}
 | 
			
		||||
          dangerouslySetInnerHTML={{
 | 
			
		||||
            __html: data,
 | 
			
		||||
          }}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
      <CopyButton className="absolute right-2 top-2" text={code} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@ -14,10 +14,18 @@ export function LogsViewer({
 | 
			
		||||
  logs,
 | 
			
		||||
  hideTimestamp,
 | 
			
		||||
  className,
 | 
			
		||||
}: { logs: LogsType; hideTimestamp?: boolean; className?: string }) {
 | 
			
		||||
  stickToBottom = true,
 | 
			
		||||
}: {
 | 
			
		||||
  logs: LogsType;
 | 
			
		||||
  hideTimestamp?: boolean;
 | 
			
		||||
  className?: string;
 | 
			
		||||
  stickToBottom?: boolean;
 | 
			
		||||
}) {
 | 
			
		||||
  const container = useRef<HTMLDivElement | null>(null);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!stickToBottom) return;
 | 
			
		||||
 | 
			
		||||
    // console.log(logs.length, container.current);
 | 
			
		||||
    if (container.current) {
 | 
			
		||||
      const scrollHeight = container.current.scrollHeight;
 | 
			
		||||
@ -27,11 +35,12 @@ export function LogsViewer({
 | 
			
		||||
        behavior: "smooth",
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [logs.length]);
 | 
			
		||||
  }, [logs.length, stickToBottom]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div
 | 
			
		||||
      ref={(ref) => {
 | 
			
		||||
        if (!stickToBottom) return;
 | 
			
		||||
        if (!container.current && ref) {
 | 
			
		||||
          const scrollHeight = ref.scrollHeight;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,25 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import { DownloadButton } from "./DownloadButton";
 | 
			
		||||
import { getFileDownloadUrl } from "@/server/getFileDownloadUrl";
 | 
			
		||||
 | 
			
		||||
export async function OutputRender(props: {
 | 
			
		||||
export function OutputRender(props: {
 | 
			
		||||
  run_id: string;
 | 
			
		||||
  filename: string;
 | 
			
		||||
}) {
 | 
			
		||||
  if (props.filename.endsWith(".mp4") || props.filename.endsWith(".webm")) {
 | 
			
		||||
    const url = await getFileDownloadUrl(
 | 
			
		||||
      `outputs/runs/${props.run_id}/${props.filename}`,
 | 
			
		||||
    );
 | 
			
		||||
  const { data: url } = useSWR(
 | 
			
		||||
    "run-outputs+" + props.run_id + props.filename,
 | 
			
		||||
    async () => {
 | 
			
		||||
      return await getFileDownloadUrl(
 | 
			
		||||
        `outputs/runs/${props.run_id}/${props.filename}`,
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  if (!url) return <></>;
 | 
			
		||||
 | 
			
		||||
  if (props.filename.endsWith(".mp4") || props.filename.endsWith(".webm")) {
 | 
			
		||||
    return (
 | 
			
		||||
      <video controls autoPlay className="w-[400px]">
 | 
			
		||||
        <source src={url} type="video/mp4" />
 | 
			
		||||
@ -25,17 +35,8 @@ export async function OutputRender(props: {
 | 
			
		||||
    props.filename.endsWith(".jpg") ||
 | 
			
		||||
    props.filename.endsWith(".jpeg")
 | 
			
		||||
  ) {
 | 
			
		||||
    const url = await getFileDownloadUrl(
 | 
			
		||||
      `outputs/runs/${props.run_id}/${props.filename}`,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return <img className="max-w-[200px]" alt={props.filename} src={url} />;
 | 
			
		||||
  } else {
 | 
			
		||||
    const url = await getFileDownloadUrl(
 | 
			
		||||
      `outputs/runs/${props.run_id}/${props.filename}`,
 | 
			
		||||
    );
 | 
			
		||||
    // console.log(url);
 | 
			
		||||
 | 
			
		||||
    return <DownloadButton filename={props.filename} href={url} />;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { RunInputs } from "@/components/RunInputs";
 | 
			
		||||
import { RunOutputs } from "@/components/RunOutputs";
 | 
			
		||||
import { Badge } from "@/components/ui/badge";
 | 
			
		||||
import {
 | 
			
		||||
  Dialog,
 | 
			
		||||
  DialogClose,
 | 
			
		||||
  DialogContent,
 | 
			
		||||
  DialogDescription,
 | 
			
		||||
  DialogFooter,
 | 
			
		||||
  DialogHeader,
 | 
			
		||||
  DialogTitle,
 | 
			
		||||
  DialogTrigger,
 | 
			
		||||
@ -19,11 +19,8 @@ import {
 | 
			
		||||
} from "@/components/ui/tooltip";
 | 
			
		||||
import { getDuration, getRelativeTime } from "@/lib/getRelativeTime";
 | 
			
		||||
import { type findAllRuns } from "@/server/findAllRuns";
 | 
			
		||||
import { Suspense } from "react";
 | 
			
		||||
import { LiveStatus } from "./LiveStatus";
 | 
			
		||||
import { LogsType, LogsViewer } from "@/components/LogsViewer";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Edit, ExternalLink } from "lucide-react";
 | 
			
		||||
import { LoadingWrapper } from "@/components/LoadingWrapper";
 | 
			
		||||
 | 
			
		||||
export function RunDisplay({
 | 
			
		||||
  run,
 | 
			
		||||
@ -78,9 +75,9 @@ export function RunDisplay({
 | 
			
		||||
        </DialogHeader>
 | 
			
		||||
        <div className="max-h-96 overflow-y-scroll">
 | 
			
		||||
          <RunInputs run={run} />
 | 
			
		||||
          <Suspense>
 | 
			
		||||
          <LoadingWrapper tag="output">
 | 
			
		||||
            <RunOutputs run={run} />
 | 
			
		||||
          </Suspense>
 | 
			
		||||
          </LoadingWrapper>
 | 
			
		||||
        </div>
 | 
			
		||||
        {/* <div className="max-h-96 overflow-y-scroll">{view}</div> */}
 | 
			
		||||
      </DialogContent>
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ import {
 | 
			
		||||
} from "@/components/ui/table";
 | 
			
		||||
import type { findAllRuns } from "@/server/findAllRuns";
 | 
			
		||||
 | 
			
		||||
export async function RunInputs({
 | 
			
		||||
export function RunInputs({
 | 
			
		||||
  run,
 | 
			
		||||
}: {
 | 
			
		||||
  run: Awaited<ReturnType<typeof findAllRuns>>[0];
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  Dialog,
 | 
			
		||||
  DialogClose,
 | 
			
		||||
@ -9,7 +11,6 @@ import {
 | 
			
		||||
  DialogTrigger,
 | 
			
		||||
} from "@/components/ui/dialog";
 | 
			
		||||
import { OutputRender } from "./OutputRender";
 | 
			
		||||
import { CodeBlock } from "@/components/CodeBlock";
 | 
			
		||||
import {
 | 
			
		||||
  Table,
 | 
			
		||||
  TableBody,
 | 
			
		||||
@ -24,11 +25,19 @@ import { Button } from "@/components/ui/button";
 | 
			
		||||
import { ExternalLink } from "lucide-react";
 | 
			
		||||
import { LogsViewer } from "@/components/LogsViewer";
 | 
			
		||||
import { CopyButton } from "@/components/CopyButton";
 | 
			
		||||
import useSWR from "swr";
 | 
			
		||||
import { CodeBlockClient } from "@/components/CodeBlockClient";
 | 
			
		||||
 | 
			
		||||
export async function RunOutputs({
 | 
			
		||||
export function RunOutputs({
 | 
			
		||||
  run,
 | 
			
		||||
}: { run: Awaited<ReturnType<typeof findAllRuns>>[0] }) {
 | 
			
		||||
  const outputs = await getRunsOutput(run.id);
 | 
			
		||||
  const { data, isValidating, error } = useSWR(
 | 
			
		||||
    "run-outputs+" + run.id,
 | 
			
		||||
    async () => {
 | 
			
		||||
      return await getRunsOutput(run.id);
 | 
			
		||||
    },
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table className="table-fixed">
 | 
			
		||||
      <TableHeader className="bg-background top-0 sticky">
 | 
			
		||||
@ -52,7 +61,7 @@ export async function RunOutputs({
 | 
			
		||||
                  <DialogHeader>
 | 
			
		||||
                    <DialogTitle>Run Log</DialogTitle>
 | 
			
		||||
                  </DialogHeader>
 | 
			
		||||
                  <LogsViewer logs={run.run_log} />
 | 
			
		||||
                  <LogsViewer logs={run.run_log} stickToBottom={false} />
 | 
			
		||||
                  <DialogFooter>
 | 
			
		||||
                    <CopyButton
 | 
			
		||||
                      className="w-fit aspect-auto p-4"
 | 
			
		||||
@ -74,7 +83,7 @@ export async function RunOutputs({
 | 
			
		||||
          </TableCell>
 | 
			
		||||
        </TableRow>
 | 
			
		||||
 | 
			
		||||
        {outputs?.map((run) => {
 | 
			
		||||
        {data?.map((run) => {
 | 
			
		||||
          const fileName =
 | 
			
		||||
            run.data.images?.[0].filename ||
 | 
			
		||||
            run.data.files?.[0].filename ||
 | 
			
		||||
@ -85,7 +94,7 @@ export async function RunOutputs({
 | 
			
		||||
              <TableRow key={run.id}>
 | 
			
		||||
                <TableCell>Output</TableCell>
 | 
			
		||||
                <TableCell className="">
 | 
			
		||||
                  <CodeBlock
 | 
			
		||||
                  <CodeBlockClient
 | 
			
		||||
                    code={JSON.stringify(run.data, null, 2)}
 | 
			
		||||
                    lang="json"
 | 
			
		||||
                  />
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,7 @@ export function RunsTable(props: {
 | 
			
		||||
  const { data, error, isLoading, isValidating } = useSWR(
 | 
			
		||||
    "runs+" + page,
 | 
			
		||||
    async () => {
 | 
			
		||||
      const data = await getAllRunstableContent({
 | 
			
		||||
      const data = await findAllRunsWithCounts({
 | 
			
		||||
        workflow_id: props.workflow_id,
 | 
			
		||||
        limit: itemPerPage,
 | 
			
		||||
        offset: (page - 1) * itemPerPage,
 | 
			
		||||
@ -68,7 +68,11 @@ export function RunsTable(props: {
 | 
			
		||||
              <TableHead className="text-right">Status</TableHead>
 | 
			
		||||
            </TableRow>
 | 
			
		||||
          </TableHeader>
 | 
			
		||||
          <TableBody>{data?.table}</TableBody>
 | 
			
		||||
          <TableBody>
 | 
			
		||||
            {data?.allRuns.map((run) => (
 | 
			
		||||
              <RunDisplay run={run} key={run.id} />
 | 
			
		||||
            ))}
 | 
			
		||||
          </TableBody>
 | 
			
		||||
        </Table>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,16 +1,10 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import { RunOutputs } from "@/components/RunOutputs";
 | 
			
		||||
import { db } from "@/db/db";
 | 
			
		||||
import { workflowRunOutputs } from "@/db/schema";
 | 
			
		||||
import { eq } from "drizzle-orm";
 | 
			
		||||
 | 
			
		||||
// export async function getRunsOutputDisplay(run_id: string) {
 | 
			
		||||
//   return <RunOutputs run_id={run_id} />;
 | 
			
		||||
// }
 | 
			
		||||
 | 
			
		||||
export async function getRunsOutput(run_id: string) {
 | 
			
		||||
  // throw new Error("Not implemented");
 | 
			
		||||
  return await db
 | 
			
		||||
    .select()
 | 
			
		||||
    .from(workflowRunOutputs)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								web/src/server/highlight.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								web/src/server/highlight.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
"use server";
 | 
			
		||||
 | 
			
		||||
import type { StringLiteralUnion } from "shikiji";
 | 
			
		||||
import { getHighlighter } from "shikiji";
 | 
			
		||||
 | 
			
		||||
export async function highlight(
 | 
			
		||||
  code: string,
 | 
			
		||||
  lang: StringLiteralUnion<string>,
 | 
			
		||||
) {
 | 
			
		||||
  const highlighter = await getHighlighter({
 | 
			
		||||
    themes: ["one-dark-pro"],
 | 
			
		||||
    langs: [lang],
 | 
			
		||||
  });
 | 
			
		||||
  return highlighter.codeToHtml(code.trim(), {
 | 
			
		||||
    lang: lang,
 | 
			
		||||
    theme: "one-dark-pro",
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user