Compare commits
No commits in common. "29d662a2661ead5835509c0f0faa8b806d1008d2" and "ea396bb1afcee1f14d93d46d9a26c52b22a7e47c" have entirely different histories.
29d662a266
...
ea396bb1af
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
"window.title": "${activeEditorShort}${separator}${separator}deer-flow/frontend",
|
|
||||||
"todo-tree.regex.regex": "((%|#|//|<!--|\\{/\\*|^\\s*\\*)\\s*($TAGS)|^\\s*- \\[ \\])",
|
|
||||||
"todo-tree.general.tags": [
|
|
||||||
"TODO:",
|
|
||||||
"BUG:",
|
|
||||||
"TAG:",
|
|
||||||
"DONE:",
|
|
||||||
"MARK:",
|
|
||||||
"TEST:",
|
|
||||||
"XXX:"
|
|
||||||
],
|
|
||||||
"todo-tree.regex.regexCaseSensitive": false,
|
|
||||||
"todo-tree.highlights.defaultHighlight": {
|
|
||||||
"foreground": "#000000",
|
|
||||||
"background": "#fff700",
|
|
||||||
"icon": "check",
|
|
||||||
"rulerColour": "#fff700",
|
|
||||||
"type": "tag",
|
|
||||||
"iconColour": "#fff700"
|
|
||||||
},
|
|
||||||
"todo-tree.highlights.customHighlight": {
|
|
||||||
"TODO:": {
|
|
||||||
"icon": "todo",
|
|
||||||
"background": "#fff700",
|
|
||||||
"rulerColour": "#fff700",
|
|
||||||
"iconColour": "#fff700"
|
|
||||||
},
|
|
||||||
"BUG:": {
|
|
||||||
"background": "#eb5c5c",
|
|
||||||
"icon": "bug",
|
|
||||||
"rulerColour": "#eb5c5c",
|
|
||||||
"iconColour": "#eb5c5c"
|
|
||||||
},
|
|
||||||
"TAG:": {
|
|
||||||
"background": "#38b2f4",
|
|
||||||
"icon": "tag",
|
|
||||||
"rulerColour": "#38b2f4",
|
|
||||||
"iconColour": "#38b2f4",
|
|
||||||
"rulerLane": "full"
|
|
||||||
},
|
|
||||||
"DONE:": {
|
|
||||||
"background": "#5eec95",
|
|
||||||
"icon": "check",
|
|
||||||
"rulerColour": "#5eec95",
|
|
||||||
"iconColour": "#5eec95"
|
|
||||||
},
|
|
||||||
"MARK:": {
|
|
||||||
"background": "#f90",
|
|
||||||
"icon": "note",
|
|
||||||
"rulerColour": "#f90",
|
|
||||||
"iconColour": "#f90"
|
|
||||||
},
|
|
||||||
"TEST:": {
|
|
||||||
"background": "#df7be6",
|
|
||||||
"icon": "flame",
|
|
||||||
"rulerColour": "#df7be6",
|
|
||||||
"iconColour": "#df7be6"
|
|
||||||
},
|
|
||||||
"XXX:": {
|
|
||||||
"background": "#d65d8e",
|
|
||||||
"icon": "versions",
|
|
||||||
"rulerColour": "#d65d8e",
|
|
||||||
"iconColour": "#d65d8e"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -6,7 +6,6 @@ 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,
|
||||||
|
|
@ -58,16 +57,13 @@ export default function ChatPage() {
|
||||||
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
|
const { threadId, isNewThread, setIsNewThread, isMock } = useThreadChat();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
// Submission strategy:
|
// Submission strategy is controlled by `isnew` query param only.
|
||||||
// - xclaw_used=true: follow `isnew` (isnew=false => reuse existing thread)
|
// - isnew=false: reuse existing thread
|
||||||
// - xclaw_used!=true: always create/start a new session (no history)
|
// - otherwise: create/start a new session
|
||||||
const createNewSession = useMemo(() => {
|
const createNewSession = useMemo(() => {
|
||||||
if (!isNewThread) {
|
if (!isNewThread) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (searchParams.get("xclaw_used")?.trim().toLowerCase() !== "true") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return searchParams.get("isnew")?.trim().toLowerCase() !== "false";
|
return searchParams.get("isnew")?.trim().toLowerCase() !== "false";
|
||||||
}, [isNewThread, searchParams]);
|
}, [isNewThread, searchParams]);
|
||||||
const streamThreadId = useMemo(() => {
|
const streamThreadId = useMemo(() => {
|
||||||
|
|
@ -114,7 +110,6 @@ export default function ChatPage() {
|
||||||
}, [thread.values?.title]);
|
}, [thread.values?.title]);
|
||||||
|
|
||||||
const [hasSubmitted, setHasSubmitted] = useState(false);
|
const [hasSubmitted, setHasSubmitted] = useState(false);
|
||||||
const showInputBox = !(isNewThread && thread.isThreadLoading);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pageTitle = isNewThread
|
const pageTitle = isNewThread
|
||||||
|
|
@ -364,41 +359,36 @@ export default function ChatPage() {
|
||||||
isNewThread && !hasSubmitted && "-translate-y-[calc(50vh-96px)]",
|
isNewThread && !hasSubmitted && "-translate-y-[calc(50vh-96px)]",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{showInputBox ? (
|
<InputBox
|
||||||
<InputBox
|
className={cn("w-full rounded-[20px] bg-[#FBFAFC]")}
|
||||||
className={cn("w-full rounded-[20px] bg-[#FBFAFC]")}
|
threadId={threadId}
|
||||||
threadId={threadId}
|
isNewThread={isNewThread}
|
||||||
isNewThread={isNewThread}
|
hasSubmitted={hasSubmitted}
|
||||||
hasSubmitted={hasSubmitted}
|
autoFocus={isNewThread}
|
||||||
autoFocus={isNewThread}
|
status={
|
||||||
status={
|
thread.error
|
||||||
thread.error
|
? "error"
|
||||||
? "error"
|
: isUploading || thread.isLoading
|
||||||
: isUploading || thread.isLoading
|
? "streaming"
|
||||||
? "streaming"
|
: "ready"
|
||||||
: "ready"
|
}
|
||||||
}
|
context={settings.context}
|
||||||
context={settings.context}
|
extraHeader={
|
||||||
extraHeader={
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex flex-col gap-4">
|
{isNewThread && !hasSubmitted && (
|
||||||
{isNewThread && !hasSubmitted && (
|
<Welcome mode={settings.context.mode} />
|
||||||
<Welcome mode={settings.context.mode} />
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
disabled={
|
||||||
disabled={
|
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ||
|
||||||
env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" ||
|
isSelectedSkillBootstrapping ||
|
||||||
isSelectedSkillBootstrapping ||
|
isUploading
|
||||||
isUploading
|
}
|
||||||
}
|
onContextChange={(context) => setSettings("context", context)}
|
||||||
onContextChange={(context) => setSettings("context", context)}
|
onSubmit={handleSubmit}
|
||||||
onSubmit={handleSubmit}
|
onStop={handleStop}
|
||||||
onStop={handleStop}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
// <InputBoxSkeleton />
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* {isSelectedSkillBootstrapping && (
|
{/* {isSelectedSkillBootstrapping && (
|
||||||
<div className="text-muted-foreground w-full translate-y-8 text-center text-xs">
|
<div className="text-muted-foreground w-full translate-y-8 text-center text-xs">
|
||||||
|
|
@ -487,21 +477,3 @@ 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,7 +9,7 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "sonner";
|
||||||
|
|
||||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||||
import { CommandPalette } from "@/components/workspace/command-palette";
|
import { CommandPalette } from "@/components/workspace/command-palette";
|
||||||
|
|
@ -121,34 +121,7 @@ export default function WorkspaceLayout({
|
||||||
<SidebarInset className="min-w-0">{children}</SidebarInset>
|
<SidebarInset className="min-w-0">{children}</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<Toaster
|
<Toaster position="top-center" />
|
||||||
position="top-center"
|
|
||||||
toastOptions={{
|
|
||||||
duration: 2200,
|
|
||||||
classNames: {
|
|
||||||
toast: [
|
|
||||||
/* 灰色圆角矩形容器 */
|
|
||||||
"rounded-[20px] border-none",
|
|
||||||
/* 浅灰色背景 + 轻微透明 */
|
|
||||||
"bg-[#999999]! backdrop-blur-sm",
|
|
||||||
/* 阴影极轻 */
|
|
||||||
"shadow-[0_2px_12px_0_rgba(0,0,0,0.18)]",
|
|
||||||
/* 内边距:宽松居中 */
|
|
||||||
"px-5 py-2.5",
|
|
||||||
/* 单行布局,内容水平居中 */
|
|
||||||
"flex items-center justify-center gap-0",
|
|
||||||
/* 整体文字样式 */
|
|
||||||
"text-white text-sm font-normal font-sans",
|
|
||||||
/* 去掉 icon 区域间距 */
|
|
||||||
"[&>[data-icon]]:hidden",
|
|
||||||
].join(" "),
|
|
||||||
title:
|
|
||||||
"text-white! text-sm font-normal text-center w-full leading-snug",
|
|
||||||
description: "hidden",
|
|
||||||
icon: "hidden",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CircleCheckIcon,
|
||||||
|
InfoIcon,
|
||||||
|
Loader2Icon,
|
||||||
|
OctagonXIcon,
|
||||||
|
TriangleAlertIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||||
|
|
||||||
|
|
@ -7,15 +14,15 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme();
|
const { theme = "system" } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
theme={theme as ToasterProps["theme"]}
|
theme={theme as ToasterProps["theme"]}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
icons={{
|
icons={{
|
||||||
success: null,
|
success: <CircleCheckIcon className="size-4" />,
|
||||||
info: null,
|
info: <InfoIcon className="size-4" />,
|
||||||
warning: null,
|
warning: <TriangleAlertIcon className="size-4" />,
|
||||||
error: null,
|
error: <OctagonXIcon className="size-4" />,
|
||||||
loading: null,
|
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||||
}}
|
}}
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,12 @@ export function useThreadChat() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const xClawUsedFromQuery = searchParams.get("xclaw_used");
|
|
||||||
const [threadId, setThreadId] = useState(() => {
|
const [threadId, setThreadId] = useState(() => {
|
||||||
if (threadIdFromPath === "new") {
|
if (threadIdFromPath === "new") {
|
||||||
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
|
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
|
||||||
const queryThreadId =
|
const queryThreadId = shouldUseQueryThreadId
|
||||||
shouldUseQueryThreadId && xClawUsedFromQuery === "true"
|
? searchParams.get("thread_id")?.trim()
|
||||||
? searchParams.get("thread_id")?.trim()
|
: undefined;
|
||||||
: undefined;
|
|
||||||
return queryThreadId ?? uuid();
|
return queryThreadId ?? uuid();
|
||||||
}
|
}
|
||||||
return threadIdFromPath;
|
return threadIdFromPath;
|
||||||
|
|
@ -31,10 +29,9 @@ export function useThreadChat() {
|
||||||
if (pathname.endsWith("/new")) {
|
if (pathname.endsWith("/new")) {
|
||||||
setIsNewThread(true);
|
setIsNewThread(true);
|
||||||
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
|
const shouldUseQueryThreadId = pathname.startsWith("/workspace/chats/");
|
||||||
const queryThreadId =
|
const queryThreadId = shouldUseQueryThreadId
|
||||||
shouldUseQueryThreadId && xClawUsedFromQuery === "true"
|
? searchParams.get("thread_id")?.trim()
|
||||||
? searchParams.get("thread_id")?.trim()
|
: undefined;
|
||||||
: undefined;
|
|
||||||
setThreadId(queryThreadId ?? uuid());
|
setThreadId(queryThreadId ?? uuid());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,6 @@ export function useIframeSkill(): UseIframeSkillReturn {
|
||||||
|
|
||||||
// 0. 监听 query 中 XClawUsed=true 且带 thread_id 时跳转并清理 query
|
// 0. 监听 query 中 XClawUsed=true 且带 thread_id 时跳转并清理 query
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(xClawUsedFromQuery, threadIdFromQuery, lastThreadIdRef.current);
|
|
||||||
|
|
||||||
if (!threadIdFromQuery) return;
|
if (!threadIdFromQuery) return;
|
||||||
if (xClawUsedFromQuery !== "true") return;
|
if (xClawUsedFromQuery !== "true") return;
|
||||||
if (lastThreadIdRef.current === threadIdFromQuery) return;
|
if (lastThreadIdRef.current === threadIdFromQuery) return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue