Spaces:
Running
Running
Commit Β·
dbd4801
1
Parent(s): d95cff9
Bug fix + fixed a couple rendering issues
Browse files- backend/main.py +2 -1
- backend/routes/agent.py +5 -0
- frontend/package-lock.json +0 -15
- frontend/src/components/Chat/ToolCallGroup.tsx +60 -11
- frontend/src/lib/sse-chat-transport.ts +2 -1
- frontend/src/store/agentStore.ts +55 -0
- uv.lock +0 -0
backend/main.py
CHANGED
|
@@ -12,7 +12,8 @@ from fastapi.staticfiles import StaticFiles
|
|
| 12 |
from routes.agent import router as agent_router
|
| 13 |
from routes.auth import router as auth_router
|
| 14 |
|
| 15 |
-
|
|
|
|
| 16 |
|
| 17 |
# Configure logging
|
| 18 |
logging.basicConfig(
|
|
|
|
| 12 |
from routes.agent import router as agent_router
|
| 13 |
from routes.auth import router as auth_router
|
| 14 |
|
| 15 |
+
# Load .env from project root (parent directory)
|
| 16 |
+
load_dotenv(Path(__file__).parent.parent / ".env")
|
| 17 |
|
| 18 |
# Configure logging
|
| 19 |
logging.basicConfig(
|
backend/routes/agent.py
CHANGED
|
@@ -206,12 +206,17 @@ async def create_session(
|
|
| 206 |
Returns 503 if the server or user has reached the session limit.
|
| 207 |
"""
|
| 208 |
# Extract the user's HF token (Bearer header or HttpOnly cookie)
|
|
|
|
| 209 |
hf_token = None
|
| 210 |
auth_header = request.headers.get("Authorization", "")
|
| 211 |
if auth_header.startswith("Bearer "):
|
| 212 |
hf_token = auth_header[7:]
|
| 213 |
if not hf_token:
|
| 214 |
hf_token = request.cookies.get("hf_access_token")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
try:
|
| 217 |
session_id = await session_manager.create_session(
|
|
|
|
| 206 |
Returns 503 if the server or user has reached the session limit.
|
| 207 |
"""
|
| 208 |
# Extract the user's HF token (Bearer header or HttpOnly cookie)
|
| 209 |
+
# In dev mode, fall back to environment variable if no token in request
|
| 210 |
hf_token = None
|
| 211 |
auth_header = request.headers.get("Authorization", "")
|
| 212 |
if auth_header.startswith("Bearer "):
|
| 213 |
hf_token = auth_header[7:]
|
| 214 |
if not hf_token:
|
| 215 |
hf_token = request.cookies.get("hf_access_token")
|
| 216 |
+
if not hf_token and user["user_id"] == "dev":
|
| 217 |
+
# Dev mode: use HF_TOKEN from environment
|
| 218 |
+
import os
|
| 219 |
+
hf_token = os.environ.get("HF_TOKEN")
|
| 220 |
|
| 221 |
try:
|
| 222 |
session_id = await session_manager.create_session(
|
frontend/package-lock.json
CHANGED
|
@@ -130,7 +130,6 @@
|
|
| 130 |
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
| 131 |
"dev": true,
|
| 132 |
"license": "MIT",
|
| 133 |
-
"peer": true,
|
| 134 |
"dependencies": {
|
| 135 |
"@babel/code-frame": "^7.28.6",
|
| 136 |
"@babel/generator": "^7.28.6",
|
|
@@ -447,7 +446,6 @@
|
|
| 447 |
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
| 448 |
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
| 449 |
"license": "MIT",
|
| 450 |
-
"peer": true,
|
| 451 |
"dependencies": {
|
| 452 |
"@babel/runtime": "^7.18.3",
|
| 453 |
"@emotion/babel-plugin": "^11.13.5",
|
|
@@ -491,7 +489,6 @@
|
|
| 491 |
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
| 492 |
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
| 493 |
"license": "MIT",
|
| 494 |
-
"peer": true,
|
| 495 |
"dependencies": {
|
| 496 |
"@babel/runtime": "^7.18.3",
|
| 497 |
"@emotion/babel-plugin": "^11.13.5",
|
|
@@ -1224,7 +1221,6 @@
|
|
| 1224 |
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz",
|
| 1225 |
"integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==",
|
| 1226 |
"license": "MIT",
|
| 1227 |
-
"peer": true,
|
| 1228 |
"dependencies": {
|
| 1229 |
"@babel/runtime": "^7.26.0",
|
| 1230 |
"@mui/core-downloads-tracker": "^6.5.0",
|
|
@@ -1919,7 +1915,6 @@
|
|
| 1919 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
| 1920 |
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
| 1921 |
"license": "MIT",
|
| 1922 |
-
"peer": true,
|
| 1923 |
"dependencies": {
|
| 1924 |
"@types/prop-types": "*",
|
| 1925 |
"csstype": "^3.2.2"
|
|
@@ -2005,7 +2000,6 @@
|
|
| 2005 |
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
| 2006 |
"dev": true,
|
| 2007 |
"license": "MIT",
|
| 2008 |
-
"peer": true,
|
| 2009 |
"dependencies": {
|
| 2010 |
"@typescript-eslint/scope-manager": "8.53.0",
|
| 2011 |
"@typescript-eslint/types": "8.53.0",
|
|
@@ -2272,7 +2266,6 @@
|
|
| 2272 |
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
| 2273 |
"dev": true,
|
| 2274 |
"license": "MIT",
|
| 2275 |
-
"peer": true,
|
| 2276 |
"bin": {
|
| 2277 |
"acorn": "bin/acorn"
|
| 2278 |
},
|
|
@@ -2421,7 +2414,6 @@
|
|
| 2421 |
}
|
| 2422 |
],
|
| 2423 |
"license": "MIT",
|
| 2424 |
-
"peer": true,
|
| 2425 |
"dependencies": {
|
| 2426 |
"baseline-browser-mapping": "^2.9.0",
|
| 2427 |
"caniuse-lite": "^1.0.30001759",
|
|
@@ -2774,7 +2766,6 @@
|
|
| 2774 |
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
| 2775 |
"dev": true,
|
| 2776 |
"license": "MIT",
|
| 2777 |
-
"peer": true,
|
| 2778 |
"dependencies": {
|
| 2779 |
"@eslint-community/eslint-utils": "^4.8.0",
|
| 2780 |
"@eslint-community/regexpp": "^4.12.1",
|
|
@@ -4673,7 +4664,6 @@
|
|
| 4673 |
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
| 4674 |
"dev": true,
|
| 4675 |
"license": "MIT",
|
| 4676 |
-
"peer": true,
|
| 4677 |
"engines": {
|
| 4678 |
"node": ">=12"
|
| 4679 |
},
|
|
@@ -4771,7 +4761,6 @@
|
|
| 4771 |
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
| 4772 |
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
| 4773 |
"license": "MIT",
|
| 4774 |
-
"peer": true,
|
| 4775 |
"dependencies": {
|
| 4776 |
"loose-envify": "^1.1.0"
|
| 4777 |
},
|
|
@@ -4784,7 +4773,6 @@
|
|
| 4784 |
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
| 4785 |
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
| 4786 |
"license": "MIT",
|
| 4787 |
-
"peer": true,
|
| 4788 |
"dependencies": {
|
| 4789 |
"loose-envify": "^1.1.0",
|
| 4790 |
"scheduler": "^0.23.2"
|
|
@@ -5269,7 +5257,6 @@
|
|
| 5269 |
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
| 5270 |
"dev": true,
|
| 5271 |
"license": "Apache-2.0",
|
| 5272 |
-
"peer": true,
|
| 5273 |
"bin": {
|
| 5274 |
"tsc": "bin/tsc",
|
| 5275 |
"tsserver": "bin/tsserver"
|
|
@@ -5435,7 +5422,6 @@
|
|
| 5435 |
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 5436 |
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 5437 |
"license": "MIT",
|
| 5438 |
-
"peer": true,
|
| 5439 |
"peerDependencies": {
|
| 5440 |
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 5441 |
}
|
|
@@ -5474,7 +5460,6 @@
|
|
| 5474 |
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
| 5475 |
"dev": true,
|
| 5476 |
"license": "MIT",
|
| 5477 |
-
"peer": true,
|
| 5478 |
"dependencies": {
|
| 5479 |
"esbuild": "^0.21.3",
|
| 5480 |
"postcss": "^8.4.43",
|
|
|
|
| 130 |
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
| 131 |
"dev": true,
|
| 132 |
"license": "MIT",
|
|
|
|
| 133 |
"dependencies": {
|
| 134 |
"@babel/code-frame": "^7.28.6",
|
| 135 |
"@babel/generator": "^7.28.6",
|
|
|
|
| 446 |
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
|
| 447 |
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
|
| 448 |
"license": "MIT",
|
|
|
|
| 449 |
"dependencies": {
|
| 450 |
"@babel/runtime": "^7.18.3",
|
| 451 |
"@emotion/babel-plugin": "^11.13.5",
|
|
|
|
| 489 |
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
|
| 490 |
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
|
| 491 |
"license": "MIT",
|
|
|
|
| 492 |
"dependencies": {
|
| 493 |
"@babel/runtime": "^7.18.3",
|
| 494 |
"@emotion/babel-plugin": "^11.13.5",
|
|
|
|
| 1221 |
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.5.0.tgz",
|
| 1222 |
"integrity": "sha512-yjvtXoFcrPLGtgKRxFaH6OQPtcLPhkloC0BML6rBG5UeldR0nPULR/2E2BfXdo5JNV7j7lOzrrLX2Qf/iSidow==",
|
| 1223 |
"license": "MIT",
|
|
|
|
| 1224 |
"dependencies": {
|
| 1225 |
"@babel/runtime": "^7.26.0",
|
| 1226 |
"@mui/core-downloads-tracker": "^6.5.0",
|
|
|
|
| 1915 |
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
|
| 1916 |
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
|
| 1917 |
"license": "MIT",
|
|
|
|
| 1918 |
"dependencies": {
|
| 1919 |
"@types/prop-types": "*",
|
| 1920 |
"csstype": "^3.2.2"
|
|
|
|
| 2000 |
"integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
|
| 2001 |
"dev": true,
|
| 2002 |
"license": "MIT",
|
|
|
|
| 2003 |
"dependencies": {
|
| 2004 |
"@typescript-eslint/scope-manager": "8.53.0",
|
| 2005 |
"@typescript-eslint/types": "8.53.0",
|
|
|
|
| 2266 |
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
| 2267 |
"dev": true,
|
| 2268 |
"license": "MIT",
|
|
|
|
| 2269 |
"bin": {
|
| 2270 |
"acorn": "bin/acorn"
|
| 2271 |
},
|
|
|
|
| 2414 |
}
|
| 2415 |
],
|
| 2416 |
"license": "MIT",
|
|
|
|
| 2417 |
"dependencies": {
|
| 2418 |
"baseline-browser-mapping": "^2.9.0",
|
| 2419 |
"caniuse-lite": "^1.0.30001759",
|
|
|
|
| 2766 |
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
| 2767 |
"dev": true,
|
| 2768 |
"license": "MIT",
|
|
|
|
| 2769 |
"dependencies": {
|
| 2770 |
"@eslint-community/eslint-utils": "^4.8.0",
|
| 2771 |
"@eslint-community/regexpp": "^4.12.1",
|
|
|
|
| 4664 |
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
| 4665 |
"dev": true,
|
| 4666 |
"license": "MIT",
|
|
|
|
| 4667 |
"engines": {
|
| 4668 |
"node": ">=12"
|
| 4669 |
},
|
|
|
|
| 4761 |
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
|
| 4762 |
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
|
| 4763 |
"license": "MIT",
|
|
|
|
| 4764 |
"dependencies": {
|
| 4765 |
"loose-envify": "^1.1.0"
|
| 4766 |
},
|
|
|
|
| 4773 |
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
| 4774 |
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
| 4775 |
"license": "MIT",
|
|
|
|
| 4776 |
"dependencies": {
|
| 4777 |
"loose-envify": "^1.1.0",
|
| 4778 |
"scheduler": "^0.23.2"
|
|
|
|
| 5257 |
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
| 5258 |
"dev": true,
|
| 5259 |
"license": "Apache-2.0",
|
|
|
|
| 5260 |
"bin": {
|
| 5261 |
"tsc": "bin/tsc",
|
| 5262 |
"tsserver": "bin/tsserver"
|
|
|
|
| 5422 |
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
|
| 5423 |
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
|
| 5424 |
"license": "MIT",
|
|
|
|
| 5425 |
"peerDependencies": {
|
| 5426 |
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 5427 |
}
|
|
|
|
| 5460 |
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
|
| 5461 |
"dev": true,
|
| 5462 |
"license": "MIT",
|
|
|
|
| 5463 |
"dependencies": {
|
| 5464 |
"esbuild": "^0.21.3",
|
| 5465 |
"postcss": "^8.4.43",
|
frontend/src/components/Chat/ToolCallGroup.tsx
CHANGED
|
@@ -397,7 +397,7 @@ function InlineApproval({
|
|
| 397 |
// ---------------------------------------------------------------------------
|
| 398 |
|
| 399 |
export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProps) {
|
| 400 |
-
const { setPanel, lockPanel, getJobUrl, getEditedScript } = useAgentStore();
|
| 401 |
const researchSteps = useAgentStore(s => {
|
| 402 |
const activeId = s.activeSessionId;
|
| 403 |
return activeId ? (s.sessionStates[activeId]?.researchSteps) : undefined;
|
|
@@ -428,6 +428,35 @@ export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProp
|
|
| 428 |
}
|
| 429 |
}, [pendingTools, isSubmitting]);
|
| 430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
const { scriptLabelMap, toolDisplayMap } = useMemo(() => {
|
| 432 |
const hfJobs = tools.filter(t => t.toolName === 'hf_jobs' && (t.input as Record<string, unknown>)?.script);
|
| 433 |
const scriptMap: Record<string, string> = {};
|
|
@@ -655,21 +684,37 @@ export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProp
|
|
| 655 |
const localDecision = decisions[tool.toolCallId];
|
| 656 |
|
| 657 |
const cancelled = isCancelledTool(tool);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 658 |
const displayState = isPending && localDecision
|
| 659 |
? (localDecision.approved ? 'input-available' : 'output-denied')
|
| 660 |
: state;
|
| 661 |
-
const label = cancelled ? 'cancelled'
|
|
|
|
|
|
|
| 662 |
|
| 663 |
// Parse job metadata from hf_jobs output and store
|
| 664 |
const jobUrlFromStore = tool.toolName === 'hf_jobs' ? getJobUrl(tool.toolCallId) : undefined;
|
| 665 |
-
const
|
| 666 |
-
|
|
|
|
|
|
|
| 667 |
: {};
|
| 668 |
-
|
| 669 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 670 |
const jobMeta = {
|
| 671 |
jobUrl: jobUrlFromStore || jobMetaFromOutput.jobUrl,
|
| 672 |
-
jobStatus: jobMetaFromOutput.jobStatus,
|
| 673 |
};
|
| 674 |
|
| 675 |
return (
|
|
@@ -691,9 +736,11 @@ export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProp
|
|
| 691 |
<StatusIcon
|
| 692 |
cancelled={cancelled}
|
| 693 |
state={
|
| 694 |
-
|
| 695 |
? 'output-error'
|
| 696 |
-
:
|
|
|
|
|
|
|
| 697 |
}
|
| 698 |
/>
|
| 699 |
|
|
@@ -724,10 +771,12 @@ export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProp
|
|
| 724 |
fontSize: '0.65rem',
|
| 725 |
fontWeight: 600,
|
| 726 |
bgcolor: cancelled ? 'rgba(255,255,255,0.05)'
|
| 727 |
-
:
|
| 728 |
: displayState === 'output-denied' ? 'rgba(255,255,255,0.05)'
|
| 729 |
: 'var(--accent-yellow-weak)',
|
| 730 |
-
color: cancelled ? 'var(--muted-text)'
|
|
|
|
|
|
|
| 731 |
letterSpacing: '0.03em',
|
| 732 |
}}
|
| 733 |
/>
|
|
|
|
| 397 |
// ---------------------------------------------------------------------------
|
| 398 |
|
| 399 |
export default function ToolCallGroup({ tools, approveTools }: ToolCallGroupProps) {
|
| 400 |
+
const { setPanel, lockPanel, getJobUrl, getEditedScript, setJobStatus, getJobStatus, setToolError, getToolError } = useAgentStore();
|
| 401 |
const researchSteps = useAgentStore(s => {
|
| 402 |
const activeId = s.activeSessionId;
|
| 403 |
return activeId ? (s.sessionStates[activeId]?.researchSteps) : undefined;
|
|
|
|
| 428 |
}
|
| 429 |
}, [pendingTools, isSubmitting]);
|
| 430 |
|
| 431 |
+
// Clean up stale decisions for tools that are no longer pending
|
| 432 |
+
useEffect(() => {
|
| 433 |
+
const pendingIds = new Set(pendingTools.map(t => t.toolCallId));
|
| 434 |
+
const decisionIds = Object.keys(decisions);
|
| 435 |
+
const hasStale = decisionIds.some(id => !pendingIds.has(id));
|
| 436 |
+
if (hasStale) {
|
| 437 |
+
setDecisions(prev => {
|
| 438 |
+
const cleaned = { ...prev };
|
| 439 |
+
for (const id of decisionIds) {
|
| 440 |
+
if (!pendingIds.has(id)) delete cleaned[id];
|
| 441 |
+
}
|
| 442 |
+
return cleaned;
|
| 443 |
+
});
|
| 444 |
+
}
|
| 445 |
+
}, [pendingTools, decisions]);
|
| 446 |
+
|
| 447 |
+
// Persist error states when tools error
|
| 448 |
+
useEffect(() => {
|
| 449 |
+
for (const tool of tools) {
|
| 450 |
+
const currentlyHasError = tool.state === 'output-error';
|
| 451 |
+
const persistedError = getToolError(tool.toolCallId);
|
| 452 |
+
|
| 453 |
+
// Persist error state if we detect it and haven't already
|
| 454 |
+
if (currentlyHasError && !persistedError) {
|
| 455 |
+
setToolError(tool.toolCallId, true);
|
| 456 |
+
}
|
| 457 |
+
}
|
| 458 |
+
}, [tools, setToolError, getToolError]);
|
| 459 |
+
|
| 460 |
const { scriptLabelMap, toolDisplayMap } = useMemo(() => {
|
| 461 |
const hfJobs = tools.filter(t => t.toolName === 'hf_jobs' && (t.input as Record<string, unknown>)?.script);
|
| 462 |
const scriptMap: Record<string, string> = {};
|
|
|
|
| 684 |
const localDecision = decisions[tool.toolCallId];
|
| 685 |
|
| 686 |
const cancelled = isCancelledTool(tool);
|
| 687 |
+
const currentlyHasError = state === 'output-error';
|
| 688 |
+
const persistedError = getToolError(tool.toolCallId);
|
| 689 |
+
|
| 690 |
+
// Use persisted error OR current error (persisting happens in useEffect)
|
| 691 |
+
const hasError = persistedError || currentlyHasError;
|
| 692 |
+
|
| 693 |
const displayState = isPending && localDecision
|
| 694 |
? (localDecision.approved ? 'input-available' : 'output-denied')
|
| 695 |
: state;
|
| 696 |
+
const label = cancelled ? 'cancelled'
|
| 697 |
+
: hasError ? 'error'
|
| 698 |
+
: statusLabel(displayState as ToolPartState);
|
| 699 |
|
| 700 |
// Parse job metadata from hf_jobs output and store
|
| 701 |
const jobUrlFromStore = tool.toolName === 'hf_jobs' ? getJobUrl(tool.toolCallId) : undefined;
|
| 702 |
+
const jobStatusFromStore = tool.toolName === 'hf_jobs' ? getJobStatus(tool.toolCallId) : undefined;
|
| 703 |
+
|
| 704 |
+
const jobMetaFromOutput = tool.toolName === 'hf_jobs' && (tool.output || (tool as Record<string, unknown>).errorText)
|
| 705 |
+
? parseJobMeta(tool.output ?? (tool as Record<string, unknown>).errorText)
|
| 706 |
: {};
|
| 707 |
+
|
| 708 |
+
// Store job status if we just parsed it and don't have it stored yet
|
| 709 |
+
if (tool.toolName === 'hf_jobs' && jobMetaFromOutput.jobStatus && !jobStatusFromStore) {
|
| 710 |
+
setJobStatus(tool.toolCallId, jobMetaFromOutput.jobStatus);
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
// Combine job URL and status from store (persisted) with output metadata (freshly parsed)
|
| 714 |
+
// Prefer stored values to ensure they persist across renders
|
| 715 |
const jobMeta = {
|
| 716 |
jobUrl: jobUrlFromStore || jobMetaFromOutput.jobUrl,
|
| 717 |
+
jobStatus: jobStatusFromStore || jobMetaFromOutput.jobStatus,
|
| 718 |
};
|
| 719 |
|
| 720 |
return (
|
|
|
|
| 736 |
<StatusIcon
|
| 737 |
cancelled={cancelled}
|
| 738 |
state={
|
| 739 |
+
hasError
|
| 740 |
? 'output-error'
|
| 741 |
+
: ((tool.toolName === 'hf_jobs' && jobMeta.jobStatus && ['ERROR', 'FAILED', 'CANCELLED'].includes(jobMeta.jobStatus))
|
| 742 |
+
? 'output-error'
|
| 743 |
+
: displayState as ToolPartState)
|
| 744 |
}
|
| 745 |
/>
|
| 746 |
|
|
|
|
| 771 |
fontSize: '0.65rem',
|
| 772 |
fontWeight: 600,
|
| 773 |
bgcolor: cancelled ? 'rgba(255,255,255,0.05)'
|
| 774 |
+
: hasError ? 'rgba(224,90,79,0.12)'
|
| 775 |
: displayState === 'output-denied' ? 'rgba(255,255,255,0.05)'
|
| 776 |
: 'var(--accent-yellow-weak)',
|
| 777 |
+
color: cancelled ? 'var(--muted-text)'
|
| 778 |
+
: hasError ? 'var(--accent-red)'
|
| 779 |
+
: statusColor(displayState as ToolPartState),
|
| 780 |
letterSpacing: '0.03em',
|
| 781 |
}}
|
| 782 |
/>
|
frontend/src/lib/sse-chat-transport.ts
CHANGED
|
@@ -277,7 +277,8 @@ export class SSEChatTransport implements ChatTransport<UIMessage> {
|
|
| 277 |
this.sessionId = sessionId;
|
| 278 |
this.sideChannel = sideChannel;
|
| 279 |
// Mark as connected immediately β no persistent connection to establish
|
| 280 |
-
|
|
|
|
| 281 |
}
|
| 282 |
|
| 283 |
updateSideChannel(sideChannel: SideChannelCallbacks): void {
|
|
|
|
| 277 |
this.sessionId = sessionId;
|
| 278 |
this.sideChannel = sideChannel;
|
| 279 |
// Mark as connected immediately β no persistent connection to establish
|
| 280 |
+
// Defer to avoid setState during render
|
| 281 |
+
queueMicrotask(() => sideChannel.onConnectionChange(true));
|
| 282 |
}
|
| 283 |
|
| 284 |
updateSideChannel(sideChannel: SideChannelCallbacks): void {
|
frontend/src/store/agentStore.ts
CHANGED
|
@@ -101,6 +101,12 @@ interface AgentStore {
|
|
| 101 |
// Job URLs (tool_call_id -> job URL) for HF jobs
|
| 102 |
jobUrls: Record<string, string>;
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
// ββ Per-session actions βββββββββββββββββββββββββββββββββββββββββββββ
|
| 105 |
|
| 106 |
/** Update a session's state. If it's the active session, also update flat state. */
|
|
@@ -138,6 +144,12 @@ interface AgentStore {
|
|
| 138 |
|
| 139 |
setJobUrl: (toolCallId: string, jobUrl: string) => void;
|
| 140 |
getJobUrl: (toolCallId: string) => string | undefined;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
}
|
| 142 |
|
| 143 |
/**
|
|
@@ -159,6 +171,25 @@ function syncSnapshot(
|
|
| 159 |
};
|
| 160 |
}
|
| 161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
export const useAgentStore = create<AgentStore>()((set, get) => ({
|
| 163 |
sessionStates: {},
|
| 164 |
activeSessionId: null,
|
|
@@ -178,6 +209,8 @@ export const useAgentStore = create<AgentStore>()((set, get) => ({
|
|
| 178 |
|
| 179 |
editedScripts: {},
|
| 180 |
jobUrls: {},
|
|
|
|
|
|
|
| 181 |
|
| 182 |
// ββ Per-session state management ββββββββββββββββββββββββββββββββββ
|
| 183 |
|
|
@@ -349,4 +382,26 @@ export const useAgentStore = create<AgentStore>()((set, get) => ({
|
|
| 349 |
},
|
| 350 |
|
| 351 |
getJobUrl: (toolCallId) => get().jobUrls[toolCallId],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
}));
|
|
|
|
| 101 |
// Job URLs (tool_call_id -> job URL) for HF jobs
|
| 102 |
jobUrls: Record<string, string>;
|
| 103 |
|
| 104 |
+
// Job statuses (tool_call_id -> job status) for HF jobs
|
| 105 |
+
jobStatuses: Record<string, string>;
|
| 106 |
+
|
| 107 |
+
// Tool error states (tool_call_id -> true if errored) - persisted across renders
|
| 108 |
+
toolErrors: Record<string, boolean>;
|
| 109 |
+
|
| 110 |
// ββ Per-session actions βββββββββββββββββββββββββββββββββββββββββββββ
|
| 111 |
|
| 112 |
/** Update a session's state. If it's the active session, also update flat state. */
|
|
|
|
| 144 |
|
| 145 |
setJobUrl: (toolCallId: string, jobUrl: string) => void;
|
| 146 |
getJobUrl: (toolCallId: string) => string | undefined;
|
| 147 |
+
|
| 148 |
+
setJobStatus: (toolCallId: string, status: string) => void;
|
| 149 |
+
getJobStatus: (toolCallId: string) => string | undefined;
|
| 150 |
+
|
| 151 |
+
setToolError: (toolCallId: string, hasError: boolean) => void;
|
| 152 |
+
getToolError: (toolCallId: string) => boolean | undefined;
|
| 153 |
}
|
| 154 |
|
| 155 |
/**
|
|
|
|
| 171 |
};
|
| 172 |
}
|
| 173 |
|
| 174 |
+
// Load persisted tool errors from localStorage
|
| 175 |
+
function loadToolErrors(): Record<string, boolean> {
|
| 176 |
+
try {
|
| 177 |
+
const stored = localStorage.getItem('hf-agent-tool-errors');
|
| 178 |
+
return stored ? JSON.parse(stored) : {};
|
| 179 |
+
} catch {
|
| 180 |
+
return {};
|
| 181 |
+
}
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
// Save tool errors to localStorage
|
| 185 |
+
function saveToolErrors(errors: Record<string, boolean>): void {
|
| 186 |
+
try {
|
| 187 |
+
localStorage.setItem('hf-agent-tool-errors', JSON.stringify(errors));
|
| 188 |
+
} catch (e) {
|
| 189 |
+
console.warn('Failed to persist tool errors:', e);
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
export const useAgentStore = create<AgentStore>()((set, get) => ({
|
| 194 |
sessionStates: {},
|
| 195 |
activeSessionId: null,
|
|
|
|
| 209 |
|
| 210 |
editedScripts: {},
|
| 211 |
jobUrls: {},
|
| 212 |
+
jobStatuses: {},
|
| 213 |
+
toolErrors: loadToolErrors(),
|
| 214 |
|
| 215 |
// ββ Per-session state management ββββββββββββββββββββββββββββββββββ
|
| 216 |
|
|
|
|
| 382 |
},
|
| 383 |
|
| 384 |
getJobUrl: (toolCallId) => get().jobUrls[toolCallId],
|
| 385 |
+
|
| 386 |
+
// ββ Job Statuses ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 387 |
+
|
| 388 |
+
setJobStatus: (toolCallId, status) => {
|
| 389 |
+
set((state) => ({
|
| 390 |
+
jobStatuses: { ...state.jobStatuses, [toolCallId]: status },
|
| 391 |
+
}));
|
| 392 |
+
},
|
| 393 |
+
|
| 394 |
+
getJobStatus: (toolCallId) => get().jobStatuses[toolCallId],
|
| 395 |
+
|
| 396 |
+
// ββ Tool Errors βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 397 |
+
|
| 398 |
+
setToolError: (toolCallId, hasError) => {
|
| 399 |
+
set((state) => {
|
| 400 |
+
const updated = { ...state.toolErrors, [toolCallId]: hasError };
|
| 401 |
+
saveToolErrors(updated);
|
| 402 |
+
return { toolErrors: updated };
|
| 403 |
+
});
|
| 404 |
+
},
|
| 405 |
+
|
| 406 |
+
getToolError: (toolCallId) => get().toolErrors[toolCallId],
|
| 407 |
}));
|
uv.lock
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|