Integrate Claude AI

Currently only terminal mode is supported
This commit is contained in:
Alessandro Pignotti 2024-12-19 10:47:33 +01:00
parent b87c531932
commit 9a6e488c8e
8 changed files with 437 additions and 7 deletions

217
package-lock.json generated
View File

@ -7,6 +7,9 @@
"": { "": {
"name": "webvm", "name": "webvm",
"version": "2.0.0", "version": "2.0.0",
"dependencies": {
"@anthropic-ai/sdk": "^0.33.0"
},
"devDependencies": { "devDependencies": {
"@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-free": "^6.6.0",
"@leaningtech/cheerpx": "latest", "@leaningtech/cheerpx": "latest",
@ -54,6 +57,20 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/@anthropic-ai/sdk": {
"version": "0.33.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.33.0.tgz",
"integrity": "sha512-FnUnCNVgO8ayDyAD8hsBEmXMzQn4xGG3EnABtgFgSMmKg+zNgdyX2iKNIm01pAhmnH213MJUBMI7egEFF08cvA==",
"dependencies": {
"@types/node": "^18.11.18",
"@types/node-fetch": "^2.6.4",
"abort-controller": "^3.0.0",
"agentkeepalive": "^4.2.1",
"form-data-encoder": "1.7.2",
"formdata-node": "^4.3.2",
"node-fetch": "^2.6.7"
}
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
@ -910,6 +927,23 @@
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true "dev": true
}, },
"node_modules/@types/node": {
"version": "18.19.68",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz",
"integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@xterm/addon-fit": { "node_modules/@xterm/addon-fit": {
"version": "0.10.0", "version": "0.10.0",
"resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz",
@ -934,6 +968,17 @@
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
"dev": true "dev": true
}, },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.0", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
@ -946,6 +991,17 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/agentkeepalive": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
"integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
"dependencies": {
"humanize-ms": "^1.2.1"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/ansi-regex": { "node_modules/ansi-regex": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@ -1004,6 +1060,11 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/autoprefixer": { "node_modules/autoprefixer": {
"version": "10.4.20", "version": "10.4.20",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
@ -1235,6 +1296,17 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true "dev": true
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": { "node_modules/commander": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
@ -1346,6 +1418,14 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/devalue": { "node_modules/devalue": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
@ -1511,6 +1591,14 @@
"@types/estree": "^1.0.0" "@types/estree": "^1.0.0"
} }
}, },
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
}
},
"node_modules/fast-glob": { "node_modules/fast-glob": {
"version": "3.3.2", "version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@ -1576,6 +1664,36 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/form-data-encoder": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
"integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
"integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
"dependencies": {
"node-domexception": "1.0.0",
"web-streams-polyfill": "4.0.0-beta.3"
},
"engines": {
"node": ">= 12.20"
}
},
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "4.3.7", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
@ -1697,6 +1815,14 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
"dependencies": {
"ms": "^2.0.0"
}
},
"node_modules/import-meta-resolve": { "node_modules/import-meta-resolve": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@ -1916,6 +2042,25 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "9.0.5", "version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@ -1961,8 +2106,7 @@
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
"dev": true
}, },
"node_modules/mz": { "node_modules/mz": {
"version": "2.7.0", "version": "2.7.0",
@ -1993,6 +2137,43 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-html-parser": { "node_modules/node-html-parser": {
"version": "6.1.13", "version": "6.1.13",
"resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz", "resolved": "https://registry.npmjs.org/node-html-parser/-/node-html-parser-6.1.13.tgz",
@ -2767,12 +2948,22 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-interface-checker": { "node_modules/ts-interface-checker": {
"version": "0.1.13", "version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "dev": true
}, },
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/universalify": { "node_modules/universalify": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@ -2909,6 +3100,28 @@
} }
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
"integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
"engines": {
"node": ">= 14"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -27,5 +27,8 @@
"vite": "^5.0.3", "vite": "^5.0.3",
"vite-plugin-static-copy": "^1.0.6" "vite-plugin-static-copy": "^1.0.6"
}, },
"type": "module" "type": "module",
"dependencies": {
"@anthropic-ai/sdk": "^0.33.0"
}
} }

View File

@ -18,6 +18,8 @@ export default {
case '.fa-circle:before': case '.fa-circle:before':
case '.fa-trash-can:before': case '.fa-trash-can:before':
case '.fa-book-open:before': case '.fa-book-open:before':
case '.fa-brain:before':
case '.fa-user:before':
case '.fa-brands:before': case '.fa-brands:before':
case '.fa-solid:before': case '.fa-solid:before':
case '.fa-regular:before': case '.fa-regular:before':

View File

@ -0,0 +1,66 @@
<script>
import { apiState, setApiKey, addMessage, messageList } from '$lib/anthropic.js'
import PanelButton from './PanelButton.svelte';
export let handleTool;
function handleKeyEnter(e)
{
if(e.key != "Enter")
return;
setApiKey(e.target.value);
}
function handleMessage(e)
{
if(e.key != "Enter")
return;
addMessage(e.target.value, handleTool);
e.target.value = "";
}
function scrollMessage(node, messageList)
{
return {
update(messageList) {
node.scrollTop = node.scrollHeight;
}
}
}
function getIconForMsg(msg)
{
if(msg.role == "user")
return "fa-user"
else
return "fa-robot"
}
function isToolUse(msg)
{
if(!Array.isArray(msg.content))
return false;
return msg.content[0].type == "tool_use";
}
function isToolResult(msg)
{
if(!Array.isArray(msg.content))
return false;
return msg.content[0].type == "tool_result";
}
</script>
<h1 class="text-lg font-bold">Claude AI Integration</h1>
<p>WebVM is integrated with Claude by Anthropic AI. You can prompt the AI to control the system.</p>
<p>You need to provide your API key. The key is only saved locally to your browser.</p>
<div class="flex grow overflow-y-scroll scrollbar" use:scrollMessage={$messageList}>
<div class="h-full w-full">
<div class="w-full min-h-full flex flex-col gap-2 justify-end">
{#each $messageList as msg}
{#if isToolUse(msg)}
<p class="bg-neutral-700 p-2 rounded-md italic"><i class='fas fa-screwdriver-wrench w-6 mr-2 text-center'></i>Use the system</p>
{:else if !isToolResult(msg)}
<p class="{msg.role == 'error' ? 'bg-red-900' : 'bg-neutral-700'} p-2 rounded-md"><i class='fas {getIconForMsg(msg)} w-6 mr-2 text-center'></i>{msg.content}</p>
{/if}
{/each}
</div>
</div>
</div>
{#if $apiState == "KEY_REQUIRED"}
<input class="bg-neutral-700 p-2 rounded-md placeholder-gray-400" placeholder="Insert your Claude API Key" on:keydown={handleKeyEnter}/>
{:else}
<input class="bg-neutral-700 p-2 rounded-md placeholder-gray-400" placeholder="Prompt..." on:keydown={handleMessage}/>
{/if}

View File

@ -4,10 +4,11 @@
import NetworkingTab from './NetworkingTab.svelte'; import NetworkingTab from './NetworkingTab.svelte';
import CpuTab from './CpuTab.svelte'; import CpuTab from './CpuTab.svelte';
import DiskTab from './DiskTab.svelte'; import DiskTab from './DiskTab.svelte';
import AnthropicTab from './AnthropicTab.svelte';
import PostsTab from './PostsTab.svelte'; import PostsTab from './PostsTab.svelte';
import DiscordTab from './DiscordTab.svelte'; import DiscordTab from './DiscordTab.svelte';
import GitHubTab from './GitHubTab.svelte'; import GitHubTab from './GitHubTab.svelte';
import { cpuActivity, diskActivity } from './activities.js' import { cpuActivity, diskActivity, aiActivity } from './activities.js'
const icons = [ const icons = [
{ icon: 'fas fa-info-circle', info: 'Information', activity: null }, { icon: 'fas fa-info-circle', info: 'Information', activity: null },
@ -15,6 +16,7 @@
{ icon: 'fas fa-microchip', info: 'CPU', activity: cpuActivity }, { icon: 'fas fa-microchip', info: 'CPU', activity: cpuActivity },
{ icon: 'fas fa-compact-disc', info: 'Disk', activity: diskActivity }, { icon: 'fas fa-compact-disc', info: 'Disk', activity: diskActivity },
null, null,
{ icon: 'fas fa-brain', info: 'ClaudeAI', activity: aiActivity },
{ icon: 'fas fa-book-open', info: 'Posts', activity: null }, { icon: 'fas fa-book-open', info: 'Posts', activity: null },
{ icon: 'fab fa-discord', info: 'Discord', activity: null }, { icon: 'fab fa-discord', info: 'Discord', activity: null },
{ icon: 'fab fa-github', info: 'GitHub', activity: null }, { icon: 'fab fa-github', info: 'GitHub', activity: null },
@ -29,19 +31,22 @@
function hideInfo() { function hideInfo() {
activeInfo = null; activeInfo = null;
} }
export let handleTool;
export let needsDisplay;
</script> </script>
<div class="flex flex-row w-14 h-full bg-neutral-700" on:mouseleave={hideInfo}> <div class="flex flex-row w-14 h-full bg-neutral-700" on:mouseleave={hideInfo}>
<div class="flex flex-col shrink-0 w-14 text-gray-300"> <div class="flex flex-col shrink-0 w-14 text-gray-300">
{#each icons as i} {#each icons as i}
{#if i} {#if i && (!needsDisplay || i.info != 'ClaudeAI')}
<Icon <Icon
icon={i.icon} icon={i.icon}
info={i.info} info={i.info}
activity={i.activity} activity={i.activity}
on:mouseover={(e) => showInfo(e.detail)} on:mouseover={(e) => showInfo(e.detail)}
/> />
{:else} {:else if i == null}
<div class="grow" on:mouseover={(e) => showInfo(null)}></div> <div class="grow" on:mouseover={(e) => showInfo(null)}></div>
{/if} {/if}
{/each} {/each}
@ -57,6 +62,8 @@
<CpuTab/> <CpuTab/>
{:else if activeInfo === 'Disk'} {:else if activeInfo === 'Disk'}
<DiskTab on:reset/> <DiskTab on:reset/>
{:else if activeInfo === 'ClaudeAI'}
<AnthropicTab handleTool={handleTool} />
{:else if activeInfo === 'Posts'} {:else if activeInfo === 'Posts'}
<PostsTab/> <PostsTab/>
{:else if activeInfo === 'Discord'} {:else if activeInfo === 'Discord'}

View File

@ -324,12 +324,53 @@
await blockCache.reset(); await blockCache.reset();
location.reload(); location.reload();
} }
async function handleTool(tool)
{
if(tool.command)
{
var sentinel = "# End of AI command";
var buffer = term.buffer.active;
// Get the current cursor position
var marker = term.registerMarker();
var startLine = marker.line;
marker.dispose();
var ret = new Promise(function(f, r)
{
var callbackDisposer = term.onWriteParsed(function()
{
var curLength = buffer.length;
// Accumulate the output and see if the sentinel has been printed
var output = "";
for(var i=startLine + 1;i<curLength;i++)
{
var curLine = buffer.getLine(i).translateToString(true, 0, term.cols);;
if(curLine.indexOf(sentinel) >= 0)
{
// We are done, cleanup and return
callbackDisposer.dispose();
return f(output);
}
output += curLine + "\n";
}
});
});
term.input(tool.command);
term.input("\n");
term.input(sentinel);
term.input("\n");
return ret;
}
else
{
debugger;
}
}
</script> </script>
<main class="relative w-full h-full"> <main class="relative w-full h-full">
<Nav /> <Nav />
<div class="absolute top-10 bottom-0 left-0 right-0"> <div class="absolute top-10 bottom-0 left-0 right-0">
<SideBar on:connect={handleConnect} on:reset={handleReset}> <SideBar on:connect={handleConnect} on:reset={handleReset} needsDisplay={configObj.needsDisplay} handleTool={handleTool}>
<slot></slot> <slot></slot>
</SideBar> </SideBar>
{#if configObj.needsDisplay} {#if configObj.needsDisplay}

View File

@ -2,5 +2,6 @@ import { writable } from 'svelte/store';
export const cpuActivity = writable(false); export const cpuActivity = writable(false);
export const diskActivity = writable(false); export const diskActivity = writable(false);
export const aiActivity = writable(false);
export const cpuPercentage = writable(0); export const cpuPercentage = writable(0);
export const diskLatency = writable(0); export const diskLatency = writable(0);

97
src/lib/anthropic.js Normal file
View File

@ -0,0 +1,97 @@
import { writable } from 'svelte/store';
import { browser } from '$app/environment'
import { aiActivity } from '$lib/activities.js'
import Anthropic from '@anthropic-ai/sdk';
var client = null;
var messages = [];
export function setApiKey(key)
{
client = new Anthropic({apiKey: key, dangerouslyAllowBrowser: true});
// Reset messages
messages = []
messageList.set(messages);
localStorage.setItem("anthropic-api-key", key);
apiState.set("READY");
}
function clearApiKey()
{
localStorage.removeItem("anthropic-api-key");
apiState.set("KEY_REQUIRED");
}
function addMessageInternal(role, content)
{
messages.push({role: role, content: content});
messageList.set(messages);
}
async function sendMessages(handleTool)
{
aiActivity.set(true);
try
{
var tools = [
{ "type": "bash_20241022", "name": "bash" }
];
const response = await client.beta.messages.create({max_tokens: 1024, messages: messages, model: 'claude-3-5-sonnet-20241022', tools: tools, betas: ["computer-use-2024-10-22"]});
var content = response.content;
// Be robust to multiple response
for(var i=0;i<content.length;i++)
{
var c = content[i];
if(c.type == "text")
{
addMessageInternal(response.role, c.text);
}
else if(c.type == "tool_use")
{
addMessageInternal(response.role, [c]);
var commandResponse = await handleTool(c.input);
addMessageInternal("user", [{type: "tool_result", tool_use_id: c.id, content: commandResponse}]);
sendMessages(handleTool);
}
else
{
debugger;
}
}
if(response.stop_reason == "end_turn")
aiActivity.set(false);
}
catch(e)
{
if(e.status == 401)
{
addMessageInternal('error', 'Invalid API key');
clearApiKey();
}
else
{
addMessageInternal('error', e.error.error.message);
}
}
}
export function addMessage(text, handleTool)
{
addMessageInternal('user', text);
sendMessages(handleTool);
}
function initialize()
{
var savedApiKey = localStorage.getItem("anthropic-api-key");
if(savedApiKey)
setApiKey(savedApiKey);
}
export const apiState = writable("KEY_REQUIRED");
export const messageList = writable(messages);
if(browser)
initialize();