diff --git a/frontend/package.json b/frontend/package.json index b681fea..d52fbc6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,8 @@ "react": "^19.2.0", "react-dom": "^19.2.0", "react-markdown": "^10.1.0", + "rehype-raw": "^7.0.0", + "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.5.0", "three": "^0.183.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c5e091e..abf11b3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Boxes, MoonStar, Sparkles, SunMedium } from 'lucide-react'; import { useAppStore, @@ -13,7 +13,7 @@ import { appEn } from './i18n/app.en'; import './App.css'; function App() { - const { theme, setTheme, locale, setLocale } = useAppStore(); + const { theme, setTheme, locale, setLocale, activeBots } = useAppStore(); const [showImageFactory, setShowImageFactory] = useState(false); const [showCreateWizard, setShowCreateWizard] = useState(false); useBotsSync(); @@ -28,6 +28,17 @@ function App() { return { forcedBotId, compactMode }; }, []); + useEffect(() => { + const forced = urlView.forcedBotId; + if (!forced) { + document.title = t.title; + return; + } + const bot = activeBots[forced]; + const botName = String(bot?.name || '').trim(); + document.title = botName ? `${t.title} - ${botName}` : `${t.title} - ${forced}`; + }, [activeBots, t.title, urlView.forcedBotId]); + return (
diff --git a/frontend/src/modules/dashboard/BotDashboardModule.tsx b/frontend/src/modules/dashboard/BotDashboardModule.tsx index 91292a5..1a938df 100644 --- a/frontend/src/modules/dashboard/BotDashboardModule.tsx +++ b/frontend/src/modules/dashboard/BotDashboardModule.tsx @@ -3,6 +3,8 @@ import axios from 'axios'; import { Activity, Boxes, Check, Clock3, EllipsisVertical, Eye, EyeOff, FileText, FolderOpen, Hammer, MessageSquareText, Paperclip, Plus, Power, PowerOff, RefreshCw, Repeat2, Save, Settings2, SlidersHorizontal, TriangleAlert, Trash2, UserRound, Waypoints, X } from 'lucide-react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; +import rehypeRaw from 'rehype-raw'; +import rehypeSanitize from 'rehype-sanitize'; import { APP_ENDPOINTS } from '../../config/env'; import { useAppStore } from '../../store/appStore'; import type { ChatMessage } from '../../types/bot'; @@ -681,7 +683,11 @@ export function BotDashboardModule({ item.role === 'user' ? (
{normalizeUserMessageText(item.text)}
) : ( - + {decorateWorkspacePathsForMarkdown(displayText)} ) @@ -2508,7 +2514,11 @@ export function BotDashboardModule({ /> ) : workspacePreview.isMarkdown ? (
- + {workspacePreview.content}
diff --git a/frontend/src/store/appStore.ts b/frontend/src/store/appStore.ts index e030d5d..901bd04 100644 --- a/frontend/src/store/appStore.ts +++ b/frontend/src/store/appStore.ts @@ -47,10 +47,13 @@ export const useAppStore = create((set) => ({ const prev = state.activeBots[bot.id]; const incomingState = (bot.current_state || '').toUpperCase(); const prevState = (prev?.current_state || '').toUpperCase(); + const latestEventTs = prev?.events?.[prev.events.length - 1]?.ts || 0; + const transientStateFresh = latestEventTs > 0 && Date.now() - latestEventTs < 15000; const keepTransientState = bot.docker_status === 'RUNNING' && (incomingState === '' || incomingState === 'IDLE') && - (prevState === 'THINKING' || prevState === 'TOOL_CALL'); + (prevState === 'THINKING' || prevState === 'TOOL_CALL') && + transientStateFresh; const incomingAction = (bot.last_action || '').trim(); nextBots[bot.id] = { diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 266c12b..5cb5b5e 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1285,6 +1285,11 @@ electron-to-chromium@^1.5.263: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz#032a5802b31f7119269959c69fe2015d8dad5edb" integrity sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + es-define-property@^1.0.1: version "1.0.1" resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" @@ -1682,6 +1687,55 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-raw@^9.0.0: + version "9.1.0" + resolved "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e" + integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-sanitize@^5.0.0: + version "5.0.2" + resolved "https://registry.npmmirror.com/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz#edb260d94e5bba2030eb9375790a8753e5bf391f" + integrity sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg== + dependencies: + "@types/hast" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + unist-util-position "^5.0.0" + hast-util-to-jsx-runtime@^2.0.0: version "2.3.6" resolved "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" @@ -1703,6 +1757,19 @@ hast-util-to-jsx-runtime@^2.0.0: unist-util-position "^5.0.0" vfile-message "^4.0.0" +hast-util-to-parse5@^8.0.0: + version "8.0.1" + resolved "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz#95aa391cc0514b4951418d01c883d1038af42f5d" + integrity sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + hast-util-whitespace@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" @@ -1710,6 +1777,17 @@ hast-util-whitespace@^3.0.0: dependencies: "@types/hast" "^3.0.0" +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + hermes-estree@0.25.1: version "0.25.1" resolved "https://registry.npmmirror.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" @@ -1732,6 +1810,11 @@ html-url-attributes@^3.0.0: resolved "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -2556,6 +2639,13 @@ parse-entities@^4.0.0: is-decimal "^2.0.0" is-hexadecimal "^2.0.0" +parse5@^7.0.0: + version "7.3.0" + resolved "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + dependencies: + entities "^6.0.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2739,6 +2829,23 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + +rehype-sanitize@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz#16e95f4a67a69cbf0f79e113c8e0df48203db73c" + integrity sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg== + dependencies: + "@types/hast" "^3.0.0" + hast-util-sanitize "^5.0.0" + remark-gfm@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b" @@ -3197,6 +3304,14 @@ utility-types@^3.11.0: resolved "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c" integrity sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw== +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + vfile-message@^4.0.0: version "4.0.3" resolved "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" @@ -3227,6 +3342,11 @@ vite@^7.3.1: optionalDependencies: fsevents "~2.3.3" +web-namespaces@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" + integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== + webgl-constants@^1.1.1: version "1.1.1" resolved "https://registry.npmmirror.com/webgl-constants/-/webgl-constants-1.1.1.tgz#f9633ee87fea56647a60b9ce735cbdfb891c6855"