Skip to content

feat: link files & folders to the global AI chat#9520

Open
Guilhem-lm wants to merge 6 commits into
mainfrom
glm/add-files-to-ai-chat
Open

feat: link files & folders to the global AI chat#9520
Guilhem-lm wants to merge 6 commits into
mainfrom
glm/add-files-to-ai-chat

Conversation

@Guilhem-lm

Copy link
Copy Markdown
Contributor

Summary

Lets users attach files and link folders to the global AI chat (sessions). Attached content is not inlined into the prompt context — instead the assistant gets search_files / read_file tools plus an auto-injected roster, so it pulls exactly the lines it needs across many/large files without blowing up the context window.

Everything is frontend-only and nothing is uploaded to a server:

  • Files are kept locally as an IndexedDB Blob snapshot — works in every browser.
  • Folders are linked live from disk via the File System Access API (capable browsers); the assistant re-reads the folder's current files and refreshes each turn.

Highlights

  • 📎 Attach files / link folders from the + menu or by drag-and-drop. Junk (node_modules, dotfiles) is skipped; a folder collapses to a single chip.
  • 🌳 @-menu file picker — attached files are pickable from @, and a linked folder's files render as a nested directory tree. Picking inserts an @filename mention.
  • 🎨 File/folder chips reuse the context-element chip style; @filename mentions highlight in the input just like context mentions.
  • 💾 Persists across reload, scoped to the session: file snapshots restore automatically; live folder handles re-grant on the next send (one click).

Screenshots

@-menu — a linked folder's files as a nested tree (chips above match the context chips):

@-menu nested file tree

@filename mentions highlight in the input like context mentions (@legacy.ts is not attached, so it stays plain):

chips and mention highlight

How it works

  • fileEngine builds a line-offset index and lazily slices files (operates on Blob, so File | Blob both work — no full-content reads).
  • fileTools exposes search_files (regex) and read_file (line ranges) to the assistant; a compact roster is appended to the system message.
  • Persistence: attachedFilesDB (idb) stores Blob snapshots + re-grantable FS-Access handles keyed by session.id — restore on session activation, re-grant locked handles on send, flush on persist, GC on session delete. Capability via feature-detection (fsAccess), never UA sniffing. Folders re-enumerate + reconcile each send so renames/adds/deletes are picked up.
  • @-menu integrates as a Files branch in the unified DrillPicker (ChatContextPicker).

Testing

  • 38 unit tests across fileEngine, fsAccess, attachedFilesDB, attachedFiles (incl. folder refresh diffing) and folderDrop.
  • Verified end-to-end in a real browser (Playwright): attach/link, @-menu nested tree, persistence round-trip, and mention highlighting.

Frontend-only; GLOBAL chat (sessions) only. No backend/OpenAPI changes.

🤖 Generated with Claude Code

Guilhem-lm and others added 3 commits June 10, 2026 15:38
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e UI

Persistence (survive reload, scoped to session.id):
- IndexedDB store (attachedFilesDB) holding Blob snapshots (every browser)
  and re-grantable File System Access directory handles (capable browsers)
- restore on session activation; re-grant locked handles on the next send;
  flush in-memory items when the session persists; GC on session delete
- capability via feature-detection (fsAccess), never UA sniffing
- folders auto-refresh (live re-enumerate + reconcile) on each send

@-mention file picker:
- Files branch in ChatContextPicker (new DrillPicker architecture); a linked
  folder's files render as a nested directory tree, picking inserts @filename
- attached-file mentions highlight in the input just like context mentions

UI polish:
- file/folder chips reuse the context-element chip style (icon -> X on hover)
- file + context badges sit above the fork/draft bar
- disabled dropdown items can surface an explanatory tooltip (DropdownV2Inner)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploying windmill with  Cloudflare Pages  Cloudflare Pages

Latest commit: 35de6e3
Status: ✅  Deploy successful!
Preview URL: https://768e546a.windmill.pages.dev
Branch Preview URL: https://glm-add-files-to-ai-chat.windmill.pages.dev

View logs

Guilhem-lm and others added 2 commits June 10, 2026 17:51
…jects

Two seam fixes from an architecture pass, no behaviour change:

- addFolder(dirHandle) now enumerates internally (same junk-filtered walk
  used on restore/refresh), so callers never pre-enumerate. The dead
  drop-walkers (collectDroppedEntries, filterFolderPickerFiles) are deleted;
  isIgnoredPath/MAX_FOLDER_FILES move next to enumerateDir in fsAccess.

- The store exposes `folders` (name + aggregate status + children) and
  `standalone` as derived views, so the bar, the @-menu picker, the folder
  chip and the system-prompt roster stop re-grouping the flat row list and
  re-deriving folder status. Placeholder rows (isFolderRoot) become an
  implementation detail; the roster renders a locked folder as one line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In GLOBAL mode selected context already appears as a highlighted @mention
in the input (deleting the mention deselects), so the hoisted badge row
above the chat duplicated it. File chips keep their row — attachments
aren't represented in the input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Guilhem-lm Guilhem-lm marked this pull request as ready for review June 10, 2026 16:45
- requestReadPermission/queryReadPermission never reject (the spec rejects
  with SecurityError when user activation is missing — now mapped to
  denied/prompt), and sendRequest wraps attachment upkeep in try/catch, so
  a permission hiccup can never silently swallow a Send.
- regrantLocked expands before dropping the locked placeholder: when the
  re-granted directory is gone from disk, the folder now shows
  "unavailable" instead of vanishing into a zombie that resurrects locked
  on the next reload.
- addFolder: re-picking a locked/unavailable folder relinks it (natural
  recovery gesture); a genuine second folder with the same basename gets a
  visible "already linked" rejection instead of a silent no-op.
- fileEngine: readFile clamps its byte slice to maxChars*4 before decoding
  and streamLines caps its per-line buffer, so newline-sparse files
  (minified JS, single-line JSONL) can't materialize unbounded strings;
  corrected the scan-cap comment's claim about catastrophic backtracking.

4 new unit tests (41 total).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant