diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 1ab6d05f..5d5ec91c 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -9,7 +9,8 @@ import { detectLocaleServer } from "@/core/i18n/server"; export const metadata: Metadata = { title: "XClaw", - description: "Desscriptions of XClawDesscriptions of XClawDesscriptions of XClaw", + description: + "Desscriptions of XClawDesscriptions of XClawDesscriptions of XClaw", }; export default async function RootLayout({ diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 8fb9a478..86f0ec1f 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -62,13 +62,8 @@ export default function ChatPage() { setFullscreen: setArtifactsFullscreen, fullscreen, } = useArtifacts(); - const { - threadId, - isNewThread, - setIsNewThread, - isMock, - showWelcomeStyle, - } = useThreadChat(); + const { threadId, isNewThread, setIsNewThread, isMock, showWelcomeStyle } = + useThreadChat(); // 新逻辑:历史渲染和新会话仅由路由 /chats/new 控制,不再读取 isnew/is_chatting 参数。 const shouldRenderHistory = !showWelcomeStyle; @@ -96,11 +91,12 @@ export default function ChatPage() { const initializedThreadRef = useRef(null); const { showNotification } = useNotification(); - const currentSlogan = - motivationSlogans[sloganIndex % motivationSlogans.length] ?? { - text: "来,一起学习工作吧", - color: "#333333", - }; + const currentSlogan = motivationSlogans[ + sloganIndex % motivationSlogans.length + ] ?? { + text: "来,一起学习工作吧", + color: "#333333", + }; const tickerCharacterList = useMemo(() => { const seen = new Set(); const uniqueChars: string[] = []; @@ -119,9 +115,12 @@ export default function ChatPage() { useEffect(() => { if (motivationSlogans.length <= 1) return; - const timer = window.setInterval(() => { - setSloganIndex((prev) => (prev + 1) % motivationSlogans.length); - }, 10 * 60 * 1000); + const timer = window.setInterval( + () => { + setSloganIndex((prev) => (prev + 1) % motivationSlogans.length); + }, + 10 * 60 * 1000, + ); return () => window.clearInterval(timer); }, []); @@ -313,7 +312,6 @@ export default function ChatPage() { setIsNewThread, ]); - return (
@@ -499,7 +499,7 @@ export default function ChatPage() {
@@ -523,44 +523,48 @@ export default function ChatPage() {
{!(showWelcomeStyle && thread.isThreadLoading) ? ( - - {showWelcomeStyle && !hasSubmitted && ( - - )} -
- } - disabled={ - env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || - isSelectedSkillBootstrapping || - isUploading || - (isNewThread && !safeThreadId) - } - onContextChange={(context) => setSettings("context", context)} - onSubmit={handleSubmit} - onStop={handleStop} - /> + <> + + {showWelcomeStyle && !hasSubmitted && ( + + )} +
+ } + disabled={ + env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" || + isSelectedSkillBootstrapping || + isUploading || + (isNewThread && !safeThreadId) + } + onContextChange={(context) => setSettings("context", context)} + onSubmit={handleSubmit} + onStop={handleStop} + /> + ) : ( // - '' + "" )} {/* {isSelectedSkillBootstrapping && ( @@ -583,7 +587,7 @@ export default function ChatPage() { 提示

- (测试中:计划销毁但是现在没有销毁) 退出后,当前会话结束并销毁,请先下载保存当前结果! + 历史记录每七天自动删除,现在将返回欢迎页,是否继续?

+ +
+ + + {/* 场景 2:skill 选择通信 */} +
+
+ ② postMessage 通信(发送到宿主) +
+
+ + + + +
+
+ + {/* 场景 3:接收宿主页 selectedSkill */} +
+
+ ③ 接收宿主页 selectedSkill +
+
+ + + + +
+
+ + {/* 场景 4:剪贴板复制(iframe 通信) */} +
+
+ + ④ 剪贴板复制(iframe 通信) + - {isSkillMode ? "skill ✅" : "普通"} + {isInIframe ? "iframe 模式" : "独立页面"} - - - selectedSkill: - - {iframeSkill.selectedSkill - ? `${iframeSkill.selectedSkill.skill_id} / ${iframeSkill.selectedSkill.title}` - : "无"} - - -
-
- - {/* 场景 1:侧边栏隐藏 */} -
-
- ① 侧边栏隐藏(layout) -
-
- - -
-
- - {/* 场景 2:skill 选择通信 */} -
-
- ② postMessage 通信(发送到宿主) -
-
- - - - -
-
- - {/* 场景 3:接收宿主页 selectedSkill */} -
-
- ③ 接收宿主页 selectedSkill -
-
- - - - -
-
- - {/* 场景 4:剪贴板复制(iframe 通信) */} -
-
- - ④ 剪贴板复制(iframe 通信) - - - {isInIframe ? "iframe 模式" : "独立页面"} - -
-
- -
- {isInIframe - ? "将通过 postMessage 请求父页面复制" - : "将直接调用 navigator.clipboard"}
-
-
- - {/* 场景 5:is_chatting */} -
-
- ⑤ is_chatting -
-
- - -
-
- - {/* 日志 */} - {log.length > 0 && ( -
-
- 操作日志 -
- {log.map((l, i) => ( -
+ +
+ {isInIframe + ? "将通过 postMessage 请求父页面复制" + : "将直接调用 navigator.clipboard"}
- ))} +
- )} - } + + {/* 场景 5:is_chatting */} +
+
+ ⑤ is_chatting +
+
+ + +
+
+ + {/* 日志 */} + {log.length > 0 && ( +
+
+ 操作日志 +
+ {log.map((l, i) => ( +
+ {l} +
+ ))} +
+ )} + + )} ); } diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index fe69aaf1..641e5628 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -1,5 +1,7 @@ "use client"; +import { useRouter } from "next/navigation"; + import type { ChatStatus } from "ai"; import { CheckIcon, @@ -58,9 +60,7 @@ import { } from "@/components/ui/dropdown-menu"; import { Tag } from "@/components/ui/tag"; import { useI18n } from "@/core/i18n/hooks"; -import type { - SelectedSkillPayloadItem, -} from "@/core/i18n/locales/types"; +import type { SelectedSkillPayloadItem } from "@/core/i18n/locales/types"; import { POST_MESSAGE_TYPES, sendToParent } from "@/core/iframe-messages"; import { useModels } from "@/core/models/hooks"; import type { AgentThreadContext } from "@/core/threads"; @@ -86,6 +86,7 @@ import { import { ModeHoverGuide } from "./mode-hover-guide"; import { Tooltip } from "./tooltip"; +import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime"; export function InputBox({ className, @@ -132,7 +133,7 @@ export function InputBox({ const searchParams = useSearchParams(); const iframeSkill = useIframeSkill({ threadId: threadIdFromProps }); const isInputDisabled = (disabled ?? false) || iframeSkill.isBootstrapping; - + const router = useRouter(); const threadId = threadIdFromProps; const { textInput } = usePromptInputController(); const attachments = usePromptInputAttachments(); @@ -375,6 +376,11 @@ export function InputBox({ /> */} + + {/* 参考 kexue 版本隐藏运行模式切换按钮 */} {/* - {showWelcomeStyle && !hasSubmitted && searchParams.get("mode") !== "skill" && ( - - )} + {showWelcomeStyle && + !hasSubmitted && + searchParams.get("mode") !== "skill" && ( + + )} {!disabled && !showWelcomeStyle && @@ -532,19 +541,19 @@ function SuggestionList({ const promptSuggestions = suggestions.filter( ( suggestion, - ): suggestion is Exclude<(typeof suggestions)[number], { type: "separator" }> => - !("type" in suggestion), + ): suggestion is Exclude< + (typeof suggestions)[number], + { type: "separator" } + > => !("type" in suggestion), ); const handleSuggestionClick = useCallback( - ( - suggestion: { - prompt: string; - skill_id?: string[]; - children?: SelectedSkillPayloadItem[]; - suggestion: string; - }, - ) => { + (suggestion: { + prompt: string; + skill_id?: string[]; + children?: SelectedSkillPayloadItem[]; + suggestion: string; + }) => { if (isBootstrapping) return; // 优先使用 children 中的 skill(保留每个 skill 自己的 name,用于 tag 展示) @@ -553,8 +562,9 @@ function SuggestionList({ id: String(item.id).trim(), name: item.name?.trim() ?? "", })) - .filter((item): item is { id: string; name: string } => - Boolean(item.id) && Boolean(item.name), + .filter( + (item): item is { id: string; name: string } => + Boolean(item.id) && Boolean(item.name), ); if (childSkills.length > 0) { void bootstrapAndLockSkills({ @@ -593,7 +603,10 @@ function SuggestionList({ [bootstrapAndLockSkills, isBootstrapping, textInput], ); return ( - + {promptSuggestions.map((suggestion) => ( ); } + +function HistoryButton({ + className, + router, + threadId, +}: { + className?: string; + router: AppRouterInstance; + threadId: string; +}) { + const { t } = useI18n(); + return ( + + + router.replace(`/workspace/chats/${threadId}?is_chatting=true`) + } + > + + + + + + ); +} // 启动iframeSkillDialog function IframeSkillDialogButton({ className, @@ -680,14 +726,17 @@ function IframeSkillDialogButton({ ) : null} {!isBootstrapping && selectedSkills.length > 0 ? (
{ if (event.deltaY === 0) return; event.currentTarget.scrollLeft += event.deltaY; }} > {selectedSkills.map((skill, index) => ( - + {skill.title} {/* TODO: 因为后端接口不支持取消选择skill,所以暂时禁用取消选择按钮 */}