feat(frontend): 增强建议快捷skill工具多层提示交互并更新计划状态
This commit is contained in:
parent
3d5a6a54ca
commit
eed425e965
|
|
@ -79,5 +79,5 @@ Plans:
|
|||
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
|
||||
|
||||
---
|
||||
*Milestone status:* `complete`
|
||||
*Next command:* `/gsd-new-milestone`
|
||||
*Milestone status:* `in_progress`
|
||||
*Next command:* `/gsd-execute-phase 6`
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
gsd_state_version: 1.0
|
||||
milestone: v1.0
|
||||
milestone_name: milestone
|
||||
status: v1.0 milestone complete
|
||||
status: v1.0 milestone in progress
|
||||
last_updated: "2026-04-17T06:09:01.300Z"
|
||||
last_activity: 2026-04-17
|
||||
progress:
|
||||
|
|
@ -20,13 +20,13 @@ progress:
|
|||
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.
|
||||
**Current focus:** Milestone v1.0 completed
|
||||
**Current focus:** Milestone v1.0 in progress
|
||||
|
||||
## Workflow State
|
||||
|
||||
- Current workflow: milestone complete (v1.0)
|
||||
- Next workflow: new-milestone
|
||||
- Next command: /gsd-new-milestone
|
||||
- Current workflow: milestone execution (v1.0)
|
||||
- Next workflow: execute-phase
|
||||
- Next command: /gsd-execute-phase 6
|
||||
|
||||
## Artifacts
|
||||
|
||||
|
|
@ -53,5 +53,6 @@ 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/) |
|
||||
| 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
|
||||
Last activity: 2026-04-17 - Completed quick task 260417-kcb: suggestion hover dropdown + child tooltip + bootstrap ids/sessionStorage
|
||||
|
|
|
|||
|
|
@ -79,5 +79,5 @@ Plans:
|
|||
- [x] 07-02-PLAN.md — gap closure:修复 ContextMenu 自动引用、提示前缀唯一化、Skill 使用 id 拼接
|
||||
|
||||
---
|
||||
*Milestone status:* `complete`
|
||||
*Next command:* `/gsd-new-milestone`
|
||||
*Milestone status:* `in_progress`
|
||||
*Next command:* `/gsd-execute-phase 6`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# 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>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
---
|
||||
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
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# 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 文案。
|
||||
- 不涉及后端接口变更。
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# 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 问题,非本次引入)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
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,6 +937,7 @@ function SuggestionList({
|
|||
}) {
|
||||
const { t } = useI18n();
|
||||
const { textInput } = usePromptInputController();
|
||||
const [openDropdownKey, setOpenDropdownKey] = useState<string | null>(null);
|
||||
const suggestions = t.inputBox.suggestions;
|
||||
const promptSuggestions = suggestions.filter(
|
||||
(
|
||||
|
|
@ -1002,18 +1003,114 @@ function SuggestionList({
|
|||
},
|
||||
[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 (
|
||||
<Suggestions
|
||||
className="min-h-16 w-fit items-start"
|
||||
data-testid="welcome-suggestions"
|
||||
>
|
||||
{promptSuggestions.map((suggestion) => (
|
||||
<Suggestion
|
||||
key={suggestion.suggestion}
|
||||
icon={suggestion.icon}
|
||||
suggestion={suggestion.suggestion}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
/>
|
||||
(() => {
|
||||
const childSkills = (suggestion.children ?? [])
|
||||
.map((item) => ({
|
||||
id: String(item.id).trim(),
|
||||
name: item.name?.trim() ?? "",
|
||||
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-5"
|
||||
onMouseEnter={() => setOpenDropdownKey(dropdownKey)}
|
||||
onMouseLeave={() => setOpenDropdownKey(null)}
|
||||
>
|
||||
<DropdownMenuLabel className="px-2 text-xs text-[#8A8694]">
|
||||
{suggestion.suggestion}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
{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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { Children, type ComponentProps, isValidElement } from "react";
|
||||
|
||||
import {
|
||||
Tooltip as TooltipPrimitive,
|
||||
TooltipContent,
|
||||
|
|
@ -9,15 +11,23 @@ import {
|
|||
export function Tooltip({
|
||||
children,
|
||||
content,
|
||||
side,
|
||||
...props
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
side?: ComponentProps<typeof TooltipContent>["side"];
|
||||
content?: React.ReactNode;
|
||||
}) {
|
||||
const hasSingleElementChild =
|
||||
Children.count(children) === 1 && isValidElement(children);
|
||||
const triggerChild = hasSingleElementChild ? children : <span>{children}</span>;
|
||||
|
||||
return (
|
||||
<TooltipPrimitive delayDuration={500} {...props}>
|
||||
<TooltipTrigger asChild>{children}</TooltipTrigger>
|
||||
<TooltipContent>{content}</TooltipContent>
|
||||
<TooltipTrigger asChild>{triggerChild}</TooltipTrigger>
|
||||
<TooltipContent side={side}>
|
||||
<span className="whitespace-pre-line">{content}</span>
|
||||
</TooltipContent>
|
||||
</TooltipPrimitive>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { LucideIcon } from "lucide-react";
|
|||
export interface SelectedSkillPayloadItem {
|
||||
id: string | number;
|
||||
name: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
export interface Translations {
|
||||
|
|
|
|||
|
|
@ -126,31 +126,66 @@ export const zhCN: Translations = {
|
|||
prompt:
|
||||
"为[主题/产品]撰写吸引人的自媒体文案,包括标题、正文和话题标签。",
|
||||
icon: PenLineIcon,
|
||||
children: [{ id: "6057", name: "生辰解语" }],
|
||||
children: [
|
||||
{
|
||||
id: "6057",
|
||||
name: "生辰解语",
|
||||
detail:
|
||||
"四柱八字命理分析。\n当用户询问八字、四柱、命理、算命、Bazi、运势预测、命盘分析,\n或想了解其八字命盘、运势、大运、流年时,请使用此功能。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
suggestion: "小红书种草",
|
||||
prompt: "编写[项目/功能]的需求文档,包含功能描述、用户故事和验收标准。",
|
||||
icon: CompassIcon,
|
||||
children: [{ id: "6099", name: "小红书笔记智造官" }],
|
||||
children: [
|
||||
{
|
||||
id: "6099",
|
||||
name: "小红书笔记智造官",
|
||||
detail:
|
||||
"根据用户需求及提供资料,撰写小红书笔记内容(标题与正文)。\n生成图片卡片(封面及正文卡片),并支持发布小红书笔记。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
suggestion: "精美报告",
|
||||
prompt: "编写[产品/功能]的使用指南,包含操作步骤、注意事项和常见问题。",
|
||||
icon: GraduationCapIcon,
|
||||
children: [{ id: "6100", name: "MD 转 PDF 助手" }],
|
||||
children: [
|
||||
{
|
||||
id: "6100",
|
||||
name: "MD 转 PDF 助手",
|
||||
detail:
|
||||
"将 Markdown(.md)文件转换为专业排版的 PDF 文档。\n支持将 markdown 转换为 PDF。\n支持将 .md 文件转为 .pdf。\n支持从 markdown 生成 PDF 报告。\n适用于中文技术报告、学术论文、文档编写及各类专业排版需求。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
suggestion: "excel数据处理",
|
||||
prompt: "对[Excel文件/数据]进行分析,生成数据洞察和可视化建议。",
|
||||
icon: MicroscopeIcon,
|
||||
children: [{ id: "17", name: "Excel处理" }],
|
||||
children: [
|
||||
{
|
||||
id: "17",
|
||||
name: "Excel处理",
|
||||
detail:
|
||||
"全面的电子表格创建、编辑与分析功能。\n支持公式运算、格式设置、数据分析和可视化呈现。\n当 Claude 需要处理各类电子表格文件(如 .xlsx、.xlsm、.csv、.tsv 等格式)时,可执行以下操作:\n(1) 创建包含公式与格式的新表格;\n(2) 读取或分析表格数据;\n(3) 在保留公式的前提下修改现有表格;\n(4) 在表格内进行数据分析与可视化处理;\n(5) 重新计算公式。",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
suggestion: "营销策划",
|
||||
prompt: "针对[行业/产品]进行市场调研,分析市场规模、竞品和趋势。",
|
||||
icon: ShapesIcon,
|
||||
children: [{ id: "217", name: "产品营销背景" }],
|
||||
children: [
|
||||
{
|
||||
id: "217",
|
||||
name: "产品营销背景",
|
||||
detail:
|
||||
"当用户需要创建或更新产品营销背景文档时使用。\n可用于沉淀目标用户、市场环境与竞争格局等关键信息。",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
suggestionsCreate: [
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ export interface SelectedSkillMessage {
|
|||
export interface SelectedSkillPayloadItem {
|
||||
id: string | number;
|
||||
name: string;
|
||||
detail?: string;
|
||||
}
|
||||
|
||||
type UnknownRecord = Record<string, unknown>;
|
||||
|
|
@ -107,8 +108,16 @@ export function isSelectedSkillsMessage(
|
|||
if (!skill) return false;
|
||||
const id = skill.id;
|
||||
const name = skill.name;
|
||||
const detail = skill.detail;
|
||||
const isValidId = typeof id === "string" || typeof id === "number";
|
||||
return isValidId && typeof name === "string" && name.trim().length > 0;
|
||||
const isValidDetail =
|
||||
detail === undefined || typeof detail === "string";
|
||||
return (
|
||||
isValidId &&
|
||||
typeof name === "string" &&
|
||||
name.trim().length > 0 &&
|
||||
isValidDetail
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,26 @@ function getThreadStorageKey(threadId?: string | null): string | null {
|
|||
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[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
|
|
@ -215,21 +235,8 @@ export function useIframeSkill(
|
|||
|
||||
// 4. 选择变化时同步到 sessionStorage
|
||||
useEffect(() => {
|
||||
const threadKey = getThreadStorageKey(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);
|
||||
}
|
||||
// 空数组也要同步到存储,避免 UI 状态与缓存不一致
|
||||
persistSkillsToSessionStorage(selectedSkills, threadId);
|
||||
}, [selectedSkills, threadId]);
|
||||
|
||||
// 发送选择预定义 skill
|
||||
|
|
@ -286,6 +293,15 @@ export function useIframeSkill(
|
|||
});
|
||||
|
||||
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({
|
||||
thread_id: threadId,
|
||||
content_ids,
|
||||
|
|
@ -308,12 +324,12 @@ export function useIframeSkill(
|
|||
}
|
||||
|
||||
sendSelectSkill(selectedSkills);
|
||||
const normalizedSkills = selectedSkills.map((item) => ({
|
||||
const latestSkills = selectedSkills.map((item) => ({
|
||||
skill_id: String(item.id),
|
||||
title: item.name,
|
||||
}));
|
||||
setSelectedSkill(normalizedSkills[0] ?? null);
|
||||
setSelectedSkills(normalizedSkills);
|
||||
setSelectedSkill(latestSkills[0] ?? null);
|
||||
setSelectedSkills(latestSkills);
|
||||
|
||||
toast.success(t.skills.loadSuccessWithTitle(title), {
|
||||
description: result.message || t.skills.createdFiles(result.created_files),
|
||||
|
|
|
|||
Loading…
Reference in New Issue