fix: 工作流画布顶栏——名称框随内容自适应且限制中英文长度,保存/运行按钮靠右始终可见
This commit is contained in:
@@ -193,6 +193,7 @@
|
||||
|
||||
### 2026-08-16
|
||||
|
||||
- 工作流画布顶栏修正:名称输入框改为随内容自适应宽度(不再占大块空白),名称长度限制为英文 ≤150 字符 / 中文 ≤50 字符(混排按中文字符权重折算);顶栏改为可收缩布局,保证「保存 / 运行」按钮始终靠右可见不被挤出。
|
||||
- 工作流画布增强:节点支持从左侧面板拖入;画布滚轮缩放(25%–250%)与空白处拖动平移;节点右键菜单(修改名称/说明/收藏/复制/删除)、连线右键删除(单击不再误删);右侧边栏只显示选中节点的选项并移除“各节点输出”总列表;新增画布导入/导出 JSON;顶栏「返回工作流」单行显示、名称框固定宽度、保存/运行右对齐。
|
||||
- 新增「工作流」选项卡:画布式节点编辑器(开始/大模型/文本/输出,节点连线传参、拖动布局),内置 4 个预制工作流(翻译助手、内容总结、两步润色、写作助手)并支持自定义与恢复;新增 `workflows` 表与 `run_workflow` 命令,后端 `apps/desktop/src/workflow.rs` 执行引擎支持拓扑排序、模板变量(`{{input}}`/`{{节点id}}`)、循环检测,LLM 节点支持本地/在线大模型(节点 → 工作流 → 运行参数 → 自动回退);`workflow://node-status` 事件实时推送节点状态;5 个引擎单元测试 + 1 个真实模型端到端测试全部跑通。
|
||||
- 新增「智能体」选项卡:内置 6 个预制智能体 + 支持自定义智能体(名称/图标/描述/系统提示词/默认模型/启用开关);每个智能体拥有独立会话区(`conversations.agent_id` 隔离),新建会话自动继承智能体系统提示词与默认模型;系统提示词统一注入 `chat_send` / `regenerate_message` / `edit_message` 链路;删除智能体时级联删除其会话;支持「恢复预制智能体」。
|
||||
|
||||
@@ -56,6 +56,30 @@ function newId(prefix: string) {
|
||||
return `${prefix}_${Date.now().toString(36)}${Math.floor(Math.random() * 1000)}`;
|
||||
}
|
||||
|
||||
/** 中文字符(CJK)按 3 个权重计,其余按 1:英文最多 150 字、中文最多 50 字,混排按权重折算。 */
|
||||
const NAME_MAX_WEIGHT = 150;
|
||||
|
||||
function isCjkChar(ch: string) {
|
||||
const code = ch.codePointAt(0) ?? 0;
|
||||
return (
|
||||
(code >= 0x4e00 && code <= 0x9fff) ||
|
||||
(code >= 0x3400 && code <= 0x4dbf) ||
|
||||
(code >= 0xf900 && code <= 0xfaff)
|
||||
);
|
||||
}
|
||||
|
||||
function clampWorkflowName(value: string) {
|
||||
let weight = 0;
|
||||
let out = "";
|
||||
for (const ch of value) {
|
||||
const w = isCjkChar(ch) ? 3 : 1;
|
||||
if (weight + w > NAME_MAX_WEIGHT) break;
|
||||
weight += w;
|
||||
out += ch;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export default function WorkflowCanvasPage() {
|
||||
const { workflowId } = useParams<{ workflowId: string }>();
|
||||
const workflows = useStore((s) => s.workflows);
|
||||
@@ -528,7 +552,7 @@ export default function WorkflowCanvasPage() {
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{/* 顶部工具栏 */}
|
||||
<div className="flex items-center gap-3 border-b border-border bg-panel px-4 py-2.5">
|
||||
<div className="flex items-center gap-2 border-b border-border bg-panel px-3 py-2.5 sm:gap-3 sm:px-4">
|
||||
<Link
|
||||
to="/workflows"
|
||||
className="flex shrink-0 items-center gap-1 whitespace-nowrap rounded-lg px-2 py-1 text-xs text-slate-400 hover:bg-panel-2 hover:text-slate-200"
|
||||
@@ -537,10 +561,12 @@ export default function WorkflowCanvasPage() {
|
||||
返回工作流
|
||||
</Link>
|
||||
<input
|
||||
className="input w-44 flex-none px-2 py-1 text-sm"
|
||||
className="input min-w-0 px-2 py-1 text-sm"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
onChange={(e) => setName(clampWorkflowName(e.target.value))}
|
||||
placeholder="工作流名称"
|
||||
title="英文不超过 150 个字符,中文不超过 50 个(混排按中文字符权重折算)"
|
||||
style={{ width: `${Math.min(44, Math.max(6, [...name].length + 2))}ch` }}
|
||||
/>
|
||||
<span
|
||||
className="shrink-0 rounded bg-panel-2 px-1.5 py-0.5 text-[10px] text-slate-400"
|
||||
@@ -548,10 +574,14 @@ export default function WorkflowCanvasPage() {
|
||||
>
|
||||
{workflow.kind === "preset" ? "预制" : "自定义"}
|
||||
</span>
|
||||
<span className="shrink-0 text-xs text-slate-500">
|
||||
<span className="min-w-0 truncate text-xs text-slate-500">
|
||||
{nodes.length} 节点 · {edges.length} 连线
|
||||
</span>
|
||||
{msg ? <span className="shrink-0 text-xs text-emerald-400">{msg}</span> : null}
|
||||
{msg ? (
|
||||
<span className="min-w-0 max-w-[140px] truncate text-xs text-emerald-400">
|
||||
{msg}
|
||||
</span>
|
||||
) : null}
|
||||
<div className="ml-auto flex shrink-0 items-center gap-2">
|
||||
<button className="btn-secondary" onClick={() => saveWorkflow()}>
|
||||
保存
|
||||
|
||||
Reference in New Issue
Block a user