Skip to content

feat(desktop): file reference & rich input enhancement文件引用与富文本输入增强#6414

Closed
SauronSkywalker wants to merge 7 commits into
esengine:main-v2from
SauronSkywalker:feat/file-ref-rich-input_20260713_170000
Closed

feat(desktop): file reference & rich input enhancement文件引用与富文本输入增强#6414
SauronSkywalker wants to merge 7 commits into
esengine:main-v2from
SauronSkywalker:feat/file-ref-rich-input_20260713_170000

Conversation

@SauronSkywalker

@SauronSkywalker SauronSkywalker commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Pull Request:文件引用内联化、输入框命令高亮与多项体验修复


CI 合规说明

本 PR 的 CI 检查项覆盖:

检查 状态 说明
desktop/gofmt gofmt -l 输出为空
desktop/go mod tidy 未新增外部依赖,go.mod 无变化
desktop/pnpm install --frozen-lockfile pnpm-lock.yaml 无变化
desktop/pnpm build wails build 通过
desktop/go vet 无 vet 错误
desktop/go build 编译通过
desktop/golangci-lint ⚠️ 非修改文件可能触发,本地已过
Go test / test -race ✅ 后端改动不影响现有测试
site/npm test ⬜ 本次无 site 改动

变更总览(11 个文件)

1. attachmentDisplay.ts@path 显示修复 + highlightRefsInText

为什么改parseAttachmentRefsForDisplay() 中的 refTokenRe() 步骤将 @path 从显示文本剥离为卡片,但 workspace 卡片已被移除,导致 @path 在消息气泡中完全消失。

改了什么

  • 删除 .replace(refTokenRe(), ...) 整个步骤(~8 行)
  • 同步删除不再需要的 unescapeRefPath 导入
  • 删除全部空格处理代码(.replace(/[ \t]+....trim() 等 3 行)——CSS white-space: pre-wrap 已保证空格保留,JS 层额外折叠是多余的
  • 新增 highlightRefsInText() 导出函数:用 refTokenRe() 分割文本,返回 {text, isRef}[] 分段

效果@src/main.go 在消息气泡中正常显示,不再消失;空格原样保留。


2. RichComposerInput.tsx — 输入框命令高亮

为什么改:用户在输入 @src/main.go/help#history!shell 等 token 时没有视觉区分。

改了什么

  • 新增 CMD_HIGHLIGHT_RE 正则:/(^|\s)(@(?!\[)[^\s]+|/[A-Za-z][A-Za-z0-9_.:-]*|#[^\s]+|![^\s]+)/g
  • 新增 TRAILING_PUNCTUATION_RE:剥离尾部标点(,.;: 等)
  • 新增 renderHighlightedText():将文本分段为普通段和高亮段
  • 集成到 contentEditable 的 children 渲染中
  • 前导空白 match[1] 移出 <span>(避免空白变色)
  • span key 使用 match.index(稳定,避免 React 不必要的 DOM 重建)

效果:输入框中 @refs/commands#chats!shell 实时显示为主题强调色。


3. ComposerContextCard.tsx — 移除按钮事件修复

为什么改:点击附件卡片的 × 按钮同时触发了卡片的打开操作。

改了什么onClick={onRemove}onClick={(e) => { e.stopPropagation(); onRemove(); }}

效果:点击 × 只移除附件,不打开文件。


4. Message.tsx — 消息气泡改进

为什么改

  • workspace 引用已改为内联 @path,不应再显示为独立卡片
  • 卡片 meta 需要显示完整路径而非简短标签
  • 消息中的 @path 需要可点击打开文件
  • 含 slash 命令的消息也需要 @path 高亮

改了什么

  • cardAttachments = orderedAttachments.filter(a => a.source !== "workspace") → 引用不生成卡片
  • 卡片 meta 从 ${ext} · ${source} 改为 {attachment.path}
  • 非图片卡片 onClick={() => app.OpenWorkspacePath(path)} → 点击可打开文件
  • 两处消息渲染路径(普通消息 + invocation 消息)均应用 highlightRefsInText() + .ref-highlight--clickable
  • 新增 unescapeRefPath 导入,用于解析 @path 的转义字符

效果:消息气泡中的 @path 显示为高亮色、可点击打开文件;卡片显示完整路径;workspace 引用不在气泡中生成卡片。


5. styles.css — 高亮样式

为什么改:没有 CSS 类,高亮不可见。

改了什么:+14 行新样式:

.composer-cmd-highlight { color: var(--accent); }
.ref-highlight { color: var(--accent); }
.ref-highlight--clickable { cursor: pointer; }
.ref-highlight--clickable:hover { text-decoration: underline; }

6. Composer.tsx — 核心改动

6.1 文件引用改为内联 @path

为什么改:右键/拖拽/@菜单插入文件引用时生成卡片,用户无法原地编辑,路径被截断。

改了什么:4 个入口点(insertRequest effect、attachDroppedPathsonDroppickEntry)从调用 addWorkspaceReference() 改为在光标处插入 formatWorkspaceReference() 构建的 @path 文本。

效果:所有文件引用方式统一为内联 @path 文本,可原地编辑。

6.2 workspaceRefs 系统清理

为什么改:4 个入口不再写入 workspaceRefs 后,state/ref/draft/渲染代码全部变为死代码。

改了什么:删除 ~34 行代码,包括 workspaceRefs state、workspaceRefsRefworkspaceReferenceKeyaddWorkspaceReferenceaddWorkspaceReferenceToDraftremoveWorkspaceReference、相关 draft 字段和卡片渲染 JSX。

6.3 forceRich 自动切换

为什么改:textarea 不支持富文本渲染,插入 @path 后需要切换到 RichComposerInput 使命令高亮可见。

改了什么:新增 forceRich state,4 个入口插入 @pathsetForceRich(true),渲染条件改为 invocations.length > 0 || forceRich ?。提交后重置为 false。

附加保护

  • restoreComposerDraft 检测 @path 自动切富文本
  • draftKey 变化时重置 forceRich
  • 切换后 requestAnimationFrame 兜底焦点

6.4 拖放错误 7 种消息映射

为什么改:所有拖放失败统一显示"附件附加失败",用户无法定位问题。

改了什么:catch 块根据 Go 错误消息关键词匹配显示准确提示:

  • no such file or directoryFile not found
  • permission deniedPermission denied
  • attachment/image file is emptyFile is empty (0 bytes)
  • path is a directoryPath is a directory, not a file
  • must be between 1 byte and 25/10 MBFile/Image exceeds limit
  • workspace is not readyWorkspace is not ready
  • must not be a symlinkSymbolic links are not allowed
  • no space left on deviceDisk is full

6.5 文件拖放光标反馈

为什么改:鼠标拖文件到窗口任意位置都显示"复制"光标,但实际只有拖入输入框才生效。

改了什么

  • document 级 dragover 拦截 → 不可拖放区域 dropEffect='none' → 🚫 禁止符号
  • composer onDragOver 添加 stopPropagation + dropEffect='copy' → ✅ 复制符号

7. locales(en/zh/zh-TW)— 翻译补充

新增 "composer.attachDropEmptyFile" key:

  • en: File is empty (0 bytes), cannot attach
  • zh: 文件为空(0字节),无法附加
  • zh-TW: 文件為空(0位元組),無法附加

8. attachments.go — Go 后端错误拆分

为什么改:Go 端对文件为空、文件过大、目录返回完全相同的错误消息,前端无法区分。

改了什么:7 处检查点从合并条件拆分为三段独立判断:

IsDir()      → "path is a directory, not a file"
Size() <= 0  → "file is empty (0 bytes)"
Size() > max → "must be between 1 byte and X MB"

9. open_workspace_windows.go — 错误消息包含路径

为什么改:文件打开失败时只返回 "The system cannot find the file specified",不包含具体路径,难以排查。

改了什么:将 openWorkspacePath 的错误包装为 fmt.Errorf("open %q: %w", path, err),错误消息包含完整路径。


效果总结

场景 改前 改后
右键引用文件 生成卡片,不可编辑 @path 直接插入文本,可编辑
消息气泡 @path 被剥离消失 高亮显示,可点击打开
输入框 @refs/commands 纯文本无区分 实时高亮
空格渲染 多余处理导致消失 CSS 原样保留
拖放空文件 "附加失败" "File is empty (0 bytes)"
拖放 >25MB 文件 "附加失败" "File exceeds 25 MB limit"
拖放目录 "附加失败" "Path is a directory, not a file"
拖放光标误导 到处"复制" 可拖放区📋,不可拖放区🚫
PixPin_2026-07-18_16-03-17

@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) labels Jul 13, 2026
@SauronSkywalker
SauronSkywalker force-pushed the feat/file-ref-rich-input_20260713_170000 branch from e6ed1bf to e7a0f11 Compare July 18, 2026 02:46
Unify workspace file references (right-click/@-menu/drag-drop) into inline
@path text instead of workspaceRef cards. Keep the workspaceRefs system
intact — just redirect the 4 entry points to insert @path at cursor.

Changes:
- Composer.tsx: 4 entry points changed to insert @path; submit cleanup;
  empty-file error handling with new locale key
- RichComposerInput.tsx: command highlighting for @refs, /commands, #chats
- attachmentDisplay.ts: highlightRefsInText() for message bubble rendering
- ComposerContextCard.tsx: stopPropagation on remove button
- Message.tsx: filter workspace refs from cards, show full path, click-to-open
- styles.css: command/ref highlight styles
- locales: attachDropEmptyFile key (en/zh/zh-TW)
Add forceRich state: once a file reference is inserted as @path text,
the composer switches from textarea to RichComposerInput so command
highlighting works for @refs, /commands, #chats, !shell.

- New useState forceRich, default false
- Render ternary: invocations.length > 0 || forceRich → RichComposerInput
- setForceRich(true) in all 4 @path insertion entry points
- setForceRich(false) on draft clear
- Textarea remains default on fresh start, switches permanently after
  first @path insertion
Remove the now-unused workspace refs card system since all file
references are inserted as inline @path text. The workspaceRefs
state, ref, draft fields, card rendering, and related functions
(workspaceReferenceKey, removeWorkspaceReference) are no longer
needed because the 4 entry points now directly insert @path text.

Still preserved (actively used):
- workspaceRef: WorkspaceReference type (used by composePickFileEntry)
- formatWorkspaceReference / parseWorkspaceReference / reader
- WORKSPACE_REF_DRAG_TYPE for drag detection
- attachments.go: split empty/directory/oversize errors for accurate messages
- attachmentDisplay.ts: remove refTokenRe step (fix @path disappearing from bubbles);
  fold spaces to single instead of vanishing (both before punctuation and at edges)
- Message.tsx: add highlightRefsInText in invocation rendering path
- Composer.tsx: 7-category error mapping for attach failures; document-level drag
  interceptor (🚫 outside composer); restoreDraft forceRich detection;
  onDragOver stopPropagation to prevent interference
…path

- attachmentDisplay.ts: fully remove whitespace folding (user request)
- RichComposerInput.tsx: use match.index as span key (stable); split leading
  whitespace out of highlight span; guard empty core
- open_workspace_windows.go: wrap path into error message for debugging
@github-actions github-actions Bot added the agent Core agent loop (internal/agent, internal/control) label Jul 18, 2026
…ssion file locks

Windows-only: WebView2 (msedgewebview2.exe) child processes may persist
after the main window closes, holding file locks on session data.
- killWebView2Processes(): enumerate processes via CreateToolhelp32Snapshot,
  find msedgewebview2.exe with ParentProcessID == our PID, TerminateProcess
- killOrphanWebView2(): startup-time cleanup for crash-leftover orphans
- Unix stubs for macOS/Linux (no WebView2)
@SauronSkywalker
SauronSkywalker deleted the feat/file-ref-rich-input_20260713_170000 branch July 18, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Core agent loop (internal/agent, internal/control) desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant