选择性整合 #547,保留主分支节点与工具条设计。新增默认折叠的节点、资产、任务和历史面板,风格与预设保留在节点内,移除重复入口。 补齐工具权限、提示词解析、资源引用保护及数据库迁移,修复提示词清空和连续编辑后的生成配置同步。 验证:前端全量 1925 项测试、构建、类型检查及 lint 通过;后端 go test ./... 通过。真实模型、PostgreSQL 实库与登录态页面验收仍待进行。 Refs #547 Co-authored-by: bensharp <bensharp@qq.com>
93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
import { useState } from "react";
|
|
import { Dropdown, type MenuProps } from "antd";
|
|
import { Grid3x3 } from "lucide-react";
|
|
|
|
import { TOOL_ICON_MAP } from "./canvas-resource-mention-textarea";
|
|
import { getNineGridMenuItems } from "./canvas-image-toolbar-tools";
|
|
|
|
function NineGridToolIcon({ iconName, className = "size-3.5 shrink-0" }: { iconName: string; className?: string }) {
|
|
const Icon = TOOL_ICON_MAP[iconName] ?? Grid3x3;
|
|
return <Icon className={className} />;
|
|
}
|
|
|
|
function buildNineGridMenuItems(onSelect: (toolId: number, label: string, icon: string) => void): MenuProps["items"] {
|
|
const items = getNineGridMenuItems();
|
|
const sections = new Map<string, typeof items>();
|
|
for (const tool of items) {
|
|
const list = sections.get(tool.section) || [];
|
|
list.push(tool);
|
|
sections.set(tool.section, list);
|
|
}
|
|
return [...sections].map(([section, entries]) => ({
|
|
type: "group" as const,
|
|
key: section,
|
|
label: section,
|
|
children: entries.map((tool) => ({
|
|
key: tool.id,
|
|
label: (
|
|
<div>
|
|
<span className="inline-flex items-center gap-2">
|
|
<NineGridToolIcon iconName={tool.toolIconName} />
|
|
{tool.label}
|
|
</span>
|
|
{tool.description ? <div className="text-[var(--fs-tiny)] opacity-60">{tool.description}</div> : null}
|
|
</div>
|
|
),
|
|
onClick: () => {
|
|
onSelect(tool.toolId, tool.label, tool.toolIconName);
|
|
},
|
|
})),
|
|
}));
|
|
}
|
|
|
|
export function CanvasNineGridPicker({
|
|
open,
|
|
onOpenChange,
|
|
icon = "Grid3x3",
|
|
onSelect,
|
|
}: {
|
|
open?: boolean;
|
|
onOpenChange?: (open: boolean) => void;
|
|
/** 当前已选九宫格工具的 lucide 图标名,无选择时回退 Grid3x3。 */
|
|
icon?: string;
|
|
onSelect: (toolId: number, label: string, icon: string) => void;
|
|
}) {
|
|
const [internalOpen, setInternalOpen] = useState(false);
|
|
const actualOpen = open ?? internalOpen;
|
|
const setOpen = (next: boolean) => {
|
|
setInternalOpen(next);
|
|
onOpenChange?.(next);
|
|
};
|
|
return (
|
|
<Dropdown
|
|
open={actualOpen}
|
|
onOpenChange={setOpen}
|
|
trigger={["click"]}
|
|
menu={{
|
|
items: buildNineGridMenuItems((toolId, label, toolIcon) => {
|
|
setOpen(false);
|
|
onSelect(toolId, label, toolIcon);
|
|
}),
|
|
}}
|
|
popupRender={(menu) => (
|
|
<div
|
|
className="canvas-node-toolbar-menu canvas-node-toolbar-menu-nine-grid"
|
|
data-canvas-no-zoom
|
|
data-canvas-wheel-scroll
|
|
onPointerDown={(event) => event.stopPropagation()}
|
|
onMouseDown={(event) => event.stopPropagation()}
|
|
onWheel={(event) => event.stopPropagation()}
|
|
onKeyDown={(event) => event.stopPropagation()}
|
|
>
|
|
<div className="canvas-node-toolbar-menu-stack">{menu}</div>
|
|
</div>
|
|
)}
|
|
>
|
|
<button type="button" className="canvas-node-composer-header-action canvas-node-fixed-chip" aria-expanded={actualOpen} aria-haspopup="menu" onClick={() => setOpen(true)} onPointerDown={(event) => event.stopPropagation()}>
|
|
<NineGridToolIcon iconName={icon} className="size-4 shrink-0" />
|
|
<span className="truncate">预设</span>
|
|
</button>
|
|
</Dropdown>
|
|
);
|
|
}
|