mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 12:24:46 +08:00
feat: implement create skill
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FilesIcon, XIcon } from "lucide-react";
|
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 { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
|
import { ConversationEmptyState } from "@/components/ai-elements/conversation";
|
||||||
@@ -52,6 +52,25 @@ export default function ChatPage() {
|
|||||||
selectedArtifact,
|
selectedArtifact,
|
||||||
} = useArtifacts();
|
} = useArtifacts();
|
||||||
const { thread_id: threadIdFromPath } = useParams<{ thread_id: string }>();
|
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(
|
const isNewThread = useMemo(
|
||||||
() => threadIdFromPath === "new",
|
() => threadIdFromPath === "new",
|
||||||
[threadIdFromPath],
|
[threadIdFromPath],
|
||||||
@@ -233,6 +252,7 @@ export default function ChatPage() {
|
|||||||
context={settings.context}
|
context={settings.context}
|
||||||
extraHeader={isNewThread && <Welcome />}
|
extraHeader={isNewThread && <Welcome />}
|
||||||
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
disabled={env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true"}
|
||||||
|
initialValue={inputInitialValue}
|
||||||
onContextChange={(context) =>
|
onContextChange={(context) =>
|
||||||
setSettings("context", context)
|
setSettings("context", context)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export function InputBox({
|
|||||||
context,
|
context,
|
||||||
extraHeader,
|
extraHeader,
|
||||||
isNewThread,
|
isNewThread,
|
||||||
|
initialValue,
|
||||||
onContextChange,
|
onContextChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onStop,
|
onStop,
|
||||||
@@ -72,6 +73,7 @@ export function InputBox({
|
|||||||
};
|
};
|
||||||
extraHeader?: React.ReactNode;
|
extraHeader?: React.ReactNode;
|
||||||
isNewThread?: boolean;
|
isNewThread?: boolean;
|
||||||
|
initialValue?: string;
|
||||||
onContextChange?: (
|
onContextChange?: (
|
||||||
context: Omit<
|
context: Omit<
|
||||||
AgentThreadContext,
|
AgentThreadContext,
|
||||||
@@ -164,6 +166,7 @@ export function InputBox({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={t.inputBox.placeholder}
|
placeholder={t.inputBox.placeholder}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
defaultValue={initialValue}
|
||||||
/>
|
/>
|
||||||
</PromptInputBody>
|
</PromptInputBody>
|
||||||
<PromptInputFooter className="flex">
|
<PromptInputFooter className="flex">
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type SettingsDialogProps = React.ComponentProps<typeof Dialog> & {
|
|||||||
|
|
||||||
export function SettingsDialog({
|
export function SettingsDialog({
|
||||||
defaultSection = "appearance",
|
defaultSection = "appearance",
|
||||||
|
onOpenChange,
|
||||||
...dialogProps
|
...dialogProps
|
||||||
}: SettingsDialogProps) {
|
}: SettingsDialogProps) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
@@ -60,7 +61,7 @@ export function SettingsDialog({
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<Dialog {...dialogProps}>
|
<Dialog {...dialogProps} onOpenChange={onOpenChange}>
|
||||||
<DialogContent
|
<DialogContent
|
||||||
className="flex h-[75vh] max-h-[calc(100vh-2rem)] flex-col sm:max-w-5xl md:max-w-6xl"
|
className="flex h-[75vh] max-h-[calc(100vh-2rem)] flex-col sm:max-w-5xl md:max-w-6xl"
|
||||||
aria-describedby={undefined}
|
aria-describedby={undefined}
|
||||||
@@ -100,7 +101,11 @@ export function SettingsDialog({
|
|||||||
<div className="space-y-8 p-6">
|
<div className="space-y-8 p-6">
|
||||||
{activeSection === "appearance" && <AppearanceSettingsPage />}
|
{activeSection === "appearance" && <AppearanceSettingsPage />}
|
||||||
{activeSection === "tools" && <ToolSettingsPage />}
|
{activeSection === "tools" && <ToolSettingsPage />}
|
||||||
{activeSection === "skills" && <SkillSettingsPage />}
|
{activeSection === "skills" && (
|
||||||
|
<SkillSettingsPage
|
||||||
|
onClose={() => onOpenChange?.(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{activeSection === "notification" && <NotificationSettingsPage />}
|
{activeSection === "notification" && <NotificationSettingsPage />}
|
||||||
{activeSection === "acknowledge" && <AcknowledgePage />}
|
{activeSection === "acknowledge" && <AcknowledgePage />}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SparklesIcon } from "lucide-react";
|
import { SparklesIcon } from "lucide-react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -28,7 +29,7 @@ import { env } from "@/env";
|
|||||||
|
|
||||||
import { SettingsSection } from "./settings-section";
|
import { SettingsSection } from "./settings-section";
|
||||||
|
|
||||||
export function SkillSettingsPage() {
|
export function SkillSettingsPage({ onClose }: { onClose?: () => void } = {}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { skills, isLoading, error } = useSkills();
|
const { skills, isLoading, error } = useSkills();
|
||||||
return (
|
return (
|
||||||
@@ -41,14 +42,21 @@ export function SkillSettingsPage() {
|
|||||||
) : error ? (
|
) : error ? (
|
||||||
<div>Error: {error.message}</div>
|
<div>Error: {error.message}</div>
|
||||||
) : (
|
) : (
|
||||||
<SkillSettingsList skills={skills} />
|
<SkillSettingsList skills={skills} onClose={onClose} />
|
||||||
)}
|
)}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SkillSettingsList({ skills }: { skills: Skill[] }) {
|
function SkillSettingsList({
|
||||||
|
skills,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
skills: Skill[];
|
||||||
|
onClose?: () => void;
|
||||||
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const router = useRouter();
|
||||||
const [filter, setFilter] = useState<string>("public");
|
const [filter, setFilter] = useState<string>("public");
|
||||||
const { mutate: enableSkill } = useEnableSkill();
|
const { mutate: enableSkill } = useEnableSkill();
|
||||||
const filteredSkills = useMemo(
|
const filteredSkills = useMemo(
|
||||||
@@ -56,7 +64,8 @@ function SkillSettingsList({ skills }: { skills: Skill[] }) {
|
|||||||
[skills, filter],
|
[skills, filter],
|
||||||
);
|
);
|
||||||
const handleCreateSkill = () => {
|
const handleCreateSkill = () => {
|
||||||
console.log("create skill");
|
onClose?.();
|
||||||
|
router.push("/workspace/chats/new?mode=skill");
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col gap-4">
|
<div className="flex w-full flex-col gap-4">
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const enUS: Translations = {
|
|||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: "How can I assist you today?",
|
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",
|
addAttachments: "Add attachments",
|
||||||
mode: "Mode",
|
mode: "Mode",
|
||||||
flashMode: "Flash",
|
flashMode: "Flash",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export interface Translations {
|
|||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
|
createSkillPrompt: string;
|
||||||
addAttachments: string;
|
addAttachments: string;
|
||||||
mode: string;
|
mode: string;
|
||||||
flashMode: string;
|
flashMode: string;
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ export const zhCN: Translations = {
|
|||||||
// Input Box
|
// Input Box
|
||||||
inputBox: {
|
inputBox: {
|
||||||
placeholder: "今天我能为你做些什么?",
|
placeholder: "今天我能为你做些什么?",
|
||||||
|
createSkillPrompt:
|
||||||
|
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
|
||||||
addAttachments: "添加附件",
|
addAttachments: "添加附件",
|
||||||
mode: "模式",
|
mode: "模式",
|
||||||
flashMode: "闪速",
|
flashMode: "闪速",
|
||||||
|
|||||||
Reference in New Issue
Block a user