Compare commits
No commits in common. "248bf67ec90b3504bfb8249db09449e7a07b37b8" and "ffd10063a97bb586adc8211038634860088163f5" have entirely different histories.
248bf67ec9
...
ffd10063a9
|
|
@ -6,6 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
|
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import {
|
import {
|
||||||
DevDialog,
|
DevDialog,
|
||||||
DevDialogContent,
|
DevDialogContent,
|
||||||
|
|
@ -26,6 +27,7 @@ import { InputBox } from "@/components/workspace/input-box";
|
||||||
import { MessageList } from "@/components/workspace/messages";
|
import { MessageList } from "@/components/workspace/messages";
|
||||||
import { ThreadContext } from "@/components/workspace/messages/context";
|
import { ThreadContext } from "@/components/workspace/messages/context";
|
||||||
import { ThreadTitle } from "@/components/workspace/thread-title";
|
import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||||
|
import { TokenUsageIndicator } from "@/components/workspace/token-usage-indicator";
|
||||||
import { Tooltip } from "@/components/workspace/tooltip";
|
import { Tooltip } from "@/components/workspace/tooltip";
|
||||||
import { useSpecificChatMode } from "@/components/workspace/use-chat-mode";
|
import { useSpecificChatMode } from "@/components/workspace/use-chat-mode";
|
||||||
import { Welcome } from "@/components/workspace/welcome";
|
import { Welcome } from "@/components/workspace/welcome";
|
||||||
|
|
@ -258,6 +260,7 @@ export default function ChatPage() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 overflow-hidden">
|
<div className="flex items-center justify-end gap-2 overflow-hidden">
|
||||||
|
{/* <TokenUsageIndicator messages={thread.messages} /> */}
|
||||||
<DevTodoList
|
<DevTodoList
|
||||||
className="bg-white"
|
className="bg-white"
|
||||||
todos={thread.values.todos ?? []}
|
todos={thread.values.todos ?? []}
|
||||||
|
|
@ -518,3 +521,21 @@ export default function ChatPage() {
|
||||||
</ThreadContext.Provider>
|
</ThreadContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function InputBoxSkeleton() {
|
||||||
|
return (
|
||||||
|
<div className="w-full rounded-[20px] bg-[#FBFAFC] p-4 shadow-[0_0_20px_0_rgba(0,0,0,0.10)]">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Skeleton className="h-6 w-[220px]" />
|
||||||
|
<Skeleton className="h-[120px] w-full rounded-[16px]" />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Skeleton className="h-9 w-9 rounded-full" />
|
||||||
|
<Skeleton className="h-9 w-9 rounded-full" />
|
||||||
|
</div>
|
||||||
|
<Skeleton className="h-9 w-20 rounded-full" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
|
|
||||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
|
||||||
import { CommandPalette } from "@/components/workspace/command-palette";
|
import { CommandPalette } from "@/components/workspace/command-palette";
|
||||||
import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar";
|
import { WorkspaceSidebar } from "@/components/workspace/workspace-sidebar";
|
||||||
import { getLocalSettings, useLocalSettings } from "@/core/settings";
|
import { getLocalSettings, useLocalSettings } from "@/core/settings";
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,8 @@ export const ArtifactContent = ({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: ArtifactContentProps) => (
|
}: ArtifactContentProps) => (
|
||||||
<div className="min-h-0 flex-1 overflow-auto rounded-[10px]" {...props} >
|
<div className="min-h-0 flex-1 overflow-auto rounded-[10px]">
|
||||||
{/* <div className={cn("mb-[207px]! p-4", className)} {...props} /> */}
|
{/* <div className={cn("mb-[207px]! p-4", className)} {...props} /> */}
|
||||||
{/* <div className={cn("mb-[150px] min-h-full p-4", className)} /> */}
|
<div className={cn("mb-[150px] min-h-full p-4", className)} {...props} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import { CodeEditor } from "@/components/workspace/code-editor";
|
||||||
import { useArtifactContent } from "@/core/artifacts/hooks";
|
import { useArtifactContent } from "@/core/artifacts/hooks";
|
||||||
import { urlOfArtifact } from "@/core/artifacts/utils";
|
import { urlOfArtifact } from "@/core/artifacts/utils";
|
||||||
import { useI18n } from "@/core/i18n/hooks";
|
import { useI18n } from "@/core/i18n/hooks";
|
||||||
|
import { installSkill } from "@/core/skills/api";
|
||||||
import { streamdownPlugins } from "@/core/streamdown";
|
import { streamdownPlugins } from "@/core/streamdown";
|
||||||
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
import { checkCodeFile, getFileName } from "@/core/utils/files";
|
||||||
import { useMarkdownDownload } from "@/core/utils/markdown-download";
|
import { useMarkdownDownload } from "@/core/utils/markdown-download";
|
||||||
|
|
@ -60,7 +61,7 @@ export function ArtifactFileDetail({
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
filepath: string;
|
filepath: string;
|
||||||
threadId?: string;
|
threadId: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { artifacts, setOpen, select, fullscreen, setFullscreen } =
|
const { artifacts, setOpen, select, fullscreen, setFullscreen } =
|
||||||
|
|
@ -97,18 +98,12 @@ export function ArtifactFileDetail({
|
||||||
return (language === "html" && !isWriteFile) || language === "markdown";
|
return (language === "html" && !isWriteFile) || language === "markdown";
|
||||||
}, [isWriteFile, language]);
|
}, [isWriteFile, language]);
|
||||||
const artifactUrl = useMemo(() => {
|
const artifactUrl = useMemo(() => {
|
||||||
if (!threadId) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return urlOfArtifact({ filepath, threadId });
|
return urlOfArtifact({ filepath, threadId });
|
||||||
}, [filepath, threadId]);
|
}, [filepath, threadId]);
|
||||||
const artifactPreviewKind = useMemo(() => {
|
const artifactPreviewKind = useMemo(() => {
|
||||||
return getArtifactPreviewKind(filepath);
|
return getArtifactPreviewKind(filepath);
|
||||||
}, [filepath]);
|
}, [filepath]);
|
||||||
const artifactViewerSrcDoc = useMemo(() => {
|
const artifactViewerSrcDoc = useMemo(() => {
|
||||||
if (!artifactUrl) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return buildArtifactViewerSrcDoc({
|
return buildArtifactViewerSrcDoc({
|
||||||
artifactUrl,
|
artifactUrl,
|
||||||
fileName,
|
fileName,
|
||||||
|
|
@ -118,7 +113,7 @@ export function ArtifactFileDetail({
|
||||||
const { content } = useArtifactContent({
|
const { content } = useArtifactContent({
|
||||||
threadId,
|
threadId,
|
||||||
filepath: filepathFromProps,
|
filepath: filepathFromProps,
|
||||||
enabled: Boolean(threadId) && isCodeFile && !isWriteFile,
|
enabled: isCodeFile && !isWriteFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
const displayContent = content ?? "";
|
const displayContent = content ?? "";
|
||||||
|
|
@ -131,6 +126,7 @@ export function ArtifactFileDetail({
|
||||||
}, [artifacts]);
|
}, [artifacts]);
|
||||||
|
|
||||||
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
|
const [viewMode, setViewMode] = useState<"code" | "preview">("code");
|
||||||
|
const [isInstalling, setIsInstalling] = useState(false);
|
||||||
const [zoom, setZoom] = useState(80);
|
const [zoom, setZoom] = useState(80);
|
||||||
|
|
||||||
// 是否可以转换为docx/pdf(仅markdown文件支持)
|
// 是否可以转换为docx/pdf(仅markdown文件支持)
|
||||||
|
|
@ -176,11 +172,33 @@ export function ArtifactFileDetail({
|
||||||
}
|
}
|
||||||
}, [previewable]);
|
}, [previewable]);
|
||||||
|
|
||||||
|
const handleInstallSkill = useCallback(async () => {
|
||||||
|
if (isInstalling) return;
|
||||||
|
|
||||||
|
setIsInstalling(true);
|
||||||
|
try {
|
||||||
|
const result = await installSkill({
|
||||||
|
thread_id: threadId,
|
||||||
|
path: filepath,
|
||||||
|
});
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(result.message);
|
||||||
|
} else {
|
||||||
|
toast.error(result.message ?? "Failed to install skill");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to install skill:", error);
|
||||||
|
toast.error("Failed to install skill");
|
||||||
|
} finally {
|
||||||
|
setIsInstalling(false);
|
||||||
|
}
|
||||||
|
}, [threadId, filepath, isInstalling]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// 给滚动遮挡头部定位relative
|
// 给滚动遮挡头部定位relative
|
||||||
<Artifact
|
<Artifact
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-background relative h-full overflow-hidden rounded-2xl",
|
"bg-background relative overflow-hidden rounded-2xl",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -340,7 +358,7 @@ export function ArtifactFileDetail({
|
||||||
<a
|
<a
|
||||||
href={urlOfArtifact({
|
href={urlOfArtifact({
|
||||||
filepath,
|
filepath,
|
||||||
threadId: threadId ?? "",
|
threadId,
|
||||||
download: true,
|
download: true,
|
||||||
})}
|
})}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -462,39 +480,33 @@ export function ArtifactFileDetail({
|
||||||
</ArtifactActions>
|
</ArtifactActions>
|
||||||
</div>
|
</div>
|
||||||
</ArtifactHeader>
|
</ArtifactHeader>
|
||||||
<ArtifactContent>
|
<ArtifactContent className="rounded-b-[10px] bg-white p-0">
|
||||||
{/* 遮挡多余的滚动顶部 */}
|
{/* 遮挡多余的滚动顶部 */}
|
||||||
{/* <div className="absolute w-[calc(100%-40px)] bg-white z-20 h-5 rounded-t-[10px] top-[57px]"></div> */}
|
{/* <div className="absolute w-[calc(100%-40px)] bg-white z-20 h-5 rounded-t-[10px] top-[57px]"></div> */}
|
||||||
{previewable &&
|
{previewable &&
|
||||||
viewMode === "preview" &&
|
viewMode === "preview" &&
|
||||||
(language === "markdown" || language === "html") && (
|
(language === "markdown" || language === "html") && (
|
||||||
<div className="min-h-full mb-[150px] rounded-b-[10px] bg-white p-0 mb-0">
|
<ArtifactFilePreview
|
||||||
<ArtifactFilePreview
|
content={displayContent}
|
||||||
content={displayContent}
|
language={language ?? "text"}
|
||||||
language={language ?? "text"}
|
zoom={zoom}
|
||||||
zoom={zoom}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{isCodeFile && viewMode === "code" && (
|
{isCodeFile && viewMode === "code" && (
|
||||||
<div className="min-h-full mb-[150px] rounded-b-[10px] bg-white p-0 mb-0">
|
<CodeEditor
|
||||||
<CodeEditor
|
className="size-full resize-none rounded-none border-none py-[20px]"
|
||||||
className="size-full resize-none rounded-none border-none py-[20px]"
|
value={displayContent ?? ""}
|
||||||
value={displayContent ?? ""}
|
zoom={zoom}
|
||||||
zoom={zoom}
|
readonly
|
||||||
readonly
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{!isCodeFile && (
|
{!isCodeFile && (
|
||||||
<div className="h-full mb-[150px] ">
|
<iframe
|
||||||
<iframe
|
className="size-full border-0"
|
||||||
className="size-full border-0"
|
srcDoc={artifactViewerSrcDoc}
|
||||||
srcDoc={artifactViewerSrcDoc}
|
sandbox="allow-same-origin allow-scripts allow-downloads"
|
||||||
sandbox="allow-same-origin allow-scripts allow-downloads"
|
title={`Artifact preview: ${fileName}`}
|
||||||
title={`Artifact preview: ${fileName}`}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</ArtifactContent>
|
</ArtifactContent>
|
||||||
</Artifact>
|
</Artifact>
|
||||||
|
|
@ -515,7 +527,7 @@ export function ArtifactFilePreview({
|
||||||
if (language === "markdown") {
|
if (language === "markdown") {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("w-full mb-[207px] p-[20px]")}
|
className={cn("w-full p-[20px]")}
|
||||||
style={{ "--zoom-scale": zoomScale } as React.CSSProperties}
|
style={{ "--zoom-scale": zoomScale } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<Streamdown
|
<Streamdown
|
||||||
|
|
@ -605,8 +617,6 @@ function buildArtifactViewerSrcDoc({
|
||||||
</div>`;
|
</div>`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const bodyClass = kind === "image" ? "fullbleed" : "";
|
|
||||||
|
|
||||||
return `<!doctype html>
|
return `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -638,12 +648,8 @@ function buildArtifactViewerSrcDoc({
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
body.fullbleed {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.preview {
|
.preview {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
|
|
@ -709,7 +715,7 @@ function buildArtifactViewerSrcDoc({
|
||||||
.link:hover { text-decoration: underline; }
|
.link:hover { text-decoration: underline; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="${bodyClass}">
|
<body>
|
||||||
${content}
|
${content}
|
||||||
</body>
|
</body>
|
||||||
</html>`;
|
</html>`;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ export function ArtifactFileList({
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
files: string[];
|
files: string[];
|
||||||
threadId?: string;
|
threadId: string;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { select: selectArtifact, setOpen } = useArtifacts();
|
const { select: selectArtifact, setOpen } = useArtifacts();
|
||||||
|
|
@ -48,7 +48,6 @@ export function ArtifactFileList({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if (!threadId) return;
|
|
||||||
if (installingFile) return;
|
if (installingFile) return;
|
||||||
|
|
||||||
setInstallingFile(filepath);
|
setInstallingFile(filepath);
|
||||||
|
|
@ -99,7 +98,7 @@ export function ArtifactFileList({
|
||||||
{file.endsWith(".skill") && (
|
{file.endsWith(".skill") && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={!threadId || installingFile === file}
|
disabled={installingFile === file}
|
||||||
onClick={(e) => handleInstallSkill(e, file)}
|
onClick={(e) => handleInstallSkill(e, file)}
|
||||||
>
|
>
|
||||||
{installingFile === file ? (
|
{installingFile === file ? (
|
||||||
|
|
@ -110,27 +109,20 @@ export function ArtifactFileList({
|
||||||
{t.common.install}
|
{t.common.install}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{threadId ? (
|
<a
|
||||||
<a
|
href={urlOfArtifact({
|
||||||
href={urlOfArtifact({
|
filepath: file,
|
||||||
filepath: file,
|
threadId: threadId,
|
||||||
threadId,
|
download: true,
|
||||||
download: true,
|
})}
|
||||||
})}
|
target="_blank"
|
||||||
target="_blank"
|
onClick={(e) => e.stopPropagation()}
|
||||||
onClick={(e) => e.stopPropagation()}
|
>
|
||||||
>
|
<Button variant="ghost">
|
||||||
<Button variant="ghost">
|
|
||||||
<DownloadIcon className="size-4" />
|
|
||||||
{t.common.download}
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
) : (
|
|
||||||
<Button variant="ghost" disabled>
|
|
||||||
<DownloadIcon className="size-4" />
|
<DownloadIcon className="size-4" />
|
||||||
{t.common.download}
|
{t.common.download}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
</a>
|
||||||
</CardAction>
|
</CardAction>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,7 @@ import { useThread } from "../messages/context";
|
||||||
const CLOSE_MODE = { chat: 100, artifacts: 0 };
|
const CLOSE_MODE = { chat: 100, artifacts: 0 };
|
||||||
const OPEN_MODE = { chat: 60, artifacts: 40 };
|
const OPEN_MODE = { chat: 60, artifacts: 40 };
|
||||||
|
|
||||||
const ChatBox: React.FC<{
|
const ChatBox: React.FC<{ children: React.ReactNode; threadId: string }> = ({
|
||||||
children: React.ReactNode;
|
|
||||||
threadId: string | undefined;
|
|
||||||
}> = ({
|
|
||||||
children,
|
children,
|
||||||
threadId,
|
threadId,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ import type { AgentThread } from "@/core/threads/types";
|
||||||
import { useThread } from "./messages/context";
|
import { useThread } from "./messages/context";
|
||||||
import { Tooltip } from "./tooltip";
|
import { Tooltip } from "./tooltip";
|
||||||
|
|
||||||
export function ExportTrigger({ threadId }: { threadId?: string }) {
|
export function ExportTrigger({ threadId }: { threadId: string }) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { thread } = useThread();
|
const { thread } = useThread();
|
||||||
|
|
||||||
|
|
@ -49,7 +49,7 @@ export function ExportTrigger({ threadId }: { threadId?: string }) {
|
||||||
[messages, thread.values, threadId, t],
|
[messages, thread.values, threadId, t],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!threadId || messages.length === 0) {
|
if (messages.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ export function MessageList({
|
||||||
paddingBottom = 160,
|
paddingBottom = 160,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
threadId?: string;
|
threadId: string;
|
||||||
thread: UseStream<AgentThreadState>;
|
thread: UseStream<AgentThreadState>;
|
||||||
/** When set (e.g. from onFinish), use instead of thread.messages so SSE end shows complete state. */
|
/** When set (e.g. from onFinish), use instead of thread.messages so SSE end shows complete state. */
|
||||||
messagesOverride?: Message[];
|
messagesOverride?: Message[];
|
||||||
|
|
@ -98,9 +98,7 @@ export function MessageList({
|
||||||
className="mb-4"
|
className="mb-4"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{threadId ? (
|
<ArtifactFileList files={files} threadId={threadId} />
|
||||||
<ArtifactFileList files={files} threadId={threadId} />
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (group.type === "assistant:subagent") {
|
} else if (group.type === "assistant:subagent") {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ export function ThreadTitle({
|
||||||
t.pages.newChat,
|
t.pages.newChat,
|
||||||
t.pages.untitled,
|
t.pages.untitled,
|
||||||
t.pages.appName,
|
t.pages.appName,
|
||||||
thread,
|
|
||||||
thread?.isThreadLoading,
|
thread?.isThreadLoading,
|
||||||
thread?.values,
|
thread?.values,
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ export function useArtifactContent({
|
||||||
enabled,
|
enabled,
|
||||||
}: {
|
}: {
|
||||||
filepath: string;
|
filepath: string;
|
||||||
threadId?: string;
|
threadId: string;
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const isWriteFile = useMemo(() => {
|
const isWriteFile = useMemo(() => {
|
||||||
|
|
@ -25,17 +25,12 @@ export function useArtifactContent({
|
||||||
return null;
|
return null;
|
||||||
}, [filepath, isWriteFile, thread]);
|
}, [filepath, isWriteFile, thread]);
|
||||||
|
|
||||||
const canFetch = Boolean(threadId) && enabled !== false;
|
|
||||||
const { data, isLoading, error } = useQuery({
|
const { data, isLoading, error } = useQuery({
|
||||||
queryKey: ["artifact", filepath, threadId, isMock],
|
queryKey: ["artifact", filepath, threadId, isMock],
|
||||||
queryFn: () => {
|
queryFn: () => {
|
||||||
return loadArtifactContent({
|
return loadArtifactContent({ filepath, threadId, isMock });
|
||||||
filepath,
|
|
||||||
threadId: threadId ?? "",
|
|
||||||
isMock,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
enabled: canFetch,
|
enabled,
|
||||||
// Cache artifact content for 5 minutes to avoid repeated fetches (especially for .skill ZIP extraction)
|
// Cache artifact content for 5 minutes to avoid repeated fetches (especially for .skill ZIP extraction)
|
||||||
staleTime: 5 * 60 * 1000,
|
staleTime: 5 * 60 * 1000,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import {
|
import {
|
||||||
CompassIcon,
|
CompassIcon,
|
||||||
|
GraduationCapIcon,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
MicroscopeIcon,
|
MicroscopeIcon,
|
||||||
PenLineIcon,
|
PenLineIcon,
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ export function useThreadStream({
|
||||||
|
|
||||||
const sendMessage = useCallback(
|
const sendMessage = useCallback(
|
||||||
async (
|
async (
|
||||||
threadId: string | undefined,
|
threadId: string,
|
||||||
message: PromptInputMessage,
|
message: PromptInputMessage,
|
||||||
extraContext?: Record<string, unknown>,
|
extraContext?: Record<string, unknown>,
|
||||||
) => {
|
) => {
|
||||||
|
|
@ -281,8 +281,6 @@ export function useThreadStream({
|
||||||
sendInFlightRef.current = true;
|
sendInFlightRef.current = true;
|
||||||
|
|
||||||
const text = message.text.trim();
|
const text = message.text.trim();
|
||||||
const resolvedThreadId =
|
|
||||||
threadId ?? threadIdRef.current ?? undefined;
|
|
||||||
|
|
||||||
// Capture current count before showing optimistic messages
|
// Capture current count before showing optimistic messages
|
||||||
prevMsgCountRef.current = thread.messages.length;
|
prevMsgCountRef.current = thread.messages.length;
|
||||||
|
|
@ -317,9 +315,7 @@ export function useThreadStream({
|
||||||
}
|
}
|
||||||
setOptimisticMessages(newOptimistic);
|
setOptimisticMessages(newOptimistic);
|
||||||
|
|
||||||
if (resolvedThreadId) {
|
_handleOnStart(threadId);
|
||||||
_handleOnStart(resolvedThreadId);
|
|
||||||
}
|
|
||||||
|
|
||||||
let uploadedFileInfo: UploadedFileInfo[] = [];
|
let uploadedFileInfo: UploadedFileInfo[] = [];
|
||||||
|
|
||||||
|
|
@ -363,12 +359,12 @@ export function useThreadStream({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resolvedThreadId) {
|
if (!threadId) {
|
||||||
throw new Error("Thread is not ready for file upload.");
|
throw new Error("Thread is not ready for file upload.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
const uploadResponse = await uploadFiles(resolvedThreadId, files);
|
const uploadResponse = await uploadFiles(threadId, files);
|
||||||
uploadedFileInfo = uploadResponse.files;
|
uploadedFileInfo = uploadResponse.files;
|
||||||
|
|
||||||
// Update optimistic human message with uploaded status + paths
|
// Update optimistic human message with uploaded status + paths
|
||||||
|
|
@ -435,7 +431,7 @@ export function useThreadStream({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
threadId: resolvedThreadId,
|
threadId: threadId,
|
||||||
streamSubgraphs: true,
|
streamSubgraphs: true,
|
||||||
streamResumable: true,
|
streamResumable: true,
|
||||||
config: {
|
config: {
|
||||||
|
|
@ -456,7 +452,7 @@ export function useThreadStream({
|
||||||
: context.mode === "thinking"
|
: context.mode === "thinking"
|
||||||
? "low"
|
? "low"
|
||||||
: undefined),
|
: undefined),
|
||||||
...(resolvedThreadId ? { thread_id: resolvedThreadId } : {}),
|
thread_id: threadId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -580,7 +576,7 @@ export function useSubmitThread({
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
...threadContext,
|
...threadContext,
|
||||||
...(threadId ? { thread_id: threadId } : {}),
|
thread_id: threadId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ export async function downloadMarkdownAsPdf(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
async function loadHtml2Pdf(): Promise<Function> {
|
async function loadHtml2Pdf(): Promise<Function> {
|
||||||
const html2pdf = await import("html2pdf.js");
|
const html2pdf = await import("html2pdf.js");
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
return html2pdf.default;
|
return html2pdf.default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ interface SkillError {
|
||||||
|
|
||||||
interface UseSelectedSkillListenerOptions {
|
interface UseSelectedSkillListenerOptions {
|
||||||
/** 当前会话 thread_id,用于调用 bootstrapRemoteSkill */
|
/** 当前会话 thread_id,用于调用 bootstrapRemoteSkill */
|
||||||
threadId?: string | null;
|
threadId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseSelectedSkillListenerReturn {
|
interface UseSelectedSkillListenerReturn {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue