feat: implement create skill

This commit is contained in:
Henry Li
2026-01-31 22:31:25 +08:00
parent f31258dd10
commit 8639dde3ad
7 changed files with 49 additions and 7 deletions

View File

@@ -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 && <Welcome />}
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
initialValue={inputInitialValue}
onContextChange={(context) =>
setSettings("context", context)
}

View File

@@ -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}
/>
</PromptInputBody>
<PromptInputFooter className="flex">

View File

@@ -31,6 +31,7 @@ type SettingsDialogProps = React.ComponentProps<typeof Dialog> & {
export function SettingsDialog({
defaultSection = "appearance",
onOpenChange,
...dialogProps
}: SettingsDialogProps) {
const { t } = useI18n();
@@ -60,7 +61,7 @@ export function SettingsDialog({
],
);
return (
<Dialog {...dialogProps}>
<Dialog {...dialogProps} onOpenChange={onOpenChange}>
<DialogContent
className="flex h-[75vh] max-h-[calc(100vh-2rem)] flex-col sm:max-w-5xl md:max-w-6xl"
aria-describedby={undefined}
@@ -100,7 +101,11 @@ export function SettingsDialog({
<div className="space-y-8 p-6">
{activeSection === "appearance" && <AppearanceSettingsPage />}
{activeSection === "tools" && <ToolSettingsPage />}
{activeSection === "skills" && <SkillSettingsPage />}
{activeSection === "skills" && (
<SkillSettingsPage
onClose={() => onOpenChange?.(false)}
/>
)}
{activeSection === "notification" && <NotificationSettingsPage />}
{activeSection === "acknowledge" && <AcknowledgePage />}
</div>

View File

@@ -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 ? (
<div>Error: {error.message}</div>
) : (
<SkillSettingsList skills={skills} />
<SkillSettingsList skills={skills} onClose={onClose} />
)}
</SettingsSection>
);
}
function SkillSettingsList({ skills }: { skills: Skill[] }) {
function SkillSettingsList({
skills,
onClose,
}: {
skills: Skill[];
onClose?: () => void;
}) {
const { t } = useI18n();
const router = useRouter();
const [filter, setFilter] = useState<string>("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 (
<div className="flex w-full flex-col gap-4">

View File

@@ -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",

View File

@@ -46,6 +46,7 @@ export interface Translations {
// Input Box
inputBox: {
placeholder: string;
createSkillPrompt: string;
addAttachments: string;
mode: string;
flashMode: string;

View File

@@ -49,6 +49,8 @@ export const zhCN: Translations = {
// Input Box
inputBox: {
placeholder: "今天我能为你做些什么?",
createSkillPrompt:
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
addAttachments: "添加附件",
mode: "模式",
flashMode: "闪速",