Revert "feat(frontend): 增强建议快捷skill工具多层提示交互并更新计划状态"
This reverts commit eed425e965.
This commit is contained in:
parent
cbbae3dbd2
commit
24a97ef7d7
|
|
@ -79,5 +79,5 @@ Plans:
|
||||||
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
|
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
|
||||||
|
|
||||||
---
|
---
|
||||||
*Milestone status:* `in_progress`
|
*Milestone status:* `complete`
|
||||||
*Next command:* `/gsd-execute-phase 6`
|
*Next command:* `/gsd-new-milestone`
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: v1.0 milestone in progress
|
status: v1.0 milestone complete
|
||||||
last_updated: "2026-04-17T06:09:01.300Z"
|
last_updated: "2026-04-17T06:09:01.300Z"
|
||||||
last_activity: 2026-04-17
|
last_activity: 2026-04-17
|
||||||
progress:
|
progress:
|
||||||
|
|
@ -20,13 +20,13 @@ progress:
|
||||||
See: .planning/PROJECT.md (updated 2026-04-07)
|
See: .planning/PROJECT.md (updated 2026-04-07)
|
||||||
|
|
||||||
**Core value:** Keep the frontend visually familiar while preserving and hardening new-system behavior end to end.
|
**Core value:** Keep the frontend visually familiar while preserving and hardening new-system behavior end to end.
|
||||||
**Current focus:** Milestone v1.0 in progress
|
**Current focus:** Milestone v1.0 completed
|
||||||
|
|
||||||
## Workflow State
|
## Workflow State
|
||||||
|
|
||||||
- Current workflow: milestone execution (v1.0)
|
- Current workflow: milestone complete (v1.0)
|
||||||
- Next workflow: execute-phase
|
- Next workflow: new-milestone
|
||||||
- Next command: /gsd-execute-phase 6
|
- Next command: /gsd-new-milestone
|
||||||
|
|
||||||
## Artifacts
|
## Artifacts
|
||||||
|
|
||||||
|
|
@ -53,6 +53,5 @@ See: .planning/PROJECT.md (updated 2026-04-07)
|
||||||
|---|-------------|------|--------|-----------|
|
|---|-------------|------|--------|-----------|
|
||||||
| 260415-owq | 归档当前git diff为Phase 06验收后补丁:检查改动、更新06-UAT/06-VERIFICATION/06-SUMMARY(必要时)与STATE,再做原子提交 | 2026-04-15 | atomic | [260415-owq-git-diff-phase-06-06-uat-06-verification](./quick/260415-owq-git-diff-phase-06-06-uat-06-verification/) |
|
| 260415-owq | 归档当前git diff为Phase 06验收后补丁:检查改动、更新06-UAT/06-VERIFICATION/06-SUMMARY(必要时)与STATE,再做原子提交 | 2026-04-15 | atomic | [260415-owq-git-diff-phase-06-06-uat-06-verification](./quick/260415-owq-git-diff-phase-06-06-uat-06-verification/) |
|
||||||
| 260416-koe | 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证) | 2026-04-16 | pending | [260416-koe-phase-06](./quick/260416-koe-phase-06/) |
|
| 260416-koe | 归档 Phase 06 明确指代(“这张图”)语义修复到 GSD 流程(已验收,通过人工确认,免验证) | 2026-04-16 | pending | [260416-koe-phase-06](./quick/260416-koe-phase-06/) |
|
||||||
| 260417-kcb | suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage | 2026-04-17 | pending | [260417-kcb-suggestion-hover-dropdown-child-tooltip-](./quick/260417-kcb-suggestion-hover-dropdown-child-tooltip-/) |
|
|
||||||
|
|
||||||
Last activity: 2026-04-17 - Completed quick task 260417-kcb: suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage
|
Last activity: 2026-04-17
|
||||||
|
|
|
||||||
|
|
@ -79,5 +79,5 @@ Plans:
|
||||||
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
|
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
|
||||||
|
|
||||||
---
|
---
|
||||||
*Milestone status:* `in_progress`
|
*Milestone status:* `complete`
|
||||||
*Next command:* `/gsd-execute-phase 6`
|
*Next command:* `/gsd-new-milestone`
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# Quick Task 260417-kcb: suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage - Context
|
|
||||||
|
|
||||||
**Gathered:** 2026-04-17
|
|
||||||
**Status:** Ready for planning
|
|
||||||
|
|
||||||
<domain>
|
|
||||||
## Task Boundary
|
|
||||||
|
|
||||||
实现 suggestion 多字内容展示:
|
|
||||||
- 悬浮 suggestion 显示 dropdown(内容来自 children)
|
|
||||||
- 悬浮 dropdown 菜单项显示 tooltip(内容来自 detail)
|
|
||||||
- 点击 suggestion:将 children 的 skill id 组合为数组并发送 bootstrap
|
|
||||||
- 点击 dropdown 菜单项:仅发送当前 id 进行 bootstrap
|
|
||||||
- 发起 bootstrap 时同步更新 sessionStorage
|
|
||||||
|
|
||||||
</domain>
|
|
||||||
|
|
||||||
<decisions>
|
|
||||||
## Implementation Decisions
|
|
||||||
|
|
||||||
### Dropdown Trigger
|
|
||||||
- 使用 suggestion hover 打开 dropdown,同时保留 suggestion click 行为。
|
|
||||||
|
|
||||||
### Tooltip Source
|
|
||||||
- tooltip 文案优先使用 children.detail,无 detail 时回退到 skill name。
|
|
||||||
|
|
||||||
### Bootstrap + Storage
|
|
||||||
- bootstrap 发起时先乐观写入内存和 sessionStorage;失败后通过既有 removeFailedSkills 回滚。
|
|
||||||
|
|
||||||
### the agent's Discretion
|
|
||||||
- 不变更现有 API 入参与消息结构语义,仅扩展可选字段与前端交互。
|
|
||||||
|
|
||||||
</decisions>
|
|
||||||
|
|
||||||
<specifics>
|
|
||||||
## Specific Ideas
|
|
||||||
|
|
||||||
新增 children.detail 可选属性以支撑菜单项 tooltip。
|
|
||||||
|
|
||||||
</specifics>
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
---
|
|
||||||
mode: quick-full
|
|
||||||
must_haves:
|
|
||||||
truths:
|
|
||||||
- suggestion hover 能看到 children dropdown
|
|
||||||
- dropdown 菜单项 hover 能看到 detail tooltip
|
|
||||||
- suggestion click 发送 children 全量 id bootstrap
|
|
||||||
- dropdown item click 仅发送当前 id bootstrap
|
|
||||||
- bootstrap 发起时 sessionStorage 立即同步,失败可回滚
|
|
||||||
artifacts:
|
|
||||||
- frontend/src/components/workspace/input-box.tsx
|
|
||||||
- frontend/src/hooks/use-iframe-skill.ts
|
|
||||||
- frontend/src/core/i18n/locales/types.ts
|
|
||||||
- frontend/src/core/iframe-messages.ts
|
|
||||||
- frontend/src/core/i18n/locales/zh-CN.ts
|
|
||||||
key_links: []
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plan
|
|
||||||
|
|
||||||
## Task 1: 类型与文案扩展
|
|
||||||
- files: `frontend/src/core/i18n/locales/types.ts`, `frontend/src/core/iframe-messages.ts`, `frontend/src/core/i18n/locales/zh-CN.ts`
|
|
||||||
- action: 为 children 增加 `detail` 可选字段并补充中文建议数据。
|
|
||||||
- verify: TypeScript 通过,i18n suggestions 可读取 detail。
|
|
||||||
- done: completed
|
|
||||||
|
|
||||||
## Task 2: suggestion hover dropdown + item tooltip
|
|
||||||
- files: `frontend/src/components/workspace/input-box.tsx`
|
|
||||||
- action: 引入 hover 打开 dropdown,菜单项展示 children,tooltip 展示 detail。
|
|
||||||
- verify: 交互符合需求,主 suggestion 与子项点击行为分离。
|
|
||||||
- done: completed
|
|
||||||
|
|
||||||
## Task 3: bootstrap 触发时同步 sessionStorage
|
|
||||||
- files: `frontend/src/hooks/use-iframe-skill.ts`
|
|
||||||
- action: 在 bootstrap 请求发起前同步 selectedSkills 到内存与 sessionStorage,失败复用回滚逻辑。
|
|
||||||
- verify: `pnpm -s typecheck` 通过。
|
|
||||||
- done: completed
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
# Quick Task 260417-kcb - Research
|
|
||||||
|
|
||||||
## Findings
|
|
||||||
|
|
||||||
1. 当前 `SuggestionList` 已具备 children 优先 bootstrap 机制,改造点集中在 UI 展示和事件分流。
|
|
||||||
2. 代码库已有 Radix `DropdownMenu` 与 `Tooltip` 封装,直接复用可最小化风险。
|
|
||||||
3. `useIframeSkill` 已在成功后同步 sessionStorage;若需“发送 bootstrap 即更新”,可在请求前乐观更新并沿用失败回滚。
|
|
||||||
|
|
||||||
## Integration Notes
|
|
||||||
|
|
||||||
- 受影响文件:`input-box.tsx`、`use-iframe-skill.ts`、i18n 类型与 zh-CN 文案。
|
|
||||||
- 不涉及后端接口变更。
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
# Quick Task 260417-kcb - Summary
|
|
||||||
|
|
||||||
## Delivered
|
|
||||||
|
|
||||||
- 新增 `detail` 可选属性并在中文 suggestions 中补充 detail 文案。
|
|
||||||
- suggestion 悬浮时可见 dropdown,内容来自 children。
|
|
||||||
- dropdown 菜单项悬浮时显示 tooltip,内容来自 detail(无 detail 回退 name)。
|
|
||||||
- 点击 suggestion:bootstrap 使用 children 的全部 id。
|
|
||||||
- 点击 dropdown 菜单项:bootstrap 仅携带该菜单项 id。
|
|
||||||
- bootstrap 发起时即更新 sessionStorage 与本地 selectedSkills,失败走回滚。
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
|
|
||||||
- `cd frontend && pnpm -s typecheck` passed
|
|
||||||
- `cd frontend && pnpm -s lint` failed(仓库已有历史 lint 问题,非本次引入)
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
status: passed
|
|
||||||
|
|
||||||
# Verification
|
|
||||||
|
|
||||||
- [x] suggestion hover shows dropdown children
|
|
||||||
- [x] dropdown item hover shows tooltip(detail)
|
|
||||||
- [x] suggestion click bootstraps all child ids
|
|
||||||
- [x] dropdown item click bootstraps only selected id
|
|
||||||
- [x] bootstrap start updates sessionStorage
|
|
||||||
- [x] TypeScript check passed
|
|
||||||
|
|
||||||
## Residual Risk
|
|
||||||
|
|
||||||
- 未在本地启动浏览器进行可视化手动回归;建议在真实页面快速 smoke test 一次。
|
|
||||||
|
|
@ -937,7 +937,6 @@ function SuggestionList({
|
||||||
}) {
|
}) {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { textInput } = usePromptInputController();
|
const { textInput } = usePromptInputController();
|
||||||
const [openDropdownKey, setOpenDropdownKey] = useState<string | null>(null);
|
|
||||||
const suggestions = t.inputBox.suggestions;
|
const suggestions = t.inputBox.suggestions;
|
||||||
const promptSuggestions = suggestions.filter(
|
const promptSuggestions = suggestions.filter(
|
||||||
(
|
(
|
||||||
|
|
@ -1003,114 +1002,18 @@ function SuggestionList({
|
||||||
},
|
},
|
||||||
[bootstrapAndLockSkills, isBootstrapping, textInput],
|
[bootstrapAndLockSkills, isBootstrapping, textInput],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSuggestionChildClick = useCallback(
|
|
||||||
({
|
|
||||||
childSkill,
|
|
||||||
suggestionTitle,
|
|
||||||
}: {
|
|
||||||
childSkill: SelectedSkillPayloadItem;
|
|
||||||
suggestionTitle: string;
|
|
||||||
}) => {
|
|
||||||
if (isBootstrapping) return;
|
|
||||||
setOpenDropdownKey(null);
|
|
||||||
const id = String(childSkill.id).trim();
|
|
||||||
const name = childSkill.name?.trim() ?? "";
|
|
||||||
if (!id || !name) return;
|
|
||||||
void bootstrapAndLockSkills({
|
|
||||||
selectedSkills: [{ id, name, detail: childSkill.detail }],
|
|
||||||
title: suggestionTitle,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[bootstrapAndLockSkills, isBootstrapping],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suggestions
|
<Suggestions
|
||||||
className="min-h-16 w-fit items-start"
|
className="min-h-16 w-fit items-start"
|
||||||
data-testid="welcome-suggestions"
|
data-testid="welcome-suggestions"
|
||||||
>
|
>
|
||||||
{promptSuggestions.map((suggestion) => (
|
{promptSuggestions.map((suggestion) => (
|
||||||
(() => {
|
<Suggestion
|
||||||
const childSkills = (suggestion.children ?? [])
|
key={suggestion.suggestion}
|
||||||
.map((item) => ({
|
icon={suggestion.icon}
|
||||||
id: String(item.id).trim(),
|
suggestion={suggestion.suggestion}
|
||||||
name: item.name?.trim() ?? "",
|
onClick={() => handleSuggestionClick(suggestion)}
|
||||||
detail: item.detail?.trim() ?? "",
|
/>
|
||||||
}))
|
|
||||||
.filter(
|
|
||||||
(
|
|
||||||
item,
|
|
||||||
): item is {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
detail: string;
|
|
||||||
} => Boolean(item.id) && Boolean(item.name),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (childSkills.length === 0) {
|
|
||||||
return (
|
|
||||||
<Suggestion
|
|
||||||
key={suggestion.suggestion}
|
|
||||||
icon={suggestion.icon}
|
|
||||||
suggestion={suggestion.suggestion}
|
|
||||||
onClick={() => handleSuggestionClick(suggestion)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dropdownKey = suggestion.suggestion;
|
|
||||||
return (
|
|
||||||
<DropdownMenu
|
|
||||||
key={dropdownKey}
|
|
||||||
modal={false}
|
|
||||||
open={openDropdownKey === dropdownKey}
|
|
||||||
onOpenChange={(open) => setOpenDropdownKey(open ? dropdownKey : null)}
|
|
||||||
>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Suggestion
|
|
||||||
icon={suggestion.icon}
|
|
||||||
suggestion={suggestion.suggestion}
|
|
||||||
onClick={() => handleSuggestionClick(suggestion)}
|
|
||||||
onMouseEnter={() => setOpenDropdownKey(dropdownKey)}
|
|
||||||
/>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent
|
|
||||||
align="start"
|
|
||||||
className="min-w-[240px] rounded-[20px] p-[20px]"
|
|
||||||
onMouseEnter={() => setOpenDropdownKey(dropdownKey)}
|
|
||||||
onMouseLeave={() => setOpenDropdownKey(null)}
|
|
||||||
>
|
|
||||||
<DropdownMenuLabel className="p-0 text-[14px] text-[#333333]">
|
|
||||||
{suggestion.suggestion}
|
|
||||||
</DropdownMenuLabel >
|
|
||||||
<DropdownMenuSeparator className="mx-0 mt-[20px] mb-0" />
|
|
||||||
<DropdownMenuGroup className=" pt-[20px] px-0">
|
|
||||||
{childSkills.map((item) => (
|
|
||||||
<Tooltip
|
|
||||||
key={`${dropdownKey}-${item.id}`}
|
|
||||||
content={item.detail || item.name}
|
|
||||||
side="right"
|
|
||||||
>
|
|
||||||
<DropdownMenuItem
|
|
||||||
className="cursor-pointer rounded-[10px] px-3 py-2"
|
|
||||||
onSelect={(event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
handleSuggestionChildClick({
|
|
||||||
childSkill: item,
|
|
||||||
suggestionTitle: suggestion.suggestion,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</Tooltip>
|
|
||||||
))}
|
|
||||||
</DropdownMenuGroup>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
);
|
|
||||||
})()
|
|
||||||
))}
|
))}
|
||||||
</Suggestions>
|
</Suggestions>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Children, type ComponentProps, isValidElement } from "react";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Tooltip as TooltipPrimitive,
|
Tooltip as TooltipPrimitive,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
|
|
@ -11,23 +9,15 @@ import {
|
||||||
export function Tooltip({
|
export function Tooltip({
|
||||||
children,
|
children,
|
||||||
content,
|
content,
|
||||||
side,
|
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
side?: ComponentProps<typeof TooltipContent>["side"];
|
|
||||||
content?: React.ReactNode;
|
content?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const hasSingleElementChild =
|
|
||||||
Children.count(children) === 1 && isValidElement(children);
|
|
||||||
const triggerChild = hasSingleElementChild ? children : <span>{children}</span>;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipPrimitive delayDuration={500} {...props}>
|
<TooltipPrimitive delayDuration={500} {...props}>
|
||||||
<TooltipTrigger asChild>{triggerChild}</TooltipTrigger>
|
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||||
<TooltipContent side={side}>
|
<TooltipContent>{content}</TooltipContent>
|
||||||
<span className="whitespace-pre-line">{content}</span>
|
|
||||||
</TooltipContent>
|
|
||||||
</TooltipPrimitive>
|
</TooltipPrimitive>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import type { LucideIcon } from "lucide-react";
|
||||||
export interface SelectedSkillPayloadItem {
|
export interface SelectedSkillPayloadItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
name: string;
|
name: string;
|
||||||
detail?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Translations {
|
export interface Translations {
|
||||||
|
|
|
||||||
|
|
@ -126,66 +126,31 @@ export const zhCN: Translations = {
|
||||||
prompt:
|
prompt:
|
||||||
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
|
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
|
||||||
icon: PenLineIcon,
|
icon: PenLineIcon,
|
||||||
children: [
|
children: [{ id: "6057", name: "生辰解语" }],
|
||||||
{
|
|
||||||
id: "6057",
|
|
||||||
name: "生辰解语",
|
|
||||||
detail:
|
|
||||||
"四柱八字命理分析。\n当用户询问八字、四柱、命理、算命、Bazi、运势预测、命盘分析,\n或想了解其八字命盘、运势、大运、流年时,请使用此功能。",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "小红书种草",
|
suggestion: "小红书种草",
|
||||||
prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。",
|
prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。",
|
||||||
icon: CompassIcon,
|
icon: CompassIcon,
|
||||||
children: [
|
children: [{ id: "6099", name: "小红书笔记智造官" }],
|
||||||
{
|
|
||||||
id: "6099",
|
|
||||||
name: "小红书笔记智造官",
|
|
||||||
detail:
|
|
||||||
"根据用户需求及提供资料,撰写小红书笔记内容(标题与正文)。\n生成图片卡片(封面及正文卡片),并支持发布小红书笔记。",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "精美报告",
|
suggestion: "精美报告",
|
||||||
prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。",
|
prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。",
|
||||||
icon: GraduationCapIcon,
|
icon: GraduationCapIcon,
|
||||||
children: [
|
children: [{ id: "6100", name: "MD 转 PDF 助手" }],
|
||||||
{
|
|
||||||
id: "6100",
|
|
||||||
name: "MD 转 PDF 助手",
|
|
||||||
detail:
|
|
||||||
"将 Markdown(.md)文件转换为专业排版的 PDF 文档。\n支持将 markdown 转换为 PDF。\n支持将 .md 文件转为 .pdf。\n支持从 markdown 生成 PDF 报告。\n适用于中文技术报告、学术论文、文档编写及各类专业排版需求。",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "excel数据处理",
|
suggestion: "excel数据处理",
|
||||||
prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。",
|
prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。",
|
||||||
icon: MicroscopeIcon,
|
icon: MicroscopeIcon,
|
||||||
children: [
|
children: [{ id: "17", name: "Excel处理" }],
|
||||||
{
|
|
||||||
id: "17",
|
|
||||||
name: "Excel处理",
|
|
||||||
detail:
|
|
||||||
"全面的电子表格创建、编辑与分析功能。\n支持公式运算、格式设置、数据分析和可视化呈现。\n当 Claude 需要处理各类电子表格文件(如 .xlsx、.xlsm、.csv、.tsv 等格式)时,可执行以下操作:\n(1) 创建包含公式与格式的新表格;\n(2) 读取或分析表格数据;\n(3) 在保留公式的前提下修改现有表格;\n(4) 在表格内进行数据分析与可视化处理;\n(5) 重新计算公式。",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
suggestion: "营销策划",
|
suggestion: "营销策划",
|
||||||
prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。",
|
prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。",
|
||||||
icon: ShapesIcon,
|
icon: ShapesIcon,
|
||||||
children: [
|
children: [{ id: "217", name: "产品营销背景" }],
|
||||||
{
|
|
||||||
id: "217",
|
|
||||||
name: "产品营销背景",
|
|
||||||
detail:
|
|
||||||
"当用户需要创建或更新产品营销背景文档时使用。\n可用于沉淀目标用户、市场环境与竞争格局等关键信息。",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
suggestionsCreate: [
|
suggestionsCreate: [
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ export interface SelectedSkillMessage {
|
||||||
export interface SelectedSkillPayloadItem {
|
export interface SelectedSkillPayloadItem {
|
||||||
id: string | number;
|
id: string | number;
|
||||||
name: string;
|
name: string;
|
||||||
detail?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UnknownRecord = Record<string, unknown>;
|
type UnknownRecord = Record<string, unknown>;
|
||||||
|
|
@ -108,16 +107,8 @@ export function isSelectedSkillsMessage(
|
||||||
if (!skill) return false;
|
if (!skill) return false;
|
||||||
const id = skill.id;
|
const id = skill.id;
|
||||||
const name = skill.name;
|
const name = skill.name;
|
||||||
const detail = skill.detail;
|
|
||||||
const isValidId = typeof id === "string" || typeof id === "number";
|
const isValidId = typeof id === "string" || typeof id === "number";
|
||||||
const isValidDetail =
|
return isValidId && typeof name === "string" && name.trim().length > 0;
|
||||||
detail === undefined || typeof detail === "string";
|
|
||||||
return (
|
|
||||||
isValidId &&
|
|
||||||
typeof name === "string" &&
|
|
||||||
name.trim().length > 0 &&
|
|
||||||
isValidDetail
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,26 +30,6 @@ function getThreadStorageKey(threadId?: string | null): string | null {
|
||||||
return `${STORAGE_KEYS.byThreadPrefix}${normalized}`;
|
return `${STORAGE_KEYS.byThreadPrefix}${normalized}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function persistSkillsToSessionStorage(
|
|
||||||
skills: SkillData[],
|
|
||||||
threadId?: string | null,
|
|
||||||
) {
|
|
||||||
const threadKey = getThreadStorageKey(threadId);
|
|
||||||
if (skills.length === 0) {
|
|
||||||
window.sessionStorage.removeItem(STORAGE_KEYS.latest);
|
|
||||||
if (threadKey) {
|
|
||||||
window.sessionStorage.removeItem(threadKey);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = JSON.stringify(skills);
|
|
||||||
window.sessionStorage.setItem(STORAGE_KEYS.latest, payload);
|
|
||||||
if (threadKey) {
|
|
||||||
window.sessionStorage.setItem(threadKey, payload);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseStoredSkills(raw: string | null): SkillData[] {
|
function parseStoredSkills(raw: string | null): SkillData[] {
|
||||||
if (!raw) return [];
|
if (!raw) return [];
|
||||||
try {
|
try {
|
||||||
|
|
@ -235,8 +215,21 @@ export function useIframeSkill(
|
||||||
|
|
||||||
// 4. 选择变化时同步到 sessionStorage
|
// 4. 选择变化时同步到 sessionStorage
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 空数组也要同步到存储,避免 UI 状态与缓存不一致
|
const threadKey = getThreadStorageKey(threadId);
|
||||||
persistSkillsToSessionStorage(selectedSkills, threadId);
|
if (selectedSkills.length === 0) {
|
||||||
|
// 空数组也要同步到存储,避免 UI 状态与缓存不一致
|
||||||
|
window.sessionStorage.removeItem(STORAGE_KEYS.latest);
|
||||||
|
if (threadKey) {
|
||||||
|
window.sessionStorage.removeItem(threadKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = JSON.stringify(selectedSkills);
|
||||||
|
window.sessionStorage.setItem(STORAGE_KEYS.latest, payload);
|
||||||
|
if (threadKey) {
|
||||||
|
window.sessionStorage.setItem(threadKey, payload);
|
||||||
|
}
|
||||||
}, [selectedSkills, threadId]);
|
}, [selectedSkills, threadId]);
|
||||||
|
|
||||||
// 发送选择预定义 skill
|
// 发送选择预定义 skill
|
||||||
|
|
@ -293,15 +286,6 @@ export function useIframeSkill(
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 发起 bootstrap 时立即同步缓存;失败会在 removeFailedSkills 中回滚。
|
|
||||||
const normalizedSkills = selectedSkills.map((item) => ({
|
|
||||||
skill_id: String(item.id),
|
|
||||||
title: item.name,
|
|
||||||
}));
|
|
||||||
setSelectedSkill(normalizedSkills[0] ?? null);
|
|
||||||
setSelectedSkills(normalizedSkills);
|
|
||||||
persistSkillsToSessionStorage(normalizedSkills, threadId);
|
|
||||||
|
|
||||||
const result = await bootstrapRemoteSkill({
|
const result = await bootstrapRemoteSkill({
|
||||||
thread_id: threadId,
|
thread_id: threadId,
|
||||||
content_ids,
|
content_ids,
|
||||||
|
|
@ -324,12 +308,12 @@ export function useIframeSkill(
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSelectSkill(selectedSkills);
|
sendSelectSkill(selectedSkills);
|
||||||
const latestSkills = selectedSkills.map((item) => ({
|
const normalizedSkills = selectedSkills.map((item) => ({
|
||||||
skill_id: String(item.id),
|
skill_id: String(item.id),
|
||||||
title: item.name,
|
title: item.name,
|
||||||
}));
|
}));
|
||||||
setSelectedSkill(latestSkills[0] ?? null);
|
setSelectedSkill(normalizedSkills[0] ?? null);
|
||||||
setSelectedSkills(latestSkills);
|
setSelectedSkills(normalizedSkills);
|
||||||
|
|
||||||
toast.success(t.skills.loadSuccessWithTitle(title), {
|
toast.success(t.skills.loadSuccessWithTitle(title), {
|
||||||
description: result.message || t.skills.createdFiles(result.created_files),
|
description: result.message || t.skills.createdFiles(result.created_files),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue