feat(skills/settings): 技能复制 + 设置持久化等会话前改动

- 技能复制:新增 POST 复制接口(_rewrite_skill_frontmatter_name 重写 frontmatter name + 校验 + 去重),前端 skills api/hooks 与技能设置页配套
- 设置持久化:lastAgentId、线程 RAG 模式锁(settings/index、local)
- i18n(en/zh/types)、agent-card/input-box/message-list 等页面文案与交互微调
- scheduler/element.py 小修

(本批为会话前已存在的工作区改动,按要求一并提交;构建产物 backend.zip 未提交)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AdministratorandClaude Opus 4.8 authored and xuhd committed 2026-06-03 11:38:47 +08:00
1 parent adcc5a358b
commit 133054e2dd
18 files changed
+526 -54

No files matched your search

@@ -41,6 +41,7 @@ export const enUS: Translations = {
code: "Code",
preview: "Preview",
cancel: "Cancel",
confirm: "Confirm",
save: "Save",
install: "Install",
create: "Create",
@@ -80,6 +81,7 @@ export const enUS: Translations = {
// Input Box
inputBox: {
placeholder: "How can I assist you today?",
editQuestion: "Edit question",
createSkillPrompt:
"We're going to build a new skill step by step with `skill-creator`. To start, what do you want this skill to do?",
addAttachments: "Add attachments",
@@ -42,6 +42,7 @@ export interface Translations {
code: string;
preview: string;
cancel: string;
confirm: string;
save: string;
install: string;
create: string;
@@ -77,6 +78,7 @@ export interface Translations {
// Input Box
inputBox: {
placeholder: string;
editQuestion: string;
createSkillPrompt: string;
addAttachments: string;
mode: string;
@@ -41,6 +41,7 @@ export const zhCN: Translations = {
code: "代码",
preview: "预览",
cancel: "取消",
confirm: "确定",
save: "保存",
install: "安装",
create: "创建",
@@ -80,6 +81,7 @@ export const zhCN: Translations = {
// Input Box
inputBox: {
placeholder: "今天我能为你做些什么?",
editQuestion: "编辑问题",
createSkillPrompt:
"我们一起用 skill-creator 技能来创建一个技能吧。先问问我希望这个技能能做什么。",
addAttachments: "添加附件",
+6 -1
View File
@@ -1,3 +1,8 @@
export { useLocalSettings, useThreadSettings } from "./hooks";
export { isThreadRagModeLocked, lockThreadRagMode } from "./local";
export {
getLastAgentId,
isThreadRagModeLocked,
lockThreadRagMode,
saveLastAgentId,
} from "./local";
export type { LocalSettings } from "./local";
+24
View File
@@ -21,6 +21,7 @@ export const DEFAULT_LOCAL_SETTINGS: LocalSettings = {
export const LOCAL_SETTINGS_KEY = "deerflow.local-settings";
export const THREAD_MODEL_KEY_PREFIX = "deerflow.thread-model.";
export const THREAD_RAG_MODE_KEY_PREFIX = "deerflow.thread-rag-mode.";
export const LAST_AGENT_ID_KEY = "deerflow.last-agent-id";
function isBrowser(): boolean {
return typeof window !== "undefined";
@@ -148,6 +149,29 @@ export function applyThreadModelOverride(
};
}
/**
* The id of the agent the user last started a conversation with. New-chat
* landing screens pre-select this agent so the user doesn't have to re-pick it
* every time. Returns ``null`` when nothing was stored yet.
*/
export function getLastAgentId(): string | null {
if (!isBrowser()) {
return null;
}
return localStorage.getItem(LAST_AGENT_ID_KEY) || null;
}
export function saveLastAgentId(agentId: string | null | undefined) {
if (!isBrowser()) {
return;
}
if (!agentId) {
localStorage.removeItem(LAST_AGENT_ID_KEY);
return;
}
localStorage.setItem(LAST_AGENT_ID_KEY, agentId);
}
export function getLocalSettings(): LocalSettings {
if (!isBrowser()) {
return DEFAULT_LOCAL_SETTINGS;
+18
View File
@@ -202,6 +202,24 @@ export async function installSkillUpload(
return response.json() as Promise<InstallSkillResponse>;
}
export async function duplicateSkill(
sourceName: string,
newName: string,
): Promise<InstallSkillResponse> {
const response = await apiFetch(
`${getBackendBaseURL()}/api/skills/custom/${encodeURIComponent(sourceName)}/duplicate`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ new_name: newName }),
},
);
if (!response.ok) {
throw await parseDetailedError(response, "Failed to duplicate skill");
}
return response.json() as Promise<InstallSkillResponse>;
}
export interface ReconcileCustomSkillsResponse {
adopted: string[];
already_registered: string[];
+17
View File
@@ -7,6 +7,7 @@ import {
import {
deleteCustomSkill,
duplicateSkill,
enableSkill,
getSkill,
getSkillContent,
@@ -192,6 +193,22 @@ export function useSetSkillPinned() {
});
}
export function useDuplicateSkill() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
sourceName,
newName,
}: {
sourceName: string;
newName: string;
}) => duplicateSkill(sourceName, newName),
onSuccess: () => {
void queryClient.invalidateQueries({ queryKey: ["skills"] });
},
});
}
export function useDeleteCustomSkill() {
const queryClient = useQueryClient();
return useMutation({