diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bfaf1dfe..2b765ff1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,7 +58,16 @@ jobs: # Gate regressions without forcing a flag-day cleanup of the existing # lint debt (mostly upstream adk.AgentMiddleware deprecations). New # issues introduced by a PR still fail. - only-new-issues: true + # + # We compute "new issues" locally with --new-from-rev instead of the + # action's only-new-issues flag: the latter fetches the PR diff from + # GitHub's API, which rejects diffs over 20k lines (this repo's + # generated registry + frontend churn exceed that). --new-from-rev + # diffs against the local origin/main checkout (fetch-depth: 0), so it + # has no line limit. On pushes to main, new-from-rev is empty ⇒ a full + # run; that path doesn't run because pushes target main directly and + # the gate is meant for PRs. + args: ${{ github.event_name == 'pull_request' && '--new-from-rev=origin/main' || '' }} web: name: Web (type-check · build) diff --git a/design/automations-redesign.html b/design/automations-redesign.html deleted file mode 100644 index 9ac78b20..00000000 --- a/design/automations-redesign.html +++ /dev/null @@ -1,1596 +0,0 @@ - - - - - -jcode — Automations Redesign - - - - - - - - - - -
-
-
-

Automations — Redesign

-

- A spec rendering of the AutomationsView page. The card is restructured - around when it runs next, runs become a status-coloured table, and the empty / - templates / editor states share one surface geometry. Tokens are verbatim from - tokens.css — toggle the theme to preview dark. -

-
-
- - -
-
- - -
-
- 01 -

Page surface — full page at rest

-
-
-
-
-

Automations

-
-
- - -
- -
-
-
-
-

Use agents to handle recurring work on a cadence you choose.

-
- -
-
-
- Daily code review - Daily -
- Ran ok -
-

Review open PRs on main, summarise risks, and post a thread to the team channel with suggested reviewers.

-
- Schedule - Daily · 09:00 - - - - - - - Next run - in 6h 12m -
-
- - -
-
-
- Weekly dependency audit - Weekly -
- Running -
-

Check go.sum for known CVEs, open issues for anything critical, and draft a bump PR for safe patches.

-
- Schedule - Mon · 04:00 - - - - - - - Started - 2m ago -
-
- - -
-
-
- Triange stale issues - Manual -
-
-

Close issues with no activity for 90 days after leaving a comment with a reopen link.

-
- Trigger - Run manually - - - - - - - Last run - 3d ago · ok -
-
-
- - -
-

Recent runs

-
- - -
-
-
-
-
- Daily code review - - Jun 24, 09:00 - · - schedule - -
- 42s -
-
-
- Weekly dependency audit - - Jun 24, 04:00 - · - schedule - -
- running… -
-
-
- Triange stale issues - - Jun 21, 18:22 - · - manual - - approval_required: tool edit needs confirmation — manual runs with Ask mode wait for a human. -
- 1m 03s -
-
-
- Daily code review - - Jun 23, 09:00 - · - schedule - -
- 38s -
-
-
-
-
-
-

- Anatomy change. The current card puts name, status, schedule, and four icon - buttons all in one crowded foot row. The redesign separates them into a status - strip + title/cadence header, a prompt, and a labelled Schedule / Next run - meta grid. "Next run" is the one number a scheduled automation exists to surface — it - now carries the brand accent so the eye finds it first. The colour strip on the card's - leading edge matches the strip on each run row, so state reads the same in both places. -

-
- - -
-
- 02 -

Card states — the four real statuses

-
-
-
-
-
-
IdleDaily
- Ran ok -
-

Healthy, last run succeeded, waiting for the next window.

-
- ScheduleDaily · 09:00 - - - - - - - Next runin 6h 12m -
-
- -
-
-
RunningWeekly
- Running -
-

Active right now — run button pulses and disables, strip turns brand orange.

-
- ScheduleMon · 04:00 - - - - - - - Started2m ago -
-
- -
-
-
FailedHourly
- Failed -
-

Last run errored. The strip + chip turn destructive red — the schedule still shows, so you know it'll retry next window.

-
- ScheduleHourly · :00 - - - - - - - Next runin 41m -
-
- -
-
-
PausedDaily
-
-

Disabled via the switch — whole card dims, the grey strip signals "not armed", no next-run line.

-
- ScheduleDaily · 09:00 - - - - - - - StatusPaused -
-
-
-
-

- State = strip colour + chip. Today a success/error is just a 3.5px icon buried - in the foot row — easy to miss, and indistinguishable from decoration. The redesign - promotes it to a labelled chip (Ran ok / Running / Failed) and mirrors it on the - left edge. Paused drops the chip entirely and dims the card, since "paused" is the - absence of a run state rather than one. -

-
- - -
-
- 03 -

Empty state — first-run

-
-
-
-
-

Automations

-
-
- - -
- -
-
-
-
-
-
-
No automations yet
-
Use agents to handle recurring work on a cadence you choose — code reviews, dependency audits, issue triage.
-
- - -
-
-
-
-
-
-
- - -
-
- 04 -

Templates — gallery

-
-
-
-
-

Automations

-
-
- - -
- -
-
-
-
-

Start from a template — pick a project and confirm.

-
- - - - -
-
-
-
-
-

- Template cards gain a small mode + trigger foot line (Autopilot/Ask/Plan · - schedule/manual) so you know what you're committing to before you click — the current - card only shows name + badge. -

-
- - -
-
- 05 -

Editor dialog — create / edit

-
-
-
- -
-
-
- - -
-
- 06 -

Run detail — a finished run, replayed

-
-
- -
-
- -
- Daily code review - Completed -
-
- triggerschedule - · - windowDaily 09:00 - · - ranJun 24, 09:00 - · - duration42s - · - tools6 calls - · - tokens4,812 -
-
- -
-
- -
-
-
-
Automation prompt
-
Review open PRs on main, summarise risks, and post a thread to the team channel with suggested reviewers.
-
-
- - -
-
- - read - internal/automation/scheduler.go -
-
- - -
-
- - execute - git log --oneline -5 origin/main -
-
-
$ git log --oneline -5 origin/main -950efe8 feat(web): sidebar nav redesign + Channels page -099b133 fix(automations): keep the left sidebar visible -d1f61c0 fix(automations): align overlay header -5073afd fix(automations): resolve CI lint -8f08269 feat(automations): scheduled & manual tasks
-
-
- - -
-
- - edit - internal/web/automation_api.go - +12/−3 -
-
- - -
-
- - jcode -
-
-

Reviewed the 3 open PRs on main. Two are low-risk doc/CI tweaks; #412 changes the scheduler's locking and needs a closer look.

-

Summary thread posted to #eng-reviews with suggested reviewers: @ada for #412, @bo for the rest.

-
-
42s · 4,812 tokens
-
-
-
- -
-
- Completed in 42s · 6 tool calls. -
- - -
-
-
-
-
-

- The run prompt is not a user message. The single piece the chat canvas lacks is - the trigger itself — here it's a brand-tinted block with an AUTOMATION PROMPT - eyebrow + bolt icon, so the timeline opens with "this came from the schedule at 09:00", - not an anonymous U avatar. Everything below reuses ChatMessage + - ToolCallCard verbatim — the run's session_id is just a session - replayed via api.session(), so no new renderer is needed. The header packs - the six facts you'd otherwise hunt for (trigger · window · when · duration · tool count · - tokens) into one dense line, and the footer is a Run again action, not a composer. -

-
- - -
-
- 07 -

Run detail — a live run, mid-execution

-
-
-
-
- -
- Weekly dependency audit - Running -
-
- triggerschedule - · - windowMon 04:00 - · - started2m ago - · - tools4 calls - · - tokens3,107 -
-
- -
-
-
-
-
-
Automation prompt
-
Check go.sum for known CVEs, open issues for anything critical, and draft a bump PR for safe patches.
-
-
- - -
-
- - execute - govulncheck ./… -
-
-
$ govulncheck ./… -=== Symbol Results === -Vulnerability #1: GO-2024-2687 (golang.org/x/crypto) - Found in: golang.org/x/crypto@v0.21.0 - Fixed in: golang.org/x/crypto@v0.22.0
-
-
- - -
-
- - grep - scanning go.sum references… -
-
- - -
- - Thinking… - 2m 04s -
-
-
- -
-
- Running for 2m 04s · 4 tool calls so far. -
- -
-
-
-
-
-

- Same surface, live. A running automation lands here the same way — clicked from its - card (status chip Running) or from the running… run row. The differences are - minimal: the active tool's name shimmers like the chat canvas, the Thinking… - footer with a live timer trails the timeline, and the footer swaps Run again for a - Stop run button. Because the session is streamed over the same WebSocket events the - chat page already handles (onAgentText, onToolCall, - onToolResult), wiring this up is pointing the existing handlers at the run's - session_id rather than building new plumbing. -

-
-
- - - - diff --git a/design/jcode-web.pen b/design/jcode-web.pen deleted file mode 100644 index 7cd44874..00000000 --- a/design/jcode-web.pen +++ /dev/null @@ -1,9983 +0,0 @@ -{ - "version": "2.11", - "children": [ - { - "type": "frame", - "id": "X41rW", - "x": 1457, - "y": 2793, - "name": "JCODE Web UI", - "width": 1440, - "height": 900, - "fill": "$--background", - "children": [ - { - "id": "tteLM", - "type": "ref", - "ref": "XBTQE", - "name": "Web Sidebar", - "x": 0, - "y": 0, - "width": 320, - "height": "fill_container" - }, - { - "type": "frame", - "id": "mU3hW", - "name": "Web Main", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "id": "CBYOl", - "type": "ref", - "ref": "jBVb4", - "name": "tb1" - }, - { - "type": "frame", - "id": "gJQea", - "name": "Web Chat", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "c9f34", - "name": "chatCenter", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "gap": 24, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "PsBzD", - "name": "logo", - "reusable": true, - "gap": 2, - "children": [ - { - "type": "text", - "id": "I1SQj", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - }, - { - "type": "text", - "id": "YiNRu", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - }, - { - "type": "text", - "id": "xqAtp", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - } - ] - }, - { - "type": "text", - "id": "cAI2S", - "fill": "$--foreground", - "content": "What would you like to build?", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "hmjJc", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 400, - "content": "Send a message to start a conversation with jcode. Use / for commands.", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "fNkkS", - "name": "chatInput1", - "width": "fill_container", - "height": 148, - "fill": "#F6F7F4", - "cornerRadius": 10, - "stroke": { - "align": "inside", - "thickness": 0, - "fill": "#00000000" - }, - "layout": "vertical", - "gap": 8, - "padding": [ - 6, - 14 - ], - "children": [ - { - "type": "frame", - "id": "nUPvS", - "name": "inputArea", - "width": "fill_container", - "height": 124, - "fill": "$--card", - "cornerRadius": 9, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "uzQet", - "name": "inputTop", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "padding": [ - 14, - 16, - 4, - 16 - ], - "children": [ - { - "type": "text", - "id": "XdktG", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ask JCODE, @agent, or /command", - "lineHeight": 1.5, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "l46Hvr", - "name": "inputBot", - "width": "fill_container", - "padding": [ - 8, - 12, - 12, - 12 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "RKMhQ", - "name": "inputBotL", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "Hjbpt", - "name": "attachBtn", - "cornerRadius": 6, - "gap": 5, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "arPhe", - "width": 13, - "height": 13, - "iconFontName": "paperclip", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "TDrsT", - "name": "agentBtn", - "height": 25, - "cornerRadius": 6, - "gap": 4, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "gShtw", - "fill": "$--foreground", - "content": "Agent", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "dDns2", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "C48MS", - "name": "modelBtn", - "height": 25, - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "u7FmN", - "fill": "$--foreground", - "content": "MiniMax-M2.7", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "nXGMd", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "wLUbg", - "name": "autoBtn", - "width": 36, - "height": 20, - "fill": "$--primary", - "cornerRadius": 999, - "gap": 6, - "padding": [ - 2, - 2, - 2, - 18 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "O4AY5n", - "name": "ellipse", - "fill": "$--white", - "width": 16, - "height": 16 - } - ] - } - ] - }, - { - "type": "frame", - "id": "jm0qY", - "name": "inputBotR", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "PdgM8", - "name": "sendBtn", - "fill": "$--primary", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "X26QeV", - "width": 12, - "height": 12, - "iconFontName": "arrow-up", - "iconFontFamily": "lucide", - "fill": "#111111" - }, - { - "type": "text", - "id": "J4lEO", - "fill": "#111111", - "content": "Send", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "g3DgZ", - "x": 2970, - "y": 2793, - "name": "JCODE Web UI - Terminal", - "width": 1440, - "height": 900, - "fill": "$--background", - "children": [ - { - "id": "quKqp", - "type": "ref", - "ref": "XBTQE", - "name": "Web Sidebar", - "width": 320, - "height": "fill_container" - }, - { - "type": "frame", - "id": "HO8CV", - "name": "Web Main", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "id": "chG8E", - "type": "ref", - "ref": "jBVb4", - "name": "tb2", - "descendants": { - "Z2h0B": { - "fill": "$--foreground" - } - } - }, - { - "type": "frame", - "id": "KF0j6", - "name": "Web Chat", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "sIogR", - "name": "chatCenter", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "gap": 24, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "KZUwk", - "name": "logoFrame", - "gap": 2, - "children": [ - { - "type": "text", - "id": "01WZ3", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - }, - { - "type": "text", - "id": "VCmOU", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - }, - { - "type": "text", - "id": "q7wsT", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - } - ] - }, - { - "type": "text", - "id": "9U2qw", - "fill": "$--foreground", - "content": "What would you like to build?", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "3Tu9S", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 400, - "content": "Send a message to start a conversation with jcode. Use / for commands.", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "nEJRl", - "name": "chatInput", - "width": "fill_container", - "height": 148, - "fill": "#F6F7F4", - "cornerRadius": 10, - "stroke": { - "align": "inside", - "thickness": 0, - "fill": "#00000000" - }, - "layout": "vertical", - "gap": 8, - "padding": [ - 6, - 14 - ], - "children": [ - { - "type": "frame", - "id": "twTHD", - "name": "inputArea", - "width": "fill_container", - "height": 124, - "fill": "$--card", - "cornerRadius": 9, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "NJmpz", - "name": "inputTop", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "padding": [ - 14, - 16, - 4, - 16 - ], - "children": [ - { - "type": "text", - "id": "KM9pw", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ask JCODE, @agent, or /command", - "lineHeight": 1.5, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "yLKLQ", - "name": "inputBot", - "width": "fill_container", - "padding": [ - 8, - 12, - 12, - 12 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "MZgTW", - "name": "inputBotL", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "kZFMP", - "name": "attachBtn", - "cornerRadius": 6, - "gap": 5, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "WhIcB", - "width": 13, - "height": 13, - "iconFontName": "paperclip", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "WqhHQ", - "name": "agentBtn", - "height": 25, - "cornerRadius": 6, - "gap": 4, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "CVXFJ", - "fill": "$--foreground", - "content": "Agent", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "Mz6F0", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "k5iQy", - "name": "modelBtn", - "height": 25, - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "c1S8pf", - "fill": "$--foreground", - "content": "MiniMax-M2.7", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "Su7BI", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "Bki65", - "name": "autoBtn", - "width": 36, - "height": 20, - "fill": "$--primary", - "cornerRadius": 999, - "gap": 6, - "padding": [ - 2, - 2, - 2, - 18 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "q6gYoM", - "name": "ellipse", - "fill": "$--white", - "width": 16, - "height": 16 - } - ] - } - ] - }, - { - "type": "frame", - "id": "yIOYr", - "name": "inputBotR", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "nwR3h", - "name": "sendBtn", - "fill": "$--primary", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "MpxVg", - "width": 12, - "height": 12, - "iconFontName": "arrow-up", - "iconFontFamily": "lucide", - "fill": "#111111" - }, - { - "type": "text", - "id": "rNVjN", - "fill": "#111111", - "content": "Send", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "bn0nd", - "name": "Terminal Panel", - "width": "fill_container", - "height": 200, - "fill": "$--card", - "stroke": { - "align": "inside", - "thickness": { - "top": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "dgzt0", - "name": "Terminal Tab Bar", - "width": "fill_container", - "height": 36, - "gap": 16, - "padding": [ - 0, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "Rgf1o", - "fill": "$--foreground", - "content": "Terminal 1", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "icon_font", - "id": "HjYdS", - "width": 12, - "height": 12, - "iconFontName": "plus", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "WuF20", - "width": 12, - "height": 12, - "iconFontName": "x", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "fukmu", - "name": "Terminal Content", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "gap": 4, - "padding": [ - 8, - 12 - ], - "children": [ - { - "type": "text", - "id": "9lHEI", - "fill": "$--foreground", - "content": "$ temp", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "rectangle", - "id": "efIn4", - "fill": "$--foreground", - "width": 8, - "height": 2 - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "2Ovo9", - "x": 1457, - "y": 3786, - "name": "JCODE Web UI - Changes", - "width": 1440, - "height": 900, - "fill": "$--background", - "children": [ - { - "id": "B1x4u", - "type": "ref", - "ref": "XBTQE", - "name": "Web Sidebar", - "width": 320, - "height": "fill_container" - }, - { - "type": "frame", - "id": "x0SbT", - "name": "Web Main", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "id": "LDxNL", - "type": "ref", - "ref": "jBVb4", - "name": "tb3" - }, - { - "type": "frame", - "id": "37bf9", - "name": "Web Chat", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "bQhnA", - "name": "welcome center", - "reusable": true, - "width": "fill_container", - "height": "fill_container", - "layout": "none", - "children": [ - { - "type": "frame", - "id": "cK2NJ", - "x": 319.5, - "y": 283, - "name": "logoFrame", - "gap": 2, - "children": [ - { - "type": "text", - "id": "G40RK", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - }, - { - "type": "text", - "id": "oeyqe", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - }, - { - "type": "text", - "id": "WgWyE", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 32, - "fontWeight": "700" - } - ] - }, - { - "type": "text", - "id": "27r8O", - "x": 249.5, - "y": 349, - "fill": "$--foreground", - "content": "What would you like to build?", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "mH90R", - "x": 190, - "y": 399, - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 400, - "content": "Send a message to start a conversation with jcode. Use / for commands.", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "T7dgeO", - "name": "chatInput2", - "width": "fill_container", - "height": 148, - "fill": "#F6F7F4", - "cornerRadius": 10, - "stroke": { - "align": "inside", - "thickness": 0, - "fill": "#00000000" - }, - "layout": "vertical", - "gap": 8, - "padding": [ - 6, - 14 - ], - "children": [ - { - "type": "frame", - "id": "iWbC0", - "name": "inputArea", - "width": "fill_container", - "height": 124, - "fill": "$--card", - "cornerRadius": 9, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "sl4Rl", - "name": "inputTop", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "padding": [ - 14, - 16, - 4, - 16 - ], - "children": [ - { - "type": "text", - "id": "rnK14", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ask JCODE, @agent, or /command", - "lineHeight": 1.5, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "XcH1i", - "name": "inputBot", - "width": "fill_container", - "padding": [ - 8, - 12, - 12, - 12 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "lGKZx", - "name": "inputBotL", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "uktLr", - "name": "attachBtn", - "cornerRadius": 6, - "gap": 5, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "XYvDR", - "width": 13, - "height": 13, - "iconFontName": "paperclip", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "Kqish", - "name": "agentBtn", - "height": 25, - "cornerRadius": 6, - "gap": 4, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "NN6Yw", - "fill": "$--foreground", - "content": "Agent", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "s5Vp4h", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "iCASt", - "name": "modelBtn", - "height": 25, - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "f4ojm", - "fill": "$--foreground", - "content": "MiniMax-M2.7", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "c9dRk", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "KlZNt", - "name": "autoBtn", - "width": 36, - "height": 20, - "fill": "$--primary", - "cornerRadius": 999, - "gap": 6, - "padding": [ - 2, - 2, - 2, - 18 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "Tt6k3", - "name": "ellipse", - "fill": "$--white", - "width": 16, - "height": 16 - } - ] - } - ] - }, - { - "type": "frame", - "id": "L2Gtj", - "name": "inputBotR", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "wn03I", - "name": "sendBtn", - "fill": "$--primary", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "zrCnw", - "width": 12, - "height": 12, - "iconFontName": "arrow-up", - "iconFontFamily": "lucide", - "fill": "#111111" - }, - { - "type": "text", - "id": "q8tWx", - "fill": "#111111", - "content": "Send", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "wtUEi", - "name": "Web Changes Panel", - "width": 400, - "height": "fill_container", - "fill": "#FAFAF8", - "stroke": { - "thickness": { - "left": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "CrFx3", - "name": "panelTop", - "width": "fill_container", - "height": 46, - "stroke": { - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "padding": [ - 0, - 14 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "bdtFd", - "fill": "$--foreground", - "content": "Review +32 -0", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "Fs6Li", - "name": "tabsRow", - "width": "fill_container", - "height": 34, - "gap": 16, - "padding": [ - 0, - 14 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "NfeyI", - "name": "tabItem", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ZbyzE", - "fill": "$--foreground", - "content": "Last turn", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "icon_font", - "id": "XiFNq", - "width": 14, - "height": 14, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "frame", - "id": "tiCOG", - "name": "content", - "width": "fill_container", - "height": 816, - "layout": "vertical", - "gap": 8, - "padding": [ - 12, - 0 - ], - "children": [ - { - "type": "frame", - "id": "sISwl", - "name": "zRow", - "width": "fill_container", - "padding": [ - 0, - 16 - ], - "children": [ - { - "type": "text", - "id": "UgsAy", - "fill": "#111111", - "content": "2 files changed", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "r1u2N", - "name": "allRow", - "width": "fill_container", - "gap": 6, - "padding": [ - 4, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "k9bjM", - "width": 14, - "height": 14, - "iconFontName": "folder-tree", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "1st1J", - "fill": "$--muted-foreground", - "content": "All changes", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "rZ2SX", - "name": "diffCard", - "clip": true, - "width": "fill_container", - "fill": "#FFFFFF", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#CBCCC9" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "SJOw9", - "name": "diffHead", - "width": "fill_container", - "fill": "#F2F3F0", - "gap": 8, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "WDwUU", - "name": "diffHeadText", - "fill": "#111111", - "content": "tests/auth_tests.rs", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "w90UMa", - "name": "diffHeadCount", - "fill": "#15803D", - "content": "+32", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "dg89z", - "name": "hunk", - "width": "fill_container", - "layout": "vertical", - "gap": 1, - "padding": [ - 6, - 0 - ], - "children": [ - { - "type": "frame", - "id": "myI5j", - "name": "row1", - "width": "fill_container", - "fill": "#EAF6EC", - "gap": 8, - "padding": [ - 3, - 10 - ], - "children": [ - { - "type": "text", - "id": "dzdH9", - "name": "ln1", - "fill": "#15803D", - "content": "26", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "S8hPpm", - "name": "code1", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "async fn register_and_login_with_special_character_password()", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "KReEL", - "name": "row2", - "width": "fill_container", - "fill": "#EAF6EC", - "gap": 8, - "padding": [ - 3, - 10 - ], - "children": [ - { - "type": "text", - "id": "fmkHe", - "name": "ln2", - "fill": "#15803D", - "content": "29", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "UgxwL", - "name": "code2", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "let password = r#\"p@$$w0rd!\\\"\\'<>{}[]()&=+?;,%#|~^\"#;", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "KMhJf", - "name": "row3", - "width": "fill_container", - "fill": "#EAF6EC", - "gap": 8, - "padding": [ - 3, - 10 - ], - "children": [ - { - "type": "text", - "id": "m1FEq", - "name": "ln3", - "fill": "#15803D", - "content": "53", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "rFvcq", - "name": "code3", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "assert_eq!(login_status, StatusCode::OK);", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "NnKhL", - "name": "folderRow", - "width": "fill_container", - "gap": 6, - "padding": [ - 4, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "5bnpy", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "TTucZ", - "width": 14, - "height": 14, - "iconFontName": "folder", - "iconFontFamily": "lucide", - "fill": "#FF8400" - }, - { - "type": "text", - "id": "Ggbwe", - "fill": "$--foreground", - "content": "services/jtype-web", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "SYIti", - "name": "fileRow", - "width": "fill_container", - "gap": 6, - "padding": [ - 4, - 16, - 4, - 40 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "4MF7m", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "v2z3H", - "fill": "$--foreground", - "content": "tests/auth_tests.rs", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "IUaP6", - "name": "fileRow2", - "width": "fill_container", - "gap": 6, - "padding": [ - 4, - 16, - 4, - 40 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "D7gzT1", - "name": "fileRow2Icon", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "#666666" - }, - { - "type": "text", - "id": "NcEjh", - "name": "fileRow2Text", - "fill": "#111111", - "content": "src/auth/util.rs", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "kU7B1", - "x": 2970, - "y": 3786, - "name": "JCODE Web UI - File View", - "width": 1440, - "height": 900, - "fill": "$--background", - "children": [ - { - "id": "z0xztV", - "type": "ref", - "ref": "XBTQE", - "name": "Web Sidebar", - "width": 320, - "height": "fill_container" - }, - { - "type": "frame", - "id": "rASPo", - "name": "Web Main", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "id": "a4mghU", - "type": "ref", - "ref": "jBVb4", - "name": "tb4" - }, - { - "type": "frame", - "id": "sEmjm", - "name": "File Editor", - "width": "fill_container", - "height": "fill_container", - "children": [ - { - "type": "frame", - "id": "3xBFI", - "name": "Code Editor Panel", - "width": "fill_container", - "height": "fill_container", - "fill": "$--background", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "VYOiV", - "name": "editorTabs", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "vGAb7", - "name": "edTab", - "fill": "$--secondary", - "cornerRadius": 6, - "padding": [ - 6, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ZtHG0", - "fill": "$--foreground", - "content": "settings.local.json", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "icon_font", - "id": "IoAT2", - "width": 12, - "height": 12, - "iconFontName": "x", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "YeFUg", - "name": "breadcrumb", - "gap": 4, - "padding": [ - 8, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "8r7z1", - "fill": "$--muted-foreground", - "content": "Review", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Gh8za", - "fill": "$--muted-foreground", - "content": "/", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "eVSny", - "fill": "$--foreground", - "content": "settings.local.json", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "rectangle", - "id": "Jfwfy", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "99o2V", - "name": "codeContent", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "gap": 4, - "padding": [ - 16, - 24 - ], - "children": [ - { - "type": "text", - "id": "iSmPj", - "fill": "$--foreground", - "content": "{", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "BOLz8", - "fill": "$--foreground", - "content": " \"permissions\": {", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "N1YR5", - "fill": "$--foreground", - "content": " \"allow\": [", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "ErzMa", - "fill": "#22C55E", - "content": " \"mcp__web-search-prime__web_search_prime\"", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "hXLj4", - "fill": "$--foreground", - "content": " ]", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "jmQkt", - "fill": "$--foreground", - "content": " }", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "s6u8T", - "fill": "$--foreground", - "content": "}", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "EdfmA", - "name": "File Tree Panel", - "width": 260, - "height": "fill_container", - "fill": "$--card", - "stroke": { - "align": "outside", - "thickness": { - "right": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "U6GiZ", - "name": "treeTabs", - "height": 36, - "gap": 8, - "padding": [ - 0, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "0arjg", - "name": "reviewTab", - "fill": "$--secondary", - "cornerRadius": 6, - "padding": [ - 6, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ezyFK", - "fill": "$--foreground", - "content": "Review", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "Ho7U1", - "name": "fileTab", - "fill": "$--card", - "cornerRadius": 6, - "padding": [ - 6, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "otbFU", - "fill": "$--muted-foreground", - "content": "settings.local.json", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "icon_font", - "id": "mw1KT", - "width": 14, - "height": 14, - "iconFontName": "plus", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "RREKv", - "name": "lastTurn", - "gap": 4, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "8UBMM", - "fill": "$--foreground", - "content": "Last turn changes", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "icon_font", - "id": "j91jm", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "DfXVp", - "name": "changesRow", - "padding": [ - 4, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "Jitb5", - "fill": "$--muted-foreground", - "content": "0 Changes", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "pt91c", - "name": "allFiles", - "gap": 6, - "padding": [ - 4, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "KUnrQ", - "width": 14, - "height": 14, - "iconFontName": "file-text", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "jyAQQ", - "fill": "$--muted-foreground", - "content": "All files", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "6qrXf", - "name": "folderRow", - "gap": 6, - "padding": [ - 6, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "WIGaf", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "V7NvPs", - "width": 14, - "height": 14, - "iconFontName": "folder-open", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "N8nc4", - "fill": "$--foreground", - "content": ".claude", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "hrqas", - "name": "fileRow", - "gap": 6, - "padding": [ - 6, - 12, - 6, - 32 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "NzALg", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "EWe1g", - "fill": "$--foreground", - "content": "settings.local.json", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "tE18T", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "TpQTa", - "x": 1457, - "y": 4886, - "name": "JCODE Multi-Agent Web", - "width": 1440, - "height": 900, - "fill": "$--background", - "children": [ - { - "id": "PZJ4B", - "type": "ref", - "ref": "XBTQE", - "name": "Web Sidebar", - "width": 320, - "height": "fill_container" - }, - { - "type": "frame", - "id": "sVCOk", - "name": "Web Main", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "id": "jOj90", - "type": "ref", - "ref": "jBVb4", - "name": "tb5" - }, - { - "type": "frame", - "id": "wETVg", - "name": "Body", - "width": "fill_container", - "height": "fill_container", - "children": [ - { - "type": "frame", - "id": "4gcz8", - "name": "ChatColumn", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "34SA1", - "name": "chatScroll", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "gap": 17, - "padding": [ - 24, - 40, - 0, - 40 - ], - "children": [ - { - "type": "frame", - "id": "hR46p", - "name": "userMsg", - "width": "fill_container", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "FrTq6", - "name": "avatar", - "width": 32, - "height": 32, - "fill": "#111111", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "Ds3sH", - "fill": "#FFFFFF", - "content": "U", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "KQgwK", - "name": "userBody", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "frame", - "id": "9i5Kh", - "name": "userHeader", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "JjeEU", - "fill": "$--foreground", - "content": "You", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "E2VaV", - "fill": "$--muted-foreground", - "content": "·", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "suWHL", - "fill": "$--muted-foreground", - "content": "to dev-team", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "6p6Bd", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "@team build me an auth system with email login, signup, and password reset. Use Postgres + JWT.", - "lineHeight": 1.5, - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "7pORb", - "name": "sysSpawn", - "width": "fill_container", - "gap": 8, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "rectangle", - "id": "c6fpj", - "name": "sysl", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "text", - "id": "utc7J", - "fill": "$--muted-foreground", - "content": "Spawned 4 teammates · dev-team", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "rectangle", - "id": "Bwegi", - "name": "sysl2", - "fill": "$--border", - "width": "fill_container", - "height": 1 - } - ] - }, - { - "type": "frame", - "id": "C9JGs", - "name": "archMsg", - "width": "fill_container", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "6C4at", - "name": "avatar", - "width": 32, - "height": 32, - "fill": "#FF8400", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "DtsjW", - "fill": "#111111", - "content": "A", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "700" - } - ] - }, - { - "type": "frame", - "id": "hK3m5", - "name": "body", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "frame", - "id": "3DSdW", - "name": "ah1", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "y0zWu", - "fill": "$--foreground", - "content": "@arch", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "WQsKx", - "fill": "$--muted-foreground", - "content": "Architect", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "ellipse", - "id": "aipH0", - "fill": "#22C55E", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "7Rl2e", - "fill": "$--color-success-foreground", - "content": "working", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "63RXf", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "On it. I'll design the schema and contracts, then hand off implementation. Spinning up plan...", - "lineHeight": 1.5, - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "isKOG", - "name": "codeCard", - "width": "fill_container", - "fill": "#0E0E0E", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "gap": 4, - "padding": [ - 10, - 14 - ], - "children": [ - { - "type": "frame", - "id": "I6BCO", - "name": "ccH", - "width": "fill_container", - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "MVQzF", - "fill": "$--muted-foreground", - "content": "schema.sql", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "A6EFG", - "fill": "$--color-success-foreground", - "content": "+ created", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "hMD2I", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "CREATE TABLE users ( id uuid PRIMARY KEY, email text UNIQUE, ...", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "qnoon", - "name": "callRow", - "width": "fill_container", - "gap": 10, - "padding": [ - 4, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "M2yhj", - "name": "callDot", - "fill": "$--primary", - "width": 6, - "height": 6 - }, - { - "type": "frame", - "id": "FNHwA", - "name": "callPill", - "fill": "$--card", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 8, - "padding": [ - 6, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "B7S40", - "name": "callFrom", - "fill": "$--primary", - "content": "@arch", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - }, - { - "type": "text", - "id": "Zobdq", - "name": "callArrow", - "fill": "$--muted-foreground", - "content": "→", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Vv4PC", - "name": "callTo", - "fill": "#7DD3FC", - "content": "@backend", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - }, - { - "type": "rectangle", - "id": "ePy4b", - "name": "callDivider", - "fill": "$--border", - "width": 1, - "height": 10 - }, - { - "type": "text", - "id": "Ym5je", - "name": "callLabel", - "fill": "$--muted-foreground", - "content": "task delegated", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "mZmBP", - "name": "callTime", - "fill": "$--muted-foreground", - "content": "12:04:18", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "uSj7r", - "name": "backendMsg", - "width": "fill_container", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "gqODf", - "name": "beAv", - "width": 32, - "height": 32, - "fill": "#0EA5E9", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "vzs0v", - "name": "beAvT", - "fill": "#FFFFFF", - "content": "B", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "RWb6p", - "name": "beBody", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "children": [ - { - "type": "frame", - "id": "9Axbm", - "name": "beHeader", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "zIqby", - "name": "beName", - "fill": "#7DD3FC", - "content": "@backend", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "PMDLp", - "name": "beRole", - "fill": "$--muted-foreground", - "content": "Backend Engineer", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "W9DMl", - "name": "beStatus", - "fill": "$--card", - "cornerRadius": 999, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 5, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "U7GBm", - "name": "beStatusDot", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "TvvGm", - "name": "beStatusT", - "fill": "$--color-success-foreground", - "content": "working", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "vBDMZ", - "name": "beTime", - "fill": "$--muted-foreground", - "content": "· 12:04:21", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "vvqjR", - "name": "beText", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Got it. I'll scaffold /api/auth routes (login, signup, reset). Hashing with argon2id, JWT access + refresh pattern. Pulling in @security to review token rotation.", - "lineHeight": 1.55, - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "pwOqI", - "name": "beCard", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "HRN60", - "name": "beCardHead", - "width": "fill_container", - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "padding": [ - 10, - 14 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "qbpnU", - "name": "beCardLeft", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "TYAkE", - "name": "beCardIcon", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "VSL7N", - "name": "beCardTitle", - "fill": "$--foreground", - "content": "routes/auth.ts", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "frame", - "id": "euVmK", - "name": "beCardBadge", - "fill": "#0EA5E920", - "cornerRadius": 4, - "gap": 6, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "gkK4F", - "name": "beCardBadgeT", - "fill": "#7DD3FC", - "content": "+ writing", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "uO3dB", - "name": "beCardBody", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "padding": [ - 12, - 14 - ], - "children": [ - { - "type": "text", - "id": "EQkoS", - "name": "beL1", - "fill": "$--muted-foreground", - "content": "router.post('/signup', async (req, res) => {", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "lg9Zt", - "name": "beL2", - "fill": "$--muted-foreground", - "content": " const { email, password } = req.body", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "BvRaR", - "name": "beL3", - "fill": "$--foreground", - "content": " const hash = await argon2.hash(password)", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "EueWV", - "name": "beL4", - "fill": "$--muted-foreground", - "content": " const user = await db.users.create…", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "tfEN4", - "name": "parallelRow", - "width": "fill_container", - "gap": 10, - "padding": [ - 2, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "MSkcu", - "name": "parDot", - "fill": "#FACC15", - "width": 6, - "height": 6 - }, - { - "type": "frame", - "id": "MA9KC", - "name": "parPill", - "fill": "$--card", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 8, - "padding": [ - 6, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "0WN47", - "name": "parIc", - "width": 11, - "height": 11, - "iconFontName": "git-branch", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "8dGmI", - "name": "parLbl", - "fill": "$--muted-foreground", - "content": "@frontend started in parallel", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "rectangle", - "id": "5RWvK", - "name": "parDiv", - "fill": "$--border", - "width": 1, - "height": 10 - }, - { - "type": "text", - "id": "qog0i", - "name": "parTag", - "fill": "#FACC15", - "content": "3 agents running", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "r1BXP", - "name": "parTime", - "fill": "$--muted-foreground", - "content": "12:04:23", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "gVI62", - "name": "feMsg", - "width": "fill_container", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "t9cHp", - "name": "feAv", - "width": 32, - "height": 32, - "fill": "#FACC15", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "T6yTK", - "name": "feAvT", - "fill": "#111111", - "content": "F", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "NZmOX", - "name": "feBody", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "children": [ - { - "type": "frame", - "id": "e3edk", - "name": "feHeader", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "rW2qv", - "name": "feName", - "fill": "#FACC15", - "content": "@frontend", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "2zD3l", - "name": "feRole", - "fill": "$--muted-foreground", - "content": "Frontend Engineer", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "rvT2j", - "name": "feStatus", - "fill": "$--card", - "cornerRadius": 999, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 5, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "uonWE", - "name": "feStatusDot", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "3NQ8B", - "name": "feStatusT", - "fill": "$--color-success-foreground", - "content": "working", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "Lqw0W", - "name": "feTime", - "fill": "$--muted-foreground", - "content": "· 12:04:23", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "tV7q6", - "name": "feText", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Spinning up the auth UI in parallel — login, signup, reset-password screens. Will use the same form components as /settings. Should I add SSO buttons for Google/GitHub now or stub them?", - "lineHeight": 1.55, - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "ky06b", - "name": "feMention", - "fill": "#FACC1510", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#FACC1530" - }, - "gap": 8, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "OftHl", - "name": "feMIc", - "width": 12, - "height": 12, - "iconFontName": "at-sign", - "iconFontFamily": "lucide", - "fill": "#FACC15" - }, - { - "type": "text", - "id": "fM9jt", - "name": "feMT", - "fill": "#FACC15", - "content": "awaiting reply from @arch", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "ets6m", - "name": "ChatInputComponentMultiAgent", - "width": "fill_container", - "height": 158, - "fill": "$--background", - "cornerRadius": 12, - "stroke": { - "align": "inside", - "thickness": 0, - "fill": "#00000000" - }, - "layout": "vertical", - "gap": 8, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "frame", - "id": "HRaMu", - "name": "inputArea", - "width": "fill_container", - "height": 132, - "fill": "$--card", - "cornerRadius": 10, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "VLBRf", - "name": "inputTop", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "padding": [ - 14, - 16, - 4, - 16 - ], - "children": [ - { - "type": "text", - "id": "zLnKp", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ask anything... Use / for commands, @ to mention agents", - "lineHeight": 1.5, - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "t9M57C", - "name": "inputBot", - "width": "fill_container", - "padding": [ - 8, - 12, - 12, - 12 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "w6O0A", - "name": "inputBotL", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "z1sYj", - "name": "attachBtn", - "cornerRadius": 6, - "gap": 5, - "padding": [ - 5, - 3 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "q5UrtZ", - "width": 13, - "height": 13, - "iconFontName": "paperclip", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "PsQWX", - "name": "mentionBtn", - "cornerRadius": 6, - "gap": 5, - "padding": [ - 5, - 3 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "u8mKf", - "width": 13, - "height": 13, - "iconFontName": "at-sign", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "u8P1kh", - "name": "agentBtn", - "height": 25, - "cornerRadius": 6, - "gap": 4, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "E8La5", - "fill": "$--foreground", - "content": "Agent", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "z2OL0", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "xO9GF", - "name": "modelBtn", - "height": 25, - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "kM8Cb", - "fill": "$--foreground", - "content": "MiniMax-M2.7", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "yaxFX", - "name": "chevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "YeEHV", - "name": "autoBtn", - "width": 36, - "height": 20, - "fill": "$--primary", - "cornerRadius": 999, - "gap": 6, - "padding": [ - 2, - 2, - 2, - 18 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "M8sjT", - "name": "ellipse", - "fill": "$--white", - "width": 16, - "height": 16 - } - ] - } - ] - }, - { - "type": "frame", - "id": "ADVDg", - "name": "inputBotR", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "g21NIE", - "name": "sendBtn", - "fill": "$--primary", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "d5bw0", - "width": 12, - "height": 12, - "iconFontName": "arrow-up", - "iconFontFamily": "lucide", - "fill": "#111111" - }, - { - "type": "text", - "id": "xjJso", - "fill": "#111111", - "content": "Send", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "s1sfA", - "name": "agentsPanel", - "width": 320, - "height": "fill_container", - "fill": "$--card", - "stroke": { - "align": "inside", - "thickness": { - "left": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "Tx7CK", - "name": "panelHead", - "width": "fill_container", - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "padding": [ - 16, - 20 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "UgFiF", - "name": "panelTitle", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "BNo1f", - "name": "pti", - "width": 14, - "height": 14, - "iconFontName": "users", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "text", - "id": "xsS1H", - "name": "ptt", - "fill": "$--foreground", - "content": "Team", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "L0nKr", - "name": "ptCnt", - "fill": "$--background", - "cornerRadius": 999, - "padding": [ - 1, - 7 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "4jWul", - "name": "ptCntT", - "fill": "$--muted-foreground", - "content": "4", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "zPYzE", - "name": "panelHeadAct", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "NK5d2", - "name": "collapseBtn", - "width": 28, - "height": 28, - "fill": "$--background", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "uUshs", - "name": "collapseIc", - "width": 14, - "height": 14, - "iconFontName": "panel-left-close", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "LCOaT", - "name": "filterRow", - "width": "fill_container", - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "gap": 6, - "padding": [ - 10, - 20 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "BWnC5", - "name": "fT1", - "fill": "$--background", - "cornerRadius": 999, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 6, - "padding": [ - 4, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "qU3Um", - "name": "fT1d", - "fill": "$--color-success-foreground", - "width": 5, - "height": 5 - }, - { - "type": "text", - "id": "03JVi", - "name": "fT1t", - "fill": "$--foreground", - "content": "Active 3", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - }, - { - "type": "frame", - "id": "c0AGv", - "name": "fT2", - "fill": "$--background", - "cornerRadius": 999, - "gap": 6, - "padding": [ - 4, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "1bmi2", - "name": "fT2d", - "fill": "#FACC15", - "width": 5, - "height": 5 - }, - { - "type": "text", - "id": "9ELdk", - "name": "fT2t", - "fill": "$--muted-foreground", - "content": "Idle 1", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "ULmOY", - "name": "fT3", - "fill": "$--background", - "cornerRadius": 999, - "gap": 6, - "padding": [ - 4, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "Qm4xn", - "name": "fT3d", - "fill": "$--muted-foreground", - "width": 5, - "height": 5 - }, - { - "type": "text", - "id": "hjbqj", - "name": "fT3t", - "fill": "$--muted-foreground", - "content": "Offline 1", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "sLCsf", - "name": "agentList", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "padding": 12, - "children": [ - { - "type": "frame", - "id": "PCBb1", - "name": "a1", - "width": "fill_container", - "fill": "$--background", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--primary" - }, - "layout": "vertical", - "gap": 10, - "padding": [ - 12, - 14 - ], - "children": [ - { - "type": "frame", - "id": "vrD8u", - "name": "a1Top", - "width": "fill_container", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "JQD0W", - "name": "a1Av", - "width": 28, - "height": 28, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "6R5PO", - "name": "a1AvT", - "fill": "#111111", - "content": "A", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "pWFZ6", - "name": "a1Mid", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "StYN1", - "name": "a1Name", - "fill": "$--foreground", - "content": "@arch", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "tPl0y", - "name": "a1Role", - "fill": "$--muted-foreground", - "content": "Architect · System design", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "C0daw", - "name": "a1Pulse", - "fill": "#1A2A1A", - "cornerRadius": 999, - "gap": 5, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "QsoJG", - "name": "a1Pd", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "GcD0p", - "name": "a1Pt", - "fill": "$--color-success-foreground", - "content": "working", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "SBKYg", - "name": "a1Task", - "width": "fill_container", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "rI0jw", - "name": "a1Ic", - "width": 11, - "height": 11, - "iconFontName": "loader", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "mYB98", - "name": "a1TaskT", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "writing schema.sql", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "MOlcB", - "name": "a2", - "width": "fill_container", - "fill": "$--background", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "gap": 10, - "padding": [ - 12, - 14 - ], - "children": [ - { - "type": "frame", - "id": "AR0u0", - "name": "a2Top", - "width": "fill_container", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "q5MWt", - "name": "a2Av", - "width": 28, - "height": 28, - "fill": "#0EA5E9", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "5fPoq", - "name": "a2AvT", - "fill": "#FFFFFF", - "content": "B", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "eR81b", - "name": "a2Mid", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "FCBEl", - "name": "a2Name", - "fill": "$--foreground", - "content": "@backend", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "NCa3o", - "name": "a2Role", - "fill": "$--muted-foreground", - "content": "Backend · API + DB", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "8dGTX", - "name": "a2Pulse", - "fill": "#1A2A1A", - "cornerRadius": 999, - "gap": 5, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "qGSQ6", - "name": "a2Pd", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "ljlfI", - "name": "a2Pt", - "fill": "$--color-success-foreground", - "content": "working", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "G6y4J", - "name": "a2Task", - "width": "fill_container", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "1xU8t", - "name": "a2Ic", - "width": 11, - "height": 11, - "iconFontName": "loader", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "U2Fzx", - "name": "a2TaskT", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "routes/auth.ts · 32 lines", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "PxrK2", - "name": "a3", - "width": "fill_container", - "fill": "$--background", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "gap": 10, - "padding": [ - 12, - 14 - ], - "children": [ - { - "type": "frame", - "id": "R761y", - "name": "a3Top", - "width": "fill_container", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "V3hsI", - "name": "a3Av", - "width": 28, - "height": 28, - "fill": "#FACC15", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "1SB36", - "name": "a3AvT", - "fill": "#111111", - "content": "F", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "4TV3F", - "name": "a3Mid", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "BNF5V", - "name": "a3Name", - "fill": "$--foreground", - "content": "@frontend", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "kB34k", - "name": "a3Role", - "fill": "$--muted-foreground", - "content": "Frontend · React/UI", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "7lzi5", - "name": "a3Pulse", - "fill": "#2A2410", - "cornerRadius": 999, - "gap": 5, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "KTalj", - "name": "a3Pd", - "fill": "#FACC15", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "RzP36", - "name": "a3Pt", - "fill": "#FACC15", - "content": "waiting", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "rNNgT", - "name": "a3Task", - "width": "fill_container", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "0NqIg", - "name": "a3Ic", - "width": 11, - "height": 11, - "iconFontName": "hourglass", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "OPO9P", - "name": "a3TaskT", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "awaiting reply from @arch", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "sadaw", - "name": "a4", - "width": "fill_container", - "fill": "$--background", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "gap": 10, - "padding": [ - 12, - 14 - ], - "children": [ - { - "type": "frame", - "id": "aIZ69", - "name": "a4Top", - "width": "fill_container", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "9pOss", - "name": "a4Av", - "opacity": 0.7, - "width": 28, - "height": 28, - "fill": "#A855F7", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "bQL46", - "name": "a4AvT", - "fill": "#FFFFFF", - "content": "S", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "xTUvy", - "name": "a4Mid", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "Bg1o3", - "name": "a4Name", - "fill": "$--muted-foreground", - "content": "@security", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "ze2hF", - "name": "a4Role", - "fill": "$--muted-foreground", - "content": "Security · Audit · offline", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "diKq7", - "name": "a4Pulse", - "fill": "$--card", - "cornerRadius": 999, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 5, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "4KskW", - "name": "a4Pd", - "fill": "$--muted-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "DWwG5", - "name": "a4Pt", - "fill": "$--muted-foreground", - "content": "offline", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "z9NWq", - "name": "a4Task", - "width": "fill_container", - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "mgYW3", - "name": "a4TLeft", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "PZMfZ", - "name": "a4Ic", - "width": 11, - "height": 11, - "iconFontName": "moon", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "bdeeB", - "name": "a4TaskT", - "fill": "$--muted-foreground", - "content": "last seen 2h ago", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "JfedU", - "name": "a4Wake", - "fill": "$--card", - "cornerRadius": 4, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "padding": [ - 3, - 9 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "JQpvu", - "name": "a4WakeT", - "fill": "$--foreground", - "content": "wake", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "500" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "2j7NH", - "name": "panelDiv", - "width": "fill_container", - "fill": "$--background", - "stroke": { - "align": "inside", - "thickness": { - "top": 1, - "bottom": 1 - }, - "fill": "$--border" - }, - "gap": 8, - "padding": [ - 8, - 20 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "qhYjJ", - "name": "pdIc", - "width": 11, - "height": 11, - "iconFontName": "git-branch", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "HwXyl", - "name": "pdT", - "fill": "$--muted-foreground", - "content": "Inter-agent calls (2)", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - } - ] - }, - { - "type": "frame", - "id": "e37Zr", - "name": "callList", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "padding": [ - 10, - 12 - ], - "children": [ - { - "type": "frame", - "id": "zolIG", - "name": "ic1", - "width": "fill_container", - "fill": "$--background", - "cornerRadius": 6, - "gap": 8, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "6zTWO", - "name": "ic1f", - "fill": "$--primary", - "content": "@arch", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - }, - { - "type": "text", - "id": "43pin", - "name": "ic1ar", - "fill": "$--muted-foreground", - "content": "→", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "AOXco", - "name": "ic1t", - "fill": "#7DD3FC", - "content": "@backend", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "fVBZx", - "name": "ic1Sp", - "width": "fill_container", - "height": 1 - }, - { - "type": "text", - "id": "ikBzA", - "name": "ic1Tm", - "fill": "$--muted-foreground", - "content": "3s", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "5c2dg", - "name": "ic2", - "width": "fill_container", - "fill": "$--background", - "cornerRadius": 6, - "gap": 8, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "mjtQn", - "name": "ic2f", - "fill": "#FACC15", - "content": "@frontend", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - }, - { - "type": "text", - "id": "gLOZH", - "name": "ic2ar", - "fill": "$--muted-foreground", - "content": "→", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "d2VD0", - "name": "ic2t", - "fill": "$--primary", - "content": "@arch", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "500" - }, - { - "type": "frame", - "id": "PiiDX", - "name": "ic2Sp", - "width": "fill_container", - "height": 1 - }, - { - "type": "text", - "id": "qCzVk", - "name": "ic2Tm", - "fill": "#FACC15", - "content": "now", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "id": "V6H82", - "type": "ref", - "ref": "bQhnA", - "x": 5275, - "y": 2051 - }, - { - "id": "bkDBP", - "type": "ref", - "ref": "PsBzD", - "x": 4571, - "y": 2165 - }, - { - "type": "frame", - "id": "t13J8I", - "x": 5275, - "y": 2810, - "name": "BashExecutionComponent", - "reusable": true, - "width": 640, - "fill": "$--card", - "cornerRadius": 12, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "oCCfb", - "name": "header", - "width": "fill_container", - "height": 40, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "LndhH", - "name": "labelGroup", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "wo9rL", - "name": "icon", - "width": 14, - "height": 14, - "iconFontName": "terminal", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "frame", - "id": "V4lGjQ", - "name": "labelPill", - "fill": "$--muted", - "cornerRadius": 4, - "padding": [ - 2, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "bV3q0", - "name": "labelText", - "fill": "$--muted-foreground", - "content": "Shell", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - }, - { - "type": "frame", - "id": "pgaMn", - "name": "statusGroup", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "cgBYr", - "name": "statusDot", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "FrPzo", - "name": "statusText", - "fill": "$--color-success-foreground", - "content": "Completed", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "ZlbKh", - "name": "divider1", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "I9c3ja", - "name": "commandBlock", - "width": "fill_container", - "gap": 8, - "padding": [ - 12, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "qYlVq", - "name": "dollar", - "fill": "$--muted-foreground", - "content": "$", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "k800vK", - "name": "command", - "fill": "$--foreground", - "content": "ls -la", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "YZpWD", - "name": "outputBlock", - "width": "fill_container", - "layout": "vertical", - "padding": [ - 0, - 16, - 12, - 16 - ], - "children": [ - { - "type": "text", - "id": "hIaR7", - "name": "output1", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "total 422", - "lineHeight": 1.6, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "SUE7x", - "name": "output2", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "drwxr-xr-x 1 Jack 197609 0 Apr 28 22:58 .", - "lineHeight": 1.6, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Dk8rZ", - "name": "output3", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "drwxr-xr-x 1 Jack 197609 0 Apr 28 22:58 ..", - "lineHeight": 1.6, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "GzAUx", - "name": "output4", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "-rw-r--r-- 1 Jack 197609 66 Apr 28 22:58 .git", - "lineHeight": 1.6, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "rectangle", - "id": "klPK8", - "name": "divider2", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "R775qS", - "name": "footer", - "width": "fill_container", - "height": 32, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "d4BEI", - "name": "footerLeft", - "fill": "$--muted-foreground", - "content": "Explored 1 search", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "N9BBxF", - "name": "footerRight", - "fill": "$--muted-foreground", - "content": "0.3s", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "id": "QpTu8", - "type": "ref", - "ref": "t13J8I", - "name": "BashExec_Running", - "x": 5275, - "y": 3068, - "descendants": { - "bV3q0": { - "content": "Shell" - }, - "cgBYr": { - "fill": "$--color-warning-foreground" - }, - "FrPzo": { - "content": "Running", - "fill": "$--color-warning-foreground" - }, - "k800vK": { - "content": "ls /root/temp" - }, - "hIaR7": { - "content": "README.md" - }, - "SUE7x": { - "content": "test.md" - }, - "Dk8rZ": { - "enabled": false - }, - "GzAUx": { - "enabled": false - }, - "d4BEI": { - "content": "Explored 1 search" - }, - "N9BBxF": { - "content": "" - } - } - }, - { - "id": "rF9UN", - "type": "ref", - "ref": "t13J8I", - "name": "BashExec_Error", - "x": 5275, - "y": 3289, - "descendants": { - "bV3q0": { - "content": "Shell" - }, - "cgBYr": { - "fill": "$--color-error-foreground" - }, - "FrPzo": { - "content": "Failed", - "fill": "$--color-error-foreground" - }, - "k800vK": { - "content": "npm run build" - }, - "hIaR7": { - "content": "> jcode-design@0.1.0 build" - }, - "SUE7x": { - "content": "> vite build" - }, - "Dk8rZ": { - "enabled": false - }, - "GzAUx": { - "content": "ERROR: Build failed with 3 errors", - "fill": "$--color-error-foreground" - }, - "d4BEI": { - "content": "Exit code: 1" - }, - "N9BBxF": { - "content": "12.4s" - } - } - }, - { - "type": "frame", - "id": "f3wqfZ", - "x": 4571, - "y": 3289, - "name": "EditFileComponent", - "reusable": true, - "width": 640, - "fill": "$--card", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "wcyMf", - "name": "header", - "width": "fill_container", - "height": 44, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "w9ddRb", - "name": "headerLeft", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "cgsFX", - "name": "editIcon", - "width": 16, - "height": 16, - "iconFontName": "pencil", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "OkwFL", - "name": "fileLabel", - "fill": "$--foreground", - "content": "Edit", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "p43rQ", - "name": "statsGroup", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "xgMGI", - "name": "fileNameHeader", - "fill": "$--muted-foreground", - "content": "test.md", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "iYZ59", - "name": "addStat", - "fill": "$--color-success-foreground", - "content": "+2", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "vzLbH", - "name": "subStat", - "fill": "$--color-error-foreground", - "content": "-1", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Qbmom", - "name": "headerRight", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "vIZxn", - "name": "chevronIcon", - "width": 16, - "height": 16, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "frame", - "id": "nuLFB", - "name": "row1", - "width": "fill_container", - "height": 24, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "F4JUI", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "1", - "textAlign": "right", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "dR8L7", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "1", - "textAlign": "right", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Z9wpno", - "fill": "$--muted-foreground", - "content": " ", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "PQV5J", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "## Origin and Early Exploration (1950-1970)", - "fontFamily": "$--font-primary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "u3x36", - "name": "row2", - "width": "fill_container", - "height": 24, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ztgJj", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "2", - "textAlign": "right", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "C8wxbg", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "2", - "textAlign": "right", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "LJ3Jb", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 12, - "content": " ", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "FL2y2", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": " ", - "fontFamily": "$--font-primary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "s0d9v3", - "name": "row3", - "width": "fill_container", - "height": 24, - "fill": "$--color-error", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "SLkzS", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "3", - "textAlign": "right", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "JBJ0E", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": " ", - "textAlign": "right", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "aqE3z", - "fill": "$--color-error-foreground", - "textGrowth": "fixed-width", - "width": 12, - "content": "-", - "fontFamily": "$--font-primary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "RRl1L", - "fill": "$--color-error-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "The concept of AI, **Alan Turing** 1950...", - "fontFamily": "$--font-primary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "e0ne1", - "name": "row4", - "width": "fill_container", - "height": 24, - "fill": "$--color-success", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "VVIxY", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": " ", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "EFZVB", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "3", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "BAz4n", - "fill": "$--color-success-foreground", - "textGrowth": "fixed-width", - "width": 12, - "content": "+", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "V7hSO", - "fill": "$--color-success-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "The concept of AI, **Alan Turing** 1950...", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "wekHN", - "name": "row5", - "width": "fill_container", - "height": 24, - "fill": "$--color-success", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "yFh1p", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": " ", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "z4eFES", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "4", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "W3hCF", - "fill": "$--color-success-foreground", - "textGrowth": "fixed-width", - "width": 12, - "content": "+", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "uJZcY", - "fill": "$--color-success-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "In 1958, **Rosenblatt** invented **Perceptron**...", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "xO4Un", - "name": "row6", - "width": "fill_container", - "height": 24, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "j9SjJn", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "5", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "vLTyM", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "5", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "aE096", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 12, - "content": " ", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "NWPfV", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "The 1960s saw optimistic predictions...", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "S0cMU", - "name": "row6", - "width": "fill_container", - "height": 24, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "frrqn", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": " ", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "e631ic", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": " ", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "dyMmj", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 12, - "content": " ", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Z9dBAU", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "No newline at end of file", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "N9OMQ4", - "x": 4571, - "y": 3570, - "name": "WriteFileComponent", - "reusable": true, - "width": 640, - "fill": "$--card", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "rectangle", - "id": "Ujh0y", - "name": "divider1", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "FHISa", - "name": "header", - "width": "fill_container", - "height": 44, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "FIIv7", - "name": "headerLeft", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "rS8JS", - "name": "editIcon", - "width": 16, - "height": 16, - "iconFontName": "pencil", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "s9X5xr", - "name": "fileLabel", - "fill": "#111111", - "content": "Write", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "Kt36s", - "name": "statsGroup", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "i0Y5k4", - "name": "fileNameHeader", - "fill": "$--muted-foreground", - "content": "test.md", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "gOhFk", - "name": "addStat", - "fill": "$--color-success-foreground", - "content": "+8", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "bbjZ9", - "name": "headerRight", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "B8S3b", - "name": "chevronIcon", - "width": 16, - "height": 16, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "mo94t", - "name": "divider2", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "yVh3t", - "name": "contentArea", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "FpS0K", - "name": "row1", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "f4oGv6", - "name": "newNum1", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "1", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "o0Xhe", - "name": "code1", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "# 人工智能发展历史", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "GCCAd", - "name": "row2", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "X4nKvB", - "name": "newNum2", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "2", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "o8Q7s", - "name": "code2", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": " ", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "NJZRf", - "name": "row3", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "JP6OH", - "name": "newNum3", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "3", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "CxFPh", - "name": "code3", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "## 起源与早期探索 (1950-1970)", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "G4q4Zk", - "name": "row4", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "r9t4ng", - "name": "newNum4", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "4", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "QR0PC", - "name": "code4", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": " ", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "qmjD3", - "name": "row5", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "xE2rH", - "name": "newNum5", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "5", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "x8RKCL", - "name": "code5", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "人工智能的概念最早可以追溯到1950年,英国数学家**艾伦·图灵**发表了著名的论文", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "CBnQL", - "name": "row6", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "r5euht", - "name": "newNum6", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "6", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "TaiMY", - "name": "code6", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "《计算机器与智能》,提出了\"图灵测试\"的概念。", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "R9A67A", - "name": "row7", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "R8DP0", - "name": "newNum7", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "7", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "I7dZ9", - "name": "code7", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": " ", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "o4iLa", - "name": "row8", - "width": "fill_container", - "height": 24, - "fill": "#FFFFFF", - "gap": 4, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "K8pKIp", - "name": "newNum8", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 32, - "content": "8", - "textAlign": "right", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "axuSU", - "name": "code8", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "## 专家系统时代 (1970-1990)", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "FT1GK", - "x": 5275, - "y": 3570, - "name": "AgentFileChangeComponent", - "width": 640, - "fill": "$--card", - "cornerRadius": 8, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "CQIED", - "name": "header", - "width": "fill_container", - "height": 48, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "OQkxC", - "name": "headerLeft", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "BhjEX", - "name": "branchIcon", - "width": 16, - "height": 16, - "iconFontName": "git-branch", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "K146t", - "name": "summaryText", - "fill": "$--foreground", - "content": "7 Changed files", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "vs3v0", - "name": "headerRight", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "abwLP", - "name": "addStat", - "fill": "$--color-success-foreground", - "content": "+5", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "i9XSx", - "name": "subStat", - "fill": "$--color-error-foreground", - "content": "-30", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "E96jP", - "name": "divider", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "c6RAWL", - "name": "fileList", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "LZSsq", - "name": "fr1", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "ZRbya", - "name": "fi1", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "uYA4U", - "name": "fp1", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/handler/handler.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "DbbdX", - "name": "fa1", - "fill": "$--color-success-foreground", - "content": "+1", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "ixB9H", - "name": "fs1", - "fill": "$--color-error-foreground", - "content": "-4", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "scZmT", - "name": "fr2", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "PeaYP", - "name": "fi2", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "DKptZ", - "name": "fp2", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/handler/tui.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "PTmmQ", - "name": "fa2", - "fill": "$--color-success-foreground", - "content": "+0", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "zO8qk", - "name": "fs2", - "fill": "$--color-error-foreground", - "content": "-3", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "yJtN8", - "name": "fr3", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "f0SnC", - "name": "fi3", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "tF75R", - "name": "fp3", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/handler/web.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "eff48", - "name": "fa3", - "fill": "$--color-success-foreground", - "content": "+1", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "wCx1n", - "name": "fs3", - "fill": "$--color-error-foreground", - "content": "-7", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "L2ejbm", - "name": "fr4", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "Rj1zz", - "name": "fi4", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "UzDiI", - "name": "fp4", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/runner/runner.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "i0O6W", - "name": "fa4", - "fill": "$--color-success-foreground", - "content": "+2", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Yp3TR", - "name": "fs4", - "fill": "$--color-error-foreground", - "content": "-6", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "DWus7", - "name": "fr5", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "gSjJq", - "name": "fi5", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "WYl3n", - "name": "fp5", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/tui/input_views.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "wZI12", - "name": "fa5", - "fill": "$--color-success-foreground", - "content": "+1", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "SkhYK", - "name": "fs5", - "fill": "$--color-error-foreground", - "content": "-1", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "eXLqv", - "name": "fr6", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "rR8mn", - "name": "fi6", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "FymHA", - "name": "fp6", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/tui/messages.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "p9M5h8", - "name": "fa6", - "fill": "$--color-success-foreground", - "content": "+0", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Bd67q", - "name": "fs6", - "fill": "$--color-error-foreground", - "content": "-3", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "adxED", - "name": "fr7", - "width": "fill_container", - "height": 36, - "gap": 8, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "zP61y", - "name": "fi7", - "width": 14, - "height": 14, - "iconFontName": "file-code", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "lY4WE", - "name": "fp7", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "internal/tui/tui.go", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "PGgux", - "name": "fa7", - "fill": "$--color-success-foreground", - "content": "+0", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "oa0aT", - "name": "fs7", - "fill": "$--color-error-foreground", - "content": "-6", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "e7NM2U", - "name": "divider2", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "W41vv6", - "name": "branchFooter", - "width": "fill_container", - "height": 44, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "J1ikLQ", - "name": "bfLeft", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "ffDCz", - "name": "bfIcon", - "width": 14, - "height": 14, - "iconFontName": "git-compare", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "nWVPw", - "name": "bfText", - "fill": "$--muted-foreground", - "content": "main ← claude/busy-allen-4eb24f", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "pjO7W", - "name": "bfRight", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "M1jYka", - "name": "bfAdd", - "fill": "$--color-success-foreground", - "content": "+19", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "NrkjQ", - "name": "bfSub", - "fill": "$--color-error-foreground", - "content": "-0", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "Nw3RH", - "name": "prBtn", - "fill": "$--primary", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 6, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ekeEZ", - "name": "prBtnText", - "fill": "$--primary-foreground", - "content": "Create PR", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "icon_font", - "id": "Odwg8", - "name": "prBtnIcon", - "width": 14, - "height": 14, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--primary-foreground" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "si96A", - "x": 4571, - "y": 3850, - "name": "AgentSearchResultComponent", - "reusable": true, - "width": 640, - "fill": "$--card", - "cornerRadius": 12, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "KWXR6", - "name": "header", - "width": "fill_container", - "height": 40, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "HL34u", - "name": "headerLeft", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "j2gaY", - "name": "searchIcon", - "width": 14, - "height": 14, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "frame", - "id": "DV2K8", - "name": "labelPill", - "fill": "$--muted", - "cornerRadius": 4, - "padding": [ - 2, - 8 - ], - "children": [ - { - "type": "text", - "id": "THXNS", - "name": "labelText", - "fill": "$--muted-foreground", - "content": "Search", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - }, - { - "type": "frame", - "id": "XRmvb", - "name": "headerRight", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "Dq3oS", - "name": "statusGroup", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "YCE89", - "name": "statusDot", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - }, - { - "type": "text", - "id": "GfXQm", - "name": "statusText", - "fill": "$--color-success-foreground", - "content": "Completed", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "icon_font", - "id": "RM87B", - "name": "copyBtn", - "width": 14, - "height": 14, - "iconFontName": "copy", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "c1yURm", - "name": "divider1", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "d23Zo", - "name": "argsBlock", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "text", - "id": "rMh12", - "name": "argsTitle", - "fill": "$--muted-foreground", - "content": "ARGS", - "fontFamily": "$--font-secondary", - "fontSize": 10, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "v5Ttru", - "name": "argsContent", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "WcfvB", - "name": "arg1", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "output_mode: content, path: /root/tmp, pattern: AlphaGo|深度学习|Transformer", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "BYEhS", - "name": "divider2", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "ZbPwL", - "name": "resultsBlock", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "frame", - "id": "U1jcxN", - "name": "match1Row", - "width": "fill_container", - "gap": 4, - "children": [ - { - "type": "text", - "id": "l4LBy", - "name": "match1Prefix", - "fill": "$--primary", - "content": "test.md:19:", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "rs2cp", - "name": "match1Content", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "# 深度学习革命 (2010s-至今)", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "wCKuA", - "name": "match2Row", - "width": "fill_container", - "gap": 4, - "children": [ - { - "type": "text", - "id": "b1nwp6", - "name": "match2Prefix", - "fill": "$--primary", - "content": "test.md:21:", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "AJ4yP", - "name": "match2Content", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "**2012年**: AlexNet在ImageNet竞赛中取得突破性成绩,深度学习时代正式开启。", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "uqQmE", - "name": "match3Row", - "width": "fill_container", - "gap": 4, - "children": [ - { - "type": "text", - "id": "h05Hfg", - "name": "match3Prefix", - "fill": "$--primary", - "content": "test.md:25:", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "lH0zm", - "name": "match3Content", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "- **2012年**: AlexNet在ImageNet竞赛中将错误率降低了近10个百分点,证明了深度神经网络的巨大潜力,掀起了深度学习的研究热潮。", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "e5m9tQ", - "name": "match4Row", - "width": "fill_container", - "gap": 4, - "children": [ - { - "type": "text", - "id": "pLmK0", - "name": "match4Prefix", - "fill": "$--primary", - "content": "test.md:26:", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "UKRRw", - "name": "match4Content", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "- **2016年**: DeepMind的AlphaGo以4:1击败围棋世界冠军李世石,这一事件被认为是AI历史上的标志性时刻。", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "QOuMZ", - "name": "match5Row", - "width": "fill_container", - "gap": 4, - "children": [ - { - "type": "text", - "id": "um9I0", - "name": "match5Prefix", - "fill": "$--primary", - "content": "test.md:27:", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "DCuJW", - "name": "match5Content", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "- **2017年**: Google发表里程碑式论文《Attention Is All You Need》,提出了Transformer架构,彻底改变了自然语言处理领域的研究范式。", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "mVuxm", - "name": "divider3", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "EF7mD", - "name": "footer", - "width": "fill_container", - "height": 32, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "zxuDH", - "name": "footerLeft", - "fill": "$--muted-foreground", - "content": "(5 matches found)", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "mTxni", - "name": "footerRight", - "fill": "$--muted-foreground", - "content": "0.8s", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "id": "WPa7y", - "type": "ref", - "ref": "si96A", - "x": 5275, - "y": 3984, - "name": "FileSearchInstance", - "descendants": { - "j2gaY": { - "iconFontName": "folder-open" - }, - "THXNS": { - "content": "Searched" - }, - "WcfvB": { - "content": "pattern: **/*.md" - }, - "ZbPwL": { - "type": "frame", - "id": "yjr4m", - "name": "fileResultsBlock", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "frame", - "id": "KbcEx", - "name": "file1Row", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "go624", - "name": "file1Icon", - "width": 14, - "height": 14, - "iconFontName": "file-text", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "yUb49", - "name": "file1Name", - "fill": "$--foreground", - "content": "design.md", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "DZNkL", - "name": "file2Row", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "CryXF", - "name": "file2Icon", - "width": 14, - "height": 14, - "iconFontName": "file-text", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "LGEWP", - "name": "file2Name", - "fill": "$--foreground", - "content": "README.md", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "tkujB", - "name": "file3Row", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "c70aTF", - "name": "file3Icon", - "width": 14, - "height": 14, - "iconFontName": "file-text", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "tqpYp", - "name": "file3Name", - "fill": "$--foreground", - "content": "test.md", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - "zxuDH": { - "content": "(3 files found)" - }, - "mTxni": { - "content": "0.2s" - } - } - }, - { - "id": "zk9DM", - "type": "ref", - "ref": "si96A", - "x": 4571, - "y": 4200, - "name": "GlobSearchInstance", - "descendants": { - "j2gaY": { - "iconFontName": "folder-tree" - }, - "THXNS": { - "content": "Glob" - }, - "WcfvB": { - "content": "path: /root/, pattern: **/test.md" - }, - "ZbPwL": { - "type": "frame", - "id": "D1Cu0f", - "name": "globResultsBlock", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "text", - "id": "O0DKw", - "name": "globSummary", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "/test.md", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "R8o1oy", - "name": "globSummary2", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "/src/app/page.tsx", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "K1BPcL", - "name": "globSummary3", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "/src/components/chat/ChatInput.tsx", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "vGW0l", - "name": "globSummary4", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "/src/components/tools/GlobSearchResult.tsx", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "TSywz", - "name": "globSummary5", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "/src/lib/search/glob.ts", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "cgSyX", - "name": "globSummary6", - "fill": "#111111", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "/tests/glob-search.test.ts", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - "zxuDH": { - "content": "(6 matches found)" - }, - "mTxni": { - "content": "0.2s" - } - } - }, - { - "id": "l6KAg", - "type": "ref", - "ref": "PsBzD", - "x": 4571, - "y": 2248, - "descendants": { - "xqAtp": { - "content": "]" - } - } - }, - { - "type": "frame", - "id": "e5qEhU", - "x": 5275, - "y": 4404, - "name": "UserMessageComponent", - "reusable": true, - "width": 640, - "gap": 12, - "children": [ - { - "type": "frame", - "id": "otGOU", - "name": "avatar", - "width": 32, - "height": 32, - "fill": "#111111", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "cI01c", - "name": "avatarLetter", - "fill": "#FFFFFF", - "content": "U", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "gytdc", - "name": "body", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "frame", - "id": "Q4uQ23", - "name": "header", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "irClO", - "name": "username", - "fill": "$--foreground", - "content": "You", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "F5VgW", - "name": "dot", - "fill": "$--muted-foreground", - "content": "·", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "HuhG9", - "name": "timestamp", - "fill": "$--muted-foreground", - "content": "10:30 AM", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "biDlC", - "name": "spacer", - "width": "fill_container", - "height": 1 - }, - { - "type": "icon_font", - "id": "GblTm", - "name": "copyIcon", - "width": 14, - "height": 14, - "iconFontName": "copy", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "text", - "id": "KYajV", - "name": "message", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "This is a user message that can span multiple lines and wrap within the container width.", - "lineHeight": 1.5, - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "qzSjK", - "x": 5275, - "y": 4286, - "name": "JcodeMessageComponent", - "reusable": true, - "width": "fill_container(640)", - "gap": 12, - "children": [ - { - "type": "frame", - "id": "Towzq", - "name": "avatar", - "width": 32, - "height": 32, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "id": "D5EkP", - "type": "ref", - "ref": "PsBzD", - "descendants": { - "I1SQj": { - "enabled": false - }, - "YiNRu": { - "fontSize": 14, - "fill": "$--primary-foreground" - }, - "xqAtp": { - "enabled": false - } - } - } - ] - }, - { - "type": "frame", - "id": "hRXRy", - "name": "body", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "frame", - "id": "K9jTWF", - "name": "header", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "N64h5Y", - "fill": "$--primary", - "content": "Jcode", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "GZiWP", - "fill": "$--muted-foreground", - "content": "·", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Kw8Em", - "fill": "$--muted-foreground", - "content": "Just now", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "kURkz", - "name": "spacer", - "width": "fill_container", - "height": 1 - }, - { - "type": "icon_font", - "id": "ybXSh", - "name": "copyIcon", - "width": 14, - "height": 14, - "iconFontName": "copy", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "text", - "id": "G3X7X", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Hello! I'm JCODE, your AI assistant. How can I help you today?", - "lineHeight": 1.5, - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Jta3b", - "x": 5275, - "y": 4517, - "name": "AgentMessageComponent", - "reusable": true, - "width": 640, - "gap": 12, - "children": [ - { - "type": "frame", - "id": "RUfr8", - "name": "agentAvatar", - "width": 32, - "height": 32, - "fill": "$--color-info", - "cornerRadius": 999, - "layout": "none", - "children": [ - { - "type": "text", - "id": "I1I3c", - "x": 10, - "y": 7, - "name": "agentLetter", - "fill": "$--color-info-foreground", - "content": "A", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "Ps5nb", - "name": "agentBody", - "width": "fill_container", - "layout": "vertical", - "gap": 6, - "children": [ - { - "type": "frame", - "id": "k2D1S", - "name": "agentHeader", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "BbFIe", - "name": "agentName", - "fill": "$--color-info-foreground", - "content": "Agent", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "crqK9", - "name": "agentDot", - "fill": "$--muted-foreground", - "content": "·", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "ZsmXz", - "name": "agentStatus", - "fill": "$--muted-foreground", - "content": "thinking...", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "gqVaM", - "name": "headerSpacer", - "width": "fill_container", - "height": 1 - }, - { - "type": "icon_font", - "id": "Hd9zV", - "name": "copyIcon", - "width": 14, - "height": 14, - "iconFontName": "copy", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "text", - "id": "DqVe9", - "name": "agentMsgText", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "I'll search the codebase for authentication-related files and help you build the auth system.", - "lineHeight": 1.5, - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "WcxL8", - "x": 4571, - "y": 3075, - "name": "ChatInputComponent", - "reusable": true, - "width": 640, - "height": 148, - "fill": "#F6F7F4", - "cornerRadius": 10, - "layout": "vertical", - "gap": 8, - "padding": [ - 6, - 14 - ], - "children": [ - { - "type": "frame", - "id": "J7JBj", - "name": "askInputArea", - "width": "fill_container", - "height": 124, - "fill": "$--card", - "cornerRadius": 9, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#CBCCC9" - }, - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "GD2gl", - "name": "askInputTop", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical", - "padding": [ - 14, - 16, - 4, - 16 - ], - "children": [ - { - "type": "text", - "id": "isVoj", - "name": "askPlaceholder", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ask JCODE, @agent, or /command", - "lineHeight": 1.5, - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "e1l3e", - "name": "askInputBot", - "width": "fill_container", - "padding": [ - 8, - 12, - 12, - 12 - ], - "justifyContent": "space_between", - "children": [ - { - "type": "frame", - "id": "N2lwOA", - "name": "askInputBotL", - "gap": 6, - "children": [ - { - "type": "frame", - "id": "aGkzT", - "name": "askAttachBtn", - "cornerRadius": 6, - "gap": 5, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "pFwyF", - "name": "askAttachIcon", - "width": 13, - "height": 13, - "iconFontName": "plus", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "VAIIL", - "name": "askAgentBtn", - "height": 25, - "cornerRadius": 6, - "gap": 4, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "fS1KE", - "name": "agentIcon", - "width": 14, - "height": 14, - "iconFontName": "bot", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "text", - "id": "u2yyJ", - "name": "askAgentText", - "fill": "$--foreground", - "content": "Agent", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "cKdOB", - "name": "askAgentChevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "i6KI13", - "name": "permissionDropdown", - "height": 25, - "cornerRadius": 6, - "gap": 4, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "y98KuQ", - "fill": "$--foreground", - "content": "Default Permission", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "B6Xad", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "wdbWN", - "name": "askModelBtn", - "height": 25, - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "XevkU", - "name": "askModelText", - "fill": "$--foreground", - "content": "MiniMax-M2.7", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "GbKAR", - "name": "askModelChevron", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "frame", - "id": "ld8Ur", - "name": "askInputBotR", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "m2qml", - "name": "sendBtn", - "fill": "$--primary", - "cornerRadius": 6, - "gap": 6, - "padding": [ - 5, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "iRrcb", - "width": 12, - "height": 12, - "iconFontName": "arrow-up", - "iconFontFamily": "lucide", - "fill": "#ffffffff" - }, - { - "type": "text", - "id": "GB7Zn", - "fill": "#ffffffff", - "content": "Send", - "fontFamily": "JetBrains Mono", - "fontSize": 11, - "fontWeight": "600" - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "ikjuo", - "x": 6020, - "y": 2810, - "name": "AgentMentionPopup", - "reusable": true, - "width": 280, - "height": 320, - "fill": "#00000000", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "CLwIj", - "name": "popupContainer", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000020", - "offset": { - "x": 0, - "y": 4 - }, - "blur": 24 - }, - "layout": "vertical", - "padding": 8, - "children": [ - { - "type": "frame", - "id": "K2wG5J", - "name": "popupHeader", - "width": "fill_container", - "padding": [ - 8, - 12 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "CpJdY", - "name": "popupTitle", - "fill": "$--foreground", - "content": "Mention an agent", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "600" - }, - { - "type": "text", - "id": "U7AKj", - "name": "popupShortcut", - "fill": "$--muted-foreground", - "content": "↑↓ to navigate", - "fontFamily": "JetBrains Mono", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "rectangle", - "id": "AsZP7", - "name": "popupDivider", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "l3b2X", - "name": "agentList", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "padding": [ - 4, - 0 - ], - "children": [ - { - "type": "frame", - "id": "y3Rrc", - "name": "a1Opt", - "width": "fill_container", - "fill": "$--muted", - "cornerRadius": 8, - "gap": 10, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "Y756MR", - "name": "a1Avatar", - "width": 28, - "height": 28, - "fill": "#FF8400", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "b7raH", - "name": "a1Initial", - "fill": "#FFFFFF", - "content": "A", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "TRRGS", - "name": "a1Info", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "tGyIz", - "name": "a1Name", - "fill": "$--primary", - "content": "@arch", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "c8iVCq", - "name": "a1Role", - "fill": "$--muted-foreground", - "content": "System architect", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "icon_font", - "id": "YSBCy", - "name": "a1Check", - "width": 14, - "height": 14, - "iconFontName": "check", - "iconFontFamily": "lucide", - "fill": "$--primary" - } - ] - }, - { - "type": "frame", - "id": "F2PZi", - "name": "a2Opt", - "width": "fill_container", - "gap": 10, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "SVy9R", - "name": "a2Avatar", - "width": 28, - "height": 28, - "fill": "#0EA5E9", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "onkov", - "name": "a2Initial", - "fill": "#FFFFFF", - "content": "B", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "ETED3", - "name": "a2Info", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "w2LE2h", - "name": "a2Name", - "fill": "$--foreground", - "content": "@backend", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "b3b9k", - "name": "a2Role", - "fill": "$--muted-foreground", - "content": "Backend developer", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "BQnKH", - "name": "a3Opt", - "width": "fill_container", - "gap": 10, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "A9BXxS", - "name": "a3Avatar", - "width": 28, - "height": 28, - "fill": "#FACC15", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "fVs4x", - "name": "a3Initial", - "fill": "#111111", - "content": "F", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "dizuG", - "name": "a3Info", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "AIEHd", - "name": "a3Name", - "fill": "$--foreground", - "content": "@frontend", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "OqXID", - "name": "a3Role", - "fill": "$--muted-foreground", - "content": "Frontend developer", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "SAAAJ", - "name": "a4Opt", - "width": "fill_container", - "gap": 10, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "uqWm2", - "name": "a4Avatar", - "width": 28, - "height": 28, - "fill": "#A855F7", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ZFc9K", - "name": "a4Initial", - "fill": "#FFFFFF", - "content": "D", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "T1npR", - "name": "a4Info", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "D8fPJ", - "name": "a4Name", - "fill": "$--foreground", - "content": "@devops", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "umH65", - "name": "a4Role", - "fill": "$--muted-foreground", - "content": "DevOps engineer", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "XBTQE", - "x": 6666, - "y": 2645, - "name": "Web Sidebar", - "reusable": true, - "width": 320, - "height": "fill_container(900)", - "fill": "#FAFAF8", - "stroke": { - "thickness": { - "right": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "gap": 8, - "padding": 12, - "children": [ - { - "id": "CwXi9", - "type": "ref", - "ref": "dTYg8", - "x": 12, - "y": 12, - "descendants": { - "IGhQ7": { - "stroke": { - "align": "inside", - "thickness": 1 - } - }, - "n84vbu": { - "fill": "$--secondary-foreground" - } - } - }, - { - "id": "yetbG", - "type": "ref", - "ref": "ENEfb", - "x": 12, - "y": 73, - "width": "fill_container" - }, - { - "id": "tQmB3", - "type": "ref", - "ref": "f587fY", - "x": 12, - "y": 119, - "width": "fill_container" - }, - { - "type": "frame", - "id": "XyFPm", - "name": "sidebarFlexibleSpacer", - "width": "fill_container", - "height": "fill_container", - "layout": "vertical" - }, - { - "id": "C5HFfe", - "type": "ref", - "ref": "YjQd7", - "x": 12, - "y": 852, - "width": "fill_container", - "descendants": { - "BxALR": { - "fontSize": 12 - }, - "HDeik": { - "x": 260, - "y": 11 - } - } - } - ] - }, - { - "id": "bgdx6", - "type": "ref", - "ref": "dTYg8", - "x": 7109, - "y": 2675, - "descendants": { - "IGhQ7": { - "stroke": { - "align": "inside", - "thickness": 1 - } - }, - "n84vbu": { - "fill": "$--secondary-foreground" - } - } - }, - { - "id": "i85SnN", - "type": "ref", - "ref": "ENEfb", - "x": 7109, - "y": 2798 - }, - { - "id": "WwoWf", - "type": "ref", - "ref": "ToEsS", - "x": 7109, - "y": 2929 - }, - { - "id": "ea6L5", - "type": "ref", - "ref": "fmOct", - "x": 7109, - "y": 3067 - }, - { - "type": "frame", - "id": "O6sWb", - "x": 7553, - "y": 2051, - "name": "Web Sidebar Component Parts", - "width": 420, - "fill": "#00000000", - "layout": "vertical", - "gap": 20, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "dTYg8", - "name": "SidebarProjectSwitcher", - "reusable": true, - "width": "fill_container", - "fill": "#FFFFFF", - "cornerRadius": 8, - "layout": "vertical", - "gap": 5, - "padding": [ - 9, - 10 - ], - "children": [ - { - "type": "frame", - "id": "n8fWWY", - "name": "projectHeader", - "width": "fill_container", - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "AFfN6", - "name": "projectIdentity", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "IGhQ7", - "name": "projectMark", - "width": 26, - "height": 26, - "fill": "$--card", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "layout": "vertical", - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "n84vbu", - "name": "projectLetter", - "fill": "#00796B", - "content": "J", - "fontFamily": "JetBrains Mono", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "wvhLI", - "name": "projectText", - "width": "fill_container", - "layout": "vertical", - "gap": 2, - "children": [ - { - "type": "text", - "id": "PXJdy", - "name": "projectName", - "fill": "$--foreground", - "content": "jcode", - "fontFamily": "Geist", - "fontSize": 15, - "fontWeight": "600" - }, - { - "type": "text", - "id": "JonNM", - "name": "projectPath", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "D:/workpath/jcode-design", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "icon_font", - "id": "KqOiX", - "name": "projectSwitchIcon", - "width": 18, - "height": 18, - "iconFontName": "chevrons-up-down", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "frame", - "id": "ENEfb", - "name": "SidebarNewSessionButton", - "reusable": true, - "width": "fill_container", - "height": 38, - "fill": "$--card", - "cornerRadius": 7, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "gap": 8, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "EtJH1", - "name": "newSessionIcon", - "width": 16, - "height": 16, - "iconFontName": "square-pen", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "v6CPn", - "name": "newSessionLabel", - "fill": "$--foreground", - "content": "New chat", - "fontFamily": "Geist", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "frame", - "id": "f587fY", - "name": "SidebarSessionList", - "reusable": true, - "width": "fill_container", - "layout": "vertical", - "gap": 3, - "padding": [ - 4, - 0 - ], - "children": [ - { - "type": "frame", - "id": "ToEsS", - "name": "SidebarSessionItemActive", - "reusable": true, - "width": "fill_container", - "fill": "#ECEDE8", - "cornerRadius": 7, - "layout": "vertical", - "gap": 4, - "padding": [ - 8, - 9 - ], - "children": [ - { - "type": "text", - "id": "q99H2n", - "name": "sessionTitle", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Support password special chars", - "lineHeight": 1.25, - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "PEKS8", - "name": "sessionMeta", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "working · 2m 45s · +32 -0", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "fmOct", - "name": "SidebarSessionItem", - "reusable": true, - "width": "fill_container", - "cornerRadius": 7, - "layout": "vertical", - "gap": 4, - "padding": [ - 8, - 9 - ], - "children": [ - { - "type": "text", - "id": "X7drRO", - "name": "sessionTitle", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Add GlobSearch sample states", - "lineHeight": 1.25, - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "DqXI4", - "name": "sessionMeta", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "review ready · yesterday", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "Qpv8f", - "name": "SidebarSessionItem", - "width": "fill_container", - "cornerRadius": 7, - "layout": "vertical", - "gap": 4, - "padding": [ - 8, - 9 - ], - "children": [ - { - "type": "text", - "id": "DSoO0", - "name": "sessionTitle", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Architecture team workspace", - "lineHeight": 1.25, - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "vNXuq", - "name": "sessionMeta", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "gpt-5.1 · Apr 25", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "Wy8iJ", - "name": "SidebarSessionItem", - "width": "fill_container", - "cornerRadius": 7, - "layout": "vertical", - "gap": 4, - "padding": [ - 8, - 9 - ], - "children": [ - { - "type": "text", - "id": "RP9T8", - "name": "sessionTitle", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Quick greeting test", - "lineHeight": 1.25, - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "RnUwL", - "name": "sessionMeta", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "gpt-5.1 · Apr 24", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "xZUqa", - "name": "SidebarSessionItem", - "width": "fill_container", - "cornerRadius": 7, - "layout": "vertical", - "gap": 4, - "padding": [ - 8, - 9 - ], - "children": [ - { - "type": "text", - "id": "msn54", - "name": "sessionTitle", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Qwen project checkpoint", - "lineHeight": 1.25, - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "text", - "id": "Ml83M", - "name": "sessionMeta", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "qwen3.6-plus · Apr 25", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "YjQd7", - "name": "SidebarStatusFooter", - "reusable": true, - "width": "fill_container", - "height": 36, - "padding": [ - 6, - 0 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "EJ42X", - "name": "footerText", - "width": "fill_container", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "BxALR", - "name": "footerModel", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "jcode-web · gpt-5.5", - "fontFamily": "Geist", - "fontSize": 11, - "fontWeight": "normal" - } - ] - }, - { - "type": "icon_font", - "id": "HDeik", - "layoutPosition": "absolute", - "x": 260, - "y": 11, - "name": "footerModeIcon", - "width": 14, - "height": 14, - "iconFontName": "moon", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "u0FaUh", - "name": "footerSettings", - "width": 15, - "height": 15, - "iconFontName": "settings", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - } - ] - }, - { - "type": "icon_font", - "id": "zKfD9", - "x": 7212, - "y": 3174, - "name": "footerModeIcon", - "width": 14, - "height": 14, - "iconFontName": "moon", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "V1vivG", - "x": 7236, - "y": 3173, - "name": "footerSettings", - "width": 15, - "height": 15, - "iconFontName": "settings", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "frame", - "id": "jBVb4", - "x": 9050, - "y": 2260, - "name": "Top Bar", - "reusable": true, - "width": "fill_container(1084)", - "height": 52, - "fill": "#FAFAF8", - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "gap": 12, - "padding": [ - 0, - 14 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "lLYE4", - "name": "leftFrame", - "gap": 10, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "ERmyq", - "name": "menuIcon", - "width": 16, - "height": 16, - "iconFontName": "menu", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "frame", - "id": "FnpS8", - "name": "topTask", - "width": 180, - "layout": "vertical", - "gap": 1, - "children": [ - { - "type": "text", - "id": "pa9Wh", - "name": "topTaskTitle", - "fill": "#111111", - "content": "Support password special chars", - "fontFamily": "Geist", - "fontSize": 12, - "fontWeight": "600" - }, - { - "type": "text", - "id": "bcQ7H", - "name": "topTaskMeta", - "fill": "#666666", - "content": "auth tests · jtype-web", - "fontFamily": "Geist", - "fontSize": 10, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "wqm5d", - "name": "searchBox", - "fill": "#FFFFFF", - "cornerRadius": 7, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "#CBCCC9" - }, - "gap": 6, - "padding": [ - 6, - 8 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "DcFFd", - "width": 13, - "height": 13, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "JSVVk", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 48, - "content": "Search", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "wiNvx", - "x": 189, - "y": 7, - "name": "shortcut", - "enabled": false, - "fill": "$--card", - "cornerRadius": 4, - "padding": [ - 1, - 5 - ], - "children": [ - { - "type": "text", - "id": "umZ6s", - "fill": "$--muted-foreground", - "content": "Ctrl+K", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "Nczcm", - "name": "rightSpacer", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "eRBGe", - "name": "Frame 1", - "height": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "Z2h0B", - "width": 18, - "height": 18, - "iconFontName": "square-terminal", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "HIF1g", - "width": 18, - "height": 18, - "iconFontName": "file-diff", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "IqQJA", - "width": 18, - "height": 18, - "iconFontName": "folder-open", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "ogoag", - "width": 18, - "height": 18, - "iconFontName": "panel-right", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "ellipse", - "id": "HcoIU", - "x": 112, - "y": 22, - "enabled": false, - "fill": "#22C55E", - "width": 8, - "height": 8 - }, - { - "type": "text", - "id": "bSMRa", - "fill": "#15803D", - "content": "+32 -0", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "600" - } - ] - } - ] - }, - { - "type": "icon_font", - "id": "OVzNw", - "x": 9050, - "y": 2051, - "name": "TopBarIconTerminal", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "square-terminal", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "T0JjFV", - "x": 9080, - "y": 2051, - "name": "TopBarIconFileDiff", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "file-diff", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "ZNp6l", - "x": 9110, - "y": 2051, - "name": "TopBarIconFolderOpen", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "folder-open", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "y0sdC7", - "x": 9140, - "y": 2051, - "name": "TopBarIconPanelRight", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "panel-right", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "frame", - "id": "T4Jpkx", - "x": 9050, - "y": 2151, - "name": "Top Bar Active", - "reusable": true, - "width": "fill_container(1084)", - "height": 56, - "fill": "$--card", - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "gap": 12, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "po3v8", - "name": "leftFrame", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "blgPF", - "name": "menuIcon", - "width": 16, - "height": 16, - "iconFontName": "menu", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "frame", - "id": "OyeZo", - "name": "searchBox", - "fill": "$--muted", - "cornerRadius": 8, - "gap": 8, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "FcIlh", - "width": 14, - "height": 14, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "OFdWQ", - "fill": "$--muted-foreground", - "content": "Search ", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "bniab", - "name": "shortcut", - "fill": "$--card", - "cornerRadius": 4, - "padding": [ - 2, - 6 - ], - "children": [ - { - "type": "text", - "id": "NocBV", - "fill": "$--muted-foreground", - "content": "Ctrl+K", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "x2Dc3", - "name": "rightSpacer", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "U78alX", - "name": "rightFrame", - "height": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "id": "w1nwaQ", - "type": "ref", - "ref": "OVzNw" - }, - { - "id": "t0u3i", - "type": "ref", - "ref": "T0JjFV" - }, - { - "id": "ZvVEe", - "type": "ref", - "ref": "ZNp6l" - }, - { - "id": "c1EMiI", - "type": "ref", - "ref": "y0sdC7" - }, - { - "type": "ellipse", - "id": "TLgq7", - "fill": "#22C55E", - "width": 8, - "height": 8 - }, - { - "type": "text", - "id": "o6tqM1", - "fill": "$--muted-foreground", - "content": "v0.2.0", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "icon_font", - "id": "gLyoH", - "x": 9050, - "y": 2051, - "name": "TopBarIconTerminal", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "square-terminal", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "urbxu", - "x": 9080, - "y": 2051, - "name": "TopBarIconFileDiff", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "file-diff", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "Z319YU", - "x": 9110, - "y": 2051, - "name": "TopBarIconFolderOpen", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "folder-open", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "icon_font", - "id": "sQGOz", - "x": 9140, - "y": 2051, - "name": "TopBarIconPanelRight", - "reusable": true, - "width": 20, - "height": 20, - "iconFontName": "panel-right", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "frame", - "id": "MKdRe", - "x": 9050, - "y": 2151, - "name": "Top Bar Active", - "reusable": true, - "width": "fill_container(1084)", - "height": 56, - "fill": "$--card", - "stroke": { - "align": "inside", - "thickness": { - "bottom": 1 - }, - "fill": "$--border" - }, - "gap": 12, - "padding": [ - 0, - 16 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "dYz9b", - "name": "leftFrame", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "rcwLB", - "name": "menuIcon", - "width": 16, - "height": 16, - "iconFontName": "menu", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "frame", - "id": "zO4hC", - "name": "searchBox", - "fill": "$--muted", - "cornerRadius": 8, - "gap": 8, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "oUCun", - "width": 14, - "height": 14, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "j1W8o", - "fill": "$--muted-foreground", - "content": "Search ", - "fontFamily": "$--font-secondary", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "qGOuD", - "name": "shortcut", - "fill": "$--card", - "cornerRadius": 4, - "padding": [ - 2, - 6 - ], - "children": [ - { - "type": "text", - "id": "Y6e6VQ", - "fill": "$--muted-foreground", - "content": "Ctrl+K", - "fontFamily": "$--font-secondary", - "fontSize": 11, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "vXDX1", - "name": "rightSpacer", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "KsF7M", - "name": "rightFrame", - "height": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "id": "JZ0I4", - "type": "ref", - "ref": "gLyoH" - }, - { - "id": "f2OGR", - "type": "ref", - "ref": "urbxu" - }, - { - "id": "E3KHA", - "type": "ref", - "ref": "Z319YU" - }, - { - "id": "okSBI", - "type": "ref", - "ref": "sQGOz" - }, - { - "type": "ellipse", - "id": "W6aOxW", - "fill": "#22C55E", - "width": 8, - "height": 8 - }, - { - "type": "text", - "id": "itJbi", - "fill": "$--muted-foreground", - "content": "v0.2.0", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "lPu6y", - "x": 10184, - "y": 2051, - "name": "AttachPopup", - "reusable": true, - "width": 240, - "fill": "$--card", - "cornerRadius": 12, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000020", - "offset": { - "x": 0, - "y": 4 - }, - "blur": 16 - }, - "layout": "vertical", - "padding": 8, - "children": [ - { - "type": "frame", - "id": "H1sZ2n", - "cornerRadius": 8, - "gap": 10, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "IWZ0W", - "width": 16, - "height": 16, - "iconFontName": "paperclip", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "text", - "id": "wLrAa", - "fill": "$--foreground", - "content": "Add photos & files", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "oUv94", - "cornerRadius": 8, - "gap": 10, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "J10UW", - "width": 16, - "height": 16, - "iconFontName": "list-checks", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "text", - "id": "kI2N2", - "fill": "$--foreground", - "content": "Plan mode", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "yUMbA", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "w1Pvz", - "width": 36, - "height": 20, - "fill": "$--muted", - "cornerRadius": 999, - "children": [ - { - "type": "ellipse", - "id": "ukbVf", - "fill": "$--white", - "width": 16, - "height": 16 - } - ] - } - ] - }, - { - "type": "frame", - "id": "eLzx8", - "cornerRadius": 8, - "gap": 10, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "Clfcc", - "width": 16, - "height": 16, - "iconFontName": "blocks", - "iconFontFamily": "lucide", - "fill": "$--foreground" - }, - { - "type": "text", - "id": "chvll", - "fill": "$--foreground", - "content": "Plugins", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "SW4pn", - "width": "fill_container", - "height": 1 - }, - { - "type": "icon_font", - "id": "g40hXa", - "width": 14, - "height": 14, - "iconFontName": "chevron-right", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "pwlLK", - "width": "fill_container", - "height": 1, - "fill": "$--border" - }, - { - "type": "frame", - "id": "EHced", - "cornerRadius": 8, - "gap": 8, - "padding": [ - 8, - 10 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "rOvLB", - "width": 16, - "height": 16, - "iconFontName": "plus", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "icon_font", - "id": "B4zDD8", - "width": 14, - "height": 14, - "iconFontName": "shield-check", - "iconFontFamily": "lucide", - "fill": "#F97316" - }, - { - "type": "text", - "id": "Rus72", - "fill": "#F97316", - "content": "Full access", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "500" - }, - { - "type": "icon_font", - "id": "RE1X8", - "width": 12, - "height": 12, - "iconFontName": "chevron-down", - "iconFontFamily": "lucide", - "fill": "#F97316" - } - ] - } - ] - } - ], - "themes": { - "Mode": [ - "Light", - "Dark" - ] - }, - "variables": { - "--sidebar": { - "type": "color", - "value": [ - { - "value": "#E7E8E5" - }, - { - "value": "#18181b", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-foreground": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-primary": { - "type": "color", - "value": [ - { - "value": "#18181b" - }, - { - "value": "#18181b", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-primary-foreground": { - "type": "color", - "value": [ - { - "value": "#fafafa" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-border": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#ffffff1a", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-accent": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2a2a30", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-accent-foreground": { - "type": "color", - "value": [ - { - "value": "#18181b" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-ring": { - "type": "color", - "value": [ - { - "value": "#71717a" - }, - { - "value": "#71717a", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--background": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--card": { - "type": "color", - "value": [ - { - "value": "#FFFFFF" - }, - { - "value": "#1A1A1A", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--card-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--popover": { - "type": "color", - "value": [ - { - "value": "#FFFFFF" - }, - { - "value": "#1A1A1A", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--popover-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--primary": { - "type": "color", - "value": [ - { - "value": "#FF8400" - }, - { - "value": "#FF8400", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--primary-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--secondary": { - "type": "color", - "value": [ - { - "value": "#E7E8E5" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--secondary-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--muted": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--muted-foreground": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#B8B9B6", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--accent": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--accent-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#F2F3F0", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--destructive": { - "type": "color", - "value": [ - { - "value": "#D93C15" - }, - { - "value": "#FF5C33", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--border": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--input": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--ring": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#666666", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--white": { - "type": "color", - "value": "#FFFFFF" - }, - "--black": { - "type": "color", - "value": "#000000" - }, - "--font-primary": { - "type": "string", - "value": "JetBrains Mono" - }, - "--font-secondary": { - "type": "string", - "value": "Geist" - }, - "--radius-none": { - "type": "number", - "value": 0 - }, - "--radius-pill": { - "type": "number", - "value": 999 - }, - "--color-success": { - "type": "color", - "value": [ - { - "value": "#DFE6E1" - }, - { - "value": "#222924", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-success-foreground": { - "type": "color", - "value": [ - { - "value": "#004D1A" - }, - { - "value": "#B6FFCE", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-warning": { - "type": "color", - "value": [ - { - "value": "#E9E3D8" - }, - { - "value": "#291C0F", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-warning-foreground": { - "type": "color", - "value": [ - { - "value": "#804200" - }, - { - "value": "#FF8400", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-error": { - "type": "color", - "value": [ - { - "value": "#E5DCDA" - }, - { - "value": "#24100B", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-error-foreground": { - "type": "color", - "value": [ - { - "value": "#8C1C00" - }, - { - "value": "#FF5C33", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-info": { - "type": "color", - "value": [ - { - "value": "#DFDFE6" - }, - { - "value": "#222229", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-info-foreground": { - "type": "color", - "value": [ - { - "value": "#000066" - }, - { - "value": "#B2B2FF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--radius-m": { - "type": "number", - "value": 16 - } - } -} \ No newline at end of file diff --git a/design/jcode.pen b/design/jcode.pen deleted file mode 100644 index 15e74b78..00000000 --- a/design/jcode.pen +++ /dev/null @@ -1,2851 +0,0 @@ -{ - "version": "2.11", - "children": [ - { - "type": "frame", - "id": "3yl0E", - "x": -50, - "y": 2793, - "name": "JCODE Landing Page", - "width": 1440, - "fill": "$--background", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "uu9gV", - "name": "Navbar Section", - "width": "fill_container", - "height": 64, - "fill": "$--background", - "padding": [ - 0, - 48 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "Q8OAM", - "name": "logo", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "sp71Q", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "Y3Ppb", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "Kr9LN", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - } - ] - }, - { - "type": "frame", - "id": "vCoNJ", - "name": "navLinks", - "gap": 32, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "J1zlM", - "fill": "$--muted-foreground", - "content": "Features", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "bepBv", - "fill": "$--muted-foreground", - "content": "Docs", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "ZBqvN", - "fill": "$--muted-foreground", - "content": "GitHub", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "160Ac", - "name": "ctaBtn", - "height": 36, - "fill": "#111111", - "cornerRadius": 6, - "padding": [ - 0, - 16 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "1MGXY", - "fill": "#FFFFFF", - "content": "Get Started", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "xIMqn", - "name": "Hero Section", - "width": "fill_container", - "fill": "$--background", - "layout": "vertical", - "gap": 24, - "padding": [ - 80, - 0 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "HuFVH", - "name": "badge", - "fill": "$--card", - "cornerRadius": 999, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000014", - "spread": 1 - }, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "PtLiQ", - "fill": "$--muted-foreground", - "content": "v0.3.3 — Terminal AI Agent", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "Dfi07", - "name": "headline", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": 800, - "content": "AI Coding Agent in Your Terminal", - "textAlign": "center", - "fontFamily": "Geist", - "fontSize": 56, - "fontWeight": "700", - "letterSpacing": -2 - }, - { - "type": "text", - "id": "t9KJs", - "name": "subtitle", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 640, - "content": "Describe tasks in natural language. JCODE reads your codebase, makes precise edits, runs commands, and shows every step — no black boxes.", - "lineHeight": 1.5, - "textAlign": "center", - "fontFamily": "Geist", - "fontSize": 18, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "y6QqJ", - "name": "ctaRow", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "ZOZWd", - "name": "primBtn", - "height": 44, - "fill": "#111111", - "cornerRadius": 6, - "padding": [ - 0, - 24 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "yQUfc", - "fill": "#FFFFFF", - "content": "Get Started", - "fontFamily": "Geist", - "fontSize": 15, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "bhlqc", - "name": "secBtn", - "height": 44, - "fill": "$--card", - "cornerRadius": 6, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000014", - "spread": 1 - }, - "padding": [ - 0, - 24 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "PlvJ0", - "fill": "$--foreground", - "content": "View on GitHub", - "fontFamily": "Geist", - "fontSize": 15, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "V8kXX", - "name": "installBox", - "fill": "#1a1a1a", - "cornerRadius": 8, - "padding": [ - 14, - 20 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ddYhO", - "fill": "#B8B9B6", - "content": "curl -fsSL https://raw.githubusercontent.com/cnjack/jcode/main/script/install.sh | sh", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Ao0en", - "name": "Terminal Preview Section", - "width": "fill_container", - "height": 420, - "fill": "$--background", - "layout": "vertical", - "padding": [ - 40, - 0 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "H2VAL", - "name": "termFrame", - "width": 720, - "fill": "#111111", - "cornerRadius": 12, - "effect": [ - { - "type": "shadow", - "shadowType": "outer", - "color": "#00000026", - "spread": 1 - }, - { - "type": "shadow", - "shadowType": "outer", - "color": "#00000026", - "offset": { - "x": 0, - "y": 4 - }, - "blur": 16 - } - ], - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "ceTyP", - "name": "termBar", - "width": "fill_container", - "height": 36, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "72Atl", - "name": "dots", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "SSi9U", - "fill": "#FF5F57", - "width": 12, - "height": 12 - }, - { - "type": "ellipse", - "id": "p1zVo", - "fill": "#FFBD2E", - "width": 12, - "height": 12 - }, - { - "type": "ellipse", - "id": "lqtcF", - "fill": "#28C840", - "width": 12, - "height": 12 - } - ] - }, - { - "type": "text", - "id": "3zbyN", - "fill": "#666666", - "content": "jcode", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "q5RaV", - "name": "termContent", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "padding": [ - 16, - 20 - ], - "children": [ - { - "type": "text", - "id": "UZNN8", - "fill": "#FFFFFF", - "content": "$ jcode", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "4m2E8", - "fill": "#FF8400", - "content": "Agent │ Model: openai / gpt-4o │ Approve: Ask │ [████░░░░░░] 2% │ MCP: 2/5", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "XXjQS", - "fill": "#B8B9B6", - "content": "Plan mode active. Use Ctrl+P to toggle.", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "71NIc", - "fill": "#B8B9B6", - "content": "Team: 3 agents online │ Tasks: 12 running │ Context: 4.2K / 128K tokens", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "YMmMw", - "fill": "#FFFFFF", - "content": "> Ready. What would you like to build?", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "SvI39", - "name": "Features Section", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "N9fXd", - "name": "featuresHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "padding": [ - 80, - 120, - 40, - 120 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "YJAal", - "name": "featuresBadge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "0sP6f", - "fill": "$--primary-foreground", - "content": "Features", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "fdUDY", - "name": "featuresTitle", - "fill": "$--foreground", - "content": "Everything You Need to Code with AI", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 40, - "fontWeight": "600" - }, - { - "type": "text", - "id": "EE3DL", - "name": "featuresSubtitle", - "fill": "$--muted-foreground", - "content": "A powerful terminal agent that brings transparency, control, and collaboration to AI-assisted coding.", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 18, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "KZ892", - "name": "featuresRow1", - "width": "fill_container", - "gap": 16, - "padding": [ - 0, - 120, - 16, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "r4TkN", - "name": "card1", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "dBsbT", - "name": "icon1", - "width": 24, - "height": 24, - "iconFontName": "eye", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "httPv", - "fill": "$--foreground", - "content": "Transparent by Design", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "BFR9L", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 276, - "height": 104, - "content": "Every tool call is visible. Every edit requires your approval. No black boxes, no hidden actions.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "OoDkX", - "name": "card2", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "xEQhQ", - "name": "icon2", - "width": 24, - "height": 24, - "iconFontName": "clipboard-list", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "vKW9z", - "fill": "$--foreground", - "content": "Plan Mode", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "jGQjZ", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 283, - "height": 87, - "content": "Ctrl+P enters read-only exploration. Review structured plans before any code is touched.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "ZJWbl", - "name": "card3", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "Es4Nn", - "name": "icon3", - "width": 24, - "height": 24, - "iconFontName": "users", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "pa59Q", - "fill": "$--foreground", - "content": "Parallel Teams", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "eIz54", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 271, - "height": 80, - "content": "Create multiple AI teammates working on different tasks simultaneously. Shift+Up/Down to switch.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "J5oXb", - "name": "featuresRow2", - "width": "fill_container", - "gap": 16, - "padding": [ - 16, - 120, - 80, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "GOIga", - "name": "card4", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "LRLY8", - "name": "icon4", - "width": 24, - "height": 24, - "iconFontName": "server", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "4Piu9", - "fill": "$--foreground", - "content": "SSH Support", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "O6Oi3", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 258, - "height": 70, - "content": "Connect to remote hosts transparently with `/ssh user@host`. Work locally or remotely without friction.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "72LB8", - "name": "card5", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "lgAZe", - "name": "icon5", - "width": 24, - "height": 24, - "iconFontName": "cpu", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "yPMpv", - "fill": "$--foreground", - "content": "Model Compatible", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "ph3N9", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 261, - "height": 59, - "content": "Supports any OpenAI-compatible API. Switch models mid-session. Use the best model for each task.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "QasXB", - "name": "card6", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "oKVNZ", - "name": "icon6", - "width": 24, - "height": 24, - "iconFontName": "plug", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "OfzOt", - "fill": "$--foreground", - "content": "MCP Integration", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "o6CXm", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 248, - "height": 59, - "content": "Connect Model Context Protocol servers to extend capabilities. Custom tools, custom workflows.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "pFUir", - "name": "Workflow Section", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "yDGgh", - "name": "workflowHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "padding": [ - 80, - 120, - 48, - 120 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "oO9tW", - "name": "workflowBadge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "Fsbrp", - "fill": "$--primary-foreground", - "content": "How It Works", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "SKiex", - "fill": "$--foreground", - "content": "From Idea to Code in Three Steps", - "fontFamily": "Inter", - "fontSize": 40, - "fontWeight": "600" - }, - { - "type": "text", - "id": "rs2Nx", - "fill": "$--muted-foreground", - "content": "Describe what you want. JCODE plans the approach. Then executes with full transparency.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 18, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "x4hYL", - "name": "workflowSteps", - "width": "fill_container", - "gap": 24, - "padding": [ - 0, - 120, - 80, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "b2FHT", - "name": "step1", - "width": 320, - "layout": "vertical", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "5ZA5p", - "name": "step1Num", - "width": 48, - "height": 48, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "8swfK", - "fill": "$--primary-foreground", - "content": "1", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "g247v", - "fill": "$--foreground", - "content": "Describe", - "fontFamily": "Inter", - "fontSize": 24, - "fontWeight": "600" - }, - { - "type": "text", - "id": "5ovce", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Tell JCODE what you want to build in plain English. No complex prompts or memorized commands needed.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "c449F", - "name": "step2", - "width": 320, - "layout": "vertical", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "6P0cl", - "name": "step2Num", - "width": 48, - "height": 48, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "dtXul", - "fill": "$--primary-foreground", - "content": "2", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "S7dGM", - "fill": "$--foreground", - "content": "Plan", - "fontFamily": "Inter", - "fontSize": 24, - "fontWeight": "600" - }, - { - "type": "text", - "id": "MVP62", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Review the structured plan before any code changes. Enter Plan Mode with Ctrl+P to explore safely.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "ITVI7", - "name": "step3", - "width": 320, - "layout": "vertical", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "JLNcz", - "name": "step3Num", - "width": 48, - "height": 48, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "q7StA", - "fill": "$--primary-foreground", - "content": "3", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "C0omr", - "fill": "$--foreground", - "content": "Execute", - "fontFamily": "Inter", - "fontSize": 24, - "fontWeight": "600" - }, - { - "type": "text", - "id": "4ZqLE", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "JCODE makes precise edits, runs commands, and shows every step. Approve or reject each action.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "iHTNd", - "name": "Team Section", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "Q38e4", - "name": "teamHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "padding": [ - 80, - 120, - 48, - 120 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "CxlmF", - "name": "teamBadge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "74H2K", - "fill": "$--primary-foreground", - "content": "Parallel Teams", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "YYaCT", - "fill": "$--foreground", - "content": "One Task. Multiple Agents. Zero Wait.", - "fontFamily": "Inter", - "fontSize": 40, - "fontWeight": "600" - }, - { - "type": "text", - "id": "yZTDa", - "fill": "$--muted-foreground", - "content": "Create specialized AI teammates and assign them different tasks. They work in parallel while you stay in control.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 18, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "6GG3E", - "name": "teamVisual", - "width": "fill_container", - "gap": 16, - "padding": [ - 0, - 120, - 80, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "WR5jf", - "name": "agent1", - "width": 280, - "height": 180, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "CQtTR", - "name": "agent1Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "QDYyp", - "name": "agent1Avatar", - "width": 32, - "height": 32, - "fill": "#0a72ef", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "GdpDC", - "fill": "#ffffff", - "content": "R", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "BAtwo", - "fill": "$--foreground", - "content": "Researcher", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "q8raN", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Exploring codebase structure and documentation", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "TB8Xl", - "name": "agent1Status", - "fill": "#dfe6e1", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "children": [ - { - "type": "text", - "id": "Pbnsx", - "fill": "#004d1a", - "content": "Working", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "S52Sm", - "name": "agent2", - "width": 280, - "height": 180, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "zRCWx", - "name": "agent2Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "AfkDk", - "name": "agent2Avatar", - "width": 32, - "height": 32, - "fill": "#de1d8d", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "o8dbM", - "fill": "#ffffff", - "content": "C", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "ZkUlu", - "fill": "$--foreground", - "content": "Coder", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "onr4v", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Implementing feature with precise file edits", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "1uqxn", - "name": "agent2Status", - "fill": "#dfe6e1", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "children": [ - { - "type": "text", - "id": "ULNtr", - "fill": "#004d1a", - "content": "Working", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "YIhxb", - "name": "agent3", - "width": 280, - "height": 180, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "vSCL8", - "name": "agent3Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "QjUYF", - "name": "agent3Avatar", - "width": 32, - "height": 32, - "fill": "#ff5b4f", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "k6oIM", - "fill": "#ffffff", - "content": "T", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "VBV6S", - "fill": "$--foreground", - "content": "Tester", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "phbsH", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Writing and running test cases", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "N9haz", - "name": "agent3Status", - "fill": "#e5dcda", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "children": [ - { - "type": "text", - "id": "T7nPB", - "fill": "#8c1c00", - "content": "Idle", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "8jBsh", - "name": "Footer Section", - "width": "fill_container", - "height": 280, - "layout": "vertical", - "children": [ - { - "type": "rectangle", - "id": "Rrxaa", - "name": "footerBorder", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "8qduK", - "name": "fc", - "width": "fill_container", - "gap": 64, - "padding": [ - 48, - 120 - ], - "justifyContent": "space_between", - "children": [ - { - "type": "frame", - "id": "CPVpu", - "name": "fl", - "width": 360, - "layout": "vertical", - "gap": 16, - "children": [ - { - "type": "text", - "id": "TQKRS", - "fill": "$--foreground", - "content": "JCODE", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "GFnrH", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "AI coding agent for your terminal. Transparent, collaborative, and fully in your control.", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "TzmZo", - "name": "fi", - "fill": "#1a1a1a", - "cornerRadius": 6, - "gap": 8, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "text", - "id": "xjrP3", - "fill": "#666666", - "content": "$", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Gj0Dd", - "fill": "#ffffff", - "content": "jcode", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "5WYtk", - "name": "flinks", - "gap": 64, - "children": [ - { - "type": "frame", - "id": "uJz8U", - "name": "fc1", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "text", - "id": "4JAhq", - "fill": "$--foreground", - "content": "Product", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - }, - { - "type": "text", - "id": "Bq1LR", - "fill": "$--muted-foreground", - "content": "Features", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "WIiYz", - "fill": "$--muted-foreground", - "content": "Documentation", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "0xNAd", - "fill": "$--muted-foreground", - "content": "Changelog", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "xiLJG", - "name": "fc2", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "text", - "id": "JLbds", - "fill": "$--foreground", - "content": "Resources", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - }, - { - "type": "text", - "id": "LNYs1", - "fill": "$--muted-foreground", - "content": "GitHub", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "3jqaC", - "fill": "$--muted-foreground", - "content": "Issues", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "QHyCP", - "fill": "$--muted-foreground", - "content": "MIT License", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Esjot", - "name": "fr", - "width": 200, - "layout": "vertical", - "gap": 16, - "alignItems": "end", - "children": [ - { - "type": "text", - "id": "B6dkb", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ready to supercharge your terminal?", - "textAlign": "right", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "idkW5", - "name": "fcta", - "fill": "$--foreground", - "cornerRadius": 6, - "padding": [ - 10, - 20 - ], - "children": [ - { - "type": "text", - "id": "TBeqD", - "fill": "$--white", - "content": "Get Started", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "500" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "jMUQl", - "name": "fcp", - "width": "fill_container", - "padding": [ - 24, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "text", - "id": "xOblr", - "fill": "$--muted-foreground", - "content": "© 2026 JCODE. Open source under MIT License.", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "pX4qN", - "x": 1430, - "y": 2793, - "name": "JCODE Documentation", - "width": 1440, - "fill": "$--background", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "BIYDF", - "name": "Doc Navbar", - "width": "fill_container", - "height": 64, - "fill": "$--card", - "padding": [ - 0, - 24 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "m8SMv", - "name": "logo", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "hoXeB", - "name": "logo", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "jLu1Z", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "FHq8i", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "xbMcX", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - } - ] - }, - { - "type": "text", - "id": "e82c0", - "fill": "$--muted-foreground", - "content": "Docs", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "89tse", - "name": "search", - "fill": "$--card", - "cornerRadius": 6, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000014", - "spread": 1 - }, - "gap": 8, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "bpaFM", - "width": 16, - "height": 16, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "ZxHgi", - "fill": "$--muted-foreground", - "content": "Search documentation...", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "uHWsA", - "name": "right", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "2g2Iw", - "name": "github", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "0JkVQ", - "width": 16, - "height": 16, - "iconFontName": "github", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "irdFG", - "fill": "$--muted-foreground", - "content": "GitHub", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "1jbeX", - "name": "badge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "Bcap9", - "fill": "$--primary-foreground", - "content": "v0.3.3", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "600" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "zGmxK", - "layoutPosition": "absolute", - "x": 0, - "y": 63, - "fill": "$--border", - "width": "fill_container(0)", - "height": 1 - } - ] - }, - { - "type": "frame", - "id": "WW9Ws", - "name": "Doc Body", - "width": "fill_container", - "children": [ - { - "type": "frame", - "id": "IFImE", - "name": "Doc Sidebar", - "width": 260, - "fill": "$--background", - "stroke": { - "align": "outside", - "thickness": { - "right": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "padding": [ - 24, - 16 - ], - "children": [ - { - "type": "frame", - "id": "P3fBN", - "name": "sb1", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": [ - 24, - 16 - ], - "children": [ - { - "type": "text", - "id": "butRn", - "fill": "$--muted-foreground", - "content": "GETTING STARTED", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "lGszZ", - "fill": "$--primary", - "content": "Introduction", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - }, - { - "type": "text", - "id": "Hk7ri", - "fill": "$--muted-foreground", - "content": "Quick Start", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "cLeEC", - "fill": "$--muted-foreground", - "content": "Installation", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "r2S2b", - "fill": "$--muted-foreground", - "content": "Configuration", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "frame", - "id": "2AMai", - "name": "activeFrame", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "rectangle", - "cornerRadius": 2, - "id": "nd57q", - "name": "indicator", - "fill": "$--primary", - "width": 3, - "height": 20 - } - ] - } - ] - }, - { - "type": "frame", - "id": "enTwR", - "name": "sb2", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": 16, - "children": [ - { - "type": "text", - "id": "3optG", - "fill": "$--muted-foreground", - "content": "CORE CONCEPTS", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "2h1fj", - "fill": "$--muted-foreground", - "content": "Plan Mode", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "8y2lF", - "fill": "$--muted-foreground", - "content": "Parallel Teams", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "VkUAf", - "fill": "$--muted-foreground", - "content": "SSH Support", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "ObtVL", - "fill": "$--muted-foreground", - "content": "Token Budget", - "fontFamily": "Inter", - "fontSize": 14 - } - ] - }, - { - "type": "frame", - "id": "cMMHX", - "name": "sb3", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": 16, - "children": [ - { - "type": "text", - "id": "Gp5hK", - "fill": "$--muted-foreground", - "content": "REFERENCE", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "QXWZI", - "fill": "$--muted-foreground", - "content": "Commands", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "0hG5q", - "fill": "$--muted-foreground", - "content": "Configuration File", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "DTLMg", - "fill": "$--muted-foreground", - "content": "Environment Variables", - "fontFamily": "Inter", - "fontSize": 14 - }, - { - "type": "text", - "id": "wXAsj", - "fill": "$--muted-foreground", - "content": "MCP Servers", - "fontFamily": "Inter", - "fontSize": 14 - } - ] - } - ] - }, - { - "type": "frame", - "id": "3k50O", - "name": "Doc Content", - "width": "fill_container", - "layout": "vertical", - "gap": 32, - "padding": [ - 48, - 64 - ], - "children": [ - { - "type": "frame", - "id": "s4hiO", - "name": "contentHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "children": [ - { - "type": "text", - "id": "b2TFx", - "fill": "$--muted-foreground", - "content": "Getting Started / Introduction", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Qlisw", - "fill": "$--foreground", - "content": "Introduction", - "fontFamily": "$--font-secondary", - "fontSize": 36, - "fontWeight": "600" - } - ] - }, - { - "type": "rectangle", - "id": "MIR0w", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "text", - "id": "t3Sx6", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "JCODE is a terminal AI coding agent that brings transparency, collaboration, and control to your development workflow. Describe tasks in natural language and watch as JCODE reads your codebase, makes precise edits, runs commands, and shows every step.", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "DrwGQ", - "name": "installSection", - "width": "fill_container", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "text", - "id": "pITpj", - "fill": "$--foreground", - "content": "Quick Install", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "WysZp", - "name": "installCode", - "width": "fill_container", - "fill": "#111111", - "cornerRadius": 8, - "gap": 8, - "padding": [ - 16, - 20 - ], - "children": [ - { - "type": "text", - "id": "pAnNV", - "fill": "#666666", - "content": "$", - "fontFamily": "$--font-primary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "M7Yq4", - "fill": "#ffffff", - "content": "curl -fsSL https://raw.githubusercontent.com/cnjack/jcode/main/script/install.sh | sh", - "fontFamily": "$--font-primary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "UY1S5", - "name": "insideSection", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "children": [ - { - "type": "text", - "id": "wh1Vx", - "fill": "$--foreground", - "content": "What's Inside", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "93ltQ", - "name": "insideGrid", - "width": "fill_container", - "gap": 16, - "children": [ - { - "type": "frame", - "id": "Cf35B", - "name": "ic1", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 8, - "padding": 20, - "children": [ - { - "type": "icon_font", - "id": "VYv7a", - "width": 20, - "height": 20, - "iconFontName": "eye", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "ba2Wi", - "fill": "$--foreground", - "content": "Transparent Design", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "FTy7s", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Every tool call is visible. Approve or reject each action before it runs.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "4ZL5W", - "name": "ic2", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 8, - "padding": 20, - "children": [ - { - "type": "icon_font", - "id": "iuyME", - "width": 20, - "height": 20, - "iconFontName": "users", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "cUc8l", - "fill": "$--foreground", - "content": "Parallel Teams", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "jxIli", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Create multiple AI teammates working on different tasks simultaneously.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "gzQtx", - "name": "ic3", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 8, - "padding": 20, - "children": [ - { - "type": "icon_font", - "id": "DxRi8", - "width": 20, - "height": 20, - "iconFontName": "server", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "rVYlX", - "fill": "$--foreground", - "content": "SSH Support", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "Iomfq", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Connect to remote hosts transparently with /ssh user@host.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ], - "themes": { - "Mode": [ - "Light", - "Dark" - ] - }, - "variables": { - "--sidebar": { - "type": "color", - "value": [ - { - "value": "#E7E8E5" - }, - { - "value": "#18181b", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-foreground": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-primary": { - "type": "color", - "value": [ - { - "value": "#18181b" - }, - { - "value": "#18181b", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-primary-foreground": { - "type": "color", - "value": [ - { - "value": "#fafafa" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-border": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#ffffff1a", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-accent": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2a2a30", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-accent-foreground": { - "type": "color", - "value": [ - { - "value": "#18181b" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-ring": { - "type": "color", - "value": [ - { - "value": "#71717a" - }, - { - "value": "#71717a", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--background": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--card": { - "type": "color", - "value": [ - { - "value": "#FFFFFF" - }, - { - "value": "#1A1A1A", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--card-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--popover": { - "type": "color", - "value": [ - { - "value": "#FFFFFF" - }, - { - "value": "#1A1A1A", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--popover-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--primary": { - "type": "color", - "value": [ - { - "value": "#FF8400" - }, - { - "value": "#FF8400", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--primary-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--secondary": { - "type": "color", - "value": [ - { - "value": "#E7E8E5" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--secondary-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--muted": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--muted-foreground": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#B8B9B6", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--accent": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--accent-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#F2F3F0", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--destructive": { - "type": "color", - "value": [ - { - "value": "#D93C15" - }, - { - "value": "#FF5C33", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--border": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--input": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--ring": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#666666", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--white": { - "type": "color", - "value": "#FFFFFF" - }, - "--black": { - "type": "color", - "value": "#000000" - }, - "--font-primary": { - "type": "string", - "value": "JetBrains Mono" - }, - "--font-secondary": { - "type": "string", - "value": "Geist" - }, - "--radius-none": { - "type": "number", - "value": 0 - }, - "--radius-pill": { - "type": "number", - "value": 999 - }, - "--color-success": { - "type": "color", - "value": [ - { - "value": "#DFE6E1" - }, - { - "value": "#222924", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-success-foreground": { - "type": "color", - "value": [ - { - "value": "#004D1A" - }, - { - "value": "#B6FFCE", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-warning": { - "type": "color", - "value": [ - { - "value": "#E9E3D8" - }, - { - "value": "#291C0F", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-warning-foreground": { - "type": "color", - "value": [ - { - "value": "#804200" - }, - { - "value": "#FF8400", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-error": { - "type": "color", - "value": [ - { - "value": "#E5DCDA" - }, - { - "value": "#24100B", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-error-foreground": { - "type": "color", - "value": [ - { - "value": "#8C1C00" - }, - { - "value": "#FF5C33", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-info": { - "type": "color", - "value": [ - { - "value": "#DFDFE6" - }, - { - "value": "#222229", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-info-foreground": { - "type": "color", - "value": [ - { - "value": "#000066" - }, - { - "value": "#B2B2FF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--radius-m": { - "type": "number", - "value": 16 - } - } -} \ No newline at end of file diff --git a/design/landing-page.pen b/design/landing-page.pen deleted file mode 100644 index 88d7f791..00000000 --- a/design/landing-page.pen +++ /dev/null @@ -1,2942 +0,0 @@ -{ - "version": "2.11", - "children": [ - { - "type": "frame", - "id": "3yl0E", - "x": -50, - "y": 2793, - "name": "JCODE Landing Page", - "width": 1440, - "fill": "$--background", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "uu9gV", - "name": "Navbar Section", - "width": "fill_container", - "height": 64, - "fill": "$--background", - "padding": [ - 0, - 48 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "Q8OAM", - "name": "logo", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "sp71Q", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "Y3Ppb", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "Kr9LN", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - } - ] - }, - { - "type": "frame", - "id": "vCoNJ", - "name": "navLinks", - "gap": 32, - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "J1zlM", - "fill": "$--muted-foreground", - "content": "Features", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "bepBv", - "fill": "$--muted-foreground", - "content": "Docs", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "ZBqvN", - "fill": "$--muted-foreground", - "content": "GitHub", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "160Ac", - "name": "ctaBtn", - "height": 36, - "fill": "#111111", - "cornerRadius": 6, - "padding": [ - 0, - 16 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "1MGXY", - "fill": "#FFFFFF", - "content": "Get Started", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "xIMqn", - "name": "Hero Section", - "width": "fill_container", - "fill": "$--background", - "layout": "vertical", - "gap": 24, - "padding": [ - 80, - 0 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "HuFVH", - "name": "badge", - "fill": "$--card", - "cornerRadius": 999, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000014", - "spread": 1 - }, - "padding": [ - 6, - 12 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "PtLiQ", - "fill": "$--muted-foreground", - "content": "v0.3.3 — Terminal AI Agent", - "fontFamily": "Geist", - "fontSize": 13, - "fontWeight": "normal" - } - ] - }, - { - "type": "text", - "id": "Dfi07", - "name": "headline", - "fill": "$--foreground", - "textGrowth": "fixed-width", - "width": 800, - "content": "AI Coding Agent in Your Terminal", - "textAlign": "center", - "fontFamily": "Geist", - "fontSize": 56, - "fontWeight": "700", - "letterSpacing": -2 - }, - { - "type": "text", - "id": "t9KJs", - "name": "subtitle", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": 640, - "content": "Describe tasks in natural language. JCODE reads your codebase, makes precise edits, runs commands, and shows every step — no black boxes.", - "lineHeight": 1.5, - "textAlign": "center", - "fontFamily": "Geist", - "fontSize": 18, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "y6QqJ", - "name": "ctaRow", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "ZOZWd", - "name": "primBtn", - "height": 44, - "fill": "#111111", - "cornerRadius": 6, - "padding": [ - 0, - 24 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "yQUfc", - "fill": "#FFFFFF", - "content": "Get Started", - "fontFamily": "Geist", - "fontSize": 15, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "bhlqc", - "name": "secBtn", - "height": 44, - "fill": "$--card", - "cornerRadius": 6, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000014", - "spread": 1 - }, - "padding": [ - 0, - 24 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "PlvJ0", - "fill": "$--foreground", - "content": "View on GitHub", - "fontFamily": "Geist", - "fontSize": 15, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "V8kXX", - "name": "installBox", - "fill": "#1a1a1a", - "cornerRadius": 8, - "padding": [ - 14, - 20 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "ddYhO", - "fill": "#B8B9B6", - "content": "curl -fsSL https://raw.githubusercontent.com/cnjack/jcode/main/script/install.sh | sh", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Ao0en", - "name": "Terminal Preview Section", - "width": "fill_container", - "height": 420, - "fill": "$--background", - "layout": "vertical", - "padding": [ - 40, - 0 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "H2VAL", - "name": "termFrame", - "width": 720, - "fill": "#111111", - "cornerRadius": 12, - "effect": [ - { - "type": "shadow", - "shadowType": "outer", - "color": "#00000026", - "spread": 1 - }, - { - "type": "shadow", - "shadowType": "outer", - "color": "#00000026", - "offset": { - "x": 0, - "y": 4 - }, - "blur": 16 - } - ], - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "ceTyP", - "name": "termBar", - "width": "fill_container", - "height": 36, - "padding": [ - 0, - 16 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "72Atl", - "name": "dots", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "ellipse", - "id": "SSi9U", - "fill": "#FF5F57", - "width": 12, - "height": 12 - }, - { - "type": "ellipse", - "id": "p1zVo", - "fill": "#FFBD2E", - "width": 12, - "height": 12 - }, - { - "type": "ellipse", - "id": "lqtcF", - "fill": "#28C840", - "width": 12, - "height": 12 - } - ] - }, - { - "type": "text", - "id": "3zbyN", - "fill": "#666666", - "content": "jcode", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "q5RaV", - "name": "termContent", - "width": "fill_container", - "layout": "vertical", - "gap": 4, - "padding": [ - 16, - 20 - ], - "children": [ - { - "type": "text", - "id": "UZNN8", - "fill": "#FFFFFF", - "content": "$ jcode", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "4m2E8", - "fill": "#FF8400", - "content": "Agent │ Model: openai / gpt-4o │ Approve: Ask │ [████░░░░░░] 2% │ MCP: 2/5", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "XXjQS", - "fill": "#B8B9B6", - "content": "Plan mode active. Use Ctrl+P to toggle.", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "71NIc", - "fill": "#B8B9B6", - "content": "Team: 3 agents online │ Tasks: 12 running │ Context: 4.2K / 128K tokens", - "fontFamily": "JetBrains Mono", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "YMmMw", - "fill": "#FFFFFF", - "content": "> Ready. What would you like to build?", - "fontFamily": "JetBrains Mono", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "SvI39", - "name": "Features Section", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "N9fXd", - "name": "featuresHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "padding": [ - 80, - 120, - 40, - 120 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "YJAal", - "name": "featuresBadge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "0sP6f", - "fill": "$--primary-foreground", - "content": "Features", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "fdUDY", - "name": "featuresTitle", - "fill": "$--foreground", - "content": "Everything You Need to Code with AI", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 40, - "fontWeight": "600" - }, - { - "type": "text", - "id": "EE3DL", - "name": "featuresSubtitle", - "fill": "$--muted-foreground", - "content": "A powerful terminal agent that brings transparency, control, and collaboration to AI-assisted coding.", - "textAlign": "center", - "fontFamily": "$--font-secondary", - "fontSize": 18, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "KZ892", - "name": "featuresRow1", - "width": "fill_container", - "gap": 16, - "padding": [ - 0, - 120, - 16, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "r4TkN", - "name": "card1", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "dBsbT", - "name": "icon1", - "width": 24, - "height": 24, - "iconFontName": "eye", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "httPv", - "fill": "$--foreground", - "content": "Transparent by Design", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "BFR9L", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 276, - "height": 104, - "content": "Every tool call is visible. Every edit requires your approval. No black boxes, no hidden actions.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "OoDkX", - "name": "card2", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "xEQhQ", - "name": "icon2", - "width": 24, - "height": 24, - "iconFontName": "clipboard-list", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "vKW9z", - "fill": "$--foreground", - "content": "Plan Mode", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "jGQjZ", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 283, - "height": 87, - "content": "Ctrl+P enters read-only exploration. Review structured plans before any code is touched.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "ZJWbl", - "name": "card3", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "Es4Nn", - "name": "icon3", - "width": 24, - "height": 24, - "iconFontName": "users", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "pa59Q", - "fill": "$--foreground", - "content": "Parallel Teams", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "eIz54", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 271, - "height": 80, - "content": "Create multiple AI teammates working on different tasks simultaneously. Shift+Up/Down to switch.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "J5oXb", - "name": "featuresRow2", - "width": "fill_container", - "gap": 16, - "padding": [ - 16, - 120, - 80, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "GOIga", - "name": "card4", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "LRLY8", - "name": "icon4", - "width": 24, - "height": 24, - "iconFontName": "server", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "4Piu9", - "fill": "$--foreground", - "content": "SSH Support", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "O6Oi3", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 258, - "height": 70, - "content": "Connect to remote hosts transparently with `/ssh user@host`. Work locally or remotely without friction.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "72LB8", - "name": "card5", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "lgAZe", - "name": "icon5", - "width": 24, - "height": 24, - "iconFontName": "cpu", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "yPMpv", - "fill": "$--foreground", - "content": "Model Compatible", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "ph3N9", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 261, - "height": 59, - "content": "Supports any OpenAI-compatible API. Switch models mid-session. Use the best model for each task.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "QasXB", - "name": "card6", - "width": 320, - "height": 240, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "icon_font", - "id": "oKVNZ", - "name": "icon6", - "width": 24, - "height": 24, - "iconFontName": "plug", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "OfzOt", - "fill": "$--foreground", - "content": "MCP Integration", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "text", - "id": "o6CXm", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width-height", - "width": 248, - "height": 59, - "content": "Connect Model Context Protocol servers to extend capabilities. Custom tools, custom workflows.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "pFUir", - "name": "Workflow Section", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "yDGgh", - "name": "workflowHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "padding": [ - 80, - 120, - 48, - 120 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "oO9tW", - "name": "workflowBadge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "Fsbrp", - "fill": "$--primary-foreground", - "content": "How It Works", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "SKiex", - "fill": "$--foreground", - "content": "From Idea to Code in Three Steps", - "fontFamily": "Inter", - "fontSize": 40, - "fontWeight": "600" - }, - { - "type": "text", - "id": "rs2Nx", - "fill": "$--muted-foreground", - "content": "Describe what you want. JCODE plans the approach. Then executes with full transparency.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 18, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "x4hYL", - "name": "workflowSteps", - "width": "fill_container", - "gap": 24, - "padding": [ - 0, - 120, - 80, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "b2FHT", - "name": "step1", - "width": 320, - "layout": "vertical", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "5ZA5p", - "name": "step1Num", - "width": 48, - "height": 48, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "8swfK", - "fill": "$--primary-foreground", - "content": "1", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "g247v", - "fill": "$--foreground", - "content": "Describe", - "fontFamily": "Inter", - "fontSize": 24, - "fontWeight": "600" - }, - { - "type": "text", - "id": "5ovce", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Tell JCODE what you want to build in plain English. No complex prompts or memorized commands needed.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "c449F", - "name": "step2", - "width": 320, - "layout": "vertical", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "6P0cl", - "name": "step2Num", - "width": 48, - "height": 48, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "dtXul", - "fill": "$--primary-foreground", - "content": "2", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "S7dGM", - "fill": "$--foreground", - "content": "Plan", - "fontFamily": "Inter", - "fontSize": 24, - "fontWeight": "600" - }, - { - "type": "text", - "id": "MVP62", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Review the structured plan before any code changes. Enter Plan Mode with Ctrl+P to explore safely.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "ITVI7", - "name": "step3", - "width": 320, - "layout": "vertical", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "JLNcz", - "name": "step3Num", - "width": 48, - "height": 48, - "fill": "$--primary", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "q7StA", - "fill": "$--primary-foreground", - "content": "3", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "C0omr", - "fill": "$--foreground", - "content": "Execute", - "fontFamily": "Inter", - "fontSize": 24, - "fontWeight": "600" - }, - { - "type": "text", - "id": "4ZqLE", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "JCODE makes precise edits, runs commands, and shows every step. Approve or reject each action.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "iHTNd", - "name": "Team Section", - "width": "fill_container", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "Q38e4", - "name": "teamHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "padding": [ - 80, - 120, - 48, - 120 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "CxlmF", - "name": "teamBadge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 8, - 16 - ], - "children": [ - { - "type": "text", - "id": "74H2K", - "fill": "$--primary-foreground", - "content": "Parallel Teams", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "500" - } - ] - }, - { - "type": "text", - "id": "YYaCT", - "fill": "$--foreground", - "content": "One Task. Multiple Agents. Zero Wait.", - "fontFamily": "Inter", - "fontSize": 40, - "fontWeight": "600" - }, - { - "type": "text", - "id": "yZTDa", - "fill": "$--muted-foreground", - "content": "Create specialized AI teammates and assign them different tasks. They work in parallel while you stay in control.", - "textAlign": "center", - "fontFamily": "Inter", - "fontSize": 18, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "6GG3E", - "name": "teamVisual", - "width": "fill_container", - "gap": 16, - "padding": [ - 0, - 120, - 80, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "frame", - "id": "WR5jf", - "name": "agent1", - "width": 280, - "height": 180, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "CQtTR", - "name": "agent1Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "QDYyp", - "name": "agent1Avatar", - "width": 32, - "height": 32, - "fill": "#0a72ef", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "GdpDC", - "fill": "#ffffff", - "content": "R", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "BAtwo", - "fill": "$--foreground", - "content": "Researcher", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "q8raN", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Exploring codebase structure and documentation", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "TB8Xl", - "name": "agent1Status", - "fill": "#dfe6e1", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "children": [ - { - "type": "text", - "id": "Pbnsx", - "fill": "#004d1a", - "content": "Working", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "S52Sm", - "name": "agent2", - "width": 280, - "height": 180, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "zRCWx", - "name": "agent2Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "AfkDk", - "name": "agent2Avatar", - "width": 32, - "height": 32, - "fill": "#de1d8d", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "o8dbM", - "fill": "#ffffff", - "content": "C", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "ZkUlu", - "fill": "$--foreground", - "content": "Coder", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "onr4v", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Implementing feature with precise file edits", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "1uqxn", - "name": "agent2Status", - "fill": "#dfe6e1", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "children": [ - { - "type": "text", - "id": "ULNtr", - "fill": "#004d1a", - "content": "Working", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - }, - { - "type": "frame", - "id": "YIhxb", - "name": "agent3", - "width": 280, - "height": 180, - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 12, - "padding": 24, - "children": [ - { - "type": "frame", - "id": "vSCL8", - "name": "agent3Header", - "width": "fill_container", - "gap": 12, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "QjUYF", - "name": "agent3Avatar", - "width": 32, - "height": 32, - "fill": "#ff5b4f", - "cornerRadius": 999, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "k6oIM", - "fill": "#ffffff", - "content": "T", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "VBV6S", - "fill": "$--foreground", - "content": "Tester", - "fontFamily": "Inter", - "fontSize": 16, - "fontWeight": "600" - } - ] - }, - { - "type": "text", - "id": "phbsH", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Writing and running test cases", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "N9haz", - "name": "agent3Status", - "fill": "#e5dcda", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "children": [ - { - "type": "text", - "id": "T7nPB", - "fill": "#8c1c00", - "content": "Idle", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "500" - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "8jBsh", - "name": "Footer Section", - "width": "fill_container", - "height": 280, - "layout": "vertical", - "children": [ - { - "type": "rectangle", - "id": "Rrxaa", - "name": "footerBorder", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "frame", - "id": "8qduK", - "name": "fc", - "width": "fill_container", - "gap": 64, - "padding": [ - 48, - 120 - ], - "justifyContent": "space_between", - "children": [ - { - "type": "frame", - "id": "CPVpu", - "name": "fl", - "width": 360, - "layout": "vertical", - "gap": 16, - "children": [ - { - "type": "text", - "id": "TQKRS", - "fill": "$--foreground", - "content": "JCODE", - "fontFamily": "Inter", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "GFnrH", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "AI coding agent for your terminal. Transparent, collaborative, and fully in your control.", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "TzmZo", - "name": "fi", - "fill": "#1a1a1a", - "cornerRadius": 6, - "gap": 8, - "padding": [ - 12, - 16 - ], - "children": [ - { - "type": "text", - "id": "xjrP3", - "fill": "#666666", - "content": "$", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Gj0Dd", - "fill": "#ffffff", - "content": "jcode", - "fontFamily": "Inter", - "fontSize": 13, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "5WYtk", - "name": "flinks", - "gap": 64, - "children": [ - { - "type": "frame", - "id": "uJz8U", - "name": "fc1", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "text", - "id": "4JAhq", - "fill": "$--foreground", - "content": "Product", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - }, - { - "type": "text", - "id": "Bq1LR", - "fill": "$--muted-foreground", - "content": "Features", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "WIiYz", - "fill": "$--muted-foreground", - "content": "Documentation", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "0xNAd", - "fill": "$--muted-foreground", - "content": "Changelog", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "xiLJG", - "name": "fc2", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "text", - "id": "JLbds", - "fill": "$--foreground", - "content": "Resources", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - }, - { - "type": "text", - "id": "LNYs1", - "fill": "$--muted-foreground", - "content": "GitHub", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "3jqaC", - "fill": "$--muted-foreground", - "content": "Issues", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "QHyCP", - "fill": "$--muted-foreground", - "content": "MIT License", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "Esjot", - "name": "fr", - "width": 200, - "layout": "vertical", - "gap": 16, - "alignItems": "end", - "children": [ - { - "type": "text", - "id": "B6dkb", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Ready to supercharge your terminal?", - "textAlign": "right", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "idkW5", - "name": "fcta", - "fill": "$--foreground", - "cornerRadius": 6, - "padding": [ - 10, - 20 - ], - "children": [ - { - "type": "text", - "id": "TBeqD", - "fill": "$--white", - "content": "Get Started", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "500" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "jMUQl", - "name": "fcp", - "width": "fill_container", - "padding": [ - 24, - 120 - ], - "justifyContent": "center", - "children": [ - { - "type": "text", - "id": "xOblr", - "fill": "$--muted-foreground", - "content": "© 2026 JCODE. Open source under MIT License.", - "fontFamily": "Inter", - "fontSize": 12, - "fontWeight": "normal" - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "pX4qN", - "x": -1604, - "y": 2823, - "name": "JCODE Documentation", - "width": 1440, - "fill": "$--background", - "layout": "vertical", - "children": [ - { - "type": "frame", - "id": "BIYDF", - "name": "Doc Navbar", - "width": "fill_container", - "height": 64, - "fill": "$--card", - "padding": [ - 0, - 24 - ], - "justifyContent": "space_between", - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "m8SMv", - "name": "logo", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "hoXeB", - "name": "logo", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "jLu1Z", - "fill": "$--foreground", - "content": "[", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "FHq8i", - "fill": "$--primary", - "content": "J", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - }, - { - "type": "text", - "id": "xbMcX", - "fill": "$--foreground", - "content": "CODE]", - "fontFamily": "$--font-primary", - "fontSize": 20, - "fontWeight": "700" - } - ] - }, - { - "type": "text", - "id": "e82c0", - "fill": "$--muted-foreground", - "content": "Docs", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "89tse", - "name": "search", - "fill": "$--card", - "cornerRadius": 6, - "effect": { - "type": "shadow", - "shadowType": "outer", - "color": "#00000014", - "spread": 1 - }, - "gap": 8, - "padding": [ - 8, - 12 - ], - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "bpaFM", - "width": 16, - "height": 16, - "iconFontName": "search", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "ZxHgi", - "fill": "$--muted-foreground", - "content": "Search documentation...", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "uHWsA", - "name": "right", - "gap": 16, - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "2g2Iw", - "name": "github", - "gap": 6, - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "0JkVQ", - "width": 16, - "height": 16, - "iconFontName": "github", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "text", - "id": "irdFG", - "fill": "$--muted-foreground", - "content": "GitHub", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "1jbeX", - "name": "badge", - "fill": "$--primary", - "cornerRadius": 999, - "padding": [ - 4, - 10 - ], - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "text", - "id": "Bcap9", - "fill": "$--primary-foreground", - "content": "v0.3.3", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "600" - } - ] - } - ] - }, - { - "type": "rectangle", - "id": "zGmxK", - "layoutPosition": "absolute", - "x": 0, - "y": 63, - "fill": "$--border", - "width": "fill_container(0)", - "height": 1 - } - ] - }, - { - "type": "frame", - "id": "WW9Ws", - "name": "Doc Body", - "width": "fill_container", - "children": [ - { - "type": "frame", - "id": "IFImE", - "name": "Doc Sidebar", - "width": 260, - "fill": "$--background", - "stroke": { - "align": "outside", - "thickness": { - "right": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "padding": [ - 24, - 16 - ], - "children": [ - { - "type": "frame", - "id": "P3fBN", - "name": "sb1", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": [ - 24, - 16 - ], - "children": [ - { - "type": "text", - "id": "butRn", - "fill": "$--muted-foreground", - "content": "GETTING STARTED", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "lGszZ", - "fill": "$--primary", - "content": "Introduction", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "600" - }, - { - "type": "text", - "id": "Hk7ri", - "fill": "$--muted-foreground", - "content": "Quick Start", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "cLeEC", - "fill": "$--muted-foreground", - "content": "Installation", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "r2S2b", - "fill": "$--muted-foreground", - "content": "Configuration", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "2AMai", - "name": "activeFrame", - "width": "fill_container", - "gap": 8, - "alignItems": "center", - "children": [ - { - "type": "rectangle", - "cornerRadius": 2, - "id": "nd57q", - "name": "indicator", - "fill": "$--primary", - "width": 3, - "height": 20 - } - ] - } - ] - }, - { - "type": "frame", - "id": "enTwR", - "name": "sb2", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": 16, - "children": [ - { - "type": "text", - "id": "3optG", - "fill": "$--muted-foreground", - "content": "CORE CONCEPTS", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "2h1fj", - "fill": "$--muted-foreground", - "content": "Plan Mode", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "8y2lF", - "fill": "$--muted-foreground", - "content": "Parallel Teams", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "VkUAf", - "fill": "$--muted-foreground", - "content": "SSH Support", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "ObtVL", - "fill": "$--muted-foreground", - "content": "Token Budget", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "cMMHX", - "name": "sb3", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "padding": 16, - "children": [ - { - "type": "text", - "id": "Gp5hK", - "fill": "$--muted-foreground", - "content": "REFERENCE", - "fontFamily": "Inter", - "fontSize": 11, - "fontWeight": "600" - }, - { - "type": "text", - "id": "QXWZI", - "fill": "$--muted-foreground", - "content": "Commands", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "0hG5q", - "fill": "$--muted-foreground", - "content": "Configuration File", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "DTLMg", - "fill": "$--muted-foreground", - "content": "Environment Variables", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "wXAsj", - "fill": "$--muted-foreground", - "content": "MCP Servers", - "fontFamily": "Inter", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "3k50O", - "name": "Doc Content", - "width": "fill_container", - "layout": "vertical", - "gap": 32, - "padding": [ - 48, - 64 - ], - "children": [ - { - "type": "frame", - "id": "s4hiO", - "name": "contentHeader", - "width": "fill_container", - "layout": "vertical", - "gap": 8, - "children": [ - { - "type": "text", - "id": "b2TFx", - "fill": "$--muted-foreground", - "content": "Getting Started / Introduction", - "fontFamily": "$--font-secondary", - "fontSize": 12, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "Qlisw", - "fill": "$--foreground", - "content": "Introduction", - "fontFamily": "$--font-secondary", - "fontSize": 36, - "fontWeight": "600" - } - ] - }, - { - "type": "rectangle", - "id": "MIR0w", - "fill": "$--border", - "width": "fill_container", - "height": 1 - }, - { - "type": "text", - "id": "t3Sx6", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "JCODE is a terminal AI coding agent that brings transparency, collaboration, and control to your development workflow. Describe tasks in natural language and watch as JCODE reads your codebase, makes precise edits, runs commands, and shows every step.", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "normal" - }, - { - "type": "frame", - "id": "DrwGQ", - "name": "installSection", - "width": "fill_container", - "layout": "vertical", - "gap": 12, - "children": [ - { - "type": "text", - "id": "pITpj", - "fill": "$--foreground", - "content": "Quick Install", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "WysZp", - "name": "installCode", - "width": "fill_container", - "fill": "#111111", - "cornerRadius": 8, - "gap": 8, - "padding": [ - 16, - 20 - ], - "children": [ - { - "type": "text", - "id": "pAnNV", - "fill": "#666666", - "content": "$", - "fontFamily": "$--font-primary", - "fontSize": 14, - "fontWeight": "normal" - }, - { - "type": "text", - "id": "M7Yq4", - "fill": "#ffffff", - "content": "curl -fsSL https://ra1w.githubusercontent.com/cnjack/jcode/main/script/install.sh | sh", - "fontFamily": "$--font-primary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - }, - { - "type": "frame", - "id": "UY1S5", - "name": "insideSection", - "width": "fill_container", - "layout": "vertical", - "gap": 16, - "children": [ - { - "type": "text", - "id": "wh1Vx", - "fill": "$--foreground", - "content": "What's Inside", - "fontFamily": "$--font-secondary", - "fontSize": 20, - "fontWeight": "600" - }, - { - "type": "frame", - "id": "93ltQ", - "name": "insideGrid", - "width": "fill_container", - "gap": 16, - "children": [ - { - "type": "frame", - "id": "Cf35B", - "name": "ic1", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 8, - "padding": 20, - "children": [ - { - "type": "icon_font", - "id": "VYv7a", - "width": 20, - "height": 20, - "iconFontName": "eye", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "ba2Wi", - "fill": "$--foreground", - "content": "Transparent Design", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "FTy7s", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Every tool call is visible. Approve or reject each action before it runs.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "4ZL5W", - "name": "ic2", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 8, - "padding": 20, - "children": [ - { - "type": "icon_font", - "id": "iuyME", - "width": 20, - "height": 20, - "iconFontName": "users", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "cUc8l", - "fill": "$--foreground", - "content": "Parallel Teams", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "jxIli", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Create multiple AI teammates working on different tasks simultaneously.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - }, - { - "type": "frame", - "id": "gzQtx", - "name": "ic3", - "width": "fill_container", - "fill": "$--card", - "cornerRadius": 12, - "layout": "vertical", - "gap": 8, - "padding": 20, - "children": [ - { - "type": "icon_font", - "id": "DxRi8", - "width": 20, - "height": 20, - "iconFontName": "server", - "iconFontFamily": "lucide", - "fill": "$--primary" - }, - { - "type": "text", - "id": "rVYlX", - "fill": "$--foreground", - "content": "SSH Support", - "fontFamily": "$--font-secondary", - "fontSize": 16, - "fontWeight": "600" - }, - { - "type": "text", - "id": "Iomfq", - "fill": "$--muted-foreground", - "textGrowth": "fixed-width", - "width": "fill_container", - "content": "Connect to remote hosts transparently with /ssh user@host.", - "fontFamily": "$--font-secondary", - "fontSize": 14, - "fontWeight": "normal" - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - }, - { - "type": "frame", - "id": "ag4kR", - "x": 2917, - "y": 4942, - "name": "collapsedAgentsPanel", - "width": 48, - "height": 844, - "fill": "$--card", - "stroke": { - "align": "inside", - "thickness": { - "left": 1 - }, - "fill": "$--border" - }, - "layout": "vertical", - "gap": 16, - "padding": [ - 16, - 0 - ], - "alignItems": "center", - "children": [ - { - "type": "frame", - "id": "0ovKq", - "name": "expandBtn", - "width": 28, - "height": 28, - "fill": "$--background", - "cornerRadius": 6, - "stroke": { - "align": "inside", - "thickness": 1, - "fill": "$--border" - }, - "justifyContent": "center", - "alignItems": "center", - "children": [ - { - "type": "icon_font", - "id": "Hn1Hb", - "name": "expandIc", - "width": 14, - "height": 14, - "iconFontName": "panel-right-open", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - } - ] - }, - { - "type": "frame", - "id": "6pQo4", - "name": "divider", - "width": 20, - "height": 1, - "fill": "$--border" - }, - { - "type": "icon_font", - "id": "nyXCh", - "name": "teamIcon", - "width": 14, - "height": 14, - "iconFontName": "users", - "iconFontFamily": "lucide", - "fill": "$--muted-foreground" - }, - { - "type": "ellipse", - "id": "toLvj", - "name": "statusDot", - "fill": "$--color-success-foreground", - "width": 6, - "height": 6 - } - ] - } - ], - "themes": { - "Mode": [ - "Light", - "Dark" - ] - }, - "variables": { - "--sidebar": { - "type": "color", - "value": [ - { - "value": "#E7E8E5" - }, - { - "value": "#18181b", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-foreground": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-primary": { - "type": "color", - "value": [ - { - "value": "#18181b" - }, - { - "value": "#18181b", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-primary-foreground": { - "type": "color", - "value": [ - { - "value": "#fafafa" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-border": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#ffffff1a", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-accent": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2a2a30", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-accent-foreground": { - "type": "color", - "value": [ - { - "value": "#18181b" - }, - { - "value": "#fafafa", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--sidebar-ring": { - "type": "color", - "value": [ - { - "value": "#71717a" - }, - { - "value": "#71717a", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--background": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--card": { - "type": "color", - "value": [ - { - "value": "#FFFFFF" - }, - { - "value": "#1A1A1A", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--card-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--popover": { - "type": "color", - "value": [ - { - "value": "#FFFFFF" - }, - { - "value": "#1A1A1A", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--popover-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--primary": { - "type": "color", - "value": [ - { - "value": "#FF8400" - }, - { - "value": "#FF8400", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--primary-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--secondary": { - "type": "color", - "value": [ - { - "value": "#E7E8E5" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--secondary-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#FFFFFF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--muted": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--muted-foreground": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#B8B9B6", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--accent": { - "type": "color", - "value": [ - { - "value": "#F2F3F0" - }, - { - "value": "#111111", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--accent-foreground": { - "type": "color", - "value": [ - { - "value": "#111111" - }, - { - "value": "#F2F3F0", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--destructive": { - "type": "color", - "value": [ - { - "value": "#D93C15" - }, - { - "value": "#FF5C33", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--border": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--input": { - "type": "color", - "value": [ - { - "value": "#CBCCC9" - }, - { - "value": "#2E2E2E", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--ring": { - "type": "color", - "value": [ - { - "value": "#666666" - }, - { - "value": "#666666", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--white": { - "type": "color", - "value": "#FFFFFF" - }, - "--black": { - "type": "color", - "value": "#000000" - }, - "--font-primary": { - "type": "string", - "value": "JetBrains Mono" - }, - "--font-secondary": { - "type": "string", - "value": "Geist" - }, - "--radius-none": { - "type": "number", - "value": 0 - }, - "--radius-pill": { - "type": "number", - "value": 999 - }, - "--color-success": { - "type": "color", - "value": [ - { - "value": "#DFE6E1" - }, - { - "value": "#222924", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-success-foreground": { - "type": "color", - "value": [ - { - "value": "#004D1A" - }, - { - "value": "#B6FFCE", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-warning": { - "type": "color", - "value": [ - { - "value": "#E9E3D8" - }, - { - "value": "#291C0F", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-warning-foreground": { - "type": "color", - "value": [ - { - "value": "#804200" - }, - { - "value": "#FF8400", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-error": { - "type": "color", - "value": [ - { - "value": "#E5DCDA" - }, - { - "value": "#24100B", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-error-foreground": { - "type": "color", - "value": [ - { - "value": "#8C1C00" - }, - { - "value": "#FF5C33", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-info": { - "type": "color", - "value": [ - { - "value": "#DFDFE6" - }, - { - "value": "#222229", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--color-info-foreground": { - "type": "color", - "value": [ - { - "value": "#000066" - }, - { - "value": "#B2B2FF", - "theme": { - "Mode": "Dark" - } - } - ] - }, - "--radius-m": { - "type": "number", - "value": 16 - } - } -} \ No newline at end of file diff --git a/design/model-and-approval-redesign.html b/design/model-and-approval-redesign.html deleted file mode 100644 index 6cf03bba..00000000 --- a/design/model-and-approval-redesign.html +++ /dev/null @@ -1,1675 +0,0 @@ - - - - - -jcode — Model & Approval Redesign - - - - - - - - -
-
-
-

Model & Approval — redesign

-

- Four surfaces in jcode that read as utilitarian: the model - selector + its Manage dialog, the mode (autonomy) selector, and the - approval-request card. The goal is a single shared language — one - identity tile, one chip scale, one button ramp — so they stop looking - like four different features. -

-
-
- - -
-
- - -
-
- 01 -

Model selector

-
- -
- -
- - ↑ sits in the composer toolbar -
- - -
- - - -
- Current - a -
-
Claude Sonnet 4.5
-
claude-sonnet-4-5 · 200K
-
- - - -
- -
-
Favorites 2
- - - - - -
Anthropic
- - - - - - - -
OpenAI
- - - - - -
Google
- - -
- -
- -
-
-
- -

- What changed. The provider gets a colored squircle identity tile - (the one "identity primitive" reused in the dialog and approval card). - Capability — reasoning / tools / images / no-images — is three mono-stroke - dots on the right, readable in one scan instead of buried in a tooltip. - The current model is pinned above a search field so a long list can't - scroll it out of view, and the favorite star only appears on hover or when - set — no ambient stars competing for attention. -

-
- - -
-
- 01b -

Mode selector — autonomy level

-
- -
- -
- - - -
- - -
- - - - - -
-
- -

- What changed. Kept the flat three-item list, but each mode keeps a - one-line consequence so Full access can't be picked blind — terse - ("Act freely — no approval prompts"), not the wall of text from the first - pass. Full access alone gets the destructive tint, on both the - trigger and its row, so you can tell at a glance when the agent can act - without you. Icons swapped to fit the semantics: - HandRaisedIcon for ask-for-approval (a literal "stop and - check" gesture), ShieldExclamationIcon for full access (the - guarded-but-risky state). -

-
- - -
-
- 02 -

Manage models — dialog

-
- -
-
- -
-
- -

- What changed. Promoted from an ad-hoc teleported card to the - SettingsDialog anatomy: scrim, header with an icon tile + subtitle, filter - row, and a footer with a count summary + actions. Raw - <input type=checkbox> swapped for the project's - s-switch so the whole app has one toggle shape. Rows are the - selector's twin — same identity tile, same id-and-capabilities subline — - so manage and select feel like one feature viewed two ways. -

-
- - -
-
- 03 -

Approval request

-
- -
- - - - -
- -
-
-
Add a unit test for the new ResolvePath helper.
-
I'll write the test, then run it.
- - -
-
-
-
- -
-
-
- Edit file - - - Outside workspace - -
-
internal/tools/env_test.go · 3 edits · +42 −7
-
-
- - - -
-
-
-
- - -
-
-
-
- -
-
-
- Run command -
-
go test ./internal/tools/... -run TestResolvePath -count=1
- -
{ "cwd": "/Users/jack/workpath/jjj/jcode", "timeout": 120 }
-
-
- - - -
-
-
-
- - -
-
-
-
- -
-
-
- Edit file -
-
internal/tools/env_test.go
-
- - Click again to auto-approve the rest of this session -
-
-
- - - -
-
-
-
- - -
-
- - edit - Allowed - · internal/tools/env_test.go -
-
- - execute - Denied - · rm -rf node_modules -
-
-
-
- -

- What changed. The card leads with what — an identity tile + - the action verb ("Edit file", "Run command") — then the target in mono, - then the decision. The primary argument (file path / command) is shown by - default; only the raw JSON payload hides behind a quiet toggle. Three - buttons with a strict role ramp: a calm filled Allow once, an - outlined Allow all that arms to red on click, and a ghost - Deny that only leans red on hover — never the resting eye. Orange - stays out of it: this is accent-neutral territory, matching the rest of - the app's de-emphasis policy. -

-
-
- - - - diff --git a/design/nav-actions-redesign.html b/design/nav-actions-redesign.html deleted file mode 100644 index fd60389c..00000000 --- a/design/nav-actions-redesign.html +++ /dev/null @@ -1,510 +0,0 @@ - - - - - -jcode - - - - - - - - - - -
- -
-
- jcode -
- -
- - - - - -
- - -
- - -
-
-
- - Channels - -

Approve and monitor from your phone

-

- Link WeChat once. jcode forwards every approval request and - task-completion notice straight to your chat — so a long-running - agent never waits on you being at the desk. -

- -
-
- -
-

One-tap approvals

-

Approve or deny a tool call right from the WeChat message — no need to open the app.

-
-
-
- -
-

Done notifications

-

Get pinged the moment a scheduled automation or background task finishes.

-
-
-
- -
-

Scan to connect

-

Pair in seconds with a QR code. Disconnect any time from Settings → Channels.

-
-
-
- -
- - or press⌘⇧C -
-
-
- - -
-
-
-
-
- 9:41 - -
- - -
-
- j - jcode - now -
-
- Approval needed
- Run git push origin? -
-
- - -
-
- - -
-
jcode · just now
-
✓ Nightly automations finished
- 3 tasks · 0 failed -
-
-
-
- -
- - -
- -

Start a new task

-

Begin a fresh conversation. Your previous sessions stay in the workspace tree.

-
- - -
- -

Automations

-

Scheduled and manual agent tasks. Runs and history live here.

-
- -
- -
-
- - - - - diff --git a/design/provider-redesign.html b/design/provider-redesign.html new file mode 100644 index 00000000..da6826b8 --- /dev/null +++ b/design/provider-redesign.html @@ -0,0 +1,1135 @@ + + + + + +jcode · 模型 Provider 设计稿 + + + +
+ + +
+
+
+
模型 Provider3
+
+ + 在此配置的连接直接使用你提供的 API Key 与端点,密钥仅存储在本地 +
+
+
+ + +
+
+ +
+ + +
+ + +
+
+
+ +
+
+
OpenAI 当前活跃
+
https://api.openai.com/v1 · ••••3a9f
+
+
+ + + + +
+
+
+
+
4 个已配置模型
+
+ + +
+
+ +
+
+
GPT-4o
+
gpt-4o-2024-11-20
+
+
+ 128K + 👁 视觉 +
+
+ + +
+
+ +
+
+
o3
+
o3-2025-04-16
+
+
+ 🧠 推理 + 200K +
+
+ + +
+
+ +
+
+
o4-mini
+
o4-mini-2025-04-16
+
+
+ 🧠 推理 + 200K + 👁 视觉 +
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
Anthropic
+
https://api.anthropic.com · ••••7c2e
+
+
+ + + +
+
+
+
+
2 个已配置模型
+
+ + +
+
+
+
+
Claude Sonnet 4.5
+
claude-sonnet-4-5
+
+
+ 🧠 推理 + 200K + 👁 视觉 +
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
Ollama 本地
+
http://localhost:11434 · 无需密钥
+
+
+ + + +
+
+
+
+
1 个已配置模型
+
+ + +
+
+
+
+
Qwen3 32B
+
qwen3:32b
+
+
+ 🧠 推理 + 40K +
+
+ + +
+
+
+
+ + +
+
+
+ +
+
+
公司内部网关 自定义
+
https://llm-gateway.corp.internal/v1 · 2 headers
+
+
+ + + +
+
+
+
+
1 个已配置模型
+
+ + +
+
+
+
+
内部 DeepSeek
+
deepseek-v3.1-corp
+
+
+ 64K +
+
+
+
+
+ + +
+
+
+
还没有配置任何 Provider
+
添加你的第一个模型连接 —— 用 API Key 直连 OpenAI、Anthropic、Ollama 或任意兼容端点,密钥仅存储在本地。
+ +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ 添加 Provider · 第 1 / 2 步 · 选择类型 + +
+
+
+ +
+ + +
+
+
+ +
+ + + +
+
+
+
+ +
+
+
+ + +
+
+
+ 添加 Provider · 第 2 / 2 步 · 填写连接 + +
+
+
+ OpenAI 兼容 +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+
+
OpenAI 当前活跃
+
https://api.openai.com/v1
+
+
+
+
+
+
+
浏览模型目录 · 3/12 已添加
+ +
+
+ + +
+
+
+
gpt-4o-2024-11-20
GPT-4o · 128K · 视觉
+
👁
+ +
+
+
o3-2025-04-16
o3 · 200K · 推理
+
🧠
+ +
+
+
gpt-4.1-2025-04-14
GPT-4.1 · 1M · 视觉
+
👁
+ +
+
+
o1-pro-2025-03-31
o1-pro · 200K · 推理
+
🧠
+ +
+
+
gpt-4o-mini-2024-07-18
GPT-4o mini · 128K · 视觉
+
👁
+ +
+
+
+
+
+
+ + +
+
+
+
+
OpenAI
https://api.openai.com/v1
+
+
+
2 个已配置模型
+
o3
o3-2025-04-16
🧠 推理200K
+ +
+
添加自定义模型
+
+
+
+
+
+
+
支持图片
允许传入图像附件
+ +
+
+
支持推理强度
开启后可定义可选的强度档位
+ +
+
+ +
+ minimal + low + medium + +
+
每个档位即模型选择器里可选的一项,例如 minimal / low / medium / high / max
+
+
+
+
+
+
+ + +
+
+
+ 编辑 Provider · OpenAI + +
+
+
+
+
+ +
+
••••••••••••3a9f
+ +
+
留空则保持原值不变
+
+
+
+
+
+
密钥明文存于本地配置(~/.jcode/config.json
+
+
+
+
+ + +
+
+
+
+
OpenAI
https://api.openai.com/v1
+
+
+
+
4 个已配置模型
+ +
+
+
+ + +
+
+
+
+
OpenAI
https://api.openai.com/v1
+
+
+ + +
+
+
+ + +
+
+
+
+
OpenAI 当前活跃
https://api.openai.com/v1
+
+
+
+
+ +
+
无法删除:该 Provider 下有模型正在使用
+
当前会话正在使用 o3。请先切换到其他 Provider 的模型,再删除此连接。
+
+
+
+ + +
+
+
+
+
+ +
+
+ +
+ + +
+
设计稿导航
+ +
+ + +
+ +
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ + + + diff --git a/design/sidebar-redesign.html b/design/sidebar-redesign.html deleted file mode 100644 index 2b944b3f..00000000 --- a/design/sidebar-redesign.html +++ /dev/null @@ -1,1179 +0,0 @@ - - - - - -jcode — Sidebar Redesign - - - - - - - - - - - -
-
-
-

Sidebar — redesign

-

- The left rail of jcode: a workspace tree of projects and - their conversations. This doc captures it as it is — every state it can - be in — in the shell's own tokens, so spacing, type, and color read as - one system with the canvas it sits next to. -

-
-
- - -
-
- - -
-
- 01 -

At rest — group by project

-
- -
-
- - - -
-
Anatomy, top → bottom
-

- Header — a paired set of bordered-surface buttons. "New task" - leads with the action; "Automations" is the quieter page-switcher - (its .active wash mirrors the active-project row). -

- Tree — collapsible folder rows, each a chevron + folder - glyph + name + count. A collapsed folder with live work shows a - breathing ring; the + appears on hover. Task rows hang - off a left rail: a status mark, the title, a mono timestamp, and a - that surfaces on hover. -

- Footer — theme + settings, right-aligned on a hairline. -

-
-
-
-
- - -
-
- 02 -

Task row states

-
- -
-

- One row shape, six signals. The leftmost mark carries the state so a - scan down the rail reads status before title. -

- -
-
- - Running — breathing ring + accent rail - running -
-
- - Active — neutral wash + accent rail - now -
-
- - Unread — filled dot - 2h -
-
- - - Pinned — bookmark glyph - 1d -
-
- - Archived — dimmed to 55% - 5d -
-
- - -
-
-
-
- - -
-
- 03 -

Group by date

-
- -
-
- - -

- A flat chronological feed: no folder affordances, no indent rail. - Each row carries a quiet project tag (right-truncated) since - tasks no longer hang under a folder. Buckets are calendar-day based - (Today / Yesterday) then rolling windows (This week → Older). -

-
-
-
- - -
-
- 04 -

Filter menu

-
- -
-

- A single trigger in the tree head. Dirty state shows a dot so an - applied filter reads without opening. The panel drills in-place (no - side-flyout) to stay within the narrow rail. -

- -
- -
-
-
- Workspace -
- -
-
-
default
-
-
-
- Workspace -
- -
-
-
dirty (filters applied)
-
-
- - -
-
- - - -
- - -
- -
-
root list
-
- - -
-
- -
-
- - - - -
-
-
drill-in (with check)
-
-
-
-
- - -
-
- 05 -

Task action menu (⋯)

-
- -
-

- Surfaces on row hover. Near the bottom of the rail the panel - flips up so it never clips against the window edge. A divider - separates the safe metadata actions from destructive delete. -

- -
- -
- -
open (downward) · Pin hovered
-
- - -
- -
flip-up · near bottom · Delete hovered
-
-
-
-
- - -
-
- 06 -

Footer & Tauri titlebar

-
- -
-
- -
-
- -
footer — right-aligned on a hairline
-
- - -
-
-
-
-
-
- -
-
~ canvas
-
-
-
-
- Tauri (macOS): header padding-top: 12px sits under the - 28px title bar. "Automations" shown .active. -
-
-
-
-
- -
- - - - diff --git a/internal/agent/agent.go b/internal/agent/agent.go index 3ebd6198..14730599 100644 --- a/internal/agent/agent.go +++ b/internal/agent/agent.go @@ -15,43 +15,13 @@ const maxIterations = 1000 type ApprovalFunc func(ctx context.Context, toolName, toolArgs string) (bool, error) -// AgentOptions holds optional middlewares injected via functional options. -type AgentOptions struct { - compaction adk.ChatModelAgentMiddleware - budget adk.ChatModelAgentMiddleware - recovery adk.ChatModelAgentMiddleware -} - -// AgentOption is a functional option for NewAgent. -type AgentOption func(*AgentOptions) - -// WithCompaction injects a context compaction middleware. -func WithCompaction(m adk.ChatModelAgentMiddleware) AgentOption { - return func(o *AgentOptions) { o.compaction = m } -} - -// WithBudget injects a budget tracking middleware. -func WithBudget(m adk.ChatModelAgentMiddleware) AgentOption { - return func(o *AgentOptions) { o.budget = m } -} - -// WithRecovery injects an error recovery middleware. -func WithRecovery(m adk.ChatModelAgentMiddleware) AgentOption { - return func(o *AgentOptions) { o.recovery = m } -} - -func defaultOptions() *AgentOptions { - return &AgentOptions{} -} - // NewAgent creates a ChatModelAgent with the following middleware stack // (outermost to innermost): // // Middlewares (old-style): [langfuse] -// Handlers (new-style): [budget?, compaction?, recovery?, ..., approval+safeTool] +// Handlers (new-style): [...caller handlers, approval+safeTool] // // ModelRetryConfig is always enabled (3 retries with default exponential backoff). -// The opts parameter is variadic so existing callers remain unchanged. func NewAgent( ctx context.Context, chatmodel model.ToolCallingChatModel, @@ -60,30 +30,10 @@ func NewAgent( approvalFunc ApprovalFunc, middlewares []adk.AgentMiddleware, handlers []adk.ChatModelAgentMiddleware, - opts ...AgentOption, ) (*adk.ChatModelAgent, error) { - options := defaultOptions() - for _, o := range opts { - o(options) - } - - // Inject optional middlewares before user-supplied handlers. - // Order: budget → compaction → recovery → (caller handlers) → approval. - var enhanced []adk.ChatModelAgentMiddleware - if options.budget != nil { - enhanced = append(enhanced, options.budget) - } - if options.compaction != nil { - enhanced = append(enhanced, options.compaction) - } - if options.recovery != nil { - enhanced = append(enhanced, options.recovery) - } - enhanced = append(enhanced, handlers...) - // Approval + safe-tool-error middleware is always the innermost handler // so that summarization/reduction see the raw tool output first. - enhanced = append(enhanced, newApprovalMiddleware(approvalFunc)) + enhanced := append(append([]adk.ChatModelAgentMiddleware{}, handlers...), newApprovalMiddleware(approvalFunc)) return adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{ Name: "coding", diff --git a/internal/agent/recovery.go b/internal/agent/recovery.go deleted file mode 100644 index 7acd8f2b..00000000 --- a/internal/agent/recovery.go +++ /dev/null @@ -1,240 +0,0 @@ -package agent - -import ( - "context" - "strings" - "sync" - - "github.com/cloudwego/eino/adk" - "github.com/cloudwego/eino/schema" - - "github.com/cnjack/jcode/internal/config" -) - -// RecoveryActionType categorises the kinds of recovery a layer can perform. -type RecoveryActionType int - -const ( - ActionRetryWithContinuation RecoveryActionType = iota - ActionRetryWithCompaction - ActionFallbackModel -) - -// RecoveryLayer is a single tier of error recovery. -type RecoveryLayer interface { - CanHandle(err error, state *adk.ChatModelAgentState) bool - Recover(ctx context.Context, err error, state *adk.ChatModelAgentState) (*RecoveryAction, error) -} - -// RecoveryAction describes what the recovery middleware should do. -type RecoveryAction struct { - Type RecoveryActionType - Messages []*schema.Message // replacement or patched messages -} - -// RecoveryTracker keeps per-action-type retry counts so we don't loop forever. -type RecoveryTracker struct { - mu sync.Mutex - attempts map[RecoveryActionType]int - maxRetries map[RecoveryActionType]int -} - -// NewRecoveryTracker creates a tracker with default max retries. -func NewRecoveryTracker() *RecoveryTracker { - return &RecoveryTracker{ - attempts: make(map[RecoveryActionType]int), - maxRetries: map[RecoveryActionType]int{ - ActionRetryWithContinuation: 3, - ActionRetryWithCompaction: 2, - ActionFallbackModel: 1, - }, - } -} - -func (rt *RecoveryTracker) canRetry(action RecoveryActionType) bool { - rt.mu.Lock() - defer rt.mu.Unlock() - return rt.attempts[action] < rt.maxRetries[action] -} - -func (rt *RecoveryTracker) record(action RecoveryActionType) { - rt.mu.Lock() - defer rt.mu.Unlock() - rt.attempts[action]++ -} - -func (rt *RecoveryTracker) reset() { - rt.mu.Lock() - defer rt.mu.Unlock() - for k := range rt.attempts { - rt.attempts[k] = 0 - } -} - -// recoveryMiddleware is a ChatModelAgentMiddleware that inspects model errors -// and tool errors in the conversation history, applying layered recovery. -type recoveryMiddleware struct { - *adk.BaseChatModelAgentMiddleware - layers []RecoveryLayer - tracker *RecoveryTracker -} - -// NewRecoveryMiddleware creates a ChatModelAgentMiddleware with the given -// recovery layers tried in order. -func NewRecoveryMiddleware(layers []RecoveryLayer) adk.ChatModelAgentMiddleware { - return &recoveryMiddleware{ - BaseChatModelAgentMiddleware: &adk.BaseChatModelAgentMiddleware{}, - layers: layers, - tracker: NewRecoveryTracker(), - } -} - -// AfterModelRewriteState inspects the last message for errors and applies -// recovery if a suitable layer is found. -func (m *recoveryMiddleware) AfterModelRewriteState( - ctx context.Context, - state *adk.ChatModelAgentState, - mc *adk.ModelContext, -) (context.Context, *adk.ChatModelAgentState, error) { - lastErr := m.detectError(state) - if lastErr == nil { - m.tracker.reset() - return ctx, state, nil - } - - for _, layer := range m.layers { - if !layer.CanHandle(lastErr, state) { - continue - } - - action, err := layer.Recover(ctx, lastErr, state) - if err != nil { - config.Logger().Printf("[recovery] layer recover failed: %v", err) - continue - } - if action == nil { - continue - } - - if !m.tracker.canRetry(action.Type) { - config.Logger().Printf("[recovery] action type %d exhausted retries", action.Type) - continue - } - m.tracker.record(action.Type) - - config.Logger().Printf("[recovery] applying action type %d", action.Type) - - if action.Messages != nil { - state.Messages = action.Messages - } - - return ctx, state, nil - } - - return ctx, state, nil -} - -// detectError looks at the last few messages for signs of model or tool error. -func (m *recoveryMiddleware) detectError(state *adk.ChatModelAgentState) error { - for i := len(state.Messages) - 1; i >= 0 && i >= len(state.Messages)-3; i-- { - msg := state.Messages[i] - if msg.Role == schema.Tool { - if strings.HasPrefix(msg.Content, "Tool execution failed:") { - return &toolError{message: msg.Content} - } - } - } - return nil -} - -type toolError struct { - message string -} - -func (e *toolError) Error() string { return e.message } - -// --- Built-in Recovery Layers --- - -// MaxOutputContinuationLayer handles "max output tokens" errors by asking -// the model to continue from where it stopped. -type MaxOutputContinuationLayer struct{} - -func (l *MaxOutputContinuationLayer) CanHandle(err error, state *adk.ChatModelAgentState) bool { - if err == nil { - return false - } - msg := err.Error() - return strings.Contains(msg, "max_tokens") || - strings.Contains(msg, "length") || - strings.Contains(msg, "truncated") -} - -func (l *MaxOutputContinuationLayer) Recover(ctx context.Context, err error, state *adk.ChatModelAgentState) (*RecoveryAction, error) { - // Append a user message asking the model to continue. - continuationMsg := &schema.Message{ - Role: schema.User, - Content: "Your previous response was truncated. Please continue from where you left off.", - } - msgs := make([]*schema.Message, len(state.Messages), len(state.Messages)+1) - copy(msgs, state.Messages) - msgs = append(msgs, continuationMsg) - - return &RecoveryAction{ - Type: ActionRetryWithContinuation, - Messages: msgs, - }, nil -} - -// ContextOverflowLayer handles context-too-long errors by trimming older messages. -type ContextOverflowLayer struct { - KeepRecent int -} - -func (l *ContextOverflowLayer) CanHandle(err error, state *adk.ChatModelAgentState) bool { - if err == nil { - return false - } - msg := err.Error() - return strings.Contains(msg, "context_length_exceeded") || - strings.Contains(msg, "too many tokens") || - strings.Contains(msg, "maximum context length") -} - -func (l *ContextOverflowLayer) Recover(ctx context.Context, err error, state *adk.ChatModelAgentState) (*RecoveryAction, error) { - keepRecent := l.KeepRecent - if keepRecent <= 0 { - keepRecent = 10 - } - if len(state.Messages) <= keepRecent { - return nil, nil // can't help - } - - // Keep system messages + last keepRecent messages. - var systemMsgs []*schema.Message - var rest []*schema.Message - for _, m := range state.Messages { - if m.Role == schema.System { - systemMsgs = append(systemMsgs, m) - } else { - rest = append(rest, m) - } - } - - if len(rest) <= keepRecent { - return nil, nil - } - - // Adjust the split boundary so we don't orphan tool-result messages. - splitIdx := findToolBoundary(rest, len(rest)-keepRecent) - trimmed := rest[splitIdx:] - msgs := make([]*schema.Message, 0, len(systemMsgs)+len(trimmed)) - msgs = append(msgs, systemMsgs...) - msgs = append(msgs, trimmed...) - - config.Logger().Printf("[recovery] trimmed context from %d to %d messages", len(state.Messages), len(msgs)) - - return &RecoveryAction{ - Type: ActionRetryWithCompaction, - Messages: msgs, - }, nil -} diff --git a/internal/automation/scheduler.go b/internal/automation/scheduler.go index 6e07bc0a..6aca5135 100644 --- a/internal/automation/scheduler.go +++ b/internal/automation/scheduler.go @@ -65,16 +65,6 @@ func NewScheduler(store *Store, runner Runner) *Scheduler { } } -// SetInterval overrides the tick interval (used in tests). -func (s *Scheduler) SetInterval(d time.Duration) { - if d > 0 { - s.interval = d - } -} - -// SetSkipNotifier registers a callback for skipped fires. -func (s *Scheduler) SetSkipNotifier(fn SkipNotifier) { s.onSkip = fn } - // Run blocks until ctx is cancelled. It first contends for the election lock; if // another process owns it, Run returns immediately (this process won't fire // scheduled runs, but manual runs still work). The flock is released by the OS diff --git a/internal/channel/messages.go b/internal/channel/messages.go index 1e5c3af8..59021690 100644 --- a/internal/channel/messages.go +++ b/internal/channel/messages.go @@ -141,32 +141,6 @@ func RichIdle(t time.Time) string { } } -// RichDone returns a varied "task finished" message. -func RichDone(summary string, err error, t time.Time) string { - if err != nil { - return "❌ Task hit an error\n" + - "————————————————\n" + - err.Error() - } - s := summary - if len(s) > 500 { - s = s[:500] + "..." - } - if s == "" { - s = "(no details)" - } - prefixes := []string{ - "✅ Done!", - "✅ All finished!", - "✅ Task wrapped up!", - "✅ Completed!", - } - prefix := prefixes[t.Second()%len(prefixes)] - return prefix + "\n" + - "————————————————\n" + - s -} - func isWeekend(t time.Time) bool { d := t.Weekday() return d == time.Saturday || d == time.Sunday diff --git a/internal/command/acp.go b/internal/command/acp.go index bc06a280..c49bdd01 100644 --- a/internal/command/acp.go +++ b/internal/command/acp.go @@ -332,9 +332,11 @@ func (a *acpAgent) buildAgentSession( baseURL = registry.GetProviderAPI(providerName) } - chatModel, err := internalmodel.NewChatModel(ctx, &internalmodel.ChatModelConfig{ - Model: modelName, APIKey: providerCfg.APIKey, BaseURL: baseURL, - }) + // Apply a per-model reasoning-effort override (set from the chat picker) + // over the provider-level default before constructing the model. + acpEffortCfg := *providerCfg + acpEffortCfg.ReasoningEffort = config.ResolveEffort(providerName, modelName, providerCfg.ReasoningEffort) + chatModel, err := internalmodel.NewChatModelFromProvider(ctx, modelName, baseURL, &acpEffortCfg) if err != nil { return nil, fmt.Errorf("error creating model: %w", err) } diff --git a/internal/command/commands.go b/internal/command/commands.go index 119071b2..a121a0b7 100644 --- a/internal/command/commands.go +++ b/internal/command/commands.go @@ -71,9 +71,11 @@ func runDoctorMode() { } fmt.Println("\n [1] Model Connection") - chatModel, err := internalmodel.NewChatModel(context.Background(), &internalmodel.ChatModelConfig{ - Model: modelName, APIKey: providerCfg.APIKey, BaseURL: baseURL, - }) + // Apply a per-model reasoning-effort override (set from the chat picker) + // over the provider-level default before constructing the model. + docEffortCfg := *providerCfg + docEffortCfg.ReasoningEffort = config.ResolveEffort(providerName, modelName, providerCfg.ReasoningEffort) + chatModel, err := internalmodel.NewChatModelFromProvider(context.Background(), modelName, baseURL, &docEffortCfg) if err != nil { fmt.Printf(" ✗ Failed to initialize: %v\n", err) } else { diff --git a/internal/command/interactive.go b/internal/command/interactive.go index 4c12e685..0f3dfbc1 100644 --- a/internal/command/interactive.go +++ b/internal/command/interactive.go @@ -587,9 +587,11 @@ func (s *interactiveState) handleConfig(cfgMsg *config.Config) { if newBaseURL == "" { newBaseURL = s.registry.GetProviderAPI(newProvName) } - newChatModel, err := internalmodel.NewChatModel(s.ctx, &internalmodel.ChatModelConfig{ - Model: newModelName, APIKey: newProvCfg.APIKey, BaseURL: newBaseURL, - }) + // Apply a per-model reasoning-effort override (set from the chat picker) + // over the provider-level default before constructing the model. + newEffortCfg := *newProvCfg + newEffortCfg.ReasoningEffort = config.ResolveEffort(newProvName, newModelName, newProvCfg.ReasoningEffort) + newChatModel, err := internalmodel.NewChatModelFromProvider(s.ctx, newModelName, newBaseURL, &newEffortCfg) if err != nil { return } @@ -664,9 +666,9 @@ func (s *interactiveState) handleAddModel() { if newBaseURL == "" { newBaseURL = s.registry.GetProviderAPI(newProvName) } - newChatModel, cmErr := internalmodel.NewChatModel(s.ctx, &internalmodel.ChatModelConfig{ - Model: newModelName, APIKey: newProvCfg.APIKey, BaseURL: newBaseURL, - }) + newEffortCfg2 := *newProvCfg + newEffortCfg2.ReasoningEffort = config.ResolveEffort(newProvName, newModelName, newProvCfg.ReasoningEffort) + newChatModel, cmErr := internalmodel.NewChatModelFromProvider(s.ctx, newModelName, newBaseURL, &newEffortCfg2) if cmErr != nil { return } @@ -918,9 +920,9 @@ func RunInteractive(prompt, resumeUUID string, unsafe bool) error { baseURL = registry.GetProviderAPI(providerName) } - chatModel, err := internalmodel.NewChatModel(ctx, &internalmodel.ChatModelConfig{ - Model: modelName, APIKey: providerCfg.APIKey, BaseURL: baseURL, - }) + effortCfg := *providerCfg + effortCfg.ReasoningEffort = config.ResolveEffort(providerName, modelName, providerCfg.ReasoningEffort) + chatModel, err := internalmodel.NewChatModelFromProvider(ctx, modelName, baseURL, &effortCfg) if err != nil { return fmt.Errorf("error creating model: %w", err) } @@ -1045,9 +1047,9 @@ func RunInteractive(prompt, resumeUUID string, unsafe bool) error { if bURL == "" { bURL = registry.GetProviderAPI(pName) } - return internalmodel.NewChatModel(mCtx, &internalmodel.ChatModelConfig{ - Model: modelID, APIKey: pCfg.APIKey, BaseURL: bURL, - }) + pEffortCfg := *pCfg + pEffortCfg.ReasoningEffort = config.ResolveEffort(pName, modelID, pCfg.ReasoningEffort) + return internalmodel.NewChatModelFromProvider(mCtx, modelID, bURL, &pEffortCfg) }, PromptBuilder: func(agentType, agentPwd, agentPlatform string) string { return prompts.GetSystemPrompt(agentPlatform, agentPwd, "local", nil, "") diff --git a/internal/command/web.go b/internal/command/web.go index c4a23b7f..d9c14d6b 100644 --- a/internal/command/web.go +++ b/internal/command/web.go @@ -216,9 +216,12 @@ func runWebServer(port int, host string, openBrowser bool) error { if bURL == "" { bURL = registry.GetProviderAPI(prov) } - cm, err := internalmodel.NewChatModel(ctx, &internalmodel.ChatModelConfig{ - Model: mod, APIKey: provCfg.APIKey, BaseURL: bURL, - }) + // Apply a per-model reasoning-effort override (set from the chat picker) + // over the provider-level default before constructing the model. + pcEffort := config.ResolveEffort(prov, mod, provCfg.ReasoningEffort) + effortCfg := *provCfg + effortCfg.ReasoningEffort = pcEffort + cm, err := internalmodel.NewChatModelFromProvider(ctx, mod, bURL, &effortCfg) if err != nil { return nil, 0, fmt.Errorf("create model %s/%s: %w", prov, mod, err) } diff --git a/internal/config/config.go b/internal/config/config.go index 46dc096f..19511d43 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,6 +17,21 @@ type ProviderConfig struct { BaseURL string `json:"base_url,omitempty"` // Name is an optional display name for custom providers not in the registry. Name string `json:"name,omitempty"` + // Headers are extra HTTP headers injected into every request to this + // provider's endpoint (e.g. a gateway's "X-Api-Key" or "X-Org-Id"). Values + // may be secrets — they are masked by the API and never logged. + Headers map[string]string `json:"headers,omitempty"` + // Vision, when non-nil, overrides registry detection of image-input support + // for this provider. nil ⇒ defer to registry metadata (default: allow images). + Vision *bool `json:"vision,omitempty"` + // Thinking, when non-nil, explicitly toggles extended reasoning for this + // provider. It is sent as the OpenAI-compatible chat_template_kwargs + // {"enable_thinking": } extension (e.g. qwen3 gateways). nil ⇒ omit. + Thinking *bool `json:"thinking,omitempty"` + // ReasoningEffort controls thinking depth via the OpenAI-compatible + // "reasoning_effort" parameter. One of "", "low", "medium", "high". + // Empty ⇒ omit the parameter. + ReasoningEffort string `json:"reasoning_effort,omitempty"` // Deprecated: model lists are now sourced from the models.dev registry. // Preserved for backward compatibility with existing config files. Models []string `json:"models,omitempty"` @@ -32,6 +47,15 @@ type CustomModelConfig struct { ToolCall bool `json:"tool_call,omitempty"` Reasoning bool `json:"reasoning,omitempty"` Context int `json:"context,omitempty"` + // Attachment marks the model as accepting image inputs. When false (the + // default) the model inherits the provider-level Vision override (if set) or + // the registry default (allow images). + Attachment bool `json:"attachment,omitempty"` + // EffortTiers are the selectable reasoning-effort levels for a reasoning + // model, e.g. ["minimal","low","medium","high","max"]. When Reasoning is true + // and this is non-empty, it overrides the default standard effort options; + // when empty the standard set (minimal/low/medium/high) is used. + EffortTiers []string `json:"effort_tiers,omitempty"` } // SSHAlias represents a saved SSH connection alias @@ -327,9 +351,14 @@ func LoadConfig() (*Config, error) { cfg.Model = cfg.Provider + "/" + cfg.Model } - // Validate Model field is set + // Validate Model field is set. This is no longer a hard error: setup no + // longer forces a model selection, and some agent-construction paths can + // pick a default at runtime. We only warn so a stale/legacy config with + // providers but no active model doesn't prevent the app from booting — the + // caller resolves a concrete model (or surfaces a clearer error) when it + // actually builds an agent. if cfg.Model == "" { - return nil, fmt.Errorf("model not configured: set 'model' field in 'provider/model' format in %s", cfgPath) + Logger().Printf("[config] warning: no active model set in %s; it will be resolved on first use", cfgPath) } if cfg.MaxIterations <= 0 { diff --git a/internal/config/model_state.go b/internal/config/model_state.go index ddc006cb..cb92047f 100644 --- a/internal/config/model_state.go +++ b/internal/config/model_state.go @@ -18,6 +18,11 @@ type ModelState struct { EnabledModels []ModelRef `json:"enabled_models,omitempty"` // DisabledModels lists models explicitly disabled by the user (hidden from model selector). DisabledModels []ModelRef `json:"disabled_models,omitempty"` + // EffortOverrides holds per-"provider/model" reasoning-effort choices made + // from the chat model picker. An empty value ("") means "use the model's + // default / unset" and clears any earlier override. This takes precedence + // over the provider-level ProviderConfig.ReasoningEffort when present. + EffortOverrides map[string]string `json:"effort_overrides,omitempty"` } // ModelRef uniquely identifies a model in "provider/model" format. @@ -153,3 +158,43 @@ func removeModelRef(refs []ModelRef, ref ModelRef) []ModelRef { } return result } + +// effortKey is the lookup key for per-model effort overrides: "provider/model". +func effortKey(ref ModelRef) string { return ref.Provider + "/" + ref.Model } + +// GetEffortOverride returns the user's per-model reasoning-effort choice, or "" +// when none is set (callers then fall back to the provider-level setting). +func (s *ModelState) GetEffortOverride(ref ModelRef) string { + if s == nil || s.EffortOverrides == nil { + return "" + } + return s.EffortOverrides[effortKey(ref)] +} + +// SetEffortOverride records the user's reasoning-effort choice for a model. +// An empty effort clears the override, restoring the default behavior. +func (s *ModelState) SetEffortOverride(ref ModelRef, effort string) { + if s.EffortOverrides == nil { + s.EffortOverrides = make(map[string]string) + } + key := effortKey(ref) + if effort == "" { + delete(s.EffortOverrides, key) + return + } + s.EffortOverrides[key] = effort +} + +// ResolveEffort returns the effective reasoning effort for a model: the +// per-model override (from the chat picker) if set, otherwise the provider-level +// fallback from ProviderConfig. Empty ("") means "send no effort parameter". +// This is the single place that defines override precedence so every +// entrypoint (web/TUI/ACP) applies the same value. +func ResolveEffort(prov, mod, providerEffort string) string { + if state, err := LoadModelState(); err == nil && state != nil { + if v := state.GetEffortOverride(ModelRef{Provider: prov, Model: mod}); v != "" { + return v + } + } + return providerEffort +} diff --git a/internal/handler/web.go b/internal/handler/web.go index e6acd1c1..52b0e664 100644 --- a/internal/handler/web.go +++ b/internal/handler/web.go @@ -544,8 +544,3 @@ func (h *WebHandler) PendingAskUserRequests() []WebAskUserRequestData { } return out } - -// MarshalEvent marshals a WebEvent to JSON bytes. -func MarshalEvent(ev WebEvent) ([]byte, error) { - return json.Marshal(ev) -} diff --git a/internal/model/chatmodel.go b/internal/model/chatmodel.go index 210a47d6..9a022bff 100644 --- a/internal/model/chatmodel.go +++ b/internal/model/chatmodel.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "net/http" "sync" "sync/atomic" @@ -300,12 +301,43 @@ type ChatModelConfig struct { Model string APIKey string BaseURL string + // Headers are extra HTTP headers injected into every request to the + // provider endpoint (custom gateways, auth proxies). Empty ⇒ none. + Headers map[string]string + // ReasoningEffort sets thinking depth via the "reasoning_effort" parameter: + // "", "low", "medium", or "high". Empty ⇒ parameter omitted. + ReasoningEffort string + // Thinking, when non-nil, sends chat_template_kwargs {"enable_thinking": v} + // to explicitly toggle extended reasoning on compatible gateways. + Thinking *bool + // Vision controls whether image parts are forwarded to the model. When + // false, multimodal image content is stripped to text before sending. + Vision bool } type chatModel struct { - client *openai.Client - model string - tools []openai.Tool + client *openai.Client + model string + tools []openai.Tool + reasoningEffort string + thinking *bool + vision bool +} + +// headerDoer wraps an http.Client to inject a fixed set of headers into every +// outgoing request. It satisfies go-openai's HTTPDoer interface so a provider's +// configured Headers reach the API. Set unconditionally so callers may override +// transport headers (including Authorization) for custom gateways. +type headerDoer struct { + base *http.Client + headers map[string]string +} + +func (h *headerDoer) Do(req *http.Request) (*http.Response, error) { + for k, v := range h.headers { + req.Header.Set(k, v) + } + return h.base.Do(req) } func NewChatModel(_ context.Context, cfg *ChatModelConfig) (einomodel.ToolCallingChatModel, error) { @@ -316,12 +348,40 @@ func NewChatModel(_ context.Context, cfg *ChatModelConfig) (einomodel.ToolCallin if cfg.BaseURL != "" { config.BaseURL = cfg.BaseURL } + if len(cfg.Headers) > 0 { + config.HTTPClient = &headerDoer{base: &http.Client{}, headers: cfg.Headers} + } return &chatModel{ - client: openai.NewClientWithConfig(config), - model: cfg.Model, + client: openai.NewClientWithConfig(config), + model: cfg.Model, + reasoningEffort: cfg.ReasoningEffort, + thinking: cfg.Thinking, + vision: cfg.Vision, }, nil } +// NewChatModelFromProvider builds a ChatModel from a provider config, applying +// its advanced settings (custom headers, thinking depth, explicit thinking +// toggle, and the vision capability — which defaults to enabled). baseURL is the +// already-resolved endpoint (config override or registry default). This is the +// single place that maps ProviderConfig → ChatModelConfig so every entrypoint +// (web, TUI, ACP, subagents) honors the same settings. +func NewChatModelFromProvider(ctx context.Context, modelName, baseURL string, pc *config.ProviderConfig) (einomodel.ToolCallingChatModel, error) { + vision := true + if pc.Vision != nil { + vision = *pc.Vision + } + return NewChatModel(ctx, &ChatModelConfig{ + Model: modelName, + APIKey: pc.APIKey, + BaseURL: baseURL, + Headers: pc.Headers, + ReasoningEffort: pc.ReasoningEffort, + Thinking: pc.Thinking, + Vision: vision, + }) +} + func (m *chatModel) WithTools(tools []*schema.ToolInfo) (einomodel.ToolCallingChatModel, error) { config.Logger().Printf("[chatmodel] WithTools called with %d tools", len(tools)) oaiTools := make([]openai.Tool, 0, len(tools)) @@ -346,7 +406,14 @@ func (m *chatModel) WithTools(tools []*schema.ToolInfo) (einomodel.ToolCallingCh for _, t := range oaiTools { config.Logger().Printf("[chatmodel] tool: %s", t.Function.Name) } - return &chatModel{client: m.client, model: m.model, tools: oaiTools}, nil + return &chatModel{ + client: m.client, + model: m.model, + tools: oaiTools, + reasoningEffort: m.reasoningEffort, + thinking: m.thinking, + vision: m.vision, + }, nil } // extractUsage maps a go-openai Usage onto AddParams. cache_creation tokens are @@ -494,7 +561,7 @@ func (m *chatModel) Stream(ctx context.Context, input []*schema.Message, opts .. func (m *chatModel) buildRequest(input []*schema.Message, stream bool, opts ...einomodel.Option) openai.ChatCompletionRequest { msgs := make([]openai.ChatCompletionMessage, 0, len(input)) for _, msg := range input { - msgs = append(msgs, toOpenAIMessage(msg)) + msgs = append(msgs, toOpenAIMessage(msg, m.vision)) } req := openai.ChatCompletionRequest{ Model: m.model, @@ -502,6 +569,17 @@ func (m *chatModel) buildRequest(input []*schema.Message, stream bool, opts ...e Stream: stream, } + // Thinking depth: forward reasoning_effort when configured. Sent for all + // models — reasoning models honor it; others ignore it (OpenAI-compatible). + if m.reasoningEffort != "" { + req.ReasoningEffort = m.reasoningEffort + } + // Explicit thinking toggle for gateways that gate reasoning behind + // chat_template_kwargs {"enable_thinking": } (e.g. qwen3). + if m.thinking != nil { + req.ChatTemplateKwargs = map[string]any{"enable_thinking": *m.thinking} + } + // Apply call-time options (e.g. model.WithTools from Eino framework). commonOpts := einomodel.GetCommonOptions(nil, opts...) if len(commonOpts.Tools) > 0 { @@ -533,7 +611,7 @@ func (m *chatModel) buildRequest(input []*schema.Message, stream bool, opts ...e return req } -func toOpenAIMessage(msg *schema.Message) openai.ChatCompletionMessage { +func toOpenAIMessage(msg *schema.Message, vision bool) openai.ChatCompletionMessage { m := openai.ChatCompletionMessage{ Role: string(msg.Role), Content: msg.Content, @@ -546,6 +624,18 @@ func toOpenAIMessage(msg *schema.Message) openai.ChatCompletionMessage { } // Convert multimodal content (text + images) to OpenAI MultiContent format. if len(msg.UserInputMultiContent) > 0 { + // Vision disabled: collapse to text-only so a non-vision endpoint + // doesn't 400 on image parts. Text segments are preserved. + if !vision { + var text string + for _, p := range msg.UserInputMultiContent { + if p.Type == schema.ChatMessagePartTypeText { + text += p.Text + } + } + m.Content = text + return m + } m.Content = "" parts := make([]openai.ChatMessagePart, 0, len(msg.UserInputMultiContent)) for _, p := range msg.UserInputMultiContent { @@ -744,16 +834,6 @@ func containsModelPattern(model, pattern string) bool { (len(model) > len(pattern) && (model[:len(pattern)] == pattern || model[len(model)-len(pattern):] == pattern))) } -// GetTokenUsage returns the current token usage statistics -func GetTokenUsage() (prompt, completion, total int64) { - return TokenTracker.Get() -} - -// ResetTokenUsage resets the token usage tracker -func ResetTokenUsage() { - TokenTracker.Reset() -} - // GetModelContextLimit returns the known context limit for a given model name. // Returns 0 if the model is not in the known list. func GetModelContextLimit(modelName string) int { diff --git a/internal/model/factory.go b/internal/model/factory.go index 6ace8916..1d03d227 100644 --- a/internal/model/factory.go +++ b/internal/model/factory.go @@ -72,11 +72,11 @@ func (f *ModelFactory) GetModel(ctx context.Context, providerModel string) (eino baseURL = f.registry.GetProviderAPI(provider) } - m, err := NewChatModel(ctx, &ChatModelConfig{ - Model: modelName, - APIKey: providerCfg.APIKey, - BaseURL: baseURL, - }) + // Apply a per-model reasoning-effort override (set from the chat picker) + // over the provider-level default before constructing the model. + facEffortCfg := *providerCfg + facEffortCfg.ReasoningEffort = config.ResolveEffort(provider, modelName, providerCfg.ReasoningEffort) + m, err := NewChatModelFromProvider(ctx, modelName, baseURL, &facEffortCfg) if err != nil { return nil, fmt.Errorf("failed to create model %q: %w", providerModel, err) } diff --git a/internal/model/registry.go b/internal/model/registry.go index 957ade44..e6b4dc99 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -16,6 +16,10 @@ type RegistryProvider struct { API string `json:"api"` Doc string `json:"doc,omitempty"` Models map[string]*RegistryModel `json:"models"` + // Custom is true for providers that exist only because the user configured + // them (an OpenAI-compatible endpoint not in models.dev), as opposed to a + // built-in registry brand. Set during MergeConfigProviders. + Custom bool `json:"custom,omitempty"` } // RegistryModel represents a model from models.dev API. @@ -38,8 +42,27 @@ type RegistryModel struct { Status string `json:"status,omitempty"` Recommended bool `json:"recommended,omitempty"` DefaultEnabled bool `json:"default_enabled,omitempty"` + // ReasoningOptions describes how this model exposes its thinking controls, + // mirroring models.dev's reasoning_options. Empty ⇒ no reasoning controls. + ReasoningOptions []ReasoningOption `json:"reasoning_options,omitempty"` } +// ReasoningOption is one reasoning/thinking control a model supports, from +// models.dev's reasoning_options. Type is one of: +// - "effort" — Values lists the supported effort levels (e.g. low/medium/high/xhigh/max) +// - "toggle" — reasoning can be switched on/off, no extra parameters +// - "budget_tokens" — a thinking token budget bounded by Min/Max (nil ⇒ open-ended) +type ReasoningOption struct { + Type string `json:"type"` + Values []string `json:"values,omitempty"` + Min *int `json:"min,omitempty"` + Max *int `json:"max,omitempty"` +} + +// intPtr returns a pointer to i. Used by the generated registry to carry +// nullable reasoning_options bounds (Min/Max). +func intPtr(i int) *int { return &i } + // ModelModalities describes input/output modalities. type ModelModalities struct { Input []string `json:"input,omitempty"` @@ -167,6 +190,7 @@ func (r *ModelRegistry) MergeConfigProviders(providers map[string]*config.Provid API: provCfg.BaseURL, Env: []string{envKey}, Models: make(map[string]*RegistryModel), + Custom: true, } r.providers[provID] = prov r.providerOrder = append(r.providerOrder, provID) @@ -185,8 +209,22 @@ func (r *ModelRegistry) MergeConfigProviders(providers map[string]*config.Provid Name: name, ToolCall: cm.ToolCall, Reasoning: cm.Reasoning, + Attachment: cm.Attachment, DefaultEnabled: true, } + // A custom model flagged as reasoning gets the standard OpenAI-compatible + // effort levels, so the chat picker's effort control can render for it. + // Custom models not flagged reasoning stay without reasoning_options — + // the effort control is hidden for them, matching "not specified ⇒ none". + // When EffortTiers is provided, it replaces the standard set so users can + // configure exactly which effort levels (e.g. high/max) a model offers. + if cm.Reasoning { + if len(cm.EffortTiers) > 0 { + rm.ReasoningOptions = []ReasoningOption{{Type: "effort", Values: cm.EffortTiers}} + } else { + rm.ReasoningOptions = standardEffortOptions() + } + } if cm.Context > 0 { rm.Limit = &ModelLimit{Context: cm.Context} } @@ -297,6 +335,30 @@ func (r *ModelRegistry) HasProvider(providerID string) bool { return r.GetProvider(providerID) != nil } +// PickDefaultModel returns the best default model id for a provider, used when +// setup completes without an explicit model selection (the wizard no longer +// forces a model pick). Selection order: first DefaultEnabled model, then the +// first Recommended model, then simply the first model. Returns "" when the +// provider is unknown or has no models (e.g. a custom OpenAI-compatible +// provider) — callers must then require an explicit model id. +func (r *ModelRegistry) PickDefaultModel(providerID string) string { + models := r.ListProviderModels(providerID, false) + for _, m := range models { + if m.DefaultEnabled { + return m.ID + } + } + for _, m := range models { + if m.Recommended { + return m.ID + } + } + if len(models) > 0 { + return models[0].ID + } + return "" +} + // ListProviders returns all providers in the curated display order. func (r *ModelRegistry) ListProviders() []*RegistryProvider { result := make([]*RegistryProvider, 0, len(r.providerOrder)) @@ -578,3 +640,13 @@ func applyRecommendedModels() { } } } + +// standardEffortOptions is the reasoning_options applied to custom models the +// user flags as reasoning-capable. These are the effort levels the +// OpenAI-compatible "reasoning_effort" parameter conventionally accepts. +func standardEffortOptions() []ReasoningOption { + return []ReasoningOption{{ + Type: "effort", + Values: []string{"minimal", "low", "medium", "high"}, + }} +} diff --git a/internal/model/validate.go b/internal/model/validate.go index ff147972..26e90ce5 100644 --- a/internal/model/validate.go +++ b/internal/model/validate.go @@ -2,43 +2,173 @@ package model import ( "context" + "encoding/json" "fmt" "net/http" "time" ) +// ValidateResult is the structured outcome of a connectivity test against a +// provider's /models endpoint. It carries everything the UI needs to render a +// status banner: success with latency + available-model count, or a classified +// failure (auth vs. network vs. server). +type ValidateResult struct { + OK bool `json:"ok"` + LatencyMS int `json:"latency_ms"` + ModelCount int `json:"model_count"` + ErrorType string `json:"error_type,omitempty"` // "" | "auth" | "network" | "server" + Error string `json:"error,omitempty"` +} + // ValidateProvider tests connectivity to a provider by making a lightweight -// GET /models request. Returns nil on success, or a descriptive error. -func ValidateProvider(ctx context.Context, apiKey, baseURL string) error { +// GET /models request. Custom headers (if any) are applied last so they can +// override the default Authorization for gateways. Returns nil on success, or +// a descriptive error. +// +// This thin wrapper preserves the original signature for any caller that only +// cares about success/failure. Callers needing latency, model count, or error +// classification should use ValidateProviderDetailed instead. +func ValidateProvider(ctx context.Context, apiKey, baseURL string, headers map[string]string) error { + res := ValidateProviderDetailed(ctx, apiKey, baseURL, headers) + if res.OK { + return nil + } + if res.Error != "" { + return fmt.Errorf("%s", res.Error) + } + return fmt.Errorf("validation failed") +} + +// ValidateProviderDetailed performs the same connectivity test as +// ValidateProvider but returns the full structured result, including the +// measured latency, the number of models advertised at /models, and a +// classified error type on failure. +func ValidateProviderDetailed(ctx context.Context, apiKey, baseURL string, headers map[string]string) ValidateResult { if baseURL == "" { - return fmt.Errorf("base URL is empty") + return ValidateResult{ErrorType: "server", Error: "base URL is empty"} } client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/models", nil) if err != nil { - return fmt.Errorf("failed to create request: %w", err) + return ValidateResult{ErrorType: "server", Error: "failed to create request: " + err.Error()} } if apiKey != "" { req.Header.Set("Authorization", "Bearer "+apiKey) } + for k, v := range headers { + req.Header.Set(k, v) + } + start := time.Now() resp, err := client.Do(req) + latency := int(time.Since(start).Milliseconds()) if err != nil { - return fmt.Errorf("connection failed: %w", err) + // A transport-level failure (DNS, connection refused, timeout, TLS) is + // classified as "network" — distinct from an HTTP error from the server. + return ValidateResult{LatencyMS: latency, ErrorType: "network", Error: "connection failed: " + err.Error()} } defer func() { _ = resp.Body.Close() }() if resp.StatusCode == http.StatusUnauthorized { - return fmt.Errorf("invalid API key (401 Unauthorized)") + return ValidateResult{LatencyMS: latency, ErrorType: "auth", Error: "invalid API key (401 Unauthorized)"} } if resp.StatusCode == http.StatusForbidden { - return fmt.Errorf("access denied (403 Forbidden) — check API key permissions") + return ValidateResult{LatencyMS: latency, ErrorType: "auth", Error: "access denied (403 Forbidden) — check API key permissions"} } if resp.StatusCode >= 400 { - return fmt.Errorf("server returned %d %s", resp.StatusCode, resp.Status) + return ValidateResult{ + LatencyMS: latency, + ErrorType: "server", + Error: fmt.Sprintf("server returned %d %s", resp.StatusCode, resp.Status), + } + } + + // Success — best-effort parse of the model count from the /models payload. + // The OpenAI-compatible shape is {"data":[{"id":"..."}, ...]}; if the body + // doesn't decode or lacks "data", we still report success with count 0. + count := countModels(resp.Body) + return ValidateResult{OK: true, LatencyMS: latency, ModelCount: count} +} + +// modelsList is the subset of the OpenAI-compatible /models response we need +// to count advertised models. +type modelsList struct { + Data []struct { + ID string `json:"id"` + } `json:"data"` +} + +// readModelsBody fully drains a response body up to a 1MiB safety cap, returning +// the accumulated bytes. Used by both ValidateProviderDetailed and +// ListProviderModelsLive so the /models payload is only fetched once per call. +func readModelsBody(r interface{ Read([]byte) (int, error) }) []byte { + var buf [1 << 16]byte // 64KiB per read is ample for a model-id listing + var full []byte + for { + n, err := r.Read(buf[:]) + if n > 0 { + full = append(full, buf[:n]...) + } + if err != nil { + break + } + if len(full) > 1<<20 { // 1MiB safety cap + break + } } + return full +} - return nil +// countModels decodes the OpenAI-compatible /models body and returns the number +// of advertised models. It never returns an error: an undecodable or unexpected +// body simply yields 0, since a successful HTTP status is enough to confirm +// connectivity. +func countModels(r interface{ Read([]byte) (int, error) }) int { + var ml modelsList + if err := json.Unmarshal(readModelsBody(r), &ml); err != nil { + return 0 + } + return len(ml.Data) +} + +// ListProviderModelsLive queries a provider's live /models endpoint and returns +// the advertised model ids. It is best-effort: any failure (network, auth, +// non-JSON body) yields an empty slice, since the catalog UI degrades to "no +// catalog, add manually" rather than erroring. +func ListProviderModelsLive(ctx context.Context, apiKey, baseURL string, headers map[string]string) []string { + if baseURL == "" { + return nil + } + client := &http.Client{Timeout: 10 * time.Second} + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/models", nil) + if err != nil { + return nil + } + if apiKey != "" { + req.Header.Set("Authorization", "Bearer "+apiKey) + } + for k, v := range headers { + req.Header.Set(k, v) + } + resp, err := client.Do(req) + if err != nil { + return nil + } + defer func() { _ = resp.Body.Close() }() + if resp.StatusCode >= 400 { + return nil + } + var ml modelsList + if err := json.NewDecoder(resp.Body).Decode(&ml); err != nil { + return nil + } + ids := make([]string, 0, len(ml.Data)) + for _, m := range ml.Data { + if m.ID != "" { + ids = append(ids, m.ID) + } + } + return ids } diff --git a/internal/prompts/async_env.go b/internal/prompts/async_env.go deleted file mode 100644 index aa50c1db..00000000 --- a/internal/prompts/async_env.go +++ /dev/null @@ -1,75 +0,0 @@ -package prompts - -import ( - "context" - "sync" - "time" - - "github.com/cnjack/jcode/internal/config" - utils "github.com/cnjack/jcode/internal/util" -) - -// AsyncEnvLoader loads environment info with a timeout. -// It wraps the synchronous CollectEnvInfo in a goroutine so callers can -// proceed with other work (e.g. memory loading) concurrently. -type AsyncEnvLoader struct { - timeout time.Duration -} - -// NewAsyncEnvLoader creates a loader with the given timeout. -func NewAsyncEnvLoader(timeout time.Duration) *AsyncEnvLoader { - if timeout <= 0 { - timeout = 5 * time.Second - } - return &AsyncEnvLoader{timeout: timeout} -} - -// Load collects environment information with a timeout guard. -// CollectEnvInfo already runs each git sub-command with its own 2s timeout; -// this wrapper provides an overall deadline so the prompt builder never blocks -// for longer than the configured duration. -func (a *AsyncEnvLoader) Load(ctx context.Context, pwd string) *utils.EnvInfo { - ctx, cancel := context.WithTimeout(ctx, a.timeout) - defer cancel() - - type result struct { - info *utils.EnvInfo - } - - ch := make(chan result, 1) - go func() { - ch <- result{info: utils.CollectEnvInfo(pwd)} - }() - - select { - case r := <-ch: - return r.info - case <-ctx.Done(): - config.Logger().Printf("[async_env] timeout loading env info for %s", pwd) - return &utils.EnvInfo{} - } -} - -// LoadAsync starts environment info collection in the background and -// returns a function that blocks until the result is ready. This enables -// the caller to kick off env loading and memory loading in parallel. -func (a *AsyncEnvLoader) LoadAsync(ctx context.Context, pwd string) func() *utils.EnvInfo { - var info *utils.EnvInfo - var once sync.Once - done := make(chan struct{}) - - go func() { - defer close(done) - info = a.Load(ctx, pwd) - }() - - return func() *utils.EnvInfo { - once.Do(func() { - <-done - }) - if info == nil { - return &utils.EnvInfo{} - } - return info - } -} diff --git a/internal/prompts/builder.go b/internal/prompts/builder.go deleted file mode 100644 index f6bf47ea..00000000 --- a/internal/prompts/builder.go +++ /dev/null @@ -1,205 +0,0 @@ -package prompts - -import ( - "bytes" - "context" - "strings" - "text/template" - "time" - - "github.com/cnjack/jcode/internal/config" - utils "github.com/cnjack/jcode/internal/util" -) - -// PromptBlock represents a segment of the system prompt. -type PromptBlock struct { - Content string - CacheScope string // "global", "session", or "" (no cache) -} - -// PromptResult is the output of a prompt build. -type PromptResult struct { - Blocks []PromptBlock - Full string // concatenated for backward compatibility -} - -// BuilderConfig is the input for prompt construction. -type BuilderConfig struct { - Platform string - Pwd string - EnvLabel string - SkillDescriptions string - PlanMode bool - CacheEnabled bool -} - -// PromptBuilder constructs system prompts with parallel loading and caching. -type PromptBuilder struct { - cfg BuilderConfig - envLoader *AsyncEnvLoader - memLoader *MemoryLoader - cache *PromptBlockCache -} - -// NewPromptBuilder creates a PromptBuilder with the given config. -// It reads prompt-related settings from the user's config file. -func NewPromptBuilder(cfg BuilderConfig) *PromptBuilder { - userCfg, _ := config.LoadConfig() - - memCfg := MemoryConfig{ - MaxTotalChars: 40000, - MaxIncDepth: 5, - } - envTimeout := 5 * time.Second - - if userCfg != nil && userCfg.Prompt != nil { - if userCfg.Prompt.MemoryMaxChars > 0 { - memCfg.MaxTotalChars = userCfg.Prompt.MemoryMaxChars - } - if userCfg.Prompt.MemoryMaxDepth > 0 { - memCfg.MaxIncDepth = userCfg.Prompt.MemoryMaxDepth - } - if userCfg.Prompt.AsyncEnvTimeout != "" { - if d, err := time.ParseDuration(userCfg.Prompt.AsyncEnvTimeout); err == nil { - envTimeout = d - } - } - cfg.CacheEnabled = userCfg.Prompt.CacheEnabled - } - - return &PromptBuilder{ - cfg: cfg, - envLoader: NewAsyncEnvLoader(envTimeout), - memLoader: NewMemoryLoader(memCfg), - cache: NewPromptBlockCache(), - } -} - -// Build constructs the system prompt. -// It loads env info and AGENTS.md in parallel, renders the template, -// and optionally caches static blocks. -func (b *PromptBuilder) Build(ctx context.Context) (*PromptResult, error) { - type envResult struct { - info *utils.EnvInfo - } - type memResult struct { - content string - err error - } - - envCh := make(chan envResult, 1) - memCh := make(chan memResult, 1) - - // Load env info in parallel. - go func() { - info := b.envLoader.Load(ctx, b.cfg.Pwd) - envCh <- envResult{info: info} - }() - - // Load AGENTS.md in parallel. - go func() { - content, err := b.memLoader.Load(b.cfg.Pwd) - memCh <- memResult{content: content, err: err} - }() - - envRes := <-envCh - memRes := <-memCh - - if memRes.err != nil { - config.Logger().Printf("[builder] memory load error: %v", memRes.err) - } - - // Select template. - tmplSrc := systemPrompt - if b.cfg.PlanMode { - tmplSrc = planPrompt - } - - // Render the core template. - coreContent, err := b.renderTemplate(tmplSrc, envRes.info) - if err != nil { - return nil, err - } - - var blocks []PromptBlock - - // Core template block — may be cached if enabled. - if b.cfg.CacheEnabled { - hash := ContentHash(coreContent) - block := b.cache.GetOrBuild(hash, func() *PromptBlock { - return &PromptBlock{Content: coreContent, CacheScope: "global"} - }) - blocks = append(blocks, *block) - } else { - blocks = append(blocks, PromptBlock{Content: coreContent}) - } - - // AGENTS.md block (session-scoped, changes per project). - if memRes.content != "" { - agentsBlock := PromptBlock{ - Content: "\n\n## Custom Agent Instructions\n\n" + memRes.content, - CacheScope: "session", - } - blocks = append(blocks, agentsBlock) - } - - // Build full string for backward compatibility. - var full strings.Builder - for _, blk := range blocks { - full.WriteString(blk.Content) - } - - return &PromptResult{ - Blocks: blocks, - Full: full.String(), - }, nil -} - -// renderTemplate renders the chosen prompt template with current data. -func (b *PromptBuilder) renderTemplate(tmplSrc string, envInfo *utils.EnvInfo) (string, error) { - t, err := template.New("prompt").Parse(tmplSrc) - if err != nil { - return "", err - } - - cfg, _ := config.LoadConfig() - var sshAliases []config.SSHAlias - if cfg != nil { - sshAliases = cfg.SSHAliases - } - - data := struct { - Platform string - Pwd string - Date string - EnvLabel string - SSHAliases []config.SSHAlias - GitBranch string - GitDirty bool - LastCommit string - ProjectType string - DirTree string - SkillDescriptions string - }{ - Platform: b.cfg.Platform, - Pwd: b.cfg.Pwd, - Date: time.Now().Format("2006-01-02"), - EnvLabel: b.cfg.EnvLabel, - SSHAliases: sshAliases, - SkillDescriptions: b.cfg.SkillDescriptions, - } - - if envInfo != nil { - data.GitBranch = envInfo.GitBranch - data.GitDirty = envInfo.GitDirty - data.LastCommit = envInfo.LastCommit - data.ProjectType = envInfo.ProjectType - data.DirTree = envInfo.DirTree - } - - var buf bytes.Buffer - if err := t.Execute(&buf, data); err != nil { - return "", err - } - return buf.String(), nil -} diff --git a/internal/prompts/cache.go b/internal/prompts/cache.go deleted file mode 100644 index c1c5e814..00000000 --- a/internal/prompts/cache.go +++ /dev/null @@ -1,54 +0,0 @@ -package prompts - -import ( - "crypto/sha256" - "fmt" - "sync" -) - -// PromptBlockCache caches static prompt blocks to avoid redundant recomputation. -type PromptBlockCache struct { - mu sync.RWMutex - staticHash string - staticBlock *PromptBlock -} - -// NewPromptBlockCache returns a new empty cache. -func NewPromptBlockCache() *PromptBlockCache { - return &PromptBlockCache{} -} - -// GetOrBuild returns a cached block if the content hash matches, otherwise calls -// buildFn to create a new block and caches it. -func (c *PromptBlockCache) GetOrBuild(contentHash string, buildFn func() *PromptBlock) *PromptBlock { - c.mu.RLock() - if c.staticHash == contentHash && c.staticBlock != nil { - block := c.staticBlock - c.mu.RUnlock() - return block - } - c.mu.RUnlock() - - block := buildFn() - - c.mu.Lock() - c.staticHash = contentHash - c.staticBlock = block - c.mu.Unlock() - - return block -} - -// Invalidate clears the cached block. -func (c *PromptBlockCache) Invalidate() { - c.mu.Lock() - c.staticHash = "" - c.staticBlock = nil - c.mu.Unlock() -} - -// ContentHash returns the first 16 hex characters of the SHA-256 digest of content. -func ContentHash(content string) string { - h := sha256.Sum256([]byte(content)) - return fmt.Sprintf("%x", h[:8]) -} diff --git a/internal/prompts/compact.go b/internal/prompts/compact.go deleted file mode 100644 index 4814748e..00000000 --- a/internal/prompts/compact.go +++ /dev/null @@ -1,165 +0,0 @@ -package prompts - -import ( - "context" - _ "embed" - "fmt" - "strings" - - einomodel "github.com/cloudwego/eino/components/model" - "github.com/cloudwego/eino/schema" - - "github.com/cnjack/jcode/internal/config" -) - -//go:embed compact_prompt.md -var compactPromptTemplate string - -// CompactConfig controls context compaction behavior. -type CompactConfig struct { - Threshold float64 // fraction of context limit that triggers compaction (0-1) - MaxRetries int // max consecutive failures before disabling - BufferTokens int // token headroom to reserve -} - -// CompactResult describes the outcome of a compaction. -type CompactResult struct { - Summary string - OriginalTokens int64 - CompactedTokens int64 - PreservedMsgCount int -} - -// ContextCompactor manages automatic context window compression. -type ContextCompactor struct { - cfg CompactConfig - consecutiveFails int - tripped bool -} - -// NewContextCompactor creates a compactor with the given config. -func NewContextCompactor(cfg CompactConfig) *ContextCompactor { - if cfg.Threshold <= 0 || cfg.Threshold > 1 { - cfg.Threshold = 0.75 - } - if cfg.MaxRetries <= 0 { - cfg.MaxRetries = 3 - } - if cfg.BufferTokens <= 0 { - cfg.BufferTokens = 1000 - } - return &ContextCompactor{cfg: cfg} -} - -// ShouldCompact returns true when token usage exceeds the configured threshold. -func (c *ContextCompactor) ShouldCompact(tokensUsed int64, contextLimit int) bool { - if c.tripped || contextLimit <= 0 { - return false - } - return float64(tokensUsed) >= float64(contextLimit)*c.cfg.Threshold -} - -// Compact compresses the message history using the provided model. -// It keeps the system message and the most recent messages, summarising the rest. -func (c *ContextCompactor) Compact( - ctx context.Context, - messages []*schema.Message, - model einomodel.ToolCallingChatModel, - contextLimit int, -) ([]*schema.Message, *CompactResult, error) { - if len(messages) < 4 { - return messages, nil, nil - } - - // Separate system messages from conversation. - var systemMsgs []*schema.Message - var convMsgs []*schema.Message - for _, m := range messages { - if m.Role == schema.System { - systemMsgs = append(systemMsgs, m) - } else { - convMsgs = append(convMsgs, m) - } - } - - // Determine how many recent messages to keep (at least 4). - keepRecent := 6 - if keepRecent > len(convMsgs) { - return messages, nil, nil - } - - toSummarise := convMsgs[:len(convMsgs)-keepRecent] - recentMsgs := convMsgs[len(convMsgs)-keepRecent:] - - // Build the compaction prompt. - var sb strings.Builder - sb.WriteString(compactPromptTemplate) - sb.WriteString("\n\n---\n\nConversation to summarise:\n\n") - for _, m := range toSummarise { - content := m.Content - if len(content) > 800 { - content = content[:800] + "…" - } - fmt.Fprintf(&sb, "[%s]: %s\n\n", m.Role, content) - } - - summaryInput := []*schema.Message{ - {Role: schema.User, Content: sb.String()}, - } - - summaryMsg, err := model.Generate(ctx, summaryInput) - if err != nil { - c.consecutiveFails++ - if c.consecutiveFails >= c.cfg.MaxRetries { - c.tripped = true - config.Logger().Printf("[compactor] disabled after %d consecutive failures", c.consecutiveFails) - } - config.Logger().Printf("[compactor] summarisation failed: %v", err) - return messages, nil, fmt.Errorf("compaction failed: %w", err) - } - - c.consecutiveFails = 0 - - summaryMessage := &schema.Message{ - Role: schema.System, - Content: "[Conversation Summary]\n" + summaryMsg.Content, - } - - result := make([]*schema.Message, 0, len(systemMsgs)+1+len(recentMsgs)) - result = append(result, systemMsgs...) - result = append(result, summaryMessage) - result = append(result, recentMsgs...) - - // Estimate token savings (rough: 4 chars per token). - var origChars int64 - for _, m := range messages { - origChars += int64(len(m.Content)) - } - var compactedChars int64 - for _, m := range result { - compactedChars += int64(len(m.Content)) - } - - compactResult := &CompactResult{ - Summary: summaryMsg.Content, - OriginalTokens: origChars / 4, - CompactedTokens: compactedChars / 4, - PreservedMsgCount: len(recentMsgs), - } - - config.Logger().Printf("[compactor] compacted %d→%d messages, ~%d→~%d tokens", - len(messages), len(result), compactResult.OriginalTokens, compactResult.CompactedTokens) - - return result, compactResult, nil -} - -// IsTripped returns true if compaction has been disabled due to repeated failures. -func (c *ContextCompactor) IsTripped() bool { - return c.tripped -} - -// Reset clears the failure counter and re-enables compaction. -func (c *ContextCompactor) Reset() { - c.consecutiveFails = 0 - c.tripped = false -} diff --git a/internal/prompts/compact_prompt.md b/internal/prompts/compact_prompt.md deleted file mode 100644 index 1f1056b0..00000000 --- a/internal/prompts/compact_prompt.md +++ /dev/null @@ -1,12 +0,0 @@ -You are a context compression assistant. Analyze the conversation below and produce a structured summary. - -Your summary MUST include: -1. **User's Original Request**: The primary goal/task -2. **Key Decisions**: Important technical decisions made -3. **Files Involved**: All file paths mentioned or modified -4. **Errors & Fixes**: Any errors encountered and how they were resolved -5. **Completed Steps**: What has been accomplished -6. **Pending Tasks**: What still needs to be done -7. **Current Context**: The current working state - -Keep the summary concise but comprehensive. Do not lose critical technical details like file paths, function names, or error messages. diff --git a/internal/remote/ssh.go b/internal/remote/ssh.go index 89e393db..233ae612 100644 --- a/internal/remote/ssh.go +++ b/internal/remote/ssh.go @@ -140,23 +140,6 @@ func ListDirs(ctx context.Context, exec tools.Executor, path string) ([]string, return dirs, nil } -// ProjectLabel returns a stable, host-qualified identifier for a remote working -// directory. It is used as the session "project" key so remote sessions are -// grouped separately from local paths of the same name. Form: -// ssh://user@host:port/remote/path -func ProjectLabel(exec *tools.SSHExecutor, remotePwd string) string { - return fmt.Sprintf("ssh://%s@%s%s", exec.User(), exec.Host(), normalizeRemotePath(remotePwd)) -} - -// normalizeRemotePath ensures the path is absolute (leading slash) for use in a -// project label. -func normalizeRemotePath(p string) string { - if !strings.HasPrefix(p, "/") { - return "/" + p - } - return p -} - // expandHome expands a leading ~ or ~/ to the current user's home directory. func expandHome(path string) string { if path == "~" { diff --git a/internal/remote/ssh_test.go b/internal/remote/ssh_test.go index 2a934b4a..62a8917d 100644 --- a/internal/remote/ssh_test.go +++ b/internal/remote/ssh_test.go @@ -91,16 +91,3 @@ func TestDiscoverPwdFallback(t *testing.T) { t.Fatalf("DiscoverPwd fallback = %q, want /root", got) } } - -func TestProjectLabel(t *testing.T) { - // ProjectLabel needs a *tools.SSHExecutor; build a zero executor isn't - // possible (unexported fields), so assert the format via a helper on a - // constructed value would require reflection. Instead, verify the path - // normalization branch indirectly through label-shaped expectations. - if got := normalizeRemotePath("home/app"); got != "/home/app" { - t.Fatalf("normalizeRemotePath = %q, want /home/app", got) - } - if got := normalizeRemotePath("/srv"); got != "/srv" { - t.Fatalf("normalizeRemotePath = %q, want /srv", got) - } -} diff --git a/internal/runner/eventbus.go b/internal/runner/eventbus.go deleted file mode 100644 index 214ddea9..00000000 --- a/internal/runner/eventbus.go +++ /dev/null @@ -1,102 +0,0 @@ -package runner - -import ( - "context" - "sync" - "sync/atomic" - - "github.com/cnjack/jcode/internal/config" -) - -// EventType categorises events flowing through the EventBus. -type EventType int - -const ( - EventAssistantText EventType = iota - EventAssistantDone - EventToolCall - EventToolResult - EventError - EventBudgetWarning - EventCompaction - EventWorkerStatus -) - -// ToolCallEvent describes a tool invocation. -type ToolCallEvent struct { - Name string - Args string -} - -// ToolResultEvent describes a tool's result. -type ToolResultEvent struct { - Name string - Output string - Err error -} - -// Event is the unit of data emitted by the agent loop. -type Event struct { - Type EventType - Text string - ToolCall *ToolCallEvent - ToolResult *ToolResultEvent - Error error - Meta map[string]any -} - -// EventBus is a channel-based event bus for streaming agent loop events -// to the TUI and other consumers. -type EventBus struct { - ch chan Event - done chan struct{} - cancel context.CancelFunc - closeOnce sync.Once - closed atomic.Bool -} - -// NewEventBus creates an event bus with the given channel buffer size. -func NewEventBus(bufferSize int) *EventBus { - if bufferSize <= 0 { - bufferSize = 64 - } - _, cancel := context.WithCancel(context.Background()) - return &EventBus{ - ch: make(chan Event, bufferSize), - done: make(chan struct{}), - cancel: cancel, - } -} - -// Emit sends an event to the bus. Non-blocking: drops the event if the -// channel is full or the bus is closed, and logs a warning. -func (eb *EventBus) Emit(event Event) { - if eb.closed.Load() { - return - } - select { - case eb.ch <- event: - default: - config.Logger().Printf("[eventbus] dropped event type=%d (buffer full)", event.Type) - } -} - -// Subscribe returns the read-only event channel. -func (eb *EventBus) Subscribe() <-chan Event { - return eb.ch -} - -// Close shuts down the event bus. Safe to call multiple times. -func (eb *EventBus) Close() { - eb.closeOnce.Do(func() { - eb.closed.Store(true) - eb.cancel() - close(eb.done) - close(eb.ch) - }) -} - -// Done returns a channel that is closed when the event bus is shut down. -func (eb *EventBus) Done() <-chan struct{} { - return eb.done -} diff --git a/internal/session/history.go b/internal/session/history.go index dbb90aca..76875511 100644 --- a/internal/session/history.go +++ b/internal/session/history.go @@ -39,73 +39,6 @@ func entryToUserMessage(e Entry) *schema.Message { } } -// ReconstructHistory converts a slice of recorded session entries back into -// LLM history messages suitable for resuming a conversation. -// It reconstructs tool call and tool result messages so that resumed sessions -// retain full context. -// -// Subagent-internal entries (tool_call / tool_result / assistant recorded -// between subagent_start and subagent_result) are skipped — only the main -// agent's own messages are included. -// -// Because the runner records assistant text AFTER tool calls in the JSONL, -// an EntryAssistant that follows tool-call entries is merged back into the -// preceding assistant message as its Content field. -func ReconstructHistory(entries []Entry) []adk.Message { - var msgs []adk.Message - var subagentDepth int - for _, e := range entries { - switch e.Type { - case EntrySubagentStart: - subagentDepth++ - continue - case EntrySubagentResult: - if subagentDepth > 0 { - subagentDepth-- - } - continue - } - // Skip entries that belong to a running subagent. - if subagentDepth > 0 { - continue - } - - switch e.Type { - case EntryUser: - msgs = append(msgs, entryToUserMessage(e)) - case EntryAssistant: - if e.Content != "" { - // The runner records assistant text after tool calls, so the - // preceding message may already be an assistant with ToolCalls - // but empty Content. Merge into it when possible. - if n := len(msgs); n > 0 { - if last := msgs[n-1]; last.Role == schema.Assistant && last.Content == "" && len(last.ToolCalls) > 0 { - last.Content = e.Content - continue - } - } - msgs = append(msgs, &schema.Message{Role: schema.Assistant, Content: e.Content}) - } - case EntryToolCall: - tc := schema.ToolCall{ - ID: e.ToolCallID, - Function: schema.FunctionCall{Name: e.Name, Arguments: e.Args}, - } - // Merge into preceding assistant message if it exists, otherwise create one. - if n := len(msgs); n > 0 { - if last := msgs[n-1]; last.Role == schema.Assistant { - last.ToolCalls = append(last.ToolCalls, tc) - continue - } - } - msgs = append(msgs, &schema.Message{Role: schema.Assistant, ToolCalls: []schema.ToolCall{tc}}) - case EntryToolResult: - msgs = append(msgs, schema.ToolMessage(e.Output, e.ToolCallID, schema.WithToolName(e.Name))) - } - } - return msgs -} - // toolPlaceholders maps tool names to actionable placeholder messages. // These tell the model what happened and how to recover the data. var toolPlaceholders = map[string]string{ @@ -200,7 +133,7 @@ type SessionState struct { // It is compact-aware: if a compact entry is found, messages before it are // replaced with the compact summary. // -// Subagent-internal entries are skipped (same logic as ReconstructHistory). +// Subagent-internal entries are skipped. func ReconstructState(entries []Entry) *SessionState { state := &SessionState{ EnvTarget: "local", @@ -327,28 +260,3 @@ func ReconstructState(entries []Entry) *SessionState { state.History = msgs return state } - -// GetLastEnvironment scans the session entries to find the last successful switch_env call, -// and returns the target environment alias. If none is found, it returns "local". -func GetLastEnvironment(entries []Entry) string { - lastEnv := "local" - var lastTarget string - - for _, e := range entries { - if e.Type == EntryToolCall && e.Name == "switch_env" { - // Extract target from args - type args struct { - Target string `json:"target"` - } - var a args - if err := json.Unmarshal([]byte(e.Args), &a); err == nil { - lastTarget = a.Target - } - } else if e.Type == EntryToolResult && e.Name == "switch_env" { - if e.Error == "" && lastTarget != "" { - lastEnv = lastTarget - } - } - } - return lastEnv -} diff --git a/internal/session/session.go b/internal/session/session.go index fe0232a6..9f903a3e 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -699,24 +699,6 @@ func updateIndexTitle(project, uuid, title string) error { return os.Rename(tmpPath, indexPath) } -// DeleteSession removes a session file and its index entry. -func DeleteSession(project, uuid string) error { - // 1. Remove from index. - if err := removeFromIndex(project, uuid); err != nil { - return fmt.Errorf("remove index entry: %w", err) - } - // 2. Delete the JSONL file. - dir, err := config.SessionsDir() - if err != nil { - return err - } - filePath := filepath.Join(dir, uuid+".json") - if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("delete session file: %w", err) - } - return nil -} - // DeleteSessionByUUID removes a session (index entry + JSONL file) located by // uuid across ALL projects. The web task tree can delete a task that does not // belong to the active project, so we must not assume a single project key. @@ -777,43 +759,6 @@ func DeleteSessionByUUID(uuid string) (bool, error) { return true, nil } -func removeFromIndex(project, uuid string) error { - indexMu.Lock() - defer indexMu.Unlock() - indexPath, err := config.SessionsIndexPath() - if err != nil { - return err - } - data, err := os.ReadFile(indexPath) - if err != nil { - if os.IsNotExist(err) { - return nil - } - return err - } - var idx sessionIndex - if err := json.Unmarshal(data, &idx); err != nil { - return err - } - metas := idx.Sessions[project] - filtered := make([]SessionMeta, 0, len(metas)) - for _, m := range metas { - if m.UUID != uuid { - filtered = append(filtered, m) - } - } - idx.Sessions[project] = filtered - newData, err := json.MarshalIndent(idx, "", " ") - if err != nil { - return err - } - tmpPath := indexPath + ".tmp" - if err := os.WriteFile(tmpPath, newData, 0644); err != nil { - return err - } - return os.Rename(tmpPath, indexPath) -} - // ListSessions returns all sessions recorded for a given project path, newest last. func ListSessions(project string) ([]SessionMeta, error) { indexPath, err := config.SessionsIndexPath() diff --git a/internal/team/context.go b/internal/team/context.go index 6f6d2617..b3bf27b4 100644 --- a/internal/team/context.go +++ b/internal/team/context.go @@ -21,12 +21,6 @@ func GetTeammateIdentity(ctx context.Context) (TeammateIdentity, bool) { return id, ok } -// IsTeammate returns true if the context belongs to a teammate (not the leader). -func IsTeammate(ctx context.Context) bool { - _, ok := GetTeammateIdentity(ctx) - return ok -} - // GetAgentName returns the agent name from the context, or TeamLeadName if not a teammate. func GetAgentName(ctx context.Context) string { if id, ok := GetTeammateIdentity(ctx); ok { diff --git a/internal/tools/mcp_manager.go b/internal/tools/mcp_manager.go index c71ede95..f3efdcb4 100644 --- a/internal/tools/mcp_manager.go +++ b/internal/tools/mcp_manager.go @@ -6,7 +6,6 @@ import ( "fmt" "math" "math/rand/v2" - "strings" "sync" "time" @@ -57,14 +56,6 @@ type MCPServerStatus struct { Error string `json:"error,omitempty"` } -// MCPResource describes a resource exposed by an MCP server. -type MCPResource struct { - URI string `json:"uri"` - Name string `json:"name"` - Description string `json:"description,omitempty"` - MimeType string `json:"mimeType,omitempty"` -} - // --------------------------------------------------------------------------- // ReconnectPolicy // --------------------------------------------------------------------------- @@ -311,11 +302,6 @@ func (m *MCPManager) AddServer(ctx context.Context, name string, cfg *config.MCP return m.Connect(ctx, name, cfg) } -// RemoveServer disconnects and removes a server. -func (m *MCPManager) RemoveServer(name string) error { - return m.Disconnect(name) -} - // GetAllTools returns tools from all connected servers. func (m *MCPManager) GetAllTools() []MCPToolInfo { m.mu.RLock() @@ -354,199 +340,6 @@ func (m *MCPManager) GetServerStatus() []MCPServerStatus { return out } -// InvokeMCPTool calls a tool on the named server. If the call fails it -// attempts one reconnection before returning an error. -func (m *MCPManager) InvokeMCPTool(ctx context.Context, serverName, toolName string, args json.RawMessage) (string, error) { - conn, err := m.getConnection(serverName) - if err != nil { - return "", err - } - - result, err := m.callTool(ctx, conn, toolName, args) - if err == nil { - return result, nil - } - - // Attempt one reconnection and retry. - config.Logger().Printf("mcp_manager: tool %q on %q failed, reconnecting: %v", toolName, serverName, err) - - if reconnErr := m.reconnectOnce(ctx, conn); reconnErr != nil { - return "", fmt.Errorf("mcp_manager: reconnect %q failed: %w (original: %v)", serverName, reconnErr, err) - } - - return m.callTool(ctx, conn, toolName, args) -} - -func (m *MCPManager) callTool(ctx context.Context, conn *MCPConnection, toolName string, args json.RawMessage) (string, error) { - conn.mu.RLock() - cli := conn.Client - state := conn.State - conn.mu.RUnlock() - - if cli == nil || state != MCPConnected { - return "", fmt.Errorf("server %q not connected (state=%s)", conn.Name, state) - } - - var arguments any - if len(args) > 0 { - var m map[string]any - if err := json.Unmarshal(args, &m); err != nil { - return "", fmt.Errorf("invalid arguments JSON: %w", err) - } - arguments = m - } - - req := mcp.CallToolRequest{} - req.Params.Name = toolName - req.Params.Arguments = arguments - - result, err := cli.CallTool(ctx, req) - if err != nil { - return "", err - } - - if result.IsError { - return "", fmt.Errorf("tool %q returned error: %s", toolName, contentToString(result.Content)) - } - - return contentToString(result.Content), nil -} - -// contentToString serialises MCP Content slices to a single string. -func contentToString(contents []mcp.Content) string { - var parts []string - for _, c := range contents { - switch v := c.(type) { - case mcp.TextContent: - parts = append(parts, v.Text) - default: - b, _ := json.Marshal(c) - parts = append(parts, string(b)) - } - } - return strings.Join(parts, "\n") -} - -// reconnectOnce performs a single reconnection attempt for a connection. -func (m *MCPManager) reconnectOnce(ctx context.Context, conn *MCPConnection) error { - conn.mu.Lock() - if conn.Client != nil { - _ = conn.Client.Close() - conn.Client = nil - } - conn.setState(MCPReconnecting) - conn.mu.Unlock() - m.notifyState(conn.Name, MCPReconnecting) - - if err := m.doConnect(ctx, conn); err != nil { - conn.mu.Lock() - conn.LastError = err - conn.setState(MCPDisconnected) - conn.mu.Unlock() - m.notifyState(conn.Name, MCPDisconnected) - return err - } - - conn.mu.Lock() - conn.setState(MCPConnected) - conn.LastConnected = time.Now() - conn.RetryCount = 0 - conn.LastError = nil - conn.mu.Unlock() - m.notifyState(conn.Name, MCPConnected) - return nil -} - -// getConnection looks up a connection by name. -func (m *MCPManager) getConnection(name string) (*MCPConnection, error) { - m.mu.RLock() - conn, ok := m.connections[name] - m.mu.RUnlock() - if !ok { - return nil, fmt.Errorf("mcp_manager: server %q not found", name) - } - return conn, nil -} - -// --------------------------------------------------------------------------- -// Resource Discovery -// --------------------------------------------------------------------------- - -// ListResources returns the resources advertised by the named server. -func (m *MCPManager) ListResources(ctx context.Context, serverName string) ([]MCPResource, error) { - conn, err := m.getConnection(serverName) - if err != nil { - return nil, err - } - - conn.mu.RLock() - cli := conn.Client - caps := conn.Capabilities - conn.mu.RUnlock() - - if caps == nil || !caps.Resources { - return nil, fmt.Errorf("server %q does not support resources", serverName) - } - if cli == nil { - return nil, fmt.Errorf("server %q not connected", serverName) - } - - result, err := cli.ListResources(ctx, mcp.ListResourcesRequest{}) - if err != nil { - return nil, fmt.Errorf("list resources on %q: %w", serverName, err) - } - - out := make([]MCPResource, 0, len(result.Resources)) - for _, r := range result.Resources { - out = append(out, MCPResource{ - URI: r.URI, - Name: r.Name, - Description: r.Description, - MimeType: r.MIMEType, - }) - } - return out, nil -} - -// ReadResource reads a single resource from the named server. -func (m *MCPManager) ReadResource(ctx context.Context, serverName, uri string) (string, error) { - conn, err := m.getConnection(serverName) - if err != nil { - return "", err - } - - conn.mu.RLock() - cli := conn.Client - caps := conn.Capabilities - conn.mu.RUnlock() - - if caps == nil || !caps.Resources { - return "", fmt.Errorf("server %q does not support resources", serverName) - } - if cli == nil { - return "", fmt.Errorf("server %q not connected", serverName) - } - - req := mcp.ReadResourceRequest{} - req.Params.URI = uri - - result, err := cli.ReadResource(ctx, req) - if err != nil { - return "", fmt.Errorf("read resource %q on %q: %w", uri, serverName, err) - } - - var parts []string - for _, c := range result.Contents { - switch v := c.(type) { - case mcp.TextResourceContents: - parts = append(parts, v.Text) - case mcp.BlobResourceContents: - parts = append(parts, v.Blob) - } - } - return strings.Join(parts, "\n"), nil -} - // --------------------------------------------------------------------------- // Close // --------------------------------------------------------------------------- diff --git a/internal/tools/mcp_manager_test.go b/internal/tools/mcp_manager_test.go index f2bcbfa0..5e2b410f 100644 --- a/internal/tools/mcp_manager_test.go +++ b/internal/tools/mcp_manager_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/cnjack/jcode/internal/config" - "github.com/mark3labs/mcp-go/mcp" ) // --------------------------------------------------------------------------- @@ -437,28 +436,6 @@ func TestAddServer_Duplicate(t *testing.T) { } } -// --------------------------------------------------------------------------- -// contentToString -// --------------------------------------------------------------------------- - -func TestContentToString(t *testing.T) { - contents := []mcp.Content{ - mcp.TextContent{Type: "text", Text: "hello"}, - mcp.TextContent{Type: "text", Text: "world"}, - } - got := contentToString(contents) - if got != "hello\nworld" { - t.Errorf("contentToString = %q, want %q", got, "hello\nworld") - } -} - -func TestContentToString_Empty(t *testing.T) { - got := contentToString(nil) - if got != "" { - t.Errorf("contentToString(nil) = %q, want empty", got) - } -} - // --------------------------------------------------------------------------- // MCPToolInfo JSON // --------------------------------------------------------------------------- diff --git a/internal/tools/storage.go b/internal/tools/storage.go index 8b00b6dd..a213d9c9 100644 --- a/internal/tools/storage.go +++ b/internal/tools/storage.go @@ -27,29 +27,6 @@ type StorageManager struct { writeQueue *WriteQueue } -// NewStorageManager creates the storage root and all subdirectories. -func NewStorageManager(sessionID string) (*StorageManager, error) { - home, err := os.UserHomeDir() - if err != nil { - return nil, err - } - base := filepath.Join(home, ".jcode", "storage") - - subdirs := []string{"file-history", "tool-results", "todos", "plans", "tasks", "oauth"} - for _, sub := range subdirs { - if err := os.MkdirAll(filepath.Join(base, sub), 0o700); err != nil { - return nil, err - } - } - - sm := &StorageManager{ - baseDir: base, - sessionID: sessionID, - writeQueue: NewWriteQueue(100 * time.Millisecond), - } - return sm, nil -} - // Write synchronously writes data to path, creating parent directories. func (sm *StorageManager) Write(path string, data []byte, mode os.FileMode) error { sm.mu.Lock() @@ -676,19 +653,6 @@ func (tl *TaskLog) Write(data []byte) (int, error) { return n, err } -// ReadAll reads the entire task log from disk. -func (tl *TaskLog) ReadAll() (string, error) { - tl.mu.Lock() - path := tl.file.Name() - tl.mu.Unlock() - - data, err := os.ReadFile(path) - if err != nil { - return "", err - } - return string(data), nil -} - // Close closes the underlying file. func (tl *TaskLog) Close() error { tl.mu.Lock() diff --git a/internal/tui/messages.go b/internal/tui/messages.go index 601692ab..1f335e1c 100644 --- a/internal/tui/messages.go +++ b/internal/tui/messages.go @@ -48,14 +48,6 @@ func GetResumeChannel() <-chan string { return resumeCh } -// approvalCh is used to pass tool approval requests from main goroutine to TUI. -var approvalCh = make(chan ToolApprovalRequestMsg, 1) - -// GetApprovalChannel returns the channel that receives tool approval requests. -func GetApprovalChannel() chan ToolApprovalRequestMsg { - return approvalCh -} - // --- Messages --- type AgentTextMsg struct{ Text string } diff --git a/internal/tui/setup.go b/internal/tui/setup.go index b006b21d..7c9490f4 100644 --- a/internal/tui/setup.go +++ b/internal/tui/setup.go @@ -51,21 +51,11 @@ func (i providerItem) Description() string { } func (i providerItem) FilterValue() string { return i.profile.Name + " " + i.profile.ID } -type modelListItem struct { - name string - desc string -} - -func (i modelListItem) Title() string { return i.name } -func (i modelListItem) Description() string { return i.desc } -func (i modelListItem) FilterValue() string { return i.name } - type SetupState int const ( - StateProvider SetupState = iota - StateModel - StateCustomModel + StateProvider SetupState = iota + StateCustomModel // only for custom/OpenAI-compatible providers (model id required) StateURL StateAPIKey ) @@ -73,14 +63,13 @@ const ( type SetupModel struct { state SetupState providerList list.Model - modelList list.Model customModelIn textinput.Model urlIn textinput.Model keyIn textinput.Model registry *model.ModelRegistry selectedProvider *ProviderProfile - selectedModel string + selectedModel string // only set for custom providers finalURL string finalKey string @@ -154,13 +143,9 @@ func NewSetupModel() SetupModel { pl.SetShowHelp(false) m.providerList = pl - ml := list.New([]list.Item{}, del, 60, 15) - ml.SetShowHelp(false) - m.modelList = ml - m.customModelIn = textinput.New() - m.customModelIn.Placeholder = "Enter custom model name..." - m.customModelIn.Prompt = "Model Name: " + m.customModelIn.Placeholder = "Enter model id (e.g. gpt-4o)..." + m.customModelIn.Prompt = "Model ID: " m.customModelIn.SetWidth(50) m.urlIn = textinput.New() @@ -251,68 +236,25 @@ func (m SetupModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if sel != nil { p := sel.(providerItem).profile m.selectedProvider = &p - - var mItems []list.Item - // Always try to load models from registry, regardless of initial FromRegistry flag. - // This handles cases where registry was temporarily unavailable during init. - models := m.registry.ListProviderModels(p.ID, false) - if len(models) > 0 { - for _, rm := range models { - desc := modelDescription(rm) - mItems = append(mItems, modelListItem{name: rm.ID, desc: desc}) - } - } - // Always offer "Custom..." as the last option - mItems = append(mItems, modelListItem{name: "Custom...", desc: "Enter a custom model name"}) - m.modelList.SetItems(mItems) - m.modelList.Title = "Select Model (" + p.Name + ")" - - m.state = StateModel - return m, nil + return m.advanceAfterProvider() } } var cmd tea.Cmd m.providerList, cmd = m.providerList.Update(msg) cmds = append(cmds, cmd) - case StateModel: - // When filtering, let keys pass through to the list - if m.modelList.FilterState() == list.Filtering { - var cmd tea.Cmd - m.modelList, cmd = m.modelList.Update(msg) - cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) - } - if msg.String() == "enter" { - sel := m.modelList.SelectedItem() - if sel != nil { - name := sel.(modelListItem).name - if name == "Custom..." { - m.state = StateCustomModel - m.customModelIn.Focus() - } else { - m.selectedModel = name - return m.advanceAfterModel() - } - return m, nil - } - } else if msg.String() == "esc" { - m.state = StateProvider - return m, nil - } - var cmd tea.Cmd - m.modelList, cmd = m.modelList.Update(msg) - cmds = append(cmds, cmd) - case StateCustomModel: if msg.String() == "enter" { val := strings.TrimSpace(m.customModelIn.Value()) if val != "" { m.selectedModel = val - return m.advanceAfterModel() + // Custom providers always need a base URL next. + m.state = StateURL + m.urlIn.Focus() + return m, nil } } else if msg.String() == "esc" { - m.state = StateModel + m.state = StateProvider return m, nil } var cmd tea.Cmd @@ -330,10 +272,13 @@ func (m SetupModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.advanceAfterURL() } } else if msg.String() == "esc" { - if m.selectedModel == "Custom..." { + // Back to custom-model step if this is a custom provider, else + // straight to the provider list. + if m.selectedProvider.NeedURL { m.state = StateCustomModel + m.customModelIn.Focus() } else { - m.state = StateModel + m.state = StateProvider } return m, nil } @@ -355,11 +300,8 @@ func (m SetupModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case m.selectedProvider.NeedURL || m.selectedProvider.BaseURL == "": m.state = StateURL m.urlIn.Focus() - case m.selectedModel == "Custom...": - m.state = StateCustomModel - m.customModelIn.Focus() default: - m.state = StateModel + m.state = StateProvider } return m, nil } @@ -372,21 +314,16 @@ func (m SetupModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width m.height = msg.Height m.providerList.SetSize(msg.Width-4, 15) - m.modelList.SetSize(msg.Width-4, 15) } - // Forward non-key/non-mouse messages (e.g. list.FilterMatchesMsg) to active list + // Forward non-key/non-mouse messages (e.g. list.FilterMatchesMsg) to the + // active list (only the provider list remains now). if _, isKey := msg.(tea.KeyPressMsg); !isKey { if _, isMouse := msg.(tea.MouseMsg); !isMouse { - switch m.state { - case StateProvider: + if m.state == StateProvider { var cmd tea.Cmd m.providerList, cmd = m.providerList.Update(msg) cmds = append(cmds, cmd) - case StateModel: - var cmd tea.Cmd - m.modelList, cmd = m.modelList.Update(msg) - cmds = append(cmds, cmd) } } } @@ -394,16 +331,27 @@ func (m SetupModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } -func (m SetupModel) advanceAfterModel() (tea.Model, tea.Cmd) { - if m.selectedProvider.NeedURL || m.selectedProvider.BaseURL == "" { +// advanceAfterProvider routes the wizard after a provider is picked. Registry +// providers don't need a model selection (a default is auto-picked on submit), +// so they go straight to URL/Key. Custom (OpenAI-compatible) providers need a +// model id and a base URL, so they start at the custom-model step. +func (m SetupModel) advanceAfterProvider() (tea.Model, tea.Cmd) { + p := m.selectedProvider + // Custom providers need a model id before anything else. + if p.NeedURL && m.selectedModel == "" { + m.state = StateCustomModel + m.customModelIn.Focus() + return m, nil + } + if p.NeedURL || p.BaseURL == "" { m.state = StateURL - if m.selectedProvider.BaseURL != "" { - m.urlIn.Placeholder = m.selectedProvider.BaseURL + if p.BaseURL != "" { + m.urlIn.Placeholder = p.BaseURL } m.urlIn.Focus() return m, nil } - m.finalURL = m.selectedProvider.BaseURL + m.finalURL = p.BaseURL return m.advanceAfterURL() } @@ -445,6 +393,17 @@ func (m SetupModel) findProviderAPIKey() string { return "" } +// containsCustomModel reports whether a model id is already registered on the +// provider config, used to avoid duplicate custom-model entries on re-setup. +func containsCustomModel(models []config.CustomModelConfig, id string) bool { + for _, cm := range models { + if cm.ID == id { + return true + } + } + return false +} + func (m SetupModel) submit() (tea.Model, tea.Cmd) { cfg, err := config.LoadConfig() if err != nil { @@ -474,8 +433,30 @@ func (m SetupModel) submit() (tea.Model, tea.Cmd) { pCfg.APIKey = m.finalKey pCfg.BaseURL = m.finalURL - // Set model in "provider/model" format - cfg.Model = pID + "/" + m.selectedModel + // Resolve the active model. For custom (OpenAI-compatible) providers the + // user entered a model id; for registry providers a default is auto-picked. + modelID := m.selectedModel + if modelID == "" && m.registry != nil { + modelID = m.registry.PickDefaultModel(pID) + } + if modelID == "" { + m.err = "No default model available for " + pID + "; please select a model in Settings" + return m, nil + } + // For custom providers, register the model so it's selectable afterwards. + if m.selectedProvider.NeedURL { + if pCfg.Name == "" { + pCfg.Name = m.selectedProvider.Name + } + if !containsCustomModel(pCfg.CustomModels, modelID) { + pCfg.CustomModels = append(pCfg.CustomModels, config.CustomModelConfig{ + ID: modelID, + Name: modelID, + ToolCall: true, + }) + } + } + cfg.Model = pID + "/" + modelID if err := config.SaveConfig(cfg); err != nil { m.err = fmt.Sprintf("Failed to save config: %v", err) @@ -486,27 +467,6 @@ func (m SetupModel) submit() (tea.Model, tea.Cmd) { return m, tea.Quit } -// modelDescription builds a short description for a registry model. -func modelDescription(rm *model.RegistryModel) string { - var parts []string - if rm.Limit != nil && rm.Limit.Context > 0 { - parts = append(parts, fmt.Sprintf("%dk ctx", rm.Limit.Context/1000)) - } - if rm.ToolCall { - parts = append(parts, "tool_call") - } - if rm.Reasoning { - parts = append(parts, "reasoning") - } - if rm.Cost != nil && rm.Cost.Input > 0 { - parts = append(parts, fmt.Sprintf("$%.2f/1M in", rm.Cost.Input)) - } - if len(parts) == 0 { - return rm.ID - } - return strings.Join(parts, " · ") -} - func (m SetupModel) View() tea.View { w := m.width if w <= 0 { @@ -522,10 +482,11 @@ func (m SetupModel) View() tea.View { switch m.state { case StateProvider: content = m.providerList.View() - case StateModel: - content = m.modelList.View() case StateCustomModel: content = m.customModelIn.View() + if m.selectedProvider != nil { + content += "\n " + lipgloss.NewStyle().Foreground(colorMuted).Render("Model id for "+m.selectedProvider.Name) + } case StateURL: content = m.urlIn.View() if m.selectedProvider.BaseURL != "" { @@ -535,7 +496,7 @@ func (m SetupModel) View() tea.View { content = m.keyIn.View() } - if m.state != StateProvider && m.state != StateModel { + if m.state != StateProvider { var helpText string if m.state == StateAPIKey { helpText = " Press Enter to submit, Esc to go back. Paste: Ctrl+Shift+V (Win/Linux) or Cmd+V (Mac)" diff --git a/internal/tui/team_view.go b/internal/tui/team_view.go index 73871403..47c0eda7 100644 --- a/internal/tui/team_view.go +++ b/internal/tui/team_view.go @@ -224,19 +224,6 @@ func RenderTeamStatusPill(count int) string { return style.Render(fmt.Sprintf("%d teammates", count)) } -// RenderTeammateMessage renders a message from a teammate with color and identity. -func RenderTeammateMessage(from, content, color, summary string) string { - nameStyle := lipgloss.NewStyle(). - Bold(true). - Foreground(lipgloss.Color(color)) - - summaryStyle := lipgloss.NewStyle(). - Faint(true) - - header := nameStyle.Render("@"+from) + " " + summaryStyle.Render(summary) - return header + "\n" + content -} - // --- Model methods for team view switching --- // switchTeamView switches the agent view by delta (-1 = prev, +1 = next). diff --git a/internal/tui/tui.go b/internal/tui/tui.go index f24e6399..ea205bc7 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -456,15 +456,3 @@ func RunTUI(hasPrompt bool, pwd string, todoStore *tools.TodoStore, opts ...Mode p := tea.NewProgram(&m) return p, m } - -func HeaderView() string { - bracketStyle := lipgloss.NewStyle().Foreground(colorMuted).Bold(true) - jStyle := lipgloss.NewStyle().Foreground(colorLogoJ).Bold(true) - codeStyle := lipgloss.NewStyle().Foreground(colorText).Bold(true) - return lipgloss.JoinHorizontal(lipgloss.Left, - bracketStyle.Render("["), - jStyle.Render("J"), - codeStyle.Render("CODE"), - bracketStyle.Render("]"), - ) -} diff --git a/internal/web/server.go b/internal/web/server.go index c8fc106d..3929b649 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -354,7 +354,12 @@ func (s *Server) Start(ctx context.Context) error { // Provider management API — add/remove providers after initial setup. mux.HandleFunc("GET /api/providers", s.handleListProviders) mux.HandleFunc("POST /api/providers", s.handleAddProvider) + mux.HandleFunc("PUT /api/providers/{id}", s.handleUpdateProvider) mux.HandleFunc("DELETE /api/providers/{id}", s.handleDeleteProvider) + // Browse a provider's model catalog. For registry providers this returns the + // built-in model list; for custom endpoints it queries the live /models + // endpoint. Each entry is flagged added=true when already configured. + mux.HandleFunc("GET /api/providers/{id}/models", s.handleProviderCatalog) // History management. mux.HandleFunc("POST /api/history/truncate", s.handleTruncateHistory) @@ -363,6 +368,7 @@ func (s *Server) Start(ctx context.Context) error { mux.HandleFunc("GET /api/model-state", s.handleGetModelState) mux.HandleFunc("POST /api/model-state/favorite", s.handleToggleFavorite) mux.HandleFunc("POST /api/model-state/enabled", s.handleToggleModelEnabled) + mux.HandleFunc("POST /api/model-state/effort", s.handleSetModelEffort) // Serve embedded frontend (SPA with fallback to index.html) mux.Handle("GET /", newSPAHandler()) @@ -1138,35 +1144,43 @@ func (s *Server) handleListModels(w http.ResponseWriter, r *http.Request) { } type modelInfo struct { - ID string `json:"id"` - Name string `json:"name"` - ToolCall bool `json:"tool_call"` - ContextLimit int `json:"context_limit,omitempty"` - Reasoning bool `json:"reasoning,omitempty"` - Recommended bool `json:"recommended,omitempty"` - DefaultEnabled bool `json:"default_enabled,omitempty"` - Enabled bool `json:"enabled"` - ImageSupport bool `json:"image_support,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + ToolCall bool `json:"tool_call"` + ContextLimit int `json:"context_limit,omitempty"` + Reasoning bool `json:"reasoning,omitempty"` + Recommended bool `json:"recommended,omitempty"` + DefaultEnabled bool `json:"default_enabled,omitempty"` + Enabled bool `json:"enabled"` + ImageSupport bool `json:"image_support,omitempty"` + ReasoningOptions []model.ReasoningOption `json:"reasoning_options,omitempty"` } type providerInfo struct { ID string `json:"id"` Name string `json:"name"` + Custom bool `json:"custom,omitempty"` Models []modelInfo `json:"models"` } modelState, _ := config.LoadModelState() + // Rebuild the registry from the live config so models added at runtime + // (custom models saved via the providers API) appear immediately in the chat + // model picker. The startup registry (s.registry) is a snapshot and would not + // reflect these additions until a restart. + registry := model.NewModelRegistryWithConfig(s.cfg) + var result []providerInfo configuredProviders := s.cfg.GetProviders() - for _, rp := range s.registry.ListProviders() { + for _, rp := range registry.ListProviders() { if _, configured := configuredProviders[rp.ID]; !configured { continue } - models := s.registry.ListProviderModels(rp.ID, true) + models := registry.ListProviderModels(rp.ID, true) if len(models) == 0 { continue } - pi := providerInfo{ID: rp.ID, Name: rp.Name} + pi := providerInfo{ID: rp.ID, Name: rp.Name, Custom: rp.Custom} for _, m := range models { ctx := 0 if m.Limit != nil { @@ -1187,7 +1201,8 @@ func (s *Server) handleListModels(w http.ResponseWriter, r *http.Request) { ID: m.ID, Name: m.Name, ToolCall: m.ToolCall, ContextLimit: ctx, Reasoning: m.Reasoning, Recommended: m.Recommended, DefaultEnabled: m.DefaultEnabled, Enabled: enabled, - ImageSupport: imageSupport, + ImageSupport: imageSupport, + ReasoningOptions: m.ReasoningOptions, }) } result = append(result, pi) @@ -2819,9 +2834,10 @@ func (s *Server) handleSetupStatus(w http.ResponseWriter, r *http.Request) { // handleSetupValidate tests connectivity to a provider with the given API key. func (s *Server) handleSetupValidate(w http.ResponseWriter, r *http.Request) { var req struct { - Provider string `json:"provider"` - APIKey string `json:"api_key"` - BaseURL string `json:"base_url,omitempty"` + Provider string `json:"provider"` + APIKey string `json:"api_key"` + BaseURL string `json:"base_url,omitempty"` + Headers map[string]string `json:"headers,omitempty"` } if err := json.NewDecoder(io.LimitReader(r.Body, 1<<16)).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) @@ -2841,16 +2857,20 @@ func (s *Server) handleSetupValidate(w http.ResponseWriter, r *http.Request) { return } - if err := model.ValidateProvider(r.Context(), req.APIKey, baseURL); err != nil { + res := model.ValidateProviderDetailed(r.Context(), req.APIKey, baseURL, req.Headers) + if !res.OK { writeJSON(w, http.StatusOK, map[string]any{ - "valid": false, - "error": err.Error(), + "valid": false, + "error": res.Error, + "error_type": res.ErrorType, }) return } writeJSON(w, http.StatusOK, map[string]any{ - "valid": true, + "valid": true, + "latency_ms": res.LatencyMS, + "model_count": res.ModelCount, }) } @@ -2933,11 +2953,13 @@ func (s *Server) handleSetupProviderModels(w http.ResponseWriter, r *http.Reques models := s.registry.ListProviderModels(providerID, true) type modelItem struct { - ID string `json:"id"` - Name string `json:"name"` - ToolCall bool `json:"tool_call"` - ContextLimit int `json:"context_limit,omitempty"` - Reasoning bool `json:"reasoning,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + ToolCall bool `json:"tool_call"` + ContextLimit int `json:"context_limit,omitempty"` + Reasoning bool `json:"reasoning,omitempty"` + Attachment bool `json:"attachment,omitempty"` + ReasoningOptions []model.ReasoningOption `json:"reasoning_options,omitempty"` } result := make([]modelItem, 0, len(models)) @@ -2947,32 +2969,246 @@ func (s *Server) handleSetupProviderModels(w http.ResponseWriter, r *http.Reques ctx = m.Limit.Context } result = append(result, modelItem{ - ID: m.ID, - Name: m.Name, - ToolCall: m.ToolCall, - ContextLimit: ctx, - Reasoning: m.Reasoning, + ID: m.ID, + Name: m.Name, + ToolCall: m.ToolCall, + ContextLimit: ctx, + Reasoning: m.Reasoning, + Attachment: m.Attachment, + ReasoningOptions: m.ReasoningOptions, }) } writeJSON(w, http.StatusOK, result) } -// handleSetupComplete handles the initial setup submission. +// handleProviderCatalog returns a provider's browsable model catalog for the +// "browse directory" UI. For registry providers it lists the built-in models +// (the official /models endpoint is not reliably complete); for custom +// (OpenAI-compatible) endpoints it queries the live /models endpoint. Each +// entry is flagged added=true when the model is already in the provider's +// config (either as a CustomModelConfig or a registry model that's enabled). +func (s *Server) handleProviderCatalog(w http.ResponseWriter, r *http.Request) { + providerID := r.PathValue("id") + if providerID == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "provider id is required"}) + return + } + + type catalogEntry struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Added bool `json:"added"` + Context int `json:"context,omitempty"` + Reasoning bool `json:"reasoning,omitempty"` + Attachment bool `json:"attachment,omitempty"` + EffortTiers []string `json:"effort_tiers,omitempty"` + // Custom marks a user-defined model (editable/removable) vs a built-in + // registry model (toggled via model_state). Surfaced so the catalog row can + // show edit/remove affordances only on user custom models. + Custom bool `json:"custom,omitempty"` + } + + // Collect the set of already-configured model ids for this provider, so each + // catalog entry can be flagged added/可移除. Custom models come from config; + // for registry providers, MergeConfigProviders has already merged custom + // models into the registry, so registry membership is the source of truth. + configured := make(map[string]bool) + customSet := make(map[string]*config.CustomModelConfig) // user-defined models by id + var apiKey, baseURL string + var headers map[string]string + cfg, _ := config.LoadConfig() + if cfg != nil { + if pc := cfg.GetProviders()[providerID]; pc != nil { + apiKey, baseURL, headers = pc.APIKey, pc.BaseURL, pc.Headers + for _, m := range pc.CustomModels { + configured[m.ID] = true + cm := m // copy for map value + customSet[m.ID] = &cm + } + } + } + + // customEntry builds a catalogEntry from a user-defined CustomModelConfig. + customEntry := func(id string) catalogEntry { + m := customSet[id] + e := catalogEntry{ID: id, Added: true, Custom: true} + if m != nil { + e.Name = m.Name + e.Context = m.Context + e.Reasoning = m.Reasoning + e.Attachment = m.Attachment + e.EffortTiers = m.EffortTiers + } + return e + } + + // resolveRegistryBrand finds a registry provider whose brand keyword appears + // in the given id/url — so a custom endpoint pointing at, say, zhipu's API + // still surfaces zhipu's models.dev catalog rather than a fragile live + // /models probe. Returns "" when no brand matches. + resolveRegistryBrand := func(hint string) string { + if s.registry == nil { + return "" + } + hint = strings.ToLower(hint) + for _, rp := range s.registry.ListProviders() { + // Use the registry id as the brand keyword (e.g. "zhipuai", "openai", + // "deepseek"); these already encode the brand and are stable. + brand := strings.ToLower(rp.ID) + if brand != "" && strings.Contains(hint, brand) { + return rp.ID + } + } + return "" + } + + // The catalog defaults to the built-in (models.dev) catalog — this is the + // reliable source, since many endpoints either lack a /models route or + // return an incomplete list. Exact id match first; otherwise a brand match + // on the provider id or base URL (so a custom zhipu endpoint still shows the + // zhipu catalog). + registryID := providerID + if s.registry == nil || !s.registry.HasProvider(registryID) { + if hint := resolveRegistryBrand(providerID + " " + baseURL); hint != "" { + registryID = hint + } + } + if s.registry != nil && s.registry.HasProvider(registryID) { + models := s.registry.ListProviderModels(registryID, true) + result := make([]catalogEntry, 0, len(models)) + for _, m := range models { + // A user-defined custom model is merged into the registry by + // MergeConfigProviders, so it appears here too. For those, build the + // entry from the stored CustomModelConfig (which carries the + // user-set name/context/tiers) rather than the derived registry view — + // otherwise effort tiers and other authored fields are lost. + if cm := customSet[m.ID]; cm != nil { + result = append(result, catalogEntry{ + ID: m.ID, + Name: cm.Name, + Added: true, + Context: cm.Context, + Reasoning: cm.Reasoning, + Attachment: cm.Attachment, + EffortTiers: cm.EffortTiers, + Custom: true, + }) + continue + } + ctx := 0 + if m.Limit != nil { + ctx = m.Limit.Context + } + result = append(result, catalogEntry{ + ID: m.ID, + Name: m.Name, + Added: configured[m.ID] || m.DefaultEnabled, + Context: ctx, + Reasoning: m.Reasoning, + Attachment: m.Attachment, + Custom: false, + }) + } + // Also surface any user-added custom models not in the brand catalog, so + // the catalog isn't missing models the user explicitly configured. + for id := range configured { + found := false + for _, e := range result { + if e.ID == id { + found = true + break + } + } + if !found { + result = append(result, customEntry(id)) + } + } + writeJSON(w, http.StatusOK, result) + return + } + + // Truly custom endpoint with no brand match: probe the live /models endpoint + // as a last resort. Many gateways support the OpenAI-compatible /models list; + // on any failure we fall back to just the configured models so the catalog is + // never empty/erroring. + if baseURL != "" { + if ids := model.ListProviderModelsLive(r.Context(), apiKey, baseURL, headers); len(ids) > 0 { + result := make([]catalogEntry, 0, len(ids)) + seen := make(map[string]bool, len(ids)) + for _, id := range ids { + if seen[id] { + continue + } + seen[id] = true + if c := customSet[id]; c != nil { + result = append(result, customEntry(id)) + } else { + result = append(result, catalogEntry{ID: id, Added: configured[id]}) + } + } + writeJSON(w, http.StatusOK, result) + return + } + } + + // No registry brand and no live /models: show the configured custom models + // (added=true, custom=true) so the catalog reflects what's actually usable. + result := make([]catalogEntry, 0, len(configured)) + for id := range configured { + result = append(result, customEntry(id)) + } + writeJSON(w, http.StatusOK, result) +} + // It saves the provider config and creates the agent. +// +// The wizard no longer forces a model selection: for registry providers, a +// default model is auto-picked (DefaultEnabled → Recommended → first). A +// caller-supplied model always wins. Custom (non-registry) providers must send +// a model explicitly since none can be inferred. func (s *Server) handleSetupComplete(w http.ResponseWriter, r *http.Request) { var req struct { - Provider string `json:"provider"` - Model string `json:"model"` - APIKey string `json:"api_key"` - BaseURL string `json:"base_url,omitempty"` + Provider string `json:"provider"` + Model string `json:"model,omitempty"` + ModelReasoning bool `json:"model_reasoning,omitempty"` + APIKey string `json:"api_key"` + BaseURL string `json:"base_url,omitempty"` + Name string `json:"name,omitempty"` // custom provider display name + Headers map[string]string `json:"headers,omitempty"` + Vision *bool `json:"vision,omitempty"` + Thinking *bool `json:"thinking,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` } if err := json.NewDecoder(io.LimitReader(r.Body, 1<<16)).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) return } - if req.Provider == "" || req.Model == "" { - writeJSON(w, http.StatusBadRequest, map[string]string{"error": "provider and model are required"}) + if req.Provider == "" || req.APIKey == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "provider and api_key are required"}) + return + } + if !validReasoningEffort(req.ReasoningEffort) { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid reasoning_effort"}) + return + } + + // Resolve the active model. An explicit model always wins; otherwise try to + // auto-pick a default for registry providers. Custom providers (not in the + // registry) cannot infer a model and require one from the caller. + resolvedModel := req.Model + isCustom := s.registry == nil || !s.registry.HasProvider(req.Provider) + if resolvedModel == "" { + if isCustom { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "model is required for custom providers"}) + return + } + if s.registry != nil { + resolvedModel = s.registry.PickDefaultModel(req.Provider) + } + } + if resolvedModel == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "no default model available for this provider; please choose one"}) return } @@ -2989,11 +3225,28 @@ func (s *Server) handleSetupComplete(w http.ResponseWriter, r *http.Request) { if cfg.Providers == nil { cfg.Providers = make(map[string]*config.ProviderConfig) } - cfg.Providers[req.Provider] = &config.ProviderConfig{ - APIKey: req.APIKey, - BaseURL: req.BaseURL, - } - cfg.Model = req.Provider + "/" + req.Model + setupPC := &config.ProviderConfig{ + APIKey: req.APIKey, + BaseURL: req.BaseURL, + Name: req.Name, + Headers: cleanHeaders(req.Headers), + Vision: req.Vision, + Thinking: req.Thinking, + ReasoningEffort: req.ReasoningEffort, + } + // For a custom provider, persist the model as a custom model so it survives + // a model switch (otherwise it exists only as the active-model string and + // vanishes from the picker once changed). + if isCustom && resolvedModel != "" { + setupPC.CustomModels = []config.CustomModelConfig{{ + ID: resolvedModel, + Name: resolvedModel, + ToolCall: true, + Reasoning: req.ModelReasoning, + }} + } + cfg.Providers[req.Provider] = setupPC + cfg.Model = req.Provider + "/" + resolvedModel if err := config.SaveConfig(cfg); err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to save config: " + err.Error()}) @@ -3006,12 +3259,12 @@ func (s *Server) handleSetupComplete(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "no active task to configure"}) return } - ag, err := eng.createAgent(req.Provider, req.Model) + ag, err := eng.createAgent(req.Provider, resolvedModel) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to create agent: " + err.Error()}) return } - eng.applyModelSwitch(ag, req.Provider, req.Model) + eng.applyModelSwitch(ag, req.Provider, resolvedModel) // Publish the new config + registry to the live server so endpoints // (/api/models, context-limit, etc.) reflect the just-configured provider // without a restart. @@ -3026,16 +3279,29 @@ func (s *Server) handleSetupComplete(w http.ResponseWriter, r *http.Request) { // Notify clients that setup is complete. s.wsBroker.Broadcast(WSEvent{Type: "model_changed", TaskID: eng.taskID, Data: map[string]string{ "provider": req.Provider, - "model": req.Model, + "model": resolvedModel, }}) writeJSON(w, http.StatusOK, map[string]string{ "status": "ok", "provider": req.Provider, - "model": req.Model, + "model": resolvedModel, }) } +// maskSecret hides a secret for display: first 4 and last 4 chars for longer +// values, "****" for short ones. Used for API keys and header values so the +// list endpoint never returns plaintext credentials. +func maskSecret(s string) string { + if s == "" { + return "" + } + if len(s) > 8 { + return s[:4] + "..." + s[len(s)-4:] + } + return "****" +} + // handleListProviders returns all configured providers (key masked). func (s *Server) handleListProviders(w http.ResponseWriter, r *http.Request) { cfg, err := config.LoadConfig() @@ -3044,29 +3310,126 @@ func (s *Server) handleListProviders(w http.ResponseWriter, r *http.Request) { return } + type customModelView struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Reasoning bool `json:"reasoning,omitempty"` + Context int `json:"context,omitempty"` + Attachment bool `json:"attachment,omitempty"` + EffortTiers []string `json:"effort_tiers,omitempty"` + // Custom marks a user-defined model (editable) vs a built-in registry + // model surfaced for display (read-only). Omitted/zero ⇒ treated as a + // user custom model for backward compatibility, but we always set it. + Custom bool `json:"custom,omitempty"` + } type providerDetail struct { - ID string `json:"id"` - APIKeySet bool `json:"api_key_set"` - APIKey string `json:"api_key,omitempty"` // masked - BaseURL string `json:"base_url,omitempty"` + ID string `json:"id"` + Name string `json:"name,omitempty"` // display name for custom providers + Custom bool `json:"custom,omitempty"` + APIKeySet bool `json:"api_key_set"` + APIKey string `json:"api_key,omitempty"` // masked + BaseURL string `json:"base_url,omitempty"` + Headers map[string]string `json:"headers,omitempty"` // values masked + CustomModels []customModelView `json:"custom_models,omitempty"` + Vision *bool `json:"vision,omitempty"` + Thinking *bool `json:"thinking,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` } result := make([]providerDetail, 0) for id, pc := range cfg.GetProviders() { detail := providerDetail{ - ID: id, - APIKeySet: pc.APIKey != "", - BaseURL: pc.BaseURL, + ID: id, + Name: pc.Name, + APIKeySet: pc.APIKey != "", + BaseURL: pc.BaseURL, + Vision: pc.Vision, + Thinking: pc.Thinking, + ReasoningEffort: pc.ReasoningEffort, + } + // A provider is "custom" when it exists only because the user configured + // it (an OpenAI-compatible endpoint), not as a built-in registry brand. + // MergeConfigProviders flags those on the registry entry; a configured id + // with no registry entry at all is custom too. The registry may be nil in + // setup mode — fall back to "has a display name" as the custom signal. + if s.registry != nil { + if prov := s.registry.GetProvider(id); prov != nil { + detail.Custom = prov.Custom + } else { + detail.Custom = true + } + } else if pc.Name != "" { + detail.Custom = true } if pc.APIKey != "" { - // Mask API key: show first 4 and last 4 chars. - key := pc.APIKey - if len(key) > 8 { - detail.APIKey = key[:4] + "..." + key[len(key)-4:] - } else { - detail.APIKey = "****" + detail.APIKey = maskSecret(pc.APIKey) + } + if len(pc.Headers) > 0 { + masked := make(map[string]string, len(pc.Headers)) + for k, v := range pc.Headers { + masked[k] = maskSecret(v) + } + detail.Headers = masked + } + // Build the card's unified model list. For registry providers this is the + // built-in (models.dev) models the user has enabled, each marked read-only + // (Custom=false); the user's CustomModels are then appended and marked + // editable (Custom=true). The card renders them identically and only + // surfaces edit/delete affordances on editable rows. + cms := make([]customModelView, 0) + seen := make(map[string]bool) + // Track which ids are user-defined custom models so the registry loop can + // skip them (they're merged into the registry by MergeConfigProviders but + // must surface with their authored fields from CustomModelConfig, handled + // in the custom loop below). + customIDs := make(map[string]bool, len(pc.CustomModels)) + for _, m := range pc.CustomModels { + customIDs[m.ID] = true + } + if !detail.Custom && s.registry != nil && s.registry.HasProvider(id) { + for _, m := range s.registry.ListProviderModels(id, true) { + if !m.DefaultEnabled { + continue + } + if customIDs[m.ID] { + continue // user custom model — emitted below with authored fields + } + if seen[m.ID] { + continue + } + seen[m.ID] = true + ctx := 0 + if m.Limit != nil { + ctx = m.Limit.Context + } + cms = append(cms, customModelView{ + ID: m.ID, + Name: m.Name, + Reasoning: m.Reasoning, + Context: ctx, + Attachment: m.Attachment, + Custom: false, + }) } } + for _, m := range pc.CustomModels { + if seen[m.ID] { + continue + } + seen[m.ID] = true + cms = append(cms, customModelView{ + ID: m.ID, + Name: m.Name, + Reasoning: m.Reasoning, + Context: m.Context, + Attachment: m.Attachment, + EffortTiers: m.EffortTiers, + Custom: true, + }) + } + if len(cms) > 0 { + detail.CustomModels = cms + } result = append(result, detail) } sort.Slice(result, func(i, j int) bool { return result[i].ID < result[j].ID }) @@ -3074,12 +3437,22 @@ func (s *Server) handleListProviders(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, result) } -// handleAddProvider adds a new provider to the config. +// handleAddProvider adds a new provider to the config. For custom +// (non-registry) providers the caller should also send a name; models are +// optional here and are added afterward from the provider card (the dialog only +// captures the connection). func (s *Server) handleAddProvider(w http.ResponseWriter, r *http.Request) { var req struct { - ID string `json:"id"` - APIKey string `json:"api_key"` - BaseURL string `json:"base_url,omitempty"` + ID string `json:"id"` + APIKey string `json:"api_key"` + BaseURL string `json:"base_url,omitempty"` + Name string `json:"name,omitempty"` + Model string `json:"model,omitempty"` + ModelReasoning bool `json:"model_reasoning,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + Vision *bool `json:"vision,omitempty"` + Thinking *bool `json:"thinking,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` } if err := json.NewDecoder(io.LimitReader(r.Body, 1<<16)).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) @@ -3089,6 +3462,10 @@ func (s *Server) handleAddProvider(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id and api_key are required"}) return } + if !validReasoningEffort(req.ReasoningEffort) { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid reasoning_effort"}) + return + } cfg, err := config.LoadConfig() if err != nil { @@ -3097,10 +3474,42 @@ func (s *Server) handleAddProvider(w http.ResponseWriter, r *http.Request) { if cfg.Providers == nil { cfg.Providers = make(map[string]*config.ProviderConfig) } - cfg.Providers[req.ID] = &config.ProviderConfig{ - APIKey: req.APIKey, - BaseURL: req.BaseURL, + + // A custom provider (not in the registry) needs a base URL so requests can + // be routed. Models are optional at creation time: the provider is created + // connection-only and models are added afterward from its card, so a brand + // new custom endpoint can be saved before its model list is known. + isCustom := s.registry == nil || !s.registry.HasProvider(req.ID) + if isCustom && req.BaseURL == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "base_url is required for custom providers"}) + return + } + + pc := &config.ProviderConfig{ + APIKey: req.APIKey, + BaseURL: req.BaseURL, + Name: req.Name, + Headers: cleanHeaders(req.Headers), + Vision: req.Vision, + Thinking: req.Thinking, + ReasoningEffort: req.ReasoningEffort, + } + if isCustom && req.Model != "" { + pc.CustomModels = []config.CustomModelConfig{{ + ID: req.Model, + Name: req.Model, + ToolCall: true, + Reasoning: req.ModelReasoning, + }} + } + cfg.Providers[req.ID] = pc + + // If there is no active model yet and this is a custom provider with an + // explicit model, adopt it as the active model so the app can boot. + if cfg.Model == "" && isCustom && req.Model != "" { + cfg.Model = req.ID + "/" + req.Model } + if err := config.SaveConfig(cfg); err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to save config: " + err.Error()}) return @@ -3109,6 +3518,221 @@ func (s *Server) handleAddProvider(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } +// validReasoningEffort whitelists the thinking-depth values accepted from +// clients. The set mirrors the effort levels models.dev publishes under +// reasoning_options (see internal/model registry). Empty means "unset / omit +// the parameter". +func validReasoningEffort(v string) bool { + switch v { + case "", "none", "minimal", "low", "medium", "high", "xhigh", "max": + return true + } + return false +} + +// cleanHeaders drops rows with an empty key and trims whitespace from both key +// and value, so blank editor rows never reach the saved config and a pasted +// token with a stray trailing space does not silently break auth. +func cleanHeaders(in map[string]string) map[string]string { + if len(in) == 0 { + return nil + } + out := make(map[string]string, len(in)) + for k, v := range in { + k = strings.TrimSpace(k) + if k == "" { + continue + } + out[k] = strings.TrimSpace(v) + } + if len(out) == 0 { + return nil + } + return out +} + +// handleUpdateProvider edits an existing provider, merging secret fields so the +// client may omit unchanged credentials. An empty api_key keeps the stored key; +// a header value left empty keeps the stored value for that key (the list +// endpoint returns masked secrets, so the UI sends blanks for untouched ones). +func (s *Server) handleUpdateProvider(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + if id == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "provider id is required"}) + return + } + var req struct { + APIKey string `json:"api_key,omitempty"` + BaseURL string `json:"base_url,omitempty"` + Name string `json:"name,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + CustomModels *[]struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Reasoning bool `json:"reasoning,omitempty"` + Context int `json:"context,omitempty"` + Attachment bool `json:"attachment,omitempty"` + EffortTiers []string `json:"effort_tiers,omitempty"` + } `json:"custom_models,omitempty"` + Vision *bool `json:"vision,omitempty"` + Thinking *bool `json:"thinking,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` + } + if err := json.NewDecoder(io.LimitReader(r.Body, 1<<16)).Decode(&req); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) + return + } + if !validReasoningEffort(req.ReasoningEffort) { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid reasoning_effort"}) + return + } + + cfg, err := config.LoadConfig() + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + pc := cfg.GetProviders()[id] + if pc == nil { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "provider not found"}) + return + } + + // Mutate in place so fields not exposed by this endpoint (display name, + // custom models, deprecated lists) are preserved untouched. + prevHeaders := pc.Headers + // base_url uses keep-on-empty semantics (like api_key): the list endpoint + // masks secrets but returns base_url verbatim, yet a client that doesn't + // touch the endpoint may still submit an empty value. Overwriting + // unconditionally would wipe a stored custom endpoint, so only adopt a + // non-empty incoming value. + if req.BaseURL != "" { + pc.BaseURL = req.BaseURL + } + pc.Vision = req.Vision + pc.Thinking = req.Thinking + pc.ReasoningEffort = req.ReasoningEffort + if req.Name != "" { + pc.Name = req.Name + } + if req.APIKey != "" { + pc.APIKey = req.APIKey + } + // Merge headers: empty incoming value ⇒ keep the stored secret for that key. + pc.Headers = nil + if cleaned := cleanHeaders(req.Headers); len(cleaned) > 0 { + merged := make(map[string]string, len(cleaned)) + for k, v := range cleaned { + if v == "" { + if ov, ok := prevHeaders[k]; ok { + merged[k] = ov + continue + } + } + merged[k] = v + } + pc.Headers = merged + } + + // Replace the provider's custom models when the client sends the list (nil ⇒ + // keep existing). Each model's stored Context is preserved by merging on id, + // ToolCall stays true (matching the add path), and the model currently set as + // active cannot be dropped so a save can't strand the running app. + if req.CustomModels != nil { + prev := make(map[string]config.CustomModelConfig, len(pc.CustomModels)) + for _, m := range pc.CustomModels { + prev[m.ID] = m + } + next := make([]config.CustomModelConfig, 0, len(*req.CustomModels)) + seen := make(map[string]bool, len(*req.CustomModels)) + for _, m := range *req.CustomModels { + mid := strings.TrimSpace(m.ID) + if mid == "" || seen[mid] { + continue + } + seen[mid] = true + cm := config.CustomModelConfig{ID: mid, Name: strings.TrimSpace(m.Name), ToolCall: true, Reasoning: m.Reasoning} + // Adopt the incoming per-model capability fields when provided; + // otherwise carry over the previously stored values so an edit that + // only renames a model doesn't silently drop its context window, + // vision flag, or configured effort tiers. + if old, ok := prev[mid]; ok { + if cm.Context == 0 { + cm.Context = old.Context + } + if !cm.Attachment { + cm.Attachment = old.Attachment + } + if len(cm.EffortTiers) == 0 { + cm.EffortTiers = old.EffortTiers + } + } + if m.Context > 0 { + cm.Context = m.Context + } + if m.Attachment { + cm.Attachment = true + } + if len(m.EffortTiers) > 0 { + cm.EffortTiers = m.EffortTiers + } + next = append(next, cm) + } + // Reject custom model ids that collide with the provider's built-in + // (registry) models. A duplicate id would shadow or be shadowed by the + // registry entry, confusing the model picker and catalog. Custom ids + // may still be edited to their own value (handled by seen dedup above). + if s.registry != nil { + if regProv := s.registry.GetProvider(id); regProv != nil { + for _, cm := range next { + if _, ok := regProv.Models[cm.ID]; ok { + // Allow it only if it was already a custom model with this id + // (editing an existing custom entry in place). + if _, wasCustom := prev[cm.ID]; !wasCustom { + writeJSON(w, http.StatusBadRequest, map[string]string{ + "error": "model id '" + cm.ID + "' duplicates a built-in model; choose another id", + }) + return + } + } + } + } + } + if strings.HasPrefix(cfg.Model, id+"/") { + active := strings.TrimPrefix(cfg.Model, id+"/") + if _, wasThere := prev[active]; wasThere && !seen[active] { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "cannot remove the active model; switch to another model first"}) + return + } + } + isCustom := s.registry == nil || !s.registry.HasProvider(id) + if isCustom && len(next) == 0 { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "custom providers need at least one model"}) + return + } + pc.CustomModels = next + } + + if cfg.Providers == nil { + cfg.Providers = make(map[string]*config.ProviderConfig) + } + cfg.Providers[id] = pc + if err := config.SaveConfig(cfg); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to save config: " + err.Error()}) + return + } + + // Publish the updated config + registry to the live server so the chat model + // picker (/api/models) and catalog reflect added/edited/removed models + // without a restart — matching handleSetupComplete's publish step. + s.cfgMu.Lock() + s.cfg = cfg + s.registry = model.NewModelRegistryWithConfig(cfg) + s.cfgMu.Unlock() + + writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) +} + // handleDeleteProvider removes a provider from the config. func (s *Server) handleDeleteProvider(w http.ResponseWriter, r *http.Request) { providerID := r.PathValue("id") @@ -3182,10 +3806,11 @@ func (s *Server) handleGetModelState(w http.ResponseWriter, r *http.Request) { } writeJSON(w, http.StatusOK, map[string]any{ - "recent": recent, - "favorite": favorites, - "enabled_models": enabledModels, - "disabled_models": disabledModels, + "recent": recent, + "favorite": favorites, + "enabled_models": enabledModels, + "disabled_models": disabledModels, + "effort_overrides": state.EffortOverrides, }) } @@ -3250,6 +3875,44 @@ func (s *Server) handleToggleModelEnabled(w http.ResponseWriter, r *http.Request }) } +// handleSetModelEffort records the user's reasoning-effort choice for a single +// model (set from the chat model picker). An empty effort clears the override, +// restoring the provider-level default. The agent is rebuilt so the change +// takes effect on the next turn. +func (s *Server) handleSetModelEffort(w http.ResponseWriter, r *http.Request) { + var req struct { + Provider string `json:"provider"` + Model string `json:"model"` + Effort string `json:"effort"` + } + if err := json.NewDecoder(io.LimitReader(r.Body, 1<<16)).Decode(&req); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) + return + } + if req.Provider == "" || req.Model == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "provider and model are required"}) + return + } + if req.Effort != "" && !validReasoningEffort(req.Effort) { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid effort"}) + return + } + + state, err := config.LoadModelState() + if err != nil { + state = &config.ModelState{} + } + state.SetEffortOverride(config.ModelRef{Provider: req.Provider, Model: req.Model}, req.Effort) + if err := config.SaveModelState(state); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "failed to save"}) + return + } + + writeJSON(w, http.StatusOK, map[string]any{ + "effort": req.Effort, + }) +} + // --- Helpers --- func writeJSON(w http.ResponseWriter, status int, data any) { diff --git a/script/generate_models.go b/script/generate_models.go index 0bca1507..d4422c94 100644 --- a/script/generate_models.go +++ b/script/generate_models.go @@ -60,22 +60,31 @@ type RegistryProvider struct { // RegistryModel represents a model from models.dev API. type RegistryModel struct { - ID string `json:"id"` - Name string `json:"name"` - Family string `json:"family,omitempty"` - Attachment bool `json:"attachment,omitempty"` - Reasoning bool `json:"reasoning,omitempty"` - ToolCall bool `json:"tool_call,omitempty"` - StructuredOutput bool `json:"structured_output,omitempty"` - Temperature bool `json:"temperature,omitempty"` - Knowledge string `json:"knowledge,omitempty"` - ReleaseDate string `json:"release_date,omitempty"` - LastUpdated string `json:"last_updated,omitempty"` - Modalities *ModelModalities `json:"modalities,omitempty"` - OpenWeights bool `json:"open_weights,omitempty"` - Cost *ModelCost `json:"cost,omitempty"` - Limit *ModelLimit `json:"limit,omitempty"` - Status string `json:"status,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Family string `json:"family,omitempty"` + Attachment bool `json:"attachment,omitempty"` + Reasoning bool `json:"reasoning,omitempty"` + ToolCall bool `json:"tool_call,omitempty"` + StructuredOutput bool `json:"structured_output,omitempty"` + Temperature bool `json:"temperature,omitempty"` + Knowledge string `json:"knowledge,omitempty"` + ReleaseDate string `json:"release_date,omitempty"` + LastUpdated string `json:"last_updated,omitempty"` + Modalities *ModelModalities `json:"modalities,omitempty"` + OpenWeights bool `json:"open_weights,omitempty"` + Cost *ModelCost `json:"cost,omitempty"` + Limit *ModelLimit `json:"limit,omitempty"` + Status string `json:"status,omitempty"` + ReasoningOptions []ReasoningOption `json:"reasoning_options,omitempty"` +} + +// ReasoningOption mirrors models.dev's reasoning_options entries. +type ReasoningOption struct { + Type string `json:"type"` + Values []string `json:"values,omitempty"` + Min *int `json:"min,omitempty"` + Max *int `json:"max,omitempty"` } // ModelModalities describes input/output modalities. @@ -305,6 +314,10 @@ func run() error { b.WriteString("\t\t\t\t},\n") } + if opts := emitReasoningOptions(model.ReasoningOptions); opts != "" { + b.WriteString(opts) + } + b.WriteString("\t\t\t},\n") } @@ -325,6 +338,50 @@ func run() error { return nil } +// emitReasoningOptions renders a model's reasoning_options as a Go literal. It +// returns "" when there are no usable options. JSON nulls in effort value lists +// (which decode to "") are dropped, and Min/Max are emitted via the intPtr +// helper so nil bounds stay nil. +func emitReasoningOptions(opts []ReasoningOption) string { + if len(opts) == 0 { + return "" + } + var b strings.Builder + b.WriteString("\t\t\t\tReasoningOptions: []ReasoningOption{\n") + for _, ro := range opts { + if ro.Type == "" { + continue + } + b.WriteString("\t\t\t\t\t{") + fmt.Fprintf(&b, "Type: %q", ro.Type) + vals := make([]string, 0, len(ro.Values)) + for _, v := range ro.Values { + if v != "" { + vals = append(vals, v) + } + } + if len(vals) > 0 { + b.WriteString(", Values: []string{") + for i, v := range vals { + if i > 0 { + b.WriteString(", ") + } + fmt.Fprintf(&b, "%q", v) + } + b.WriteString("}") + } + if ro.Min != nil { + fmt.Fprintf(&b, ", Min: intPtr(%d)", *ro.Min) + } + if ro.Max != nil { + fmt.Fprintf(&b, ", Max: intPtr(%d)", *ro.Max) + } + b.WriteString("},\n") + } + b.WriteString("\t\t\t\t},\n") + return b.String() +} + func escapeString(s string) string { // Basic escaping for Go string literals s = strings.ReplaceAll(s, "\\", "\\\\") diff --git a/web/package.json b/web/package.json index 8595aab6..fb8b3f37 100644 --- a/web/package.json +++ b/web/package.json @@ -15,7 +15,6 @@ "format": "oxfmt src/" }, "dependencies": { - "@headlessui/tailwindcss": "^0.2.2", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.2.0", "@lobehub/icons-static-svg": "^1.91.0", @@ -25,7 +24,6 @@ "@tauri-apps/plugin-dialog": "^2.4.0", "@tauri-apps/plugin-notification": "^2.3.1", "@tauri-apps/plugin-opener": "^2.5.0", - "@types/dompurify": "^3.2.0", "@types/qrcode": "^1.5.6", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", @@ -37,8 +35,7 @@ "pinia": "^3.0.4", "qrcode": "^1.5.4", "vue": "^3.5.31", - "vue-i18n": "^11.4.6", - "vue-router": "^5.0.4" + "vue-i18n": "^11.4.6" }, "devDependencies": { "@tsconfig/node24": "^24.0.4", @@ -47,7 +44,6 @@ "@vitejs/plugin-vue-jsx": "^5.1.5", "@vue/eslint-config-typescript": "^14.7.0", "@vue/tsconfig": "^0.9.1", - "autoprefixer": "^10.4.27", "eslint": "^10.1.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-oxlint": "~1.57.0", @@ -56,11 +52,9 @@ "npm-run-all2": "^8.0.4", "oxfmt": "^0.42.0", "oxlint": "~1.57.0", - "postcss": "^8.5.10", "tailwindcss": "^4.2.2", "typescript": "~6.0.0", "vite": "^8.0.16", - "vite-plugin-vue-devtools": "^8.1.1", "vue-tsc": "^3.2.6" }, "engines": { diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index ac6063cc..5a112b05 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -12,9 +12,6 @@ importers: .: dependencies: - '@headlessui/tailwindcss': - specifier: ^0.2.2 - version: 0.2.2(tailwindcss@4.2.2) '@headlessui/vue': specifier: ^1.7.23 version: 1.7.23(vue@3.5.32(typescript@6.0.2)) @@ -42,9 +39,6 @@ importers: '@tauri-apps/plugin-opener': specifier: ^2.5.0 version: 2.5.4 - '@types/dompurify': - specifier: ^3.2.0 - version: 3.2.0 '@types/qrcode': specifier: ^1.5.6 version: 1.5.6 @@ -81,9 +75,6 @@ importers: vue-i18n: specifier: ^11.4.6 version: 11.4.6(vue@3.5.32(typescript@6.0.2)) - vue-router: - specifier: ^5.0.4 - version: 5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@6.0.2)(vue@3.5.32(typescript@6.0.2)))(vue@3.5.32(typescript@6.0.2)) devDependencies: '@tsconfig/node24': specifier: ^24.0.4 @@ -103,9 +94,6 @@ importers: '@vue/tsconfig': specifier: ^0.9.1 version: 0.9.1(typescript@6.0.2)(vue@3.5.32(typescript@6.0.2)) - autoprefixer: - specifier: ^10.4.27 - version: 10.4.27(postcss@8.5.10) eslint: specifier: ^10.1.0 version: 10.2.0(jiti@2.6.1) @@ -130,9 +118,6 @@ importers: oxlint: specifier: ~1.57.0 version: 1.57.0 - postcss: - specifier: ^8.5.10 - version: 8.5.10 tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -142,9 +127,6 @@ importers: vite: specifier: ^8.0.16 version: 8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - vite-plugin-vue-devtools: - specifier: ^8.1.1 - version: 8.1.1(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)) vue-tsc: specifier: ^3.2.6 version: 3.2.6(typescript@6.0.2) @@ -267,29 +249,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-proposal-decorators@7.29.0': - resolution: {integrity: sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.6 - - '@babel/plugin-syntax-decorators@7.28.6': - resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.6 - - '@babel/plugin-syntax-import-attributes@7.28.6': - resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.29.6 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.29.6 - '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} @@ -371,12 +330,6 @@ packages: resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@headlessui/tailwindcss@0.2.2': - resolution: {integrity: sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==} - engines: {node: '>=10'} - peerDependencies: - tailwindcss: ^3.0 || ^4.0 - '@headlessui/vue@1.7.23': resolution: {integrity: sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==} engines: {node: '>=10'} @@ -704,9 +657,6 @@ packages: cpu: [x64] os: [win32] - '@polka/url@1.0.0-next.29': - resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@rolldown/binding-android-arm64@1.1.2': resolution: {integrity: sha512-2cZ+7xRS+DBcuJBJKnfzsbleumJhBqSlJVpuzHC0nTqfd3QQ7Vx2/x5YR/D7cBamKSeWplwo82Fn9lqYUDEMfA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -936,10 +886,6 @@ packages: '@tybys/wasm-util@0.10.3': resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/dompurify@3.2.0': - resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==} - deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed. - '@types/esrecurse@4.3.1': resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} @@ -1040,29 +986,9 @@ packages: '@volar/typescript@2.4.28': resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} - '@vue-macros/common@3.1.2': - resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==} - engines: {node: '>=20.19.0'} - peerDependencies: - vue: ^2.7.0 || ^3.2.25 - peerDependenciesMeta: - vue: - optional: true - - '@vue/babel-helper-vue-transform-on@1.5.0': - resolution: {integrity: sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==} - '@vue/babel-helper-vue-transform-on@2.0.1': resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==} - '@vue/babel-plugin-jsx@1.5.0': - resolution: {integrity: sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==} - peerDependencies: - '@babel/core': ^7.29.6 - peerDependenciesMeta: - '@babel/core': - optional: true - '@vue/babel-plugin-jsx@2.0.1': resolution: {integrity: sha512-a8CaLQjD/s4PVdhrLD/zT574ZNPnZBOY+IhdtKWRB4HRZ0I2tXBi5ne7d9eCfaYwp5gU5+4KIyFTV1W1YL9xZA==} peerDependencies: @@ -1071,11 +997,6 @@ packages: '@babel/core': optional: true - '@vue/babel-plugin-resolve-type@1.5.0': - resolution: {integrity: sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==} - peerDependencies: - '@babel/core': ^7.29.6 - '@vue/babel-plugin-resolve-type@2.0.1': resolution: {integrity: sha512-ybwgIuRGRRBhOU37GImDoWQoz+TlSqap65qVI6iwg/J7FfLTLmMf97TS7xQH9I7Qtr/gp161kYVdhr1ZMraSYQ==} peerDependencies: @@ -1099,26 +1020,12 @@ packages: '@vue/devtools-api@7.7.9': resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} - '@vue/devtools-api@8.1.1': - resolution: {integrity: sha512-bsDMJ07b3GN1puVwJb/fyFnj/U2imyswK5UQVLZwVl7O05jDrt6BHxeG5XffmOOdasOj/bOmIjxJvGPxU7pcqw==} - - '@vue/devtools-core@8.1.1': - resolution: {integrity: sha512-bCCsSABp1/ot4j8xJEycM6Mtt2wbuucfByr6hMgjbYhrtlscOJypZKvy8f1FyWLYrLTchB5Qz216Lm92wfbq0A==} - peerDependencies: - vue: ^3.0.0 - '@vue/devtools-kit@7.7.9': resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} - '@vue/devtools-kit@8.1.1': - resolution: {integrity: sha512-gVBaBv++i+adg4JpH71k9ppl4soyR7Y2McEqO5YNgv0BI1kMZ7BDX5gnwkZ5COYgiCyhejZG+yGNrBAjj6Coqg==} - '@vue/devtools-shared@7.7.9': resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} - '@vue/devtools-shared@8.1.1': - resolution: {integrity: sha512-+h4ttmJYl/txpxHKaoZcaKpC+pvckgLzIDiSQlaQ7kKthKh8KuwoLW2D8hPJEnqKzXOvu15UHEoGyngAXCz0EQ==} - '@vue/eslint-config-typescript@14.7.0': resolution: {integrity: sha512-iegbMINVc+seZ/QxtzWiOBozctrHiF2WvGedruu2EbLujg9VuU0FQiNcN2z1ycuaoKKpF4m2qzB5HDEMKbxtIg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1198,25 +1105,6 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} - ansis@4.2.0: - resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} - engines: {node: '>=14'} - - ast-kit@2.2.0: - resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} - engines: {node: '>=20.19.0'} - - ast-walker-scope@0.8.3: - resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==} - engines: {node: '>=20.19.0'} - - autoprefixer@10.4.27: - resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -1245,10 +1133,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -1256,10 +1140,6 @@ packages: caniuse-lite@1.0.30001787: resolution: {integrity: sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==} - chokidar@5.0.0: - resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} - engines: {node: '>= 20.19.0'} - cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -1270,12 +1150,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1311,18 +1185,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} - - default-browser@5.5.0: - resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} - engines: {node: '>=18'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1347,9 +1209,6 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} - error-stack-parser-es@1.0.5: - resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1428,9 +1287,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1479,9 +1335,6 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1525,11 +1378,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1542,11 +1390,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1555,10 +1398,6 @@ packages: resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} engines: {node: '>=18'} - is-wsl@3.1.1: - resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} - engines: {node: '>=16'} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1602,9 +1441,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1683,10 +1519,6 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -1698,10 +1530,6 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - magic-string-ast@1.0.3: - resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} - engines: {node: '>=20.19.0'} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1734,13 +1562,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - - mrmime@2.0.1: - resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} - engines: {node: '>=10'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1770,13 +1591,6 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - open@10.2.0: - resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} - engines: {node: '>=18'} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -1827,15 +1641,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - perfect-debounce@2.1.0: - resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1861,12 +1669,6 @@ packages: typescript: optional: true - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -1879,9 +1681,6 @@ packages: resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} engines: {node: '>=4'} - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.10: resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} @@ -1903,9 +1702,6 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1913,10 +1709,6 @@ packages: resolution: {integrity: sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==} engines: {node: ^18.17.0 || >=20.5.0} - readdirp@5.0.0: - resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} - engines: {node: '>= 20.19.0'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1936,16 +1728,9 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - scule@1.3.0: - resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1970,10 +1755,6 @@ packages: resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} - sirv@3.0.2: - resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} - engines: {node: '>=18'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2001,10 +1782,6 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - tinyglobby@0.2.16: - resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.17: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} @@ -2017,10 +1794,6 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - ts-api-utils@2.5.0: resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} engines: {node: '>=18.12'} @@ -2046,20 +1819,9 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - unplugin-utils@0.3.1: - resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} - engines: {node: '>=20.19.0'} - - unplugin@3.0.0: - resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} - engines: {node: ^20.19.0 || >=22.12.0} - update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2072,37 +1834,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - vite-dev-rpc@1.1.0: - resolution: {integrity: sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==} - peerDependencies: - vite: ^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 - - vite-hot-client@2.1.0: - resolution: {integrity: sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==} - peerDependencies: - vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - - vite-plugin-inspect@11.3.3: - resolution: {integrity: sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==} - engines: {node: '>=14'} - peerDependencies: - '@nuxt/kit': '*' - vite: ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - '@nuxt/kit': - optional: true - - vite-plugin-vue-devtools@8.1.1: - resolution: {integrity: sha512-9qTpOmZ2vHpvlI9hdVXAQ1Ry4I8GcBArU7aPi0qfIaV7fQIXy0L1nb6X4mFY2Gw0dYshHuLbIl0Ulb572SCjsQ==} - engines: {node: '>=v14.21.3'} - peerDependencies: - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - - vite-plugin-vue-inspector@5.4.0: - resolution: {integrity: sha512-Iq/024CydcE46FZqWPU4t4lw4uYOdLnFSO1RNxJVt2qY9zxIjmnkBqhHnYaReWM82kmNnaXs7OkfgRrV2GEjyw==} - peerDependencies: - vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - vite@8.1.0: resolution: {integrity: sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2161,21 +1892,6 @@ packages: peerDependencies: vue: ^3.0.0 - vue-router@5.0.4: - resolution: {integrity: sha512-lCqDLCI2+fKVRl2OzXuzdSWmxXFLQRxQbmHugnRpTMyYiT+hNaycV0faqG5FBHDXoYrZ6MQcX87BvbY8mQ20Bg==} - peerDependencies: - '@pinia/colada': '>=0.21.2' - '@vue/compiler-sfc': ^3.5.17 - pinia: ^3.0.4 - vue: ^3.5.0 - peerDependenciesMeta: - '@pinia/colada': - optional: true - '@vue/compiler-sfc': - optional: true - pinia: - optional: true - vue-tsc@3.2.6: resolution: {integrity: sha512-gYW/kWI0XrwGzd0PKc7tVB/qpdeAkIZLNZb10/InizkQjHjnT8weZ/vBarZoj4kHKbUTZT/bAVgoOr8x4NsQ/Q==} hasBin: true @@ -2190,9 +1906,6 @@ packages: typescript: optional: true - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} @@ -2214,10 +1927,6 @@ packages: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} - xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} @@ -2401,30 +2110,6 @@ snapshots: dependencies: '@babel/types': 7.29.7 - '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.7)': dependencies: '@babel/core': 7.29.7 @@ -2538,10 +2223,6 @@ snapshots: '@eslint/core': 1.2.1 levn: 0.4.1 - '@headlessui/tailwindcss@0.2.2(tailwindcss@4.2.2)': - dependencies: - tailwindcss: 4.2.2 - '@headlessui/vue@1.7.23(vue@3.5.32(typescript@6.0.2))': dependencies: '@tanstack/vue-virtual': 3.13.23(vue@3.5.32(typescript@6.0.2)) @@ -2736,8 +2417,6 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.57.0': optional: true - '@polka/url@1.0.0-next.29': {} - '@rolldown/binding-android-arm64@1.1.2': optional: true @@ -2894,10 +2573,6 @@ snapshots: tslib: 2.8.1 optional: true - '@types/dompurify@3.2.0': - dependencies: - dompurify: 3.4.11 - '@types/esrecurse@4.3.1': {} '@types/estree@1.0.8': {} @@ -2984,7 +2659,7 @@ snapshots: debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 - tinyglobby: 0.2.16 + tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@6.0.2) typescript: 6.0.2 transitivePeerDependencies: @@ -3036,36 +2711,8 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue-macros/common@3.1.2(vue@3.5.32(typescript@6.0.2))': - dependencies: - '@vue/compiler-sfc': 3.5.32 - ast-kit: 2.2.0 - local-pkg: 1.1.2 - magic-string-ast: 1.0.3 - unplugin-utils: 0.3.1 - optionalDependencies: - vue: 3.5.32(typescript@6.0.2) - - '@vue/babel-helper-vue-transform-on@1.5.0': {} - '@vue/babel-helper-vue-transform-on@2.0.1': {} - '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.7)': - dependencies: - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.7) - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@vue/babel-helper-vue-transform-on': 1.5.0 - '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.7) - '@vue/shared': 3.5.32 - optionalDependencies: - '@babel/core': 7.29.7 - transitivePeerDependencies: - - supports-color - '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.7)': dependencies: '@babel/helper-module-imports': 7.28.6 @@ -3082,17 +2729,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.7)': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/parser': 7.29.2 - '@vue/compiler-sfc': 3.5.32 - transitivePeerDependencies: - - supports-color - '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.7)': dependencies: '@babel/code-frame': 7.29.0 @@ -3140,16 +2776,6 @@ snapshots: dependencies: '@vue/devtools-kit': 7.7.9 - '@vue/devtools-api@8.1.1': - dependencies: - '@vue/devtools-kit': 8.1.1 - - '@vue/devtools-core@8.1.1(vue@3.5.32(typescript@6.0.2))': - dependencies: - '@vue/devtools-kit': 8.1.1 - '@vue/devtools-shared': 8.1.1 - vue: 3.5.32(typescript@6.0.2) - '@vue/devtools-kit@7.7.9': dependencies: '@vue/devtools-shared': 7.7.9 @@ -3160,19 +2786,10 @@ snapshots: speakingurl: 14.0.1 superjson: 2.2.6 - '@vue/devtools-kit@8.1.1': - dependencies: - '@vue/devtools-shared': 8.1.1 - birpc: 2.9.0 - hookable: 5.5.3 - perfect-debounce: 2.1.0 - '@vue/devtools-shared@7.7.9': dependencies: rfdc: 1.4.1 - '@vue/devtools-shared@8.1.1': {} - '@vue/eslint-config-typescript@14.7.0(eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2))(eslint@10.2.0(jiti@2.6.1))(vue-eslint-parser@10.4.0(eslint@10.2.0(jiti@2.6.1))))(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2)': dependencies: '@typescript-eslint/utils': 8.58.1(eslint@10.2.0(jiti@2.6.1))(typescript@6.0.2) @@ -3254,27 +2871,6 @@ snapshots: ansi-styles@6.2.3: {} - ansis@4.2.0: {} - - ast-kit@2.2.0: - dependencies: - '@babel/parser': 7.29.2 - pathe: 2.0.3 - - ast-walker-scope@0.8.3: - dependencies: - '@babel/parser': 7.29.2 - ast-kit: 2.2.0 - - autoprefixer@10.4.27(postcss@8.5.10): - dependencies: - browserslist: 4.28.2 - caniuse-lite: 1.0.30001787 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.10 - postcss-value-parser: 4.2.0 - balanced-match@4.0.4: {} baseline-browser-mapping@2.10.18: {} @@ -3299,18 +2895,10 @@ snapshots: node-releases: 2.0.37 update-browserslist-db: 1.2.3(browserslist@4.28.2) - bundle-name@4.1.0: - dependencies: - run-applescript: 7.1.0 - camelcase@5.3.1: {} caniuse-lite@1.0.30001787: {} - chokidar@5.0.0: - dependencies: - readdirp: 5.0.0 - cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -3323,10 +2911,6 @@ snapshots: color-name@1.1.4: {} - confbox@0.1.8: {} - - confbox@0.2.4: {} - convert-source-map@2.0.0: {} copy-anything@4.0.5: @@ -3351,15 +2935,6 @@ snapshots: deep-is@0.1.4: {} - default-browser-id@5.0.1: {} - - default-browser@5.5.0: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 - - define-lazy-prop@3.0.0: {} - detect-libc@2.1.2: {} dijkstrajs@1.0.3: {} @@ -3379,8 +2954,6 @@ snapshots: entities@7.0.1: {} - error-stack-parser-es@1.0.5: {} - escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -3475,8 +3048,6 @@ snapshots: esutils@2.0.3: {} - exsolve@1.0.8: {} - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -3524,8 +3095,6 @@ snapshots: flatted@3.4.2: {} - fraction.js@5.3.4: {} - fsevents@2.3.3: optional: true @@ -3553,8 +3122,6 @@ snapshots: imurmurhash@0.1.4: {} - is-docker@3.0.0: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -3563,18 +3130,10 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - is-number@7.0.0: {} is-what@5.5.0: {} - is-wsl@3.1.1: - dependencies: - is-inside-container: 1.0.0 - isexe@2.0.0: {} isexe@3.1.5: {} @@ -3601,8 +3160,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kolorist@1.8.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -3657,12 +3214,6 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 - local-pkg@1.1.2: - dependencies: - mlly: 1.8.2 - pkg-types: 2.3.0 - quansync: 0.2.11 - locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -3675,10 +3226,6 @@ snapshots: dependencies: yallist: 3.1.1 - magic-string-ast@1.0.3: - dependencies: - magic-string: 0.30.21 - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3704,15 +3251,6 @@ snapshots: mitt@3.0.1: {} - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 - - mrmime@2.0.1: {} - ms@2.1.3: {} muggle-string@0.4.1: {} @@ -3740,15 +3278,6 @@ snapshots: dependencies: boolbase: 1.0.0 - ohash@2.0.11: {} - - open@10.2.0: - dependencies: - default-browser: 5.5.0 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - wsl-utils: 0.1.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -3828,12 +3357,8 @@ snapshots: path-key@3.1.1: {} - pathe@2.0.3: {} - perfect-debounce@1.0.0: {} - perfect-debounce@2.1.0: {} - picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -3849,18 +3374,6 @@ snapshots: optionalDependencies: typescript: 6.0.2 - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - - pkg-types@2.3.0: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - pngjs@5.0.0: {} postcss-selector-parser@6.0.10: @@ -3873,8 +3386,6 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-value-parser@4.2.0: {} - postcss@8.5.10: dependencies: nanoid: 3.3.12 @@ -3897,8 +3408,6 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - quansync@0.2.11: {} - queue-microtask@1.2.3: {} read-package-json-fast@4.0.0: @@ -3906,8 +3415,6 @@ snapshots: json-parse-even-better-errors: 4.0.0 npm-normalize-package-bin: 4.0.0 - readdirp@5.0.0: {} - require-directory@2.1.1: {} require-main-filename@2.0.0: {} @@ -3937,14 +3444,10 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.2 '@rolldown/binding-win32-x64-msvc': 1.1.2 - run-applescript@7.1.0: {} - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - scule@1.3.0: {} - semver@6.3.1: {} semver@7.7.4: {} @@ -3959,12 +3462,6 @@ snapshots: shell-quote@1.8.4: {} - sirv@3.0.2: - dependencies: - '@polka/url': 1.0.0-next.29 - mrmime: 2.0.1 - totalist: 3.0.1 - source-map-js@1.2.1: {} speakingurl@14.0.1: {} @@ -3987,11 +3484,6 @@ snapshots: tapable@2.3.2: {} - tinyglobby@0.2.16: - dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 - tinyglobby@0.2.17: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -4003,8 +3495,6 @@ snapshots: dependencies: is-number: 7.0.0 - totalist@3.0.1: {} - ts-api-utils@2.5.0(typescript@6.0.2): dependencies: typescript: 6.0.2 @@ -4029,21 +3519,8 @@ snapshots: typescript@6.0.2: {} - ufo@1.6.3: {} - undici-types@7.16.0: {} - unplugin-utils@0.3.1: - dependencies: - pathe: 2.0.3 - picomatch: 4.0.4 - - unplugin@3.0.0: - dependencies: - '@jridgewell/remapping': 2.3.5 - picomatch: 4.0.4 - webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -4056,60 +3533,6 @@ snapshots: util-deprecate@1.0.2: {} - vite-dev-rpc@1.1.0(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)): - dependencies: - birpc: 2.9.0 - vite: 8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - vite-hot-client: 2.1.0(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) - - vite-hot-client@2.1.0(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)): - dependencies: - vite: 8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - - vite-plugin-inspect@11.3.3(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)): - dependencies: - ansis: 4.2.0 - debug: 4.4.3 - error-stack-parser-es: 1.0.5 - ohash: 2.0.11 - open: 10.2.0 - perfect-debounce: 2.1.0 - sirv: 3.0.2 - unplugin-utils: 0.3.1 - vite: 8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - vite-dev-rpc: 1.1.0(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) - transitivePeerDependencies: - - supports-color - - vite-plugin-vue-devtools@8.1.1(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3))(vue@3.5.32(typescript@6.0.2)): - dependencies: - '@vue/devtools-core': 8.1.1(vue@3.5.32(typescript@6.0.2)) - '@vue/devtools-kit': 8.1.1 - '@vue/devtools-shared': 8.1.1 - sirv: 3.0.2 - vite: 8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - vite-plugin-inspect: 11.3.3(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) - vite-plugin-vue-inspector: 5.4.0(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) - transitivePeerDependencies: - - '@nuxt/kit' - - supports-color - - vue - - vite-plugin-vue-inspector@5.4.0(vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)): - dependencies: - '@babel/core': 7.29.7 - '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.7) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.7) - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.7) - '@vue/compiler-dom': 3.5.32 - kolorist: 1.8.0 - magic-string: 0.30.21 - vite: 8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - transitivePeerDependencies: - - supports-color - vite@8.1.0(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 @@ -4145,30 +3568,6 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.32(typescript@6.0.2) - vue-router@5.0.4(@vue/compiler-sfc@3.5.32)(pinia@3.0.4(typescript@6.0.2)(vue@3.5.32(typescript@6.0.2)))(vue@3.5.32(typescript@6.0.2)): - dependencies: - '@babel/generator': 7.29.1 - '@vue-macros/common': 3.1.2(vue@3.5.32(typescript@6.0.2)) - '@vue/devtools-api': 8.1.1 - ast-walker-scope: 0.8.3 - chokidar: 5.0.0 - json5: 2.2.3 - local-pkg: 1.1.2 - magic-string: 0.30.21 - mlly: 1.8.2 - muggle-string: 0.4.1 - pathe: 2.0.3 - picomatch: 4.0.4 - scule: 1.3.0 - tinyglobby: 0.2.16 - unplugin: 3.0.0 - unplugin-utils: 0.3.1 - vue: 3.5.32(typescript@6.0.2) - yaml: 2.8.3 - optionalDependencies: - '@vue/compiler-sfc': 3.5.32 - pinia: 3.0.4(typescript@6.0.2)(vue@3.5.32(typescript@6.0.2)) - vue-tsc@3.2.6(typescript@6.0.2): dependencies: '@volar/typescript': 2.4.28 @@ -4185,8 +3584,6 @@ snapshots: optionalDependencies: typescript: 6.0.2 - webpack-virtual-modules@0.6.2: {} - which-module@2.0.1: {} which@2.0.2: @@ -4205,17 +3602,14 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 - wsl-utils@0.1.0: - dependencies: - is-wsl: 3.1.1 - xml-name-validator@4.0.0: {} y18n@4.0.3: {} yallist@3.1.1: {} - yaml@2.8.3: {} + yaml@2.8.3: + optional: true yargs-parser@18.1.3: dependencies: diff --git a/web/src/components/ChatInput.vue b/web/src/components/ChatInput.vue index 4bf61657..b535d746 100644 --- a/web/src/components/ChatInput.vue +++ b/web/src/components/ChatInput.vue @@ -28,6 +28,7 @@ const { t } = useI18n() const input = ref('') const textarea = ref(null) const showModelPicker = ref(false) +const showEffortPicker = ref(false) const showModePicker = ref(false) const showAddMenu = ref(false) const showContextPopup = ref(false) @@ -129,10 +130,42 @@ function modelInfoFor(provider: string, model: string) { return p?.models.find((m) => m.id === model) } +// Custom (OpenAI-compatible) provider ids, so their icon falls back to the +// OpenAI mark instead of a monogram. +const customProviderIds = computed(() => new Set(store.providers.filter((p) => p.custom).map((p) => p.id))) +function isCustomProvider(id: string) { + return customProviderIds.value.has(id) +} + // The ModelInfo for the currently-active model — drives the pinned row subline // + capability dots. const currentModelInfo = computed(() => modelInfoFor(store.providerName, store.modelName)) +// The reasoning-effort levels the current model accepts, taken from its +// models.dev reasoning_options (type === 'effort'). Empty when the model has +// no effort control — in which case the effort control is hidden entirely. +const currentEffortOptions = computed(() => { + const info = currentModelInfo.value + if (!info?.reasoning_options) return [] + for (const o of info.reasoning_options) { + if (o.type === 'effort' && o.values?.length) return o.values + } + return [] +}) + +// Whether to show the per-model effort control: only when the active model +// advertises effort levels. A "" (off) option is always prepended so the user +// can clear the override. +const showEffortControl = computed(() => currentEffortOptions.value.length > 0) + +// The user's saved effort choice for the current model ('' = unset/default). +const currentEffort = computed(() => store.getEffortOverride(store.providerName, store.modelName)) + +async function pickEffort(effort: string) { + // Empty means "clear override" → send '' so the provider default is restored. + await store.setModelEffort(store.providerName, store.modelName, effort) +} + // Favorite recent models (recent keeps the recency order), filtered by the // search box and excluding the current model (it's pinned above). const favoriteModelRefs = computed(() => { @@ -290,6 +323,7 @@ async function send() { function selectModel(provider: string, model: string) { showModelPicker.value = false + showEffortPicker.value = false store.switchModel(provider, model) } @@ -360,6 +394,7 @@ function handleClickOutside(e: MouseEvent) { showAddMenu.value = false showSlashMenu.value = false showContextPopup.value = false + showEffortPicker.value = false if (showManageModels.value) { showManageModels.value = false modelFilter.value = '' @@ -629,20 +664,58 @@ watch(() => store.imageSupport, (supported) => { -
+
+ + + +
+ + +
+
store.imageSupport, (supported) => {
- + {{ getModelDisplayName(store.providerName, store.modelName) }} {{ modelSubline(store.providerName, currentModelInfo) }} @@ -684,7 +757,7 @@ watch(() => store.imageSupport, (supported) => { class="mm-row" @click="selectModel(r.provider, r.model)" > - + {{ getModelDisplayName(r.provider, r.model) }} {{ modelSubline(r.provider, modelInfoFor(r.provider, r.model)) }} @@ -712,7 +785,7 @@ watch(() => store.imageSupport, (supported) => { @keydown.enter.prevent="selectModel(p.id, m.id)" @keydown.space.prevent="selectModel(p.id, m.id)" > - + {{ m.name || m.id }} {{ modelSubline(p.id, m) }} @@ -1488,6 +1561,64 @@ watch(() => store.imageSupport, (supported) => { } .mm-trigger[aria-expanded='true'] .mm-trigger-chev { transform: rotate(180deg); } +/* Per-model reasoning-effort control — visually matches the model picker + trigger (transparent border, no background; muted bg only on hover). */ +.effort-trigger { + display: inline-flex; + align-items: center; + gap: 4px; + height: 28px; + padding: 0 8px; + border: 1px solid transparent; + border-radius: var(--radius-lg); + background: transparent; + color: var(--color-foreground); + font-size: 12px; + font-weight: 500; + cursor: pointer; + transition: background var(--duration-fast); +} +.effort-trigger:hover { background: var(--color-muted); } +/* When an effort is selected, tint only the text (no chip fill/border), so the + trigger stays visually consistent with the adjacent model picker. */ +.effort-trigger.on { color: var(--color-primary); } +.effort-chev { opacity: 0.55; transition: transform var(--duration-normal); } +.effort-trigger[aria-expanded='true'] .effort-chev { transform: rotate(180deg); } +.effort-panel { + position: absolute; + bottom: 100%; + right: 0; + margin-bottom: 4px; + z-index: var(--z-dropdown); + min-width: 140px; + padding: 4px; + border: 1px solid var(--color-border); + border-radius: var(--radius-md); + background: var(--color-surface); + box-shadow: var(--shadow-md); + display: flex; + flex-direction: column; + gap: 1px; +} +.effort-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + width: 100%; + padding: 6px 8px; + border: none; + border-radius: var(--radius-sm); + background: transparent; + color: var(--color-foreground); + font-size: 12px; + cursor: pointer; + transition: background var(--duration-fast); +} +.effort-row:hover { background: var(--color-muted); } +.effort-row.active { color: var(--color-primary); font-weight: 600; } +.effort-row .heroicon { color: var(--color-primary); } + .mm-panel { position: absolute; bottom: 100%; diff --git a/web/src/components/FileViewer.vue b/web/src/components/FileViewer.vue deleted file mode 100644 index 7dd844d2..00000000 --- a/web/src/components/FileViewer.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - - - diff --git a/web/src/components/ModelEditDialog.vue b/web/src/components/ModelEditDialog.vue new file mode 100644 index 00000000..17e67a83 --- /dev/null +++ b/web/src/components/ModelEditDialog.vue @@ -0,0 +1,384 @@ + + + + + diff --git a/web/src/components/ProviderEditDialog.vue b/web/src/components/ProviderEditDialog.vue new file mode 100644 index 00000000..37877529 --- /dev/null +++ b/web/src/components/ProviderEditDialog.vue @@ -0,0 +1,598 @@ + + + + + diff --git a/web/src/components/ProviderIcon.vue b/web/src/components/ProviderIcon.vue index ba9847bd..c981cc36 100644 --- a/web/src/components/ProviderIcon.vue +++ b/web/src/components/ProviderIcon.vue @@ -2,12 +2,14 @@ import { computed } from 'vue' import { iconForProvider } from '@/utils/providerIcons' -const props = withDefaults(defineProps<{ provider: string; size?: number }>(), { +const props = withDefaults(defineProps<{ provider: string; size?: number; custom?: boolean }>(), { size: 18, + custom: false, }) // Trusted, build-time bundled SVG strings (not user input), so v-html is safe. -const svg = computed(() => iconForProvider(props.provider)) +// Custom providers fall back to the OpenAI mark (they're OpenAI-compatible). +const svg = computed(() => iconForProvider(props.provider, props.custom)) // First alphanumeric character, shown when the provider has no brand icon. const initial = computed( diff --git a/web/src/components/SettingsDialog.vue b/web/src/components/SettingsDialog.vue index dca10d21..0d038b85 100644 --- a/web/src/components/SettingsDialog.vue +++ b/web/src/components/SettingsDialog.vue @@ -1,9 +1,9 @@