From 9d508cb28febfb4bfcaa69bfecbf174bfe549a95 Mon Sep 17 00:00:00 2001 From: sunilkumarvalmiki Date: Tue, 26 May 2026 15:56:14 +0530 Subject: [PATCH 1/2] feat(intelligence): add architecture diagram viewer Signed-off-by: sunilkumarvalmiki --- .github/workflows/test-reusable.yml | 6 +- .../intelligence/DiagramViewerTab.test.tsx | 73 ++++++++ .../intelligence/DiagramViewerTab.tsx | 160 ++++++++++++++++++ app/src/lib/i18n/chunks/ar-1.ts | 1 + app/src/lib/i18n/chunks/ar-4.ts | 13 ++ app/src/lib/i18n/chunks/bn-1.ts | 1 + app/src/lib/i18n/chunks/bn-4.ts | 13 ++ app/src/lib/i18n/chunks/de-1.ts | 1 + app/src/lib/i18n/chunks/de-4.ts | 13 ++ app/src/lib/i18n/chunks/en-1.ts | 1 + app/src/lib/i18n/chunks/en-4.ts | 13 ++ app/src/lib/i18n/chunks/es-1.ts | 1 + app/src/lib/i18n/chunks/es-4.ts | 13 ++ app/src/lib/i18n/chunks/fr-1.ts | 1 + app/src/lib/i18n/chunks/fr-4.ts | 13 ++ app/src/lib/i18n/chunks/hi-1.ts | 1 + app/src/lib/i18n/chunks/hi-4.ts | 13 ++ app/src/lib/i18n/chunks/id-1.ts | 1 + app/src/lib/i18n/chunks/id-4.ts | 13 ++ app/src/lib/i18n/chunks/it-1.ts | 1 + app/src/lib/i18n/chunks/it-4.ts | 13 ++ app/src/lib/i18n/chunks/ko-1.ts | 1 + app/src/lib/i18n/chunks/ko-4.ts | 13 ++ app/src/lib/i18n/chunks/pt-1.ts | 1 + app/src/lib/i18n/chunks/pt-4.ts | 13 ++ app/src/lib/i18n/chunks/ru-1.ts | 1 + app/src/lib/i18n/chunks/ru-4.ts | 13 ++ app/src/lib/i18n/chunks/zh-CN-1.ts | 1 + app/src/lib/i18n/chunks/zh-CN-4.ts | 13 ++ app/src/lib/i18n/en.ts | 14 ++ app/src/pages/Intelligence.tsx | 6 +- .../__tests__/Intelligence.diagram.test.tsx | 72 ++++++++ app/src/services/rpcMethods.ts | 2 + app/src/utils/tauriCommands/config.ts | 19 +++ src/core/legacy_aliases.rs | 4 + src/openhuman/config/mod.rs | 32 ++-- src/openhuman/config/ops.rs | 57 +++++++ src/openhuman/config/schema/dashboard.rs | 80 +++++++++ src/openhuman/config/schema/mod.rs | 2 + src/openhuman/config/schema/types.rs | 4 + src/openhuman/config/schemas.rs | 21 +++ .../inference/provider/factory_test.rs | 8 + src/openhuman/memory/chat.rs | 6 +- .../tools/impl/network/polymarket_tests.rs | 64 +++++-- 44 files changed, 775 insertions(+), 37 deletions(-) create mode 100644 app/src/components/intelligence/DiagramViewerTab.test.tsx create mode 100644 app/src/components/intelligence/DiagramViewerTab.tsx create mode 100644 app/src/pages/__tests__/Intelligence.diagram.test.tsx create mode 100644 src/openhuman/config/schema/dashboard.rs diff --git a/.github/workflows/test-reusable.yml b/.github/workflows/test-reusable.yml index 5de69323d9..5ab4687244 100644 --- a/.github/workflows/test-reusable.yml +++ b/.github/workflows/test-reusable.yml @@ -128,7 +128,7 @@ jobs: if: inputs.run_rust_core name: Rust Core Tests (Windows — secrets ACL) runs-on: windows-latest - timeout-minutes: 20 + timeout-minutes: 35 env: CARGO_INCREMENTAL: '0' SCCACHE_GHA_ENABLED: 'true' @@ -150,10 +150,10 @@ jobs: - name: Install sccache uses: mozilla-actions/sccache-action@v0.0.9 - name: Run Windows-specific secrets tests - # Runs the full security::secrets suite including all #[cfg(windows)] + # Runs the encrypted secret-store suite including all #[cfg(windows)] # tests: self-repair ACL path (OPENHUMAN-TAURI-GN), domain-qualified # icacls username, is_permission_error, repair_windows_acl. - run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman -- security::secrets --nocapture + run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman -- keyring::encrypted_store::tests --nocapture rust-tauri-tests: if: inputs.run_rust_tauri diff --git a/app/src/components/intelligence/DiagramViewerTab.test.tsx b/app/src/components/intelligence/DiagramViewerTab.test.tsx new file mode 100644 index 0000000000..e1b7f5e241 --- /dev/null +++ b/app/src/components/intelligence/DiagramViewerTab.test.tsx @@ -0,0 +1,73 @@ +import { fireEvent, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { renderWithProviders } from '../../test/test-utils'; +import DiagramViewerTab, { buildDiagramImageUrl } from './DiagramViewerTab'; + +vi.mock('../../utils/tauriCommands/config', () => ({ + openhumanGetDashboardSettings: vi + .fn() + .mockResolvedValue({ + result: { + diagram_viewer: { + enabled: true, + source_url: 'http://localhost:8787/workspace/diagrams/latest.png', + refresh_interval_seconds: 10, + }, + }, + logs: [], + }), +})); + +describe('buildDiagramImageUrl', () => { + it('adds a cache-busting refresh parameter to absolute URLs', () => { + expect(buildDiagramImageUrl('http://localhost:8787/latest.png?format=png', 4)).toBe( + 'http://localhost:8787/latest.png?format=png&openhuman_refresh=4' + ); + }); + + it('adds a cache-busting refresh parameter to relative URLs', () => { + expect(buildDiagramImageUrl('/workspace/diagrams/latest.png', 2)).toBe( + '/workspace/diagrams/latest.png?openhuman_refresh=2' + ); + }); +}); + +describe('DiagramViewerTab', () => { + it('refreshes the diagram image URL on demand', async () => { + renderWithProviders(); + + const image = await screen.findByRole('img', { + name: 'Latest generated OpenHuman architecture diagram', + }); + expect(image).toHaveAttribute('src', expect.stringContaining('openhuman_refresh=0')); + + fireEvent.click(screen.getByRole('button', { name: 'Refresh diagram' })); + + expect( + screen.getByRole('img', { name: 'Latest generated OpenHuman architecture diagram' }) + ).toHaveAttribute('src', expect.stringContaining('openhuman_refresh=1')); + }); + + it('shows an empty state instead of a broken image after load failure', async () => { + renderWithProviders(); + + const image = await screen.findByRole('img', { + name: 'Latest generated OpenHuman architecture diagram', + }); + fireEvent.error(image); + + expect(screen.getByText('No diagram available yet')).toBeInTheDocument(); + expect( + screen.getByText('npx skills add yizhiyanhua-ai/fireworks-tech-graph') + ).toBeInTheDocument(); + expect( + screen.getByText( + 'Generate an architecture diagram of the current swarm in dark terminal style' + ) + ).toBeInTheDocument(); + expect( + screen.queryByRole('img', { name: 'Latest generated OpenHuman architecture diagram' }) + ).not.toBeInTheDocument(); + }); +}); diff --git a/app/src/components/intelligence/DiagramViewerTab.tsx b/app/src/components/intelligence/DiagramViewerTab.tsx new file mode 100644 index 0000000000..f8102bcb83 --- /dev/null +++ b/app/src/components/intelligence/DiagramViewerTab.tsx @@ -0,0 +1,160 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { LuImage, LuRefreshCw } from 'react-icons/lu'; + +import { useT } from '../../lib/i18n/I18nContext'; +import { + type DiagramViewerSettings, + openhumanGetDashboardSettings, +} from '../../utils/tauriCommands/config'; + +const DEFAULT_SETTINGS: DiagramViewerSettings = { + enabled: true, + source_url: 'http://localhost:8787/workspace/diagrams/latest.png', + refresh_interval_seconds: 10, +}; + +type ImageState = 'idle' | 'loaded' | 'error'; + +function normalizeSettings( + settings?: Partial | null +): DiagramViewerSettings { + const sourceUrl = settings?.source_url?.trim() || DEFAULT_SETTINGS.source_url; + const refreshInterval = Number(settings?.refresh_interval_seconds); + + return { + enabled: settings?.enabled ?? DEFAULT_SETTINGS.enabled, + source_url: sourceUrl, + refresh_interval_seconds: + Number.isFinite(refreshInterval) && refreshInterval > 0 + ? Math.round(refreshInterval) + : DEFAULT_SETTINGS.refresh_interval_seconds, + }; +} + +export function buildDiagramImageUrl(sourceUrl: string, refreshKey: number): string { + try { + const url = new URL(sourceUrl); + url.searchParams.set('openhuman_refresh', String(refreshKey)); + return url.toString(); + } catch { + const separator = sourceUrl.includes('?') ? '&' : '?'; + return `${sourceUrl}${separator}openhuman_refresh=${refreshKey}`; + } +} + +export default function DiagramViewerTab() { + const { t } = useT(); + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [refreshKey, setRefreshKey] = useState(0); + const [imageState, setImageState] = useState('idle'); + + useEffect(() => { + let alive = true; + + openhumanGetDashboardSettings() + .then(response => { + if (!alive) return; + setSettings(normalizeSettings(response.result.diagram_viewer)); + }) + .catch(() => { + if (!alive) return; + setSettings(DEFAULT_SETTINGS); + }); + + return () => { + alive = false; + }; + }, []); + + const refreshDiagram = useCallback(() => { + setImageState('idle'); + setRefreshKey(prev => prev + 1); + }, []); + + useEffect(() => { + if (!settings.enabled || settings.refresh_interval_seconds <= 0) return undefined; + + const interval = window.setInterval(refreshDiagram, settings.refresh_interval_seconds * 1000); + return () => window.clearInterval(interval); + }, [refreshDiagram, settings.enabled, settings.refresh_interval_seconds]); + + const sourceUrl = settings.source_url.trim(); + const imageUrl = useMemo( + () => (sourceUrl ? buildDiagramImageUrl(sourceUrl, refreshKey) : ''), + [refreshKey, sourceUrl] + ); + + const showImage = settings.enabled && sourceUrl.length > 0 && imageState !== 'error'; + const showEmptyState = !settings.enabled || sourceUrl.length === 0 || imageState === 'error'; + + return ( +
+
+
+

+ {t('intelligence.diagram.title')} +

+

+ {t('intelligence.diagram.description')} +

+
+ +
+ + {showEmptyState && ( +
+
+
+
+

+ {t('intelligence.diagram.emptyTitle')} +

+

+ {t('intelligence.diagram.emptyDescription')} +

+
+
+ + {t('intelligence.diagram.skillInstallCommand')} + + + {t('intelligence.diagram.promptExample')} + +
+
+ )} + + {showImage && ( +
+ {t('intelligence.diagram.imageAlt')} setImageState('loaded')} + onError={() => setImageState('error')} + /> +
+ + {t('intelligence.diagram.refreshesEvery').replace( + '{seconds}', + String(settings.refresh_interval_seconds) + )} + + {sourceUrl} +
+
+ )} +
+ ); +} diff --git a/app/src/lib/i18n/chunks/ar-1.ts b/app/src/lib/i18n/chunks/ar-1.ts index 4971d7960e..abc02ca475 100644 --- a/app/src/lib/i18n/chunks/ar-1.ts +++ b/app/src/lib/i18n/chunks/ar-1.ts @@ -178,6 +178,7 @@ const ar1: TranslationMap = { 'memory.tab.subconscious': 'اللاوعي', 'memory.tab.dreams': 'الأحلام', 'memory.tab.calls': 'المكالمات', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'الإعدادات', 'memory.analyzeNow': 'تحليل الآن', 'alerts.title': 'التنبيهات', diff --git a/app/src/lib/i18n/chunks/ar-4.ts b/app/src/lib/i18n/chunks/ar-4.ts index a0edc7989c..6f91dfc672 100644 --- a/app/src/lib/i18n/chunks/ar-4.ts +++ b/app/src/lib/i18n/chunks/ar-4.ts @@ -102,6 +102,19 @@ const ar4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'مُسقَط', 'intelligence.memoryChunk.scoreBars.heading': 'س ب ب ا ل ح ف ظ', 'intelligence.memoryChunk.scoreBars.kept': 'محفوظ', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'نوع الكيان', 'intelligence.screenDebug.active': 'نشط', 'intelligence.screenDebug.app': 'التطبيق', diff --git a/app/src/lib/i18n/chunks/bn-1.ts b/app/src/lib/i18n/chunks/bn-1.ts index 249e303c59..7675280ec8 100644 --- a/app/src/lib/i18n/chunks/bn-1.ts +++ b/app/src/lib/i18n/chunks/bn-1.ts @@ -180,6 +180,7 @@ const bn1: TranslationMap = { 'memory.tab.subconscious': 'সাবকনশাস', 'memory.tab.dreams': 'স্বপ্ন', 'memory.tab.calls': 'কল', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'সেটিংস', 'memory.analyzeNow': 'এখনই বিশ্লেষণ করুন', 'alerts.title': 'সতর্কতা', diff --git a/app/src/lib/i18n/chunks/bn-4.ts b/app/src/lib/i18n/chunks/bn-4.ts index a02cc57d54..b0ff8f4dd1 100644 --- a/app/src/lib/i18n/chunks/bn-4.ts +++ b/app/src/lib/i18n/chunks/bn-4.ts @@ -103,6 +103,19 @@ const bn4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'বাদ দেওয়া হয়েছে', 'intelligence.memoryChunk.scoreBars.heading': 'কে ন রা খা', 'intelligence.memoryChunk.scoreBars.kept': 'রাখা হয়েছে', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'এনটিটি ধরন', 'intelligence.screenDebug.active': 'সক্রিয়', 'intelligence.screenDebug.app': 'অ্যাপ', diff --git a/app/src/lib/i18n/chunks/de-1.ts b/app/src/lib/i18n/chunks/de-1.ts index 3e8fb7af6a..54c52d3fb3 100644 --- a/app/src/lib/i18n/chunks/de-1.ts +++ b/app/src/lib/i18n/chunks/de-1.ts @@ -222,6 +222,7 @@ const de1: TranslationMap = { 'memory.tab.subconscious': 'Unterbewusstsein', 'memory.tab.dreams': 'Träume', 'memory.tab.calls': 'Anrufe', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Einstellungen', 'memory.analyzeNow': 'Jetzt analysieren', 'alerts.title': 'Warnungen', diff --git a/app/src/lib/i18n/chunks/de-4.ts b/app/src/lib/i18n/chunks/de-4.ts index 5a3489e3bf..c7b9b775bb 100644 --- a/app/src/lib/i18n/chunks/de-4.ts +++ b/app/src/lib/i18n/chunks/de-4.ts @@ -103,6 +103,19 @@ const de4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'fallen gelassen', 'intelligence.memoryChunk.scoreBars.heading': 'Warum hast du es behalten?', 'intelligence.memoryChunk.scoreBars.kept': 'gehalten', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Entitätstyp', 'intelligence.screenDebug.active': 'Aktiv', 'intelligence.screenDebug.app': 'App', diff --git a/app/src/lib/i18n/chunks/en-1.ts b/app/src/lib/i18n/chunks/en-1.ts index 4ef4ed2dad..38e501c26d 100644 --- a/app/src/lib/i18n/chunks/en-1.ts +++ b/app/src/lib/i18n/chunks/en-1.ts @@ -464,6 +464,7 @@ const en1: TranslationMap = { 'memory.tab.subconscious': 'Subconscious', 'memory.tab.dreams': 'Dreams', 'memory.tab.calls': 'Calls', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Settings', 'memory.analyzeNow': 'Analyze Now', 'alerts.title': 'Alerts', diff --git a/app/src/lib/i18n/chunks/en-4.ts b/app/src/lib/i18n/chunks/en-4.ts index ff06674f01..74f8950299 100644 --- a/app/src/lib/i18n/chunks/en-4.ts +++ b/app/src/lib/i18n/chunks/en-4.ts @@ -111,6 +111,19 @@ const en4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'dropped', 'intelligence.memoryChunk.scoreBars.heading': 'w h y k e p t', 'intelligence.memoryChunk.scoreBars.kept': 'kept', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Entity type', 'intelligence.screenDebug.active': 'Active', 'intelligence.screenDebug.app': 'App', diff --git a/app/src/lib/i18n/chunks/es-1.ts b/app/src/lib/i18n/chunks/es-1.ts index 0874b86d40..34d9dc5e55 100644 --- a/app/src/lib/i18n/chunks/es-1.ts +++ b/app/src/lib/i18n/chunks/es-1.ts @@ -186,6 +186,7 @@ const es1: TranslationMap = { 'memory.tab.subconscious': 'Subconsciente', 'memory.tab.dreams': 'Sueños', 'memory.tab.calls': 'Llamadas', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Configuración', 'memory.analyzeNow': 'Analizar ahora', 'alerts.title': 'Alertas', diff --git a/app/src/lib/i18n/chunks/es-4.ts b/app/src/lib/i18n/chunks/es-4.ts index 267f7639b8..6427d0e685 100644 --- a/app/src/lib/i18n/chunks/es-4.ts +++ b/app/src/lib/i18n/chunks/es-4.ts @@ -103,6 +103,19 @@ const es4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'descartado', 'intelligence.memoryChunk.scoreBars.heading': 'p o r q u é s e c o n s e r v ó', 'intelligence.memoryChunk.scoreBars.kept': 'conservado', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Tipo de entidad', 'intelligence.screenDebug.active': 'Activo', 'intelligence.screenDebug.app': 'Aplicación', diff --git a/app/src/lib/i18n/chunks/fr-1.ts b/app/src/lib/i18n/chunks/fr-1.ts index aa41875bf1..93c934e6ea 100644 --- a/app/src/lib/i18n/chunks/fr-1.ts +++ b/app/src/lib/i18n/chunks/fr-1.ts @@ -186,6 +186,7 @@ const fr1: TranslationMap = { 'memory.tab.subconscious': 'Subconscient', 'memory.tab.dreams': 'Rêves', 'memory.tab.calls': 'Appels', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Paramètres', 'memory.analyzeNow': 'Analyser maintenant', 'alerts.title': 'Alertes', diff --git a/app/src/lib/i18n/chunks/fr-4.ts b/app/src/lib/i18n/chunks/fr-4.ts index 6a11e0c301..760bf031a1 100644 --- a/app/src/lib/i18n/chunks/fr-4.ts +++ b/app/src/lib/i18n/chunks/fr-4.ts @@ -103,6 +103,19 @@ const fr4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'abandonné', 'intelligence.memoryChunk.scoreBars.heading': 'p o u r q u o i c o n s e r v é', 'intelligence.memoryChunk.scoreBars.kept': 'conservé', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': "Type d'entité", 'intelligence.screenDebug.active': 'Actif', 'intelligence.screenDebug.app': 'Application', diff --git a/app/src/lib/i18n/chunks/hi-1.ts b/app/src/lib/i18n/chunks/hi-1.ts index cd0c10e288..90c9d855b0 100644 --- a/app/src/lib/i18n/chunks/hi-1.ts +++ b/app/src/lib/i18n/chunks/hi-1.ts @@ -178,6 +178,7 @@ const hi1: TranslationMap = { 'memory.tab.subconscious': 'सबकॉन्शस', 'memory.tab.dreams': 'ड्रीम्स', 'memory.tab.calls': 'कॉल्स', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'सेटिंग्स', 'memory.analyzeNow': 'अभी एनालाइज़ करें', 'alerts.title': 'अलर्ट', diff --git a/app/src/lib/i18n/chunks/hi-4.ts b/app/src/lib/i18n/chunks/hi-4.ts index 907c988300..ddf0c3b8f3 100644 --- a/app/src/lib/i18n/chunks/hi-4.ts +++ b/app/src/lib/i18n/chunks/hi-4.ts @@ -103,6 +103,19 @@ const hi4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'हटाया गया', 'intelligence.memoryChunk.scoreBars.heading': 'क्यों रखा', 'intelligence.memoryChunk.scoreBars.kept': 'रखा गया', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'इकाई प्रकार', 'intelligence.screenDebug.active': 'एक्टिव', 'intelligence.screenDebug.app': 'ऐप', diff --git a/app/src/lib/i18n/chunks/id-1.ts b/app/src/lib/i18n/chunks/id-1.ts index 94009b1314..5198debbca 100644 --- a/app/src/lib/i18n/chunks/id-1.ts +++ b/app/src/lib/i18n/chunks/id-1.ts @@ -179,6 +179,7 @@ const id1: TranslationMap = { 'memory.tab.subconscious': 'Bawah sadar', 'memory.tab.dreams': 'Mimpi', 'memory.tab.calls': 'Panggilan', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Pengaturan', 'memory.analyzeNow': 'Analisis Sekarang', 'alerts.title': 'Peringatan', diff --git a/app/src/lib/i18n/chunks/id-4.ts b/app/src/lib/i18n/chunks/id-4.ts index aa9b33980d..e771c1a340 100644 --- a/app/src/lib/i18n/chunks/id-4.ts +++ b/app/src/lib/i18n/chunks/id-4.ts @@ -103,6 +103,19 @@ const id4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'dibuang', 'intelligence.memoryChunk.scoreBars.heading': 'm e n g a p a d i s i m p a n', 'intelligence.memoryChunk.scoreBars.kept': 'dipertahankan', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Tipe entitas', 'intelligence.screenDebug.active': 'Aktif', 'intelligence.screenDebug.app': 'Aplikasi', diff --git a/app/src/lib/i18n/chunks/it-1.ts b/app/src/lib/i18n/chunks/it-1.ts index fea7247956..6f134055b3 100644 --- a/app/src/lib/i18n/chunks/it-1.ts +++ b/app/src/lib/i18n/chunks/it-1.ts @@ -184,6 +184,7 @@ const it1: TranslationMap = { 'memory.tab.subconscious': 'Subconscio', 'memory.tab.dreams': 'Sogni', 'memory.tab.calls': 'Chiamate', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Impostazioni', 'memory.analyzeNow': 'Analizza ora', 'alerts.title': 'Avvisi', diff --git a/app/src/lib/i18n/chunks/it-4.ts b/app/src/lib/i18n/chunks/it-4.ts index b0109588fc..c250ab6637 100644 --- a/app/src/lib/i18n/chunks/it-4.ts +++ b/app/src/lib/i18n/chunks/it-4.ts @@ -103,6 +103,19 @@ const it4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'scartati', 'intelligence.memoryChunk.scoreBars.heading': 'p e r c h é t e n u t i', 'intelligence.memoryChunk.scoreBars.kept': 'tenuti', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Tipo di entità', 'intelligence.screenDebug.active': 'Attivo', 'intelligence.screenDebug.app': 'App', diff --git a/app/src/lib/i18n/chunks/ko-1.ts b/app/src/lib/i18n/chunks/ko-1.ts index 4da368dd6f..70a8ff51f4 100644 --- a/app/src/lib/i18n/chunks/ko-1.ts +++ b/app/src/lib/i18n/chunks/ko-1.ts @@ -178,6 +178,7 @@ const ko1: TranslationMap = { 'memory.tab.subconscious': '잠재의식', 'memory.tab.dreams': '꿈', 'memory.tab.calls': '통화', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': '설정', 'memory.analyzeNow': '지금 분석', 'alerts.title': '알림', diff --git a/app/src/lib/i18n/chunks/ko-4.ts b/app/src/lib/i18n/chunks/ko-4.ts index 8651e859d7..c0755ed1d8 100644 --- a/app/src/lib/i18n/chunks/ko-4.ts +++ b/app/src/lib/i18n/chunks/ko-4.ts @@ -94,6 +94,19 @@ const ko4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': '제외됨', 'intelligence.memoryChunk.scoreBars.heading': '유지된 이유', 'intelligence.memoryChunk.scoreBars.kept': '유지됨', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': '엔터티 유형', 'intelligence.screenDebug.active': '활성', 'intelligence.screenDebug.app': '앱', diff --git a/app/src/lib/i18n/chunks/pt-1.ts b/app/src/lib/i18n/chunks/pt-1.ts index a939c0e3c4..f0802a6d78 100644 --- a/app/src/lib/i18n/chunks/pt-1.ts +++ b/app/src/lib/i18n/chunks/pt-1.ts @@ -185,6 +185,7 @@ const pt1: TranslationMap = { 'memory.tab.subconscious': 'Subconsciente', 'memory.tab.dreams': 'Sonhos', 'memory.tab.calls': 'Chamadas', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Configurações', 'memory.analyzeNow': 'Analisar Agora', 'alerts.title': 'Alertas', diff --git a/app/src/lib/i18n/chunks/pt-4.ts b/app/src/lib/i18n/chunks/pt-4.ts index 2c4520ff14..0d05ce5b97 100644 --- a/app/src/lib/i18n/chunks/pt-4.ts +++ b/app/src/lib/i18n/chunks/pt-4.ts @@ -104,6 +104,19 @@ const pt4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'descartado', 'intelligence.memoryChunk.scoreBars.heading': 'p o r q u ê m a n t i d o', 'intelligence.memoryChunk.scoreBars.kept': 'mantido', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Tipo de entidade', 'intelligence.screenDebug.active': 'Ativo', 'intelligence.screenDebug.app': 'Aplicativo', diff --git a/app/src/lib/i18n/chunks/ru-1.ts b/app/src/lib/i18n/chunks/ru-1.ts index 8b20d7c140..4b8744f2ab 100644 --- a/app/src/lib/i18n/chunks/ru-1.ts +++ b/app/src/lib/i18n/chunks/ru-1.ts @@ -179,6 +179,7 @@ const ru1: TranslationMap = { 'memory.tab.subconscious': 'Подсознание', 'memory.tab.dreams': 'Сны', 'memory.tab.calls': 'Звонки', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Настройки', 'memory.analyzeNow': 'Анализировать сейчас', 'alerts.title': 'Оповещения', diff --git a/app/src/lib/i18n/chunks/ru-4.ts b/app/src/lib/i18n/chunks/ru-4.ts index f7c71e07ab..b0ad73f5dc 100644 --- a/app/src/lib/i18n/chunks/ru-4.ts +++ b/app/src/lib/i18n/chunks/ru-4.ts @@ -103,6 +103,19 @@ const ru4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'отброшено', 'intelligence.memoryChunk.scoreBars.heading': 'п о ч е м у с о х р а н е н о', 'intelligence.memoryChunk.scoreBars.kept': 'сохранено', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Тип сущности', 'intelligence.screenDebug.active': 'Активно', 'intelligence.screenDebug.app': 'Приложение', diff --git a/app/src/lib/i18n/chunks/zh-CN-1.ts b/app/src/lib/i18n/chunks/zh-CN-1.ts index 6490555059..18f8d4f6ab 100644 --- a/app/src/lib/i18n/chunks/zh-CN-1.ts +++ b/app/src/lib/i18n/chunks/zh-CN-1.ts @@ -174,6 +174,7 @@ const zhCN1: TranslationMap = { 'memory.tab.subconscious': '潜意识', 'memory.tab.dreams': '梦境', 'memory.tab.calls': '调用记录', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': '设置', 'memory.analyzeNow': '立即分析', 'alerts.title': '通知', diff --git a/app/src/lib/i18n/chunks/zh-CN-4.ts b/app/src/lib/i18n/chunks/zh-CN-4.ts index 02dc31d084..a9b945f0cb 100644 --- a/app/src/lib/i18n/chunks/zh-CN-4.ts +++ b/app/src/lib/i18n/chunks/zh-CN-4.ts @@ -100,6 +100,19 @@ const zhCN4: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': '已丢弃', 'intelligence.memoryChunk.scoreBars.heading': '保留原因', 'intelligence.memoryChunk.scoreBars.kept': '已保留', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': '实体类型', 'intelligence.screenDebug.active': '活跃', 'intelligence.screenDebug.app': '应用', diff --git a/app/src/lib/i18n/en.ts b/app/src/lib/i18n/en.ts index bcff636abb..e943d5ad72 100644 --- a/app/src/lib/i18n/en.ts +++ b/app/src/lib/i18n/en.ts @@ -270,6 +270,7 @@ const en: TranslationMap = { 'memory.tab.subconscious': 'Subconscious', 'memory.tab.dreams': 'Dreams', 'memory.tab.calls': 'Calls', + 'memory.tab.diagram': 'Diagram', 'memory.tab.settings': 'Settings', 'memory.analyzeNow': 'Analyze Now', @@ -2224,6 +2225,19 @@ const en: TranslationMap = { 'intelligence.memoryChunk.scoreBars.dropped': 'dropped', 'intelligence.memoryChunk.scoreBars.heading': 'w h y k e p t', 'intelligence.memoryChunk.scoreBars.kept': 'kept', + 'intelligence.diagram.title': 'Architecture Diagram', + 'intelligence.diagram.description': + 'Latest local architecture output from the configured diagram endpoint.', + 'intelligence.diagram.refresh': 'Refresh', + 'intelligence.diagram.refreshAria': 'Refresh diagram', + 'intelligence.diagram.emptyTitle': 'No diagram available yet', + 'intelligence.diagram.emptyDescription': + 'Generate an architecture diagram from the orchestrator and this panel will refresh from the configured local endpoint.', + 'intelligence.diagram.skillInstallCommand': 'npx skills add yizhiyanhua-ai/fireworks-tech-graph', + 'intelligence.diagram.promptExample': + 'Generate an architecture diagram of the current swarm in dark terminal style', + 'intelligence.diagram.imageAlt': 'Latest generated OpenHuman architecture diagram', + 'intelligence.diagram.refreshesEvery': 'Refreshes every {seconds}s', 'intelligence.memoryText.entityTypePrefix': 'Entity type', 'intelligence.screenDebug.active': 'Active', 'intelligence.screenDebug.app': 'App', diff --git a/app/src/pages/Intelligence.tsx b/app/src/pages/Intelligence.tsx index 73ea640cf2..23a98f56ff 100644 --- a/app/src/pages/Intelligence.tsx +++ b/app/src/pages/Intelligence.tsx @@ -1,6 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { ConfirmationModal } from '../components/intelligence/ConfirmationModal'; +import DiagramViewerTab from '../components/intelligence/DiagramViewerTab'; import IntelligenceCallsTab from '../components/intelligence/IntelligenceCallsTab'; import IntelligenceDreamsTab from '../components/intelligence/IntelligenceDreamsTab'; import IntelligenceSubconsciousTab from '../components/intelligence/IntelligenceSubconsciousTab'; @@ -22,7 +23,7 @@ import type { ToastNotification, } from '../types/intelligence'; -type IntelligenceTab = 'memory' | 'subconscious' | 'calls' | 'dreams' | 'tasks'; +type IntelligenceTab = 'memory' | 'subconscious' | 'calls' | 'dreams' | 'tasks' | 'diagram'; export default function Intelligence() { const { t } = useT(); @@ -134,6 +135,7 @@ export default function Intelligence() { { id: 'memory', label: t('memory.tab.memory') }, { id: 'subconscious', label: t('memory.tab.subconscious') }, { id: 'tasks', label: 'Tasks' }, + { id: 'diagram', label: t('memory.tab.diagram') }, { id: 'calls', label: t('memory.tab.calls') }, { id: 'dreams', label: t('memory.tab.dreams') }, ]; @@ -252,6 +254,8 @@ export default function Intelligence() { {activeTab === 'tasks' && } + {activeTab === 'diagram' && } + {activeTab === 'calls' && } {activeTab === 'dreams' && } diff --git a/app/src/pages/__tests__/Intelligence.diagram.test.tsx b/app/src/pages/__tests__/Intelligence.diagram.test.tsx new file mode 100644 index 0000000000..c815396b50 --- /dev/null +++ b/app/src/pages/__tests__/Intelligence.diagram.test.tsx @@ -0,0 +1,72 @@ +import { fireEvent, screen } from '@testing-library/react'; +import { describe, expect, it, vi } from 'vitest'; + +import { renderWithProviders } from '../../test/test-utils'; +import Intelligence from '../Intelligence'; + +vi.mock('../../components/intelligence/MemoryWorkspace', () => ({ + MemoryWorkspace: () =>
Memory workspace
, +})); + +vi.mock('../../components/intelligence/IntelligenceSubconsciousTab', () => ({ + default: () =>
Subconscious tab
, +})); + +vi.mock('../../components/intelligence/IntelligenceTasksTab', () => ({ + default: () =>
Tasks tab
, +})); + +vi.mock('../../components/intelligence/IntelligenceCallsTab', () => ({ + default: () =>
Calls tab
, +})); + +vi.mock('../../components/intelligence/IntelligenceDreamsTab', () => ({ + default: () =>
Dreams tab
, +})); + +vi.mock('../../hooks/useConsciousItems', () => ({ + useConsciousItems: () => ({ isRunning: false }), +})); + +vi.mock('../../hooks/useIntelligenceStats', () => ({ + useIntelligenceStats: () => ({ aiStatus: 'ready' }), +})); + +vi.mock('../../hooks/useMemoryIngestionStatus', () => ({ + useMemoryIngestionStatus: () => ({ status: { running: false, queueDepth: 0 } }), +})); + +const connectMock = vi.fn(); + +vi.mock('../../hooks/useIntelligenceSocket', () => ({ + useIntelligenceSocket: () => ({ isConnected: true }), + useIntelligenceSocketManager: () => ({ connect: connectMock }), +})); + +vi.mock('../../hooks/useSubconscious', () => ({ + useSubconscious: () => ({ + tasks: [], + escalations: [], + logEntries: [], + status: 'idle', + loading: false, + triggering: false, + triggerTick: vi.fn(), + addTask: vi.fn(), + removeTask: vi.fn(), + toggleTask: vi.fn(), + approveEscalation: vi.fn(), + dismissEscalation: vi.fn(), + }), +})); + +describe('Intelligence diagram tab', () => { + it('shows an architecture diagram viewer from the Intelligence tabs', () => { + renderWithProviders(); + + fireEvent.click(screen.getByRole('tab', { name: 'Diagram' })); + + expect(screen.getByRole('heading', { name: 'Architecture Diagram' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Refresh diagram' })).toBeInTheDocument(); + }); +}); diff --git a/app/src/services/rpcMethods.ts b/app/src/services/rpcMethods.ts index a5cb50490f..7603c5d091 100644 --- a/app/src/services/rpcMethods.ts +++ b/app/src/services/rpcMethods.ts @@ -3,6 +3,7 @@ export const CORE_RPC_METHODS = { configGetAnalyticsSettings: 'openhuman.config_get_analytics_settings', configGetAutonomySettings: 'openhuman.config_get_autonomy_settings', configGetComposioTriggerSettings: 'openhuman.config_get_composio_trigger_settings', + configGetDashboardSettings: 'openhuman.config_get_dashboard_settings', configGetRuntimeFlags: 'openhuman.config_get_runtime_flags', configGetSearchSettings: 'openhuman.config_get_search_settings', configUpdateSearchSettings: 'openhuman.config_update_search_settings', @@ -42,6 +43,7 @@ export type CoreRpcMethod = (typeof CORE_RPC_METHODS)[keyof typeof CORE_RPC_METH export const LEGACY_METHOD_ALIASES: Record = { 'openhuman.get_analytics_settings': CORE_RPC_METHODS.configGetAnalyticsSettings, 'openhuman.get_composio_trigger_settings': CORE_RPC_METHODS.configGetComposioTriggerSettings, + 'openhuman.get_dashboard_settings': CORE_RPC_METHODS.configGetDashboardSettings, 'openhuman.get_config': CORE_RPC_METHODS.configGet, 'openhuman.get_runtime_flags': CORE_RPC_METHODS.configGetRuntimeFlags, 'openhuman.ping': CORE_RPC_METHODS.corePing, diff --git a/app/src/utils/tauriCommands/config.ts b/app/src/utils/tauriCommands/config.ts index 9cc2b254c8..83bf078431 100644 --- a/app/src/utils/tauriCommands/config.ts +++ b/app/src/utils/tauriCommands/config.ts @@ -450,6 +450,25 @@ export interface SearchSettings { allow_all: boolean; } +export interface DiagramViewerSettings { + enabled: boolean; + source_url: string; + refresh_interval_seconds: number; +} + +export interface DashboardSettings { + diagram_viewer: DiagramViewerSettings; +} + +export async function openhumanGetDashboardSettings(): Promise> { + if (!isTauri()) { + throw new Error('Not running in Tauri'); + } + return await callCoreRpc>({ + method: CORE_RPC_METHODS.configGetDashboardSettings, + }); +} + export async function openhumanGetSearchSettings(): Promise> { if (!isTauri()) { throw new Error('Not running in Tauri'); diff --git a/src/core/legacy_aliases.rs b/src/core/legacy_aliases.rs index bb9e476438..27ced779b9 100644 --- a/src/core/legacy_aliases.rs +++ b/src/core/legacy_aliases.rs @@ -29,6 +29,10 @@ const LEGACY_ALIASES: &[(&str, &str)] = &[ "openhuman.get_composio_trigger_settings", "openhuman.config_get_composio_trigger_settings", ), + ( + "openhuman.get_dashboard_settings", + "openhuman.config_get_dashboard_settings", + ), ("openhuman.get_config", "openhuman.config_get"), ( "openhuman.get_runtime_flags", diff --git a/src/openhuman/config/mod.rs b/src/openhuman/config/mod.rs index 295695244f..8f4d1d896b 100644 --- a/src/openhuman/config/mod.rs +++ b/src/openhuman/config/mod.rs @@ -26,22 +26,22 @@ pub use schema::{ build_runtime_proxy_client_with_timeouts, output_language_directive, runtime_proxy_config, set_runtime_proxy_config, AgentConfig, AuditConfig, AutocompleteConfig, AutonomyConfig, BrowserComputerUseConfig, BrowserConfig, ChannelsConfig, ComposioConfig, Config, ContextConfig, - CostConfig, CronConfig, CurlConfig, DelegateAgentConfig, DictationActivationMode, - DictationConfig, DiscordConfig, DockerRuntimeConfig, EmbeddingRouteConfig, GitbooksConfig, - HeartbeatConfig, HttpRequestConfig, IMessageConfig, IntegrationToggle, IntegrationsConfig, - LarkConfig, LearningConfig, LlmBackend, LocalAiConfig, MatrixConfig, McpAuthConfig, - McpClientConfig, McpClientIdentityConfig, McpServerConfig, MeetConfig, MemoryConfig, - MemoryTreeConfig, ModelRouteConfig, MultimodalConfig, ObservabilityConfig, - OrchestratorModelConfig, PolymarketClobCredentials, PolymarketConfig, ProxyConfig, ProxyScope, - ReflectionSource, ReliabilityConfig, ResourceLimitsConfig, RuntimeConfig, SandboxBackend, - SandboxConfig, SchedulerConfig, SchedulerGateConfig, SchedulerGateMode, - ScreenIntelligenceConfig, SearchConfig, SearchEngine, SearchEngineCredentials, SearxngConfig, - SecretsConfig, SecurityConfig, SlackConfig, StorageConfig, StorageProviderConfig, - StorageProviderSection, StreamMode, TeamModelConfig, TelegramConfig, UpdateConfig, - UpdateRestartStrategy, VoiceActivationMode, VoiceServerConfig, WebSearchConfig, WebhookConfig, - DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MODEL, MODEL_AGENTIC_V1, MODEL_CHAT_V1, MODEL_CODING_V1, - MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, MODEL_SUMMARIZATION_V1, SEARCH_ENGINE_BRAVE, - SEARCH_ENGINE_MANAGED, SEARCH_ENGINE_PARALLEL, + CostConfig, CronConfig, CurlConfig, DashboardConfig, DelegateAgentConfig, DiagramViewerConfig, + DictationActivationMode, DictationConfig, DiscordConfig, DockerRuntimeConfig, + EmbeddingRouteConfig, GitbooksConfig, HeartbeatConfig, HttpRequestConfig, IMessageConfig, + IntegrationToggle, IntegrationsConfig, LarkConfig, LearningConfig, LlmBackend, LocalAiConfig, + MatrixConfig, McpAuthConfig, McpClientConfig, McpClientIdentityConfig, McpServerConfig, + MeetConfig, MemoryConfig, MemoryTreeConfig, ModelRouteConfig, MultimodalConfig, + ObservabilityConfig, OrchestratorModelConfig, PolymarketClobCredentials, PolymarketConfig, + ProxyConfig, ProxyScope, ReflectionSource, ReliabilityConfig, ResourceLimitsConfig, + RuntimeConfig, SandboxBackend, SandboxConfig, SchedulerConfig, SchedulerGateConfig, + SchedulerGateMode, ScreenIntelligenceConfig, SearchConfig, SearchEngine, + SearchEngineCredentials, SearxngConfig, SecretsConfig, SecurityConfig, SlackConfig, + StorageConfig, StorageProviderConfig, StorageProviderSection, StreamMode, TeamModelConfig, + TelegramConfig, UpdateConfig, UpdateRestartStrategy, VoiceActivationMode, VoiceServerConfig, + WebSearchConfig, WebhookConfig, DEFAULT_CLOUD_LLM_MODEL, DEFAULT_MODEL, MODEL_AGENTIC_V1, + MODEL_CHAT_V1, MODEL_CODING_V1, MODEL_REASONING_QUICK_V1, MODEL_REASONING_V1, + MODEL_SUMMARIZATION_V1, SEARCH_ENGINE_BRAVE, SEARCH_ENGINE_MANAGED, SEARCH_ENGINE_PARALLEL, }; pub use schema::{ clear_active_user, default_projects_dir, default_root_openhuman_dir, pre_login_user_dir, diff --git a/src/openhuman/config/ops.rs b/src/openhuman/config/ops.rs index ff82eeccef..175587f123 100644 --- a/src/openhuman/config/ops.rs +++ b/src/openhuman/config/ops.rs @@ -1129,6 +1129,63 @@ pub async fn get_search_settings() -> Result, Stri )) } +/// Reads dashboard settings exposed to the desktop UI. +pub async fn get_dashboard_settings() -> Result, String> { + let request_id = uuid::Uuid::new_v4().to_string(); + tracing::debug!( + target: "openhuman_core::config", + request_id = %request_id, + method = "openhuman.config_get_dashboard_settings", + "OPENHUMAN: get_dashboard_settings entry" + ); + tracing::debug!( + target: "openhuman_core::config", + request_id = %request_id, + method = "openhuman.config_get_dashboard_settings", + "OPENHUMAN: get_dashboard_settings loading config" + ); + + let config = load_config_with_timeout().await.map_err(|error| { + tracing::warn!( + target: "openhuman_core::config", + request_id = %request_id, + method = "openhuman.config_get_dashboard_settings", + error = %error, + "OPENHUMAN: get_dashboard_settings config load failed" + ); + error + })?; + + tracing::debug!( + target: "openhuman_core::config", + request_id = %request_id, + method = "openhuman.config_get_dashboard_settings", + "OPENHUMAN: get_dashboard_settings serializing dashboard settings" + ); + let result = serde_json::to_value(&config.dashboard).map_err(|error| { + let message = error.to_string(); + tracing::warn!( + target: "openhuman_core::config", + request_id = %request_id, + method = "openhuman.config_get_dashboard_settings", + error = %message, + "OPENHUMAN: get_dashboard_settings serialization failed" + ); + message + })?; + + tracing::debug!( + target: "openhuman_core::config", + request_id = %request_id, + method = "openhuman.config_get_dashboard_settings", + "OPENHUMAN: get_dashboard_settings exit" + ); + Ok(RpcOutcome::new( + result, + vec!["dashboard settings read".to_string()], + )) +} + /// Loads the configuration, applies browser settings updates, and saves it. pub async fn load_and_apply_browser_settings( update: BrowserSettingsPatch, diff --git a/src/openhuman/config/schema/dashboard.rs b/src/openhuman/config/schema/dashboard.rs new file mode 100644 index 0000000000..21f3a81aa9 --- /dev/null +++ b/src/openhuman/config/schema/dashboard.rs @@ -0,0 +1,80 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +fn default_diagram_viewer_enabled() -> bool { + true +} + +fn default_diagram_viewer_source_url() -> String { + "http://localhost:8787/workspace/diagrams/latest.png".to_string() +} + +fn default_diagram_viewer_refresh_interval_seconds() -> u64 { + 10 +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] +pub struct DashboardConfig { + pub diagram_viewer: DiagramViewerConfig, +} + +impl Default for DashboardConfig { + fn default() -> Self { + Self { + diagram_viewer: DiagramViewerConfig::default(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(default)] +pub struct DiagramViewerConfig { + #[serde(default = "default_diagram_viewer_enabled")] + pub enabled: bool, + #[serde(default = "default_diagram_viewer_source_url")] + pub source_url: String, + #[serde(default = "default_diagram_viewer_refresh_interval_seconds")] + pub refresh_interval_seconds: u64, +} + +impl Default for DiagramViewerConfig { + fn default() -> Self { + Self { + enabled: default_diagram_viewer_enabled(), + source_url: default_diagram_viewer_source_url(), + refresh_interval_seconds: default_diagram_viewer_refresh_interval_seconds(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dashboard_config_defaults_enable_local_diagram_viewer() { + let config = DashboardConfig::default(); + + assert!(config.diagram_viewer.enabled); + assert_eq!( + config.diagram_viewer.source_url, + "http://localhost:8787/workspace/diagrams/latest.png" + ); + assert_eq!(config.diagram_viewer.refresh_interval_seconds, 10); + } + + #[test] + fn diagram_viewer_partial_toml_uses_missing_defaults() { + let config: DashboardConfig = + toml::from_str("[diagram_viewer]\nsource_url = \"http://localhost:9000/latest.svg\"") + .expect("dashboard config should deserialize"); + + assert!(config.diagram_viewer.enabled); + assert_eq!( + config.diagram_viewer.source_url, + "http://localhost:9000/latest.svg" + ); + assert_eq!(config.diagram_viewer.refresh_interval_seconds, 10); + } +} diff --git a/src/openhuman/config/schema/mod.rs b/src/openhuman/config/schema/mod.rs index 7722e1f8bc..320c5549a1 100644 --- a/src/openhuman/config/schema/mod.rs +++ b/src/openhuman/config/schema/mod.rs @@ -13,6 +13,7 @@ mod autocomplete; mod autonomy; mod channels; mod context; +mod dashboard; mod defaults; mod dictation; mod heartbeat_cron; @@ -50,6 +51,7 @@ pub use channels::{ TelegramConfig, WebhookConfig, WhatsAppConfig, }; pub use context::ContextConfig; +pub use dashboard::{DashboardConfig, DiagramViewerConfig}; pub use dictation::{DictationActivationMode, DictationConfig}; pub use heartbeat_cron::{CronConfig, HeartbeatConfig}; pub use identity_cost::{CostConfig, ModelPricing}; diff --git a/src/openhuman/config/schema/types.rs b/src/openhuman/config/schema/types.rs index 81999c11d4..3e04190e64 100644 --- a/src/openhuman/config/schema/types.rs +++ b/src/openhuman/config/schema/types.rs @@ -79,6 +79,9 @@ pub struct Config { #[serde(default)] pub observability: ObservabilityConfig, + #[serde(default)] + pub dashboard: DashboardConfig, + #[serde(default)] pub autonomy: AutonomyConfig, @@ -592,6 +595,7 @@ impl Default for Config { output_language: None, temperature_unsupported_models: default_temperature_unsupported_models(), observability: ObservabilityConfig::default(), + dashboard: DashboardConfig::default(), autonomy: AutonomyConfig::default(), runtime: RuntimeConfig::default(), screen_intelligence: ScreenIntelligenceConfig::default(), diff --git a/src/openhuman/config/schemas.rs b/src/openhuman/config/schemas.rs index c6b32fa4b7..d3dcabea4b 100644 --- a/src/openhuman/config/schemas.rs +++ b/src/openhuman/config/schemas.rs @@ -236,6 +236,7 @@ pub fn all_controller_schemas() -> Vec { schemas("workspace_onboarding_flag_set"), schemas("update_analytics_settings"), schemas("get_analytics_settings"), + schemas("get_dashboard_settings"), schemas("update_meet_settings"), schemas("get_meet_settings"), schemas("agent_server_status"), @@ -318,6 +319,10 @@ pub fn all_registered_controllers() -> Vec { schema: schemas("get_analytics_settings"), handler: handle_get_analytics_settings, }, + RegisteredController { + schema: schemas("get_dashboard_settings"), + handler: handle_get_dashboard_settings, + }, RegisteredController { schema: schemas("update_meet_settings"), handler: handle_update_meet_settings, @@ -769,6 +774,18 @@ pub fn schemas(function: &str) -> ControllerSchema { required: true, }], }, + "get_dashboard_settings" => ControllerSchema { + namespace: "config", + function: "get_dashboard_settings", + description: "Read dashboard settings, including the local architecture diagram viewer.", + inputs: vec![], + outputs: vec![FieldSchema { + name: "dashboard", + ty: TypeSchema::Json, + comment: "Current [dashboard] config block.", + required: true, + }], + }, "update_meet_settings" => ControllerSchema { namespace: "config", function: "update_meet_settings", @@ -1321,6 +1338,10 @@ fn handle_get_analytics_settings(_params: Map) -> ControllerFutur }) } +fn handle_get_dashboard_settings(_params: Map) -> ControllerFuture { + Box::pin(async { to_json(config_rpc::get_dashboard_settings().await?) }) +} + fn handle_update_meet_settings(params: Map) -> ControllerFuture { Box::pin(async move { log::debug!("[config][rpc] update_meet_settings enter"); diff --git a/src/openhuman/inference/provider/factory_test.rs b/src/openhuman/inference/provider/factory_test.rs index 3706b00e1c..5b9ecef513 100644 --- a/src/openhuman/inference/provider/factory_test.rs +++ b/src/openhuman/inference/provider/factory_test.rs @@ -770,6 +770,14 @@ fn make_openhuman_backend_forwards_unknown_hint_verbatim() { } } +#[test] +fn make_openhuman_backend_translates_summarization_hint() { + let mut config = Config::default(); + config.default_model = Some("hint:summarization".to_string()); + let (_, model) = make_openhuman_backend(&config).expect("factory should succeed"); + assert_eq!(model, crate::openhuman::config::MODEL_SUMMARIZATION_V1); +} + #[test] fn make_openhuman_backend_falls_back_for_invalid_model() { // An invalid default_model must not be forwarded to the backend. diff --git a/src/openhuman/memory/chat.rs b/src/openhuman/memory/chat.rs index 900a3cfa07..437a39fe4d 100644 --- a/src/openhuman/memory/chat.rs +++ b/src/openhuman/memory/chat.rs @@ -216,9 +216,9 @@ mod tests { let cfg = Config::default(); let (_provider, model) = build_chat_runtime(&cfg).unwrap(); // build_chat_runtime resolves the "summarization" role, which gained a - // dedicated tier (summarization-v1) — the role no longer falls back to - // the generic reasoning-v1 default. - assert_eq!(model, "summarization-v1"); + // dedicated tier through DEFAULT_CLOUD_LLM_MODEL — the role no longer + // falls back to the generic reasoning-v1 default. + assert_eq!(model, DEFAULT_CLOUD_LLM_MODEL); } #[test] diff --git a/src/openhuman/tools/impl/network/polymarket_tests.rs b/src/openhuman/tools/impl/network/polymarket_tests.rs index a16009df24..49c7b97a66 100644 --- a/src/openhuman/tools/impl/network/polymarket_tests.rs +++ b/src/openhuman/tools/impl/network/polymarket_tests.rs @@ -243,13 +243,10 @@ fn pop_response( routes: &mut HashMap>, target: &str, ) -> MockResponse { - if let Some(response) = pop_from_queue(routes.get_mut(target)) { - return response; - } - - let path_only = target.split('?').next().unwrap_or(target); - if let Some(response) = pop_from_queue(routes.get_mut(path_only)) { - return response; + for key in route_lookup_keys(target) { + if let Some(response) = pop_from_queue(routes.get_mut(&key)) { + return response; + } } MockResponse { @@ -259,6 +256,36 @@ fn pop_response( } } +fn route_lookup_keys(target: &str) -> Vec { + let mut keys = Vec::new(); + push_lookup_key(&mut keys, target.to_string()); + push_path_only_key(&mut keys, target); + + if let Ok(url) = reqwest::Url::parse(target) { + let mut normalized = url.path().to_string(); + if let Some(query) = url.query() { + normalized.push('?'); + normalized.push_str(query); + } + push_lookup_key(&mut keys, normalized.clone()); + push_path_only_key(&mut keys, &normalized); + } + + keys +} + +fn push_path_only_key(keys: &mut Vec, target: &str) { + if let Some(path_only) = target.split('?').next() { + push_lookup_key(keys, path_only.to_string()); + } +} + +fn push_lookup_key(keys: &mut Vec, key: String) { + if !keys.iter().any(|existing| existing == &key) { + keys.push(key); + } +} + fn pop_from_queue(queue: Option<&mut VecDeque>) -> Option { let queue = queue?; if queue.len() <= 1 { @@ -281,6 +308,16 @@ fn reason_phrase(status: u16) -> &'static str { } } +#[test] +fn route_lookup_keys_normalizes_absolute_targets() { + let keys = route_lookup_keys("http://127.0.0.1:1234/nonce?user=0xabc"); + + assert!(keys.contains(&"http://127.0.0.1:1234/nonce?user=0xabc".to_string())); + assert!(keys.contains(&"http://127.0.0.1:1234/nonce".to_string())); + assert!(keys.contains(&"/nonce?user=0xabc".to_string())); + assert!(keys.contains(&"/nonce".to_string())); +} + fn parse_tool_output(result: &ToolResult) -> Value { serde_json::from_str::(&result.output()).expect("tool output should be valid json") } @@ -770,10 +807,9 @@ async fn place_order_happy_path_posts_signed_order() { .expect("wallet setup"); let mut routes = HashMap::new(); - routes.insert( - format!("/nonce?user={user}"), - vec![MockResponse::body(200, r#"{"nonce": 42}"#)], - ); + let nonce_response = vec![MockResponse::body(200, r#"{"nonce": 42}"#)]; + routes.insert(format!("/nonce?user={user}"), nonce_response.clone()); + routes.insert("/nonce".to_string(), nonce_response); routes.insert( "/order".to_string(), vec![MockResponse::body( @@ -812,10 +848,11 @@ async fn place_order_happy_path_posts_signed_order() { .await .unwrap(); + let requests = captured.lock().unwrap().clone(); assert!( !result.is_error, - "expected success, got: {}", - result.output() + "expected success, got: {}\nobserved requests: {requests:#?}", + result.output(), ); let output = parse_tool_output(&result); assert_eq!(output["action"], "place_order"); @@ -823,7 +860,6 @@ async fn place_order_happy_path_posts_signed_order() { assert_eq!(output["data"]["success"], true); assert_eq!(output["data"]["orderID"], "ord-test-1"); - let requests = captured.lock().unwrap().clone(); assert_eq!(requests.len(), 2); let nonce_request = &requests[0]; From 4cf6c32a978cf04e0371f3471c740f2d5c716a38 Mon Sep 17 00:00:00 2001 From: sanil-23 Date: Thu, 28 May 2026 16:59:09 +0200 Subject: [PATCH 2/2] chore(pr-fix): sync app/src-tauri/Cargo.lock after merge `cargo check` resolved a transitive unicode-normalization dep into the Tauri shell lockfile after merging main into pr/2687. --- app/src-tauri/Cargo.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index 7e6f73e10b..05750db2c5 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -5306,6 +5306,7 @@ dependencies = [ "tracing-appender", "tracing-log", "tracing-subscriber", + "unicode-normalization", "unicode-segmentation", "unicode-width", "url",