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