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:
1 parent
d9afaca51f
commit
f590f672be
18 files changed
+524
-44
No files matched your search
@@ -26,7 +26,7 @@ import { useAgent } from "@/core/agents";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import { useNotification } from "@/core/notification/hooks";
|
||||
import { useThreadSettings } from "@/core/settings";
|
||||
import { saveLastAgentId, useThreadSettings } from "@/core/settings";
|
||||
import { useThreadStream } from "@/core/threads/hooks";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
import { env } from "@/env";
|
||||
@@ -114,6 +114,9 @@ export default function AgentChatPage() {
|
||||
const handleSubmit = useCallback(
|
||||
(message: PromptInputMessage) => {
|
||||
setHasStartedConversation(true);
|
||||
// Remember this agent so the new-chat landing screen pre-selects it next
|
||||
// time (covers both direct agent chats and the ChatPage handoff replay).
|
||||
saveLastAgentId(agent_id);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("workspace:sandbox-resume-autoscroll"),
|
||||
);
|
||||
@@ -216,6 +219,7 @@ export default function AgentChatPage() {
|
||||
paddingBottom={messageListPaddingBottom}
|
||||
tokenUsageEnabled={tokenUsageEnabled}
|
||||
onClarificationSubmit={handleClarificationSubmit}
|
||||
onEditResubmit={(text) => handleSubmit({ text, files: [] })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,10 +23,15 @@ import { ThreadTitle } from "@/components/workspace/thread-title";
|
||||
import { TodoList } from "@/components/workspace/todo-list";
|
||||
import { TokenUsageIndicator } from "@/components/workspace/token-usage-indicator";
|
||||
import { Welcome } from "@/components/workspace/welcome";
|
||||
import { useAgentSelector } from "@/core/agents";
|
||||
import { useI18n } from "@/core/i18n/hooks";
|
||||
import { useModels } from "@/core/models/hooks";
|
||||
import { useNotification } from "@/core/notification/hooks";
|
||||
import { useThreadSettings } from "@/core/settings";
|
||||
import {
|
||||
getLastAgentId,
|
||||
saveLastAgentId,
|
||||
useThreadSettings,
|
||||
} from "@/core/settings";
|
||||
import { useThreadStream } from "@/core/threads/hooks";
|
||||
import { useRouteBase } from "@/core/route-base";
|
||||
import { textOfMessage } from "@/core/threads/utils";
|
||||
@@ -47,6 +52,8 @@ export default function ChatPage() {
|
||||
const { data: promptPrefixSettings } = usePromptPrefixSettings();
|
||||
const [promptPrefixOn, setPromptPrefixOn] = useState(false);
|
||||
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null);
|
||||
const { agents: selectorAgents, isLoading: selectorLoading } =
|
||||
useAgentSelector();
|
||||
const { selectedArtifact } = useArtifacts();
|
||||
useSpecificChatMode();
|
||||
|
||||
@@ -58,10 +65,22 @@ export default function ChatPage() {
|
||||
if (isNewThread) {
|
||||
setHasStartedConversation(false);
|
||||
setPromptPrefixOn(false);
|
||||
setSelectedAgentId(null);
|
||||
// Pre-select the agent the user last started a conversation with so a
|
||||
// fresh chat defaults to it. Validated against the selector list below.
|
||||
setSelectedAgentId(getLastAgentId());
|
||||
}
|
||||
}, [isNewThread, threadId]);
|
||||
|
||||
// Drop a restored last-used agent that is no longer in the selector list
|
||||
// (deleted / unpublished / no longer curated), so we never hand off to a
|
||||
// dead agent. Wait for the list to load before deciding.
|
||||
useEffect(() => {
|
||||
if (selectorLoading || !selectedAgentId) return;
|
||||
if (!selectorAgents.some((a) => a.id === selectedAgentId)) {
|
||||
setSelectedAgentId(null);
|
||||
}
|
||||
}, [selectedAgentId, selectorAgents, selectorLoading]);
|
||||
|
||||
const { showNotification } = useNotification();
|
||||
|
||||
const [thread, sendMessage, isUploading] = useThreadStream({
|
||||
@@ -184,6 +203,14 @@ export default function ChatPage() {
|
||||
},
|
||||
[sendMessage, threadId],
|
||||
);
|
||||
|
||||
// Persist the picked agent the moment it changes so the next new chat
|
||||
// defaults to it — even if the user never sends a message this session.
|
||||
// Passing null (the ✕ clear) wipes the stored preference.
|
||||
const handleAgentChange = useCallback((agentId: string | null) => {
|
||||
setSelectedAgentId(agentId);
|
||||
saveLastAgentId(agentId);
|
||||
}, []);
|
||||
const handleStop = useCallback(async () => {
|
||||
await thread.stop();
|
||||
}, [thread]);
|
||||
@@ -248,6 +275,9 @@ export default function ChatPage() {
|
||||
paddingBottom={messageListPaddingBottom}
|
||||
tokenUsageEnabled={tokenUsageEnabled}
|
||||
onClarificationSubmit={handleClarificationSubmit}
|
||||
onEditResubmit={(text) =>
|
||||
handleSubmit({ text, files: [] })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -305,7 +335,7 @@ export default function ChatPage() {
|
||||
isLandingState
|
||||
? {
|
||||
selectedAgentId,
|
||||
onAgentChange: setSelectedAgentId,
|
||||
onAgentChange: handleAgentChange,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -545,13 +545,13 @@ export default function NewAgentPage() {
|
||||
没有匹配的技能。
|
||||
</p>
|
||||
) : (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
||||
{filteredSkills.map((skill) => {
|
||||
const checked = selectedSkills.includes(skill.name);
|
||||
return (
|
||||
<div
|
||||
key={skill.name}
|
||||
className="inline-flex max-w-full items-center"
|
||||
className="flex w-full min-w-0 items-center"
|
||||
>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -560,7 +560,7 @@ export default function NewAgentPage() {
|
||||
onClick={() => toggleSkill(skill)}
|
||||
title={skill.description}
|
||||
className={cn(
|
||||
"h-8 max-w-[200px] rounded-r-none",
|
||||
"h-8 min-w-0 flex-1 rounded-r-none",
|
||||
!checked && "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user