From 8639dde3adfdbfd992e8b1d9fced98b7f104e48e Mon Sep 17 00:00:00 2001 From: Henry Li Date: Sat, 31 Jan 2026 22:31:25 +0800 Subject: [PATCH] feat: implement create skill --- .../app/workspace/chats/[thread_id]/page.tsx | 22 ++++++++++++++++++- .../src/components/workspace/input-box.tsx | 3 +++ .../workspace/settings/settings-dialog.tsx | 9 ++++++-- .../settings/skill-settings-page.tsx | 17 ++++++++++---- frontend/src/core/i18n/locales/en-US.ts | 2 ++ frontend/src/core/i18n/locales/types.ts | 1 + frontend/src/core/i18n/locales/zh-CN.ts | 2 ++ 7 files changed, 49 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/workspace/chats/[thread_id]/page.tsx b/frontend/src/app/workspace/chats/[thread_id]/page.tsx index 4787019..9babdcc 100644 --- a/frontend/src/app/workspace/chats/[thread_id]/page.tsx +++ b/frontend/src/app/workspace/chats/[thread_id]/page.tsx @@ -1,7 +1,7 @@ "use client"; import { FilesIcon, XIcon } from "lucide-react"; -import { useParams, useRouter } from "next/navigation"; +import { useParams, useRouter, useSearchParams } from "next/navigation"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ConversationEmptyState } from "@/components/ai-elements/conversation"; @@ -52,6 +52,25 @@ export default function ChatPage() { selectedArtifact, } = useArtifacts(); const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>(); + const searchParams = useSearchParams(); + const inputInitialValue = useMemo(() => { + if (threadIdFromPath !== "new" || searchParams.get("mode") !== "skill") { + return undefined; + } + return t.inputBox.createSkillPrompt; + }, [threadIdFromPath, searchParams, t.inputBox.createSkillPrompt]); + useEffect(() => { + if (inputInitialValue) { + setTimeout(() => { + const textarea = document.querySelector("textarea"); + if (textarea) { + textarea.focus(); + textarea.selectionStart = textarea.value.length; + textarea.selectionEnd = textarea.value.length; + } + }, 100); + } + }, [inputInitialValue]); const isNewThread = useMemo( () => threadIdFromPath === "new", [threadIdFromPath], @@ -233,6 +252,7 @@ export default function ChatPage() { context={settings.context} extraHeader={isNewThread && } disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"} + initialValue={inputInitialValue} onContextChange={(context) => setSettings("context", context) } diff --git a/frontend/src/components/workspace/input-box.tsx b/frontend/src/components/workspace/input-box.tsx index eeb946b..6b56ba4 100644 --- a/frontend/src/components/workspace/input-box.tsx +++ b/frontend/src/components/workspace/input-box.tsx @@ -56,6 +56,7 @@ export function InputBox({ context, extraHeader, isNewThread, + initialValue, onContextChange, onSubmit, onStop, @@ -72,6 +73,7 @@ export function InputBox({ }; extraHeader?: React.ReactNode; isNewThread?: boolean; + initialValue?: string; onContextChange?: ( context: Omit< AgentThreadContext, @@ -164,6 +166,7 @@ export function InputBox({ disabled={disabled} placeholder={t.inputBox.placeholder} autoFocus={autoFocus} + defaultValue={initialValue} /> diff --git a/frontend/src/components/workspace/settings/settings-dialog.tsx b/frontend/src/components/workspace/settings/settings-dialog.tsx index fd8a98f..d3ede84 100644 --- a/frontend/src/components/workspace/settings/settings-dialog.tsx +++ b/frontend/src/components/workspace/settings/settings-dialog.tsx @@ -31,6 +31,7 @@ type SettingsDialogProps = React.ComponentProps & { export function SettingsDialog({ defaultSection = "appearance", + onOpenChange, ...dialogProps }: SettingsDialogProps) { const { t } = useI18n(); @@ -60,7 +61,7 @@ export function SettingsDialog({ ], ); return ( - + {activeSection === "appearance" && } {activeSection === "tools" && } - {activeSection === "skills" && } + {activeSection === "skills" && ( + onOpenChange?.(false)} + /> + )} {activeSection === "notification" && } {activeSection === "acknowledge" && } diff --git a/frontend/src/components/workspace/settings/skill-settings-page.tsx b/frontend/src/components/workspace/settings/skill-settings-page.tsx index 9546191..63f5017 100644 --- a/frontend/src/components/workspace/settings/skill-settings-page.tsx +++ b/frontend/src/components/workspace/settings/skill-settings-page.tsx @@ -1,6 +1,7 @@ "use client"; import { SparklesIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; import { useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; @@ -28,7 +29,7 @@ import { env } from "@/env"; import { SettingsSection } from "./settings-section"; -export function SkillSettingsPage() { +export function SkillSettingsPage({ onClose }: { onClose?: () => void } = {}) { const { t } = useI18n(); const { skills, isLoading, error } = useSkills(); return ( @@ -41,14 +42,21 @@ export function SkillSettingsPage() { ) : error ? (
Error: {error.message}
) : ( - + )} ); } -function SkillSettingsList({ skills }: { skills: Skill[] }) { +function SkillSettingsList({ + skills, + onClose, +}: { + skills: Skill[]; + onClose?: () => void; +}) { const { t } = useI18n(); + const router = useRouter(); const [filter, setFilter] = useState("public"); const { mutate: enableSkill } = useEnableSkill(); const filteredSkills = useMemo( @@ -56,7 +64,8 @@ function SkillSettingsList({ skills }: { skills: Skill[] }) { [skills, filter], ); const handleCreateSkill = () => { - console.log("create skill"); + onClose?.(); + router.push("/workspace/chats/new?mode=skill"); }; return (
diff --git a/frontend/src/core/i18n/locales/en-US.ts b/frontend/src/core/i18n/locales/en-US.ts index 21c8bc6..c3e3238 100644 --- a/frontend/src/core/i18n/locales/en-US.ts +++ b/frontend/src/core/i18n/locales/en-US.ts @@ -49,6 +49,8 @@ export const enUS: Translations = { // Input Box inputBox: { placeholder: "How can I assist you today?", + createSkillPrompt: + "Let's create a skill together using your skill-creator skill. First ask me what the skill should do.", addAttachments: "Add attachments", mode: "Mode", flashMode: "Flash", diff --git a/frontend/src/core/i18n/locales/types.ts b/frontend/src/core/i18n/locales/types.ts index dd1860d..f1e9c76 100644 --- a/frontend/src/core/i18n/locales/types.ts +++ b/frontend/src/core/i18n/locales/types.ts @@ -46,6 +46,7 @@ export interface Translations { // Input Box inputBox: { placeholder: string; + createSkillPrompt: string; addAttachments: string; mode: string; flashMode: string; diff --git a/frontend/src/core/i18n/locales/zh-CN.ts b/frontend/src/core/i18n/locales/zh-CN.ts index b416f62..cf1ecfa 100644 --- a/frontend/src/core/i18n/locales/zh-CN.ts +++ b/frontend/src/core/i18n/locales/zh-CN.ts @@ -49,6 +49,8 @@ export const zhCN: Translations = { // Input Box inputBox: { placeholder: "今天我能为你做些什么?", + createSkillPrompt: + "我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。", addAttachments: "添加附件", mode: "模式", flashMode: "闪速",