workspace: add rich console rendering
This commit is contained in:
parent
5d07e9b9d5
commit
db5c1d64d1
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
title: 'Add rich markdown tool rendering to workspace console'
|
title: 'Add rich markdown tool rendering to workspace console'
|
||||||
state: 'inprogress'
|
state: 'done'
|
||||||
created_at: '2026-07-10T22:31:21Z'
|
created_at: '2026-07-10T22:31:21Z'
|
||||||
updated_at: '2026-07-10T22:31:41Z'
|
updated_at: '2026-07-10T22:45:55Z'
|
||||||
assignee: null
|
assignee: null
|
||||||
queued_by: 'yoi ticket'
|
queued_by: 'yoi ticket'
|
||||||
queued_at: '2026-07-10T22:31:41Z'
|
queued_at: '2026-07-10T22:31:41Z'
|
||||||
|
|
|
||||||
|
|
@ -39,4 +39,35 @@ Ticket を `yoi ticket` が queued にしました。
|
||||||
State changed to `inprogress`.
|
State changed to `inprogress`.
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: implementation_report author: hare at: 2026-07-10T22:45:55Z -->
|
||||||
|
|
||||||
|
## Implementation report
|
||||||
|
|
||||||
|
Implemented rich Workspace Console rendering.
|
||||||
|
|
||||||
|
- Added safe Markdown-to-HTML rendering for console bodies; raw HTML is escaped.
|
||||||
|
- Added Shiki highlighting for fenced code blocks.
|
||||||
|
- Added TUI-inspired Console colors for message kinds, tool types, tool states, and diff rows.
|
||||||
|
- Added structured Edit diff projection using old_string/new_string and rendered add/remove/context rows.
|
||||||
|
- Kept Read aggregate behavior: consecutive Read calls are grouped and file content is not shown in the body.
|
||||||
|
- Added tests for Markdown escaping/Shiki rendering and Edit diff projection.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
- cd web/workspace && deno task check
|
||||||
|
- cd web/workspace && deno task test
|
||||||
|
- git diff --check
|
||||||
|
- nix build .#yoi --no-link
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- event: state_changed author: "yoi ticket" at: 2026-07-10T22:45:55Z from: inprogress to: done reason: cli_state field: state -->
|
||||||
|
|
||||||
|
## State changed
|
||||||
|
|
||||||
|
State changed to `done`.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"dev": "deno run -A npm:vite@7.2.7 dev",
|
"dev": "deno run -A npm:vite@7.2.7 dev",
|
||||||
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server -- serve --workspace . --db .yoi/workspace.db --listen 127.0.0.1:8787",
|
"dev:backend": "cd ../.. && cargo run -p yoi-workspace-server -- serve --workspace . --db .yoi/workspace.db --listen 127.0.0.1:8787",
|
||||||
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
|
"check": "deno run -A npm:@sveltejs/kit@2.49.4 sync && deno run -A npm:svelte-check@4.3.4 --tsconfig ./tsconfig.json",
|
||||||
"test": "deno test --allow-read=src src/lib/workspace-api/http.test.ts src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/worker-launch.test.ts src/lib/workspace-sidebar/repository-nav.test.ts",
|
"test": "deno test --allow-read=src --allow-env=VSCODE_TEXTMATE_DEBUG src/lib/workspace-api/http.test.ts src/lib/workspace-console/markdown.test.ts src/lib/workspace-console/model.test.ts src/lib/workspace-console/worker-console.ui.test.ts src/lib/workspace-settings/model.test.ts src/lib/workspace-sidebar/worker-launch.test.ts src/lib/workspace-sidebar/repository-nav.test.ts",
|
||||||
"build": "deno run -A npm:vite@7.2.7 build",
|
"build": "deno run -A npm:vite@7.2.7 build",
|
||||||
"preview": "deno run -A npm:vite@7.2.7 preview"
|
"preview": "deno run -A npm:vite@7.2.7 preview"
|
||||||
},
|
},
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
"cookie": "npm:cookie@0.6.0",
|
"cookie": "npm:cookie@0.6.0",
|
||||||
"devalue": "npm:devalue@5.6.4",
|
"devalue": "npm:devalue@5.6.4",
|
||||||
"set-cookie-parser": "npm:set-cookie-parser@2.7.2",
|
"set-cookie-parser": "npm:set-cookie-parser@2.7.2",
|
||||||
|
"shiki": "npm:shiki@3.13.0",
|
||||||
"svelte": "npm:svelte@5.45.6",
|
"svelte": "npm:svelte@5.45.6",
|
||||||
"svelte-check": "npm:svelte-check@4.3.4",
|
"svelte-check": "npm:svelte-check@4.3.4",
|
||||||
"typescript": "npm:typescript@5.9.3",
|
"typescript": "npm:typescript@5.9.3",
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
"npm:decodal-codemirror@0.1.2": "0.1.2",
|
"npm:decodal-codemirror@0.1.2": "0.1.2",
|
||||||
"npm:devalue@5.6.4": "5.6.4",
|
"npm:devalue@5.6.4": "5.6.4",
|
||||||
"npm:set-cookie-parser@2.7.2": "2.7.2",
|
"npm:set-cookie-parser@2.7.2": "2.7.2",
|
||||||
|
"npm:shiki@3.13.0": "3.13.0",
|
||||||
"npm:svelte-check@4.3.4": "4.3.4_svelte@5.45.6_typescript@5.9.3",
|
"npm:svelte-check@4.3.4": "4.3.4_svelte@5.45.6_typescript@5.9.3",
|
||||||
"npm:svelte@5.45.6": "5.45.6",
|
"npm:svelte@5.45.6": "5.45.6",
|
||||||
"npm:typescript@5.9.3": "5.9.3",
|
"npm:typescript@5.9.3": "5.9.3",
|
||||||
|
|
@ -346,6 +347,52 @@
|
||||||
"os": ["win32"],
|
"os": ["win32"],
|
||||||
"cpu": ["x64"]
|
"cpu": ["x64"]
|
||||||
},
|
},
|
||||||
|
"@shikijs/core@3.13.0": {
|
||||||
|
"integrity": "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/types",
|
||||||
|
"@shikijs/vscode-textmate",
|
||||||
|
"@types/hast",
|
||||||
|
"hast-util-to-html"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@shikijs/engine-javascript@3.13.0": {
|
||||||
|
"integrity": "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/types",
|
||||||
|
"@shikijs/vscode-textmate",
|
||||||
|
"oniguruma-to-es"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@shikijs/engine-oniguruma@3.13.0": {
|
||||||
|
"integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/types",
|
||||||
|
"@shikijs/vscode-textmate"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@shikijs/langs@3.13.0": {
|
||||||
|
"integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@shikijs/themes@3.13.0": {
|
||||||
|
"integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@shikijs/types@3.13.0": {
|
||||||
|
"integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/vscode-textmate",
|
||||||
|
"@types/hast"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@shikijs/vscode-textmate@10.0.2": {
|
||||||
|
"integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="
|
||||||
|
},
|
||||||
"@standard-schema/spec@1.1.0": {
|
"@standard-schema/spec@1.1.0": {
|
||||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="
|
||||||
},
|
},
|
||||||
|
|
@ -414,9 +461,27 @@
|
||||||
"@types/estree@1.0.8": {
|
"@types/estree@1.0.8": {
|
||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
|
||||||
},
|
},
|
||||||
|
"@types/hast@3.0.4": {
|
||||||
|
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@types/mdast@4.0.4": {
|
||||||
|
"integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@types/unist@3.0.3": {
|
||||||
|
"integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="
|
||||||
|
},
|
||||||
"@typescript-eslint/types@8.57.2": {
|
"@typescript-eslint/types@8.57.2": {
|
||||||
"integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="
|
"integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA=="
|
||||||
},
|
},
|
||||||
|
"@ungap/structured-clone@1.3.0": {
|
||||||
|
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="
|
||||||
|
},
|
||||||
"acorn@8.16.0": {
|
"acorn@8.16.0": {
|
||||||
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
|
||||||
"bin": true
|
"bin": true
|
||||||
|
|
@ -427,6 +492,15 @@
|
||||||
"axobject-query@4.1.0": {
|
"axobject-query@4.1.0": {
|
||||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
|
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
|
||||||
},
|
},
|
||||||
|
"ccount@2.0.1": {
|
||||||
|
"integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="
|
||||||
|
},
|
||||||
|
"character-entities-html4@2.1.0": {
|
||||||
|
"integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="
|
||||||
|
},
|
||||||
|
"character-entities-legacy@3.0.0": {
|
||||||
|
"integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="
|
||||||
|
},
|
||||||
"chokidar@4.0.3": {
|
"chokidar@4.0.3": {
|
||||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -436,6 +510,9 @@
|
||||||
"clsx@2.1.1": {
|
"clsx@2.1.1": {
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
|
||||||
},
|
},
|
||||||
|
"comma-separated-tokens@2.0.3": {
|
||||||
|
"integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="
|
||||||
|
},
|
||||||
"cookie@0.6.0": {
|
"cookie@0.6.0": {
|
||||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
|
||||||
},
|
},
|
||||||
|
|
@ -459,9 +536,18 @@
|
||||||
"deepmerge@4.3.1": {
|
"deepmerge@4.3.1": {
|
||||||
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
|
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="
|
||||||
},
|
},
|
||||||
|
"dequal@2.0.3": {
|
||||||
|
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="
|
||||||
|
},
|
||||||
"devalue@5.6.4": {
|
"devalue@5.6.4": {
|
||||||
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="
|
"integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="
|
||||||
},
|
},
|
||||||
|
"devlop@1.1.0": {
|
||||||
|
"integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
|
||||||
|
"dependencies": [
|
||||||
|
"dequal"
|
||||||
|
]
|
||||||
|
},
|
||||||
"esbuild@0.25.12": {
|
"esbuild@0.25.12": {
|
||||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||||
"optionalDependencies": [
|
"optionalDependencies": [
|
||||||
|
|
@ -519,6 +605,31 @@
|
||||||
"os": ["darwin"],
|
"os": ["darwin"],
|
||||||
"scripts": true
|
"scripts": true
|
||||||
},
|
},
|
||||||
|
"hast-util-to-html@9.0.5": {
|
||||||
|
"integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/hast",
|
||||||
|
"@types/unist",
|
||||||
|
"ccount",
|
||||||
|
"comma-separated-tokens",
|
||||||
|
"hast-util-whitespace",
|
||||||
|
"html-void-elements",
|
||||||
|
"mdast-util-to-hast",
|
||||||
|
"property-information",
|
||||||
|
"space-separated-tokens",
|
||||||
|
"stringify-entities",
|
||||||
|
"zwitch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hast-util-whitespace@3.0.0": {
|
||||||
|
"integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/hast"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"html-void-elements@3.0.0": {
|
||||||
|
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="
|
||||||
|
},
|
||||||
"is-reference@3.0.3": {
|
"is-reference@3.0.3": {
|
||||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -537,6 +648,44 @@
|
||||||
"@jridgewell/sourcemap-codec"
|
"@jridgewell/sourcemap-codec"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"mdast-util-to-hast@13.2.1": {
|
||||||
|
"integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/hast",
|
||||||
|
"@types/mdast",
|
||||||
|
"@ungap/structured-clone",
|
||||||
|
"devlop",
|
||||||
|
"micromark-util-sanitize-uri",
|
||||||
|
"trim-lines",
|
||||||
|
"unist-util-position",
|
||||||
|
"unist-util-visit",
|
||||||
|
"vfile"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"micromark-util-character@2.1.1": {
|
||||||
|
"integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"micromark-util-symbol",
|
||||||
|
"micromark-util-types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"micromark-util-encode@2.0.1": {
|
||||||
|
"integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="
|
||||||
|
},
|
||||||
|
"micromark-util-sanitize-uri@2.0.1": {
|
||||||
|
"integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"micromark-util-character",
|
||||||
|
"micromark-util-encode",
|
||||||
|
"micromark-util-symbol"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"micromark-util-symbol@2.0.1": {
|
||||||
|
"integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="
|
||||||
|
},
|
||||||
|
"micromark-util-types@2.0.2": {
|
||||||
|
"integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="
|
||||||
|
},
|
||||||
"mri@1.2.0": {
|
"mri@1.2.0": {
|
||||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
|
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="
|
||||||
},
|
},
|
||||||
|
|
@ -553,6 +702,17 @@
|
||||||
"obug@2.1.1": {
|
"obug@2.1.1": {
|
||||||
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="
|
"integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="
|
||||||
},
|
},
|
||||||
|
"oniguruma-parser@0.12.1": {
|
||||||
|
"integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="
|
||||||
|
},
|
||||||
|
"oniguruma-to-es@4.3.4": {
|
||||||
|
"integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==",
|
||||||
|
"dependencies": [
|
||||||
|
"oniguruma-parser",
|
||||||
|
"regex",
|
||||||
|
"regex-recursion"
|
||||||
|
]
|
||||||
|
},
|
||||||
"picocolors@1.1.1": {
|
"picocolors@1.1.1": {
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
|
||||||
},
|
},
|
||||||
|
|
@ -567,9 +727,27 @@
|
||||||
"source-map-js"
|
"source-map-js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"property-information@7.1.0": {
|
||||||
|
"integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="
|
||||||
|
},
|
||||||
"readdirp@4.1.2": {
|
"readdirp@4.1.2": {
|
||||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
|
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="
|
||||||
},
|
},
|
||||||
|
"regex-recursion@6.0.2": {
|
||||||
|
"integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==",
|
||||||
|
"dependencies": [
|
||||||
|
"regex-utilities"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"regex-utilities@2.3.0": {
|
||||||
|
"integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="
|
||||||
|
},
|
||||||
|
"regex@6.1.0": {
|
||||||
|
"integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==",
|
||||||
|
"dependencies": [
|
||||||
|
"regex-utilities"
|
||||||
|
]
|
||||||
|
},
|
||||||
"rollup@4.60.0": {
|
"rollup@4.60.0": {
|
||||||
"integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
|
"integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -614,6 +792,19 @@
|
||||||
"set-cookie-parser@2.7.2": {
|
"set-cookie-parser@2.7.2": {
|
||||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="
|
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="
|
||||||
},
|
},
|
||||||
|
"shiki@3.13.0": {
|
||||||
|
"integrity": "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@shikijs/core",
|
||||||
|
"@shikijs/engine-javascript",
|
||||||
|
"@shikijs/engine-oniguruma",
|
||||||
|
"@shikijs/langs",
|
||||||
|
"@shikijs/themes",
|
||||||
|
"@shikijs/types",
|
||||||
|
"@shikijs/vscode-textmate",
|
||||||
|
"@types/hast"
|
||||||
|
]
|
||||||
|
},
|
||||||
"sirv@3.0.2": {
|
"sirv@3.0.2": {
|
||||||
"integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
|
"integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -625,6 +816,16 @@
|
||||||
"source-map-js@1.2.1": {
|
"source-map-js@1.2.1": {
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
|
||||||
},
|
},
|
||||||
|
"space-separated-tokens@2.0.2": {
|
||||||
|
"integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="
|
||||||
|
},
|
||||||
|
"stringify-entities@4.0.4": {
|
||||||
|
"integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
|
||||||
|
"dependencies": [
|
||||||
|
"character-entities-html4",
|
||||||
|
"character-entities-legacy"
|
||||||
|
]
|
||||||
|
},
|
||||||
"style-mod@4.1.3": {
|
"style-mod@4.1.3": {
|
||||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
|
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="
|
||||||
},
|
},
|
||||||
|
|
@ -671,10 +872,60 @@
|
||||||
"totalist@3.0.1": {
|
"totalist@3.0.1": {
|
||||||
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="
|
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="
|
||||||
},
|
},
|
||||||
|
"trim-lines@3.0.1": {
|
||||||
|
"integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="
|
||||||
|
},
|
||||||
"typescript@5.9.3": {
|
"typescript@5.9.3": {
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"bin": true
|
"bin": true
|
||||||
},
|
},
|
||||||
|
"unist-util-is@6.0.1": {
|
||||||
|
"integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unist-util-position@5.0.0": {
|
||||||
|
"integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unist-util-stringify-position@4.0.0": {
|
||||||
|
"integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unist-util-visit-parents@6.0.2": {
|
||||||
|
"integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist",
|
||||||
|
"unist-util-is"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"unist-util-visit@5.1.0": {
|
||||||
|
"integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist",
|
||||||
|
"unist-util-is",
|
||||||
|
"unist-util-visit-parents"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vfile-message@4.0.3": {
|
||||||
|
"integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist",
|
||||||
|
"unist-util-stringify-position"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"vfile@6.0.3": {
|
||||||
|
"integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
|
||||||
|
"dependencies": [
|
||||||
|
"@types/unist",
|
||||||
|
"vfile-message"
|
||||||
|
]
|
||||||
|
},
|
||||||
"vite@7.2.7": {
|
"vite@7.2.7": {
|
||||||
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
|
"integrity": "sha512-ITcnkFeR3+fI8P1wMgItjGrR10170d8auB4EpMLPqmx6uxElH3a/hHGQabSHKdqd4FXWO1nFIp9rRn7JQ34ACQ==",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
|
|
@ -704,6 +955,9 @@
|
||||||
},
|
},
|
||||||
"zimmerframe@1.1.4": {
|
"zimmerframe@1.1.4": {
|
||||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="
|
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="
|
||||||
|
},
|
||||||
|
"zwitch@2.0.4": {
|
||||||
|
"integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
|
|
@ -718,6 +972,7 @@
|
||||||
"npm:decodal-codemirror@0.1.2",
|
"npm:decodal-codemirror@0.1.2",
|
||||||
"npm:devalue@5.6.4",
|
"npm:devalue@5.6.4",
|
||||||
"npm:set-cookie-parser@2.7.2",
|
"npm:set-cookie-parser@2.7.2",
|
||||||
|
"npm:shiki@3.13.0",
|
||||||
"npm:svelte-check@4.3.4",
|
"npm:svelte-check@4.3.4",
|
||||||
"npm:svelte@5.45.6",
|
"npm:svelte@5.45.6",
|
||||||
"npm:typescript@5.9.3",
|
"npm:typescript@5.9.3",
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,14 @@
|
||||||
--success: oklch(48% 0.11 145);
|
--success: oklch(48% 0.11 145);
|
||||||
--warning: oklch(62% 0.12 85);
|
--warning: oklch(62% 0.12 85);
|
||||||
--danger: oklch(54% 0.14 25);
|
--danger: oklch(54% 0.14 25);
|
||||||
|
--tui-green: #76946a;
|
||||||
|
--tui-red: #c34043;
|
||||||
|
--tui-yellow: #c0a36e;
|
||||||
|
--tui-blue: #7e9cd8;
|
||||||
|
--tui-cyan: #6a9589;
|
||||||
|
--tui-magenta: #957fb8;
|
||||||
|
--tui-gray: #727169;
|
||||||
|
--tui-dark-gray: #54546d;
|
||||||
|
|
||||||
--space-1: 4px;
|
--space-1: 4px;
|
||||||
--space-2: 8px;
|
--space-2: 8px;
|
||||||
|
|
@ -1033,13 +1041,157 @@
|
||||||
.console-log li {
|
.console-log li {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--space-1);
|
gap: var(--space-1);
|
||||||
padding: 0.2rem 0;
|
padding: 0.45rem 0 0.45rem 0.75rem;
|
||||||
|
border-left: 3px solid var(--line);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.assistant {
|
||||||
|
border-left-color: var(--tui-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.user {
|
||||||
|
border-left-color: var(--tui-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.thinking {
|
||||||
|
border-left-color: var(--tui-magenta);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool {
|
||||||
|
border-left-color: var(--tui-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool-read {
|
||||||
|
border-left-color: var(--tui-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool-write,
|
||||||
|
.console-log li.console-line.tool-edit {
|
||||||
|
border-left-color: var(--tui-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool-bash {
|
||||||
|
border-left-color: var(--tui-magenta);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool-state-running,
|
||||||
|
.console-log li.console-line.tool-state-streaming_args,
|
||||||
|
.console-log li.console-line.tool-state-pending {
|
||||||
|
border-left-color: var(--tui-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool-state-done {
|
||||||
|
border-left-color: var(--tui-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-log li.console-line.tool-state-error,
|
||||||
|
.console-log li.console-line.error-line {
|
||||||
|
border-left-color: var(--tui-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown {
|
||||||
|
color: inherit;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown > :first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown > :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown p,
|
||||||
|
.rich-markdown ul,
|
||||||
|
.rich-markdown blockquote,
|
||||||
|
.rich-markdown pre {
|
||||||
|
margin: 0.45rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown ul {
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown h1,
|
||||||
|
.rich-markdown h2,
|
||||||
|
.rich-markdown h3,
|
||||||
|
.rich-markdown h4 {
|
||||||
|
margin: 0.7rem 0 0.35rem;
|
||||||
|
color: var(--text-strong);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown blockquote {
|
||||||
|
border-left: 2px solid var(--tui-dark-gray);
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding-left: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown :not(pre) > code {
|
||||||
|
background: color-mix(in oklch, var(--bg-raised) 80%, var(--tui-blue));
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
color: var(--tui-blue);
|
||||||
|
padding: 0.05rem 0.28rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown a {
|
||||||
|
color: var(--tui-cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rich-markdown .shiki {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.console-diff {
|
||||||
|
background: color-mix(in oklch, var(--bg-raised) 85%, black);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
margin: 0.6rem 0 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.45rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-line {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 3.2rem 3.2rem 1.4rem minmax(0, 1fr);
|
||||||
|
min-width: max-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-line.add {
|
||||||
|
background: color-mix(in oklch, var(--tui-green) 18%, transparent);
|
||||||
|
color: color-mix(in oklch, var(--tui-green) 75%, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-line.remove {
|
||||||
|
background: color-mix(in oklch, var(--tui-red) 18%, transparent);
|
||||||
|
color: color-mix(in oklch, var(--tui-red) 72%, white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-line.context {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-gutter,
|
||||||
|
.diff-marker {
|
||||||
|
color: var(--tui-gray);
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
text-align: right;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diff-content {
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
.console-log li.error-line {
|
.console-log li.error-line {
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
|
|
|
||||||
39
web/workspace/src/lib/workspace-console/RichMarkdown.svelte
Normal file
39
web/workspace/src/lib/workspace-console/RichMarkdown.svelte
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { markdownToHtml } from "$lib/workspace-console/markdown";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
text: string;
|
||||||
|
class?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { text, class: className = "" }: Props = $props();
|
||||||
|
let html = $state("");
|
||||||
|
let rendering = $state(false);
|
||||||
|
|
||||||
|
async function render(value: string): Promise<void> {
|
||||||
|
const current = value;
|
||||||
|
rendering = true;
|
||||||
|
try {
|
||||||
|
const next = await markdownToHtml(current);
|
||||||
|
if (text === current) {
|
||||||
|
html = next;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (text === current) {
|
||||||
|
rendering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
void render(text);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class={`rich-markdown ${className}`} class:is-rendering={rendering}>
|
||||||
|
{#if html}
|
||||||
|
{@html html}
|
||||||
|
{:else}
|
||||||
|
<p>{text}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
36
web/workspace/src/lib/workspace-console/markdown.test.ts
Normal file
36
web/workspace/src/lib/workspace-console/markdown.test.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { markdownToHtml } from "./markdown.ts";
|
||||||
|
|
||||||
|
declare const Deno: {
|
||||||
|
test(name: string, fn: () => void | Promise<void>): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function assert(condition: unknown, message: string): asserts condition {
|
||||||
|
if (!condition) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test("markdownToHtml escapes raw html and renders basic markdown", async () => {
|
||||||
|
const html = await markdownToHtml(
|
||||||
|
"# Title\n\nhello **world** `<x>`\n\n<script>bad()</script>",
|
||||||
|
);
|
||||||
|
assert(html.includes("<h1>Title</h1>"), "heading should render");
|
||||||
|
assert(html.includes("<strong>world</strong>"), "strong should render");
|
||||||
|
assert(
|
||||||
|
html.includes("<script>bad()</script>"),
|
||||||
|
"raw html should be escaped",
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
!html.includes("<script>bad()</script>"),
|
||||||
|
"raw html must not pass through",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("markdownToHtml renders fenced code through shiki", async () => {
|
||||||
|
const html = await markdownToHtml("```ts\nconst answer: number = 42;\n```");
|
||||||
|
assert(
|
||||||
|
html.includes("shiki"),
|
||||||
|
"highlighted code should include shiki markup",
|
||||||
|
);
|
||||||
|
assert(html.includes("answer"), "code content should be present");
|
||||||
|
});
|
||||||
225
web/workspace/src/lib/workspace-console/markdown.ts
Normal file
225
web/workspace/src/lib/workspace-console/markdown.ts
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
import { codeToHtml } from "shiki";
|
||||||
|
|
||||||
|
const SHIKI_THEME = "kanagawa-wave";
|
||||||
|
|
||||||
|
export async function markdownToHtml(markdown: string): Promise<string> {
|
||||||
|
if (!markdown.trim()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const blocks = splitFencedCode(markdown);
|
||||||
|
const html: string[] = [];
|
||||||
|
let textBuffer: string[] = [];
|
||||||
|
|
||||||
|
async function flushText(): Promise<void> {
|
||||||
|
if (textBuffer.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
html.push(renderMarkdownText(textBuffer.join("\n")));
|
||||||
|
textBuffer = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const block of blocks) {
|
||||||
|
if (block.kind === "text") {
|
||||||
|
textBuffer.push(block.text);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await flushText();
|
||||||
|
html.push(await highlightCode(block.code, block.lang));
|
||||||
|
}
|
||||||
|
await flushText();
|
||||||
|
return html.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarkdownBlock =
|
||||||
|
| { kind: "text"; text: string }
|
||||||
|
| { kind: "code"; lang: string; code: string };
|
||||||
|
|
||||||
|
function splitFencedCode(markdown: string): MarkdownBlock[] {
|
||||||
|
const lines = markdown.split(/\r?\n/);
|
||||||
|
const blocks: MarkdownBlock[] = [];
|
||||||
|
let text: string[] = [];
|
||||||
|
let code: string[] | null = null;
|
||||||
|
let lang = "text";
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const fence = line.match(/^```\s*([^`]*)\s*$/);
|
||||||
|
if (fence) {
|
||||||
|
if (code) {
|
||||||
|
blocks.push({ kind: "text", text: text.join("\n") });
|
||||||
|
text = [];
|
||||||
|
blocks.push({ kind: "code", lang, code: code.join("\n") });
|
||||||
|
code = null;
|
||||||
|
lang = "text";
|
||||||
|
} else {
|
||||||
|
if (text.length > 0) {
|
||||||
|
blocks.push({ kind: "text", text: text.join("\n") });
|
||||||
|
text = [];
|
||||||
|
}
|
||||||
|
lang = normalizeLanguage(fence[1]);
|
||||||
|
code = [];
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (code) {
|
||||||
|
code.push(line);
|
||||||
|
} else {
|
||||||
|
text.push(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
text.push("```" + (lang === "text" ? "" : lang));
|
||||||
|
text.push(...code);
|
||||||
|
}
|
||||||
|
if (text.length > 0) {
|
||||||
|
blocks.push({ kind: "text", text: text.join("\n") });
|
||||||
|
}
|
||||||
|
|
||||||
|
return blocks.filter((block) =>
|
||||||
|
block.kind === "code" || block.text.trim().length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMarkdownText(markdown: string): string {
|
||||||
|
const lines = markdown.split(/\r?\n/);
|
||||||
|
const html: string[] = [];
|
||||||
|
let paragraph: string[] = [];
|
||||||
|
let list: string[] = [];
|
||||||
|
let blockquote: string[] = [];
|
||||||
|
|
||||||
|
function flushParagraph(): void {
|
||||||
|
if (paragraph.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
html.push(`<p>${paragraph.map(renderInline).join("<br>")}</p>`);
|
||||||
|
paragraph = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushList(): void {
|
||||||
|
if (list.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
html.push(
|
||||||
|
`<ul>${
|
||||||
|
list.map((item) => `<li>${renderInline(item)}</li>`).join("")
|
||||||
|
}</ul>`,
|
||||||
|
);
|
||||||
|
list = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushBlockquote(): void {
|
||||||
|
if (blockquote.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
html.push(
|
||||||
|
`<blockquote>${blockquote.map(renderInline).join("<br>")}</blockquote>`,
|
||||||
|
);
|
||||||
|
blockquote = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushAll(): void {
|
||||||
|
flushParagraph();
|
||||||
|
flushList();
|
||||||
|
flushBlockquote();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const rawLine of lines) {
|
||||||
|
const line = rawLine.trimEnd();
|
||||||
|
if (!line.trim()) {
|
||||||
|
flushAll();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const heading = line.match(/^(#{1,4})\s+(.+)$/);
|
||||||
|
if (heading) {
|
||||||
|
flushAll();
|
||||||
|
const level = heading[1].length;
|
||||||
|
html.push(`<h${level}>${renderInline(heading[2])}</h${level}>`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const listItem = line.match(/^\s*[-*]\s+(.+)$/);
|
||||||
|
if (listItem) {
|
||||||
|
flushParagraph();
|
||||||
|
flushBlockquote();
|
||||||
|
list.push(listItem[1]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quote = line.match(/^>\s?(.*)$/);
|
||||||
|
if (quote) {
|
||||||
|
flushParagraph();
|
||||||
|
flushList();
|
||||||
|
blockquote.push(quote[1]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
flushList();
|
||||||
|
flushBlockquote();
|
||||||
|
paragraph.push(line);
|
||||||
|
}
|
||||||
|
flushAll();
|
||||||
|
return html.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInline(value: string): string {
|
||||||
|
let escaped = escapeHtml(value);
|
||||||
|
escaped = escaped.replace(/`([^`]+)`/g, "<code>$1</code>");
|
||||||
|
escaped = escaped.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
||||||
|
escaped = escaped.replace(/\b(https?:\/\/[^\s<]+[^\s<.,;:)])/g, (url) => {
|
||||||
|
const safe = escapeAttribute(url);
|
||||||
|
return `<a href="${safe}" rel="noreferrer" target="_blank">${url}</a>`;
|
||||||
|
});
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function highlightCode(code: string, lang: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
return await codeToHtml(code, {
|
||||||
|
lang: lang || "text",
|
||||||
|
theme: SHIKI_THEME,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
return `<pre class="shiki fallback"><code>${escapeHtml(code)}</code></pre>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLanguage(value: string | undefined): string {
|
||||||
|
const raw = value?.trim().split(/\s+/, 1)[0] ?? "";
|
||||||
|
if (!raw) {
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
switch (raw) {
|
||||||
|
case "sh":
|
||||||
|
case "shell":
|
||||||
|
case "zsh":
|
||||||
|
return "bash";
|
||||||
|
case "ts":
|
||||||
|
return "typescript";
|
||||||
|
case "js":
|
||||||
|
return "javascript";
|
||||||
|
default:
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(value: string): string {
|
||||||
|
return value.replace(/[&<>"]/g, (char) => {
|
||||||
|
switch (char) {
|
||||||
|
case "&":
|
||||||
|
return "&";
|
||||||
|
case "<":
|
||||||
|
return "<";
|
||||||
|
case ">":
|
||||||
|
return ">";
|
||||||
|
case '"':
|
||||||
|
return """;
|
||||||
|
default:
|
||||||
|
return char;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeAttribute(value: string): string {
|
||||||
|
return escapeHtml(value).replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
@ -304,6 +304,49 @@ Deno.test("projectConsole aggregates Read calls without showing file content", (
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("projectConsole renders Edit calls with structured diff lines", () => {
|
||||||
|
const projection = projectConsole([
|
||||||
|
{
|
||||||
|
cursor: "60",
|
||||||
|
event: {
|
||||||
|
event: "tool_call_done",
|
||||||
|
data: {
|
||||||
|
id: "edit-1",
|
||||||
|
name: "Edit",
|
||||||
|
arguments: JSON.stringify({
|
||||||
|
file_path: "/tmp/a.md",
|
||||||
|
old_string: "one\ntwo\nthree",
|
||||||
|
new_string: "one\nTWO\nthree\nfour",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} satisfies Event,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cursor: "61",
|
||||||
|
event: {
|
||||||
|
event: "tool_result",
|
||||||
|
data: {
|
||||||
|
id: "edit-1",
|
||||||
|
summary: "edited",
|
||||||
|
output: "ok",
|
||||||
|
is_error: false,
|
||||||
|
},
|
||||||
|
} satisfies Event,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [line] = projection.lines.filter((line) => line.kind === "tool");
|
||||||
|
assertEquals(line.title, "Call · Edit");
|
||||||
|
assert(line.body.includes("diff: -1 +2"), "diff summary should be shown");
|
||||||
|
assertEquals(line.diff?.map((row) => row.kind), [
|
||||||
|
"context",
|
||||||
|
"remove",
|
||||||
|
"add",
|
||||||
|
"context",
|
||||||
|
"add",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
Deno.test("projectConsole preserves in-progress assistant protocol stream", () => {
|
Deno.test("projectConsole preserves in-progress assistant protocol stream", () => {
|
||||||
const projection = projectConsole([
|
const projection = projectConsole([
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,20 @@ type ToolCallView = {
|
||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ConsoleDiffLine = {
|
||||||
|
kind: "context" | "add" | "remove";
|
||||||
|
oldNumber?: number;
|
||||||
|
newNumber?: number;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ConsoleLine = {
|
export type ConsoleLine = {
|
||||||
id: string;
|
id: string;
|
||||||
kind: ConsoleLineKind;
|
kind: ConsoleLineKind;
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
detail?: string;
|
detail?: string;
|
||||||
|
diff?: ConsoleDiffLine[];
|
||||||
cursor?: string | null;
|
cursor?: string | null;
|
||||||
source: "event";
|
source: "event";
|
||||||
streaming?: boolean;
|
streaming?: boolean;
|
||||||
|
|
@ -455,6 +463,7 @@ function refreshToolLine(item: ConsoleLine): void {
|
||||||
: `Call · ${toolCall.name}`;
|
: `Call · ${toolCall.name}`;
|
||||||
item.body = renderToolCall(toolCall);
|
item.body = renderToolCall(toolCall);
|
||||||
item.detail = toolCallDetail(toolCall);
|
item.detail = toolCallDetail(toolCall);
|
||||||
|
item.diff = toolCall.name === "Edit" ? editDiff(toolCall) : undefined;
|
||||||
item.streaming = !["done", "error"].includes(toolCall.state);
|
item.streaming = !["done", "error"].includes(toolCall.state);
|
||||||
item.error = toolCall.state === "error";
|
item.error = toolCall.state === "error";
|
||||||
}
|
}
|
||||||
|
|
@ -563,21 +572,95 @@ function renderWriteTool(toolCall: ToolCallView): string {
|
||||||
function renderEditTool(toolCall: ToolCallView): string {
|
function renderEditTool(toolCall: ToolCallView): string {
|
||||||
const args = parsedArgs(toolCall);
|
const args = parsedArgs(toolCall);
|
||||||
const path = stringField(args, "file_path") ?? "?";
|
const path = stringField(args, "file_path") ?? "?";
|
||||||
const oldString = stringField(args, "old_string");
|
const diff = editDiff(toolCall) ?? [];
|
||||||
const newString = stringField(args, "new_string");
|
const removes = diff.filter((line) => line.kind === "remove").length;
|
||||||
const change = oldString || newString
|
const adds = diff.filter((line) => line.kind === "add").length;
|
||||||
? compactLines([
|
|
||||||
oldString ? `- ${firstLine(oldString)}` : undefined,
|
|
||||||
newString ? `+ ${firstLine(newString)}` : undefined,
|
|
||||||
])
|
|
||||||
: undefined;
|
|
||||||
return compactLines([
|
return compactLines([
|
||||||
`Edit — ${path} (${stateSuffix(toolCall.state)})`,
|
`Edit — ${path} (${stateSuffix(toolCall.state)})`,
|
||||||
change,
|
diff.length > 0 ? `diff: -${removes} +${adds}` : undefined,
|
||||||
resultText(toolCall),
|
resultText(toolCall),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editDiff(toolCall: ToolCallView): ConsoleDiffLine[] | undefined {
|
||||||
|
const args = parsedArgs(toolCall);
|
||||||
|
const oldString = stringField(args, "old_string");
|
||||||
|
const newString = stringField(args, "new_string");
|
||||||
|
if (oldString === undefined && newString === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return diffLines(oldString ?? "", newString ?? "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function diffLines(oldText: string, newText: string): ConsoleDiffLine[] {
|
||||||
|
const oldLines = oldText.split(/\r?\n/);
|
||||||
|
const newLines = newText.split(/\r?\n/);
|
||||||
|
const table = lcsTable(oldLines, newLines);
|
||||||
|
const rows: ConsoleDiffLine[] = [];
|
||||||
|
let oldIndex = 0;
|
||||||
|
let newIndex = 0;
|
||||||
|
while (oldIndex < oldLines.length && newIndex < newLines.length) {
|
||||||
|
if (oldLines[oldIndex] === newLines[newIndex]) {
|
||||||
|
rows.push({
|
||||||
|
kind: "context",
|
||||||
|
oldNumber: oldIndex + 1,
|
||||||
|
newNumber: newIndex + 1,
|
||||||
|
content: oldLines[oldIndex],
|
||||||
|
});
|
||||||
|
oldIndex += 1;
|
||||||
|
newIndex += 1;
|
||||||
|
} else if (
|
||||||
|
table[oldIndex + 1]?.[newIndex] >= table[oldIndex]?.[newIndex + 1]
|
||||||
|
) {
|
||||||
|
rows.push({
|
||||||
|
kind: "remove",
|
||||||
|
oldNumber: oldIndex + 1,
|
||||||
|
content: oldLines[oldIndex],
|
||||||
|
});
|
||||||
|
oldIndex += 1;
|
||||||
|
} else {
|
||||||
|
rows.push({
|
||||||
|
kind: "add",
|
||||||
|
newNumber: newIndex + 1,
|
||||||
|
content: newLines[newIndex],
|
||||||
|
});
|
||||||
|
newIndex += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (oldIndex < oldLines.length) {
|
||||||
|
rows.push({
|
||||||
|
kind: "remove",
|
||||||
|
oldNumber: oldIndex + 1,
|
||||||
|
content: oldLines[oldIndex],
|
||||||
|
});
|
||||||
|
oldIndex += 1;
|
||||||
|
}
|
||||||
|
while (newIndex < newLines.length) {
|
||||||
|
rows.push({
|
||||||
|
kind: "add",
|
||||||
|
newNumber: newIndex + 1,
|
||||||
|
content: newLines[newIndex],
|
||||||
|
});
|
||||||
|
newIndex += 1;
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lcsTable(oldLines: string[], newLines: string[]): number[][] {
|
||||||
|
const rows = Array.from(
|
||||||
|
{ length: oldLines.length + 1 },
|
||||||
|
() => Array(newLines.length + 1).fill(0),
|
||||||
|
);
|
||||||
|
for (let oldIndex = oldLines.length - 1; oldIndex >= 0; oldIndex -= 1) {
|
||||||
|
for (let newIndex = newLines.length - 1; newIndex >= 0; newIndex -= 1) {
|
||||||
|
rows[oldIndex][newIndex] = oldLines[oldIndex] === newLines[newIndex]
|
||||||
|
? rows[oldIndex + 1][newIndex + 1] + 1
|
||||||
|
: Math.max(rows[oldIndex + 1][newIndex], rows[oldIndex][newIndex + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
function renderSearchTool(toolCall: ToolCallView): string {
|
function renderSearchTool(toolCall: ToolCallView): string {
|
||||||
const summary = toolCall.summary?.trim();
|
const summary = toolCall.summary?.trim();
|
||||||
return compactLines([
|
return compactLines([
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
import RichMarkdown from '$lib/workspace-console/RichMarkdown.svelte';
|
||||||
import {
|
import {
|
||||||
projectConsole,
|
projectConsole,
|
||||||
type ConsoleLine
|
type ConsoleLine
|
||||||
|
|
@ -243,6 +244,12 @@
|
||||||
return line.error ? 'error' : line.kind;
|
return line.error ? 'error' : line.kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toolClass(line: ConsoleLine): string {
|
||||||
|
const name = line.toolCall?.name?.toLowerCase() ?? '';
|
||||||
|
const state = line.toolCall?.state ?? (line.streaming ? 'streaming' : 'done');
|
||||||
|
return [name ? `tool-${name}` : '', `tool-state-${state}`].filter(Boolean).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
function isNearConsoleBottom(element: HTMLElement): boolean {
|
function isNearConsoleBottom(element: HTMLElement): boolean {
|
||||||
return element.scrollHeight - element.scrollTop - element.clientHeight <= CONSOLE_BOTTOM_THRESHOLD_PX;
|
return element.scrollHeight - element.scrollTop - element.clientHeight <= CONSOLE_BOTTOM_THRESHOLD_PX;
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +333,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<ol class="console-log">
|
<ol class="console-log">
|
||||||
{#each lines as item}
|
{#each lines as item}
|
||||||
<li class:assistant={lineClass(item) === 'assistant'} class:user={lineClass(item) === 'user'} class:system={lineClass(item) !== 'assistant' && lineClass(item) !== 'user'} class:error-line={item.error}>
|
<li class={`console-line ${lineClass(item)} ${toolClass(item)}`} class:error-line={item.error}>
|
||||||
{#if lineClass(item) !== 'assistant' && lineClass(item) !== 'user'}
|
{#if lineClass(item) !== 'assistant' && lineClass(item) !== 'user'}
|
||||||
<div class="message-heading">
|
<div class="message-heading">
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
|
|
@ -337,7 +344,11 @@
|
||||||
<small>streaming</small>
|
<small>streaming</small>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<pre>{item.body || '—'}</pre>
|
<RichMarkdown text={item.body || '—'} />
|
||||||
|
{#if item.diff}
|
||||||
|
<pre class="console-diff" aria-label="Edit diff">{#each item.diff as diffLine}
|
||||||
|
<span class={`diff-line ${diffLine.kind}`}><span class="diff-gutter">{diffLine.oldNumber ?? ''}</span><span class="diff-gutter">{diffLine.newNumber ?? ''}</span><span class="diff-marker">{diffLine.kind === 'add' ? '+' : diffLine.kind === 'remove' ? '-' : ' '}</span><span class="diff-content">{diffLine.content}</span></span>{/each}</pre>
|
||||||
|
{/if}
|
||||||
{#if item.detail}
|
{#if item.detail}
|
||||||
<details class="message-detail">
|
<details class="message-detail">
|
||||||
<summary>detail</summary>
|
<summary>detail</summary>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user