From b17782eec2ef414fc98b378f8274328f3024303c Mon Sep 17 00:00:00 2001 From: javad Date: Mon, 12 Oct 2026 19:26:46 +0330 Subject: [PATCH] Add Didar connections and a data hub for attaching sources. The assistant can pick connectors and show subagent steps, and the databases page is replaced by connections, sources, and a dashboard. Co-authored-by: Cursor --- nginx.conf | 4 +- public/brands/didar.png | Bin 0 -> 1856 bytes src/app/nav.test.ts | 18 +- src/app/nav.ts | 12 +- src/app/providers/FeatureFlagProvider.tsx | 6 + src/app/router.tsx | 20 +- src/components/layout/AppSidebar.tsx | 9 +- src/components/layout/MobileNav.tsx | 9 +- src/components/layout/PrimarySidebar.tsx | 4 +- src/components/ui/SelectMenu.tsx | 11 +- .../assistant/adapters/historyToMessages.ts | 30 ++ .../assistant/adapters/stepLabels.test.ts | 7 + src/features/assistant/adapters/stepLabels.ts | 9 +- src/features/assistant/api/chat.api.ts | 2 +- .../assistant/components/HtmlPreview.test.tsx | 17 +- .../assistant/components/HtmlPreview.tsx | 13 +- .../components/StepTimeline.test.tsx | 32 ++ .../assistant/components/StepTimeline.tsx | 41 +- .../execution/ExecutionPanel.test.tsx | 23 ++ .../assistant/execution/ExecutionPanel.tsx | 25 +- .../assistant/execution/ManusStepCard.tsx | 7 +- .../assistant/execution/StepDetailCard.tsx | 41 +- .../assistant/execution/SubagentLane.tsx | 230 +++++++++++ .../assistant/execution/toolIcons.tsx | 2 + .../assistant/input/EnhancedChatInput.tsx | 47 ++- .../assistant/input/ResourcePicker.test.tsx | 25 ++ .../assistant/input/ResourcePicker.tsx | 170 +++++--- src/features/assistant/input/ToolsBar.tsx | 9 + src/features/assistant/input/toolbarChip.tsx | 1 + .../session-files/AttachmentRail.tsx | 32 +- .../assistant/stream/downloadFiles.test.ts | 25 ++ .../assistant/stream/downloadFiles.ts | 14 + .../assistant/stream/eventReducer.test.ts | 87 ++++ src/features/assistant/stream/eventReducer.ts | 114 ++++++ src/features/assistant/stream/useRunStream.ts | 9 +- src/features/assistant/types/chat.ts | 3 + .../workspace/ChatMessageList.test.tsx | 28 ++ .../assistant/workspace/ChatMessageList.tsx | 9 +- .../workspace/ConversationContextBar.tsx | 42 +- .../assistant/workspace/SessionTurn.tsx | 22 +- src/features/connectors/ConnectDidarModal.tsx | 149 +++++++ .../connectors/ConnectorsPage.test.tsx | 54 +++ src/features/connectors/ConnectorsPage.tsx | 264 ++++++++++++ src/features/connectors/DidarMark.tsx | 9 + src/features/connectors/api.ts | 91 +++++ src/features/data/ConnectionsPage.tsx | 244 +++++++++++ src/features/data/DataDashboardPage.tsx | 357 ++++++++++++++++ src/features/data/DataHubLayout.test.tsx | 41 ++ src/features/data/DataHubLayout.tsx | 65 +++ src/features/data/SourcesPage.tsx | 380 ++++++++++++++++++ .../landing/components/FeaturePillars.tsx | 6 +- src/features/settings/SettingsLayout.tsx | 9 +- src/i18n/en.ts | 88 +++- src/i18n/fa.ts | 88 +++- src/pages/AssistantPage.tsx | 115 ++++-- src/pages/DatabasesPage.tsx | 356 ---------------- src/pages/ShareReportPage.test.tsx | 18 +- src/pages/ShareReportPage.tsx | 13 +- vite.config.ts | 9 +- 59 files changed, 2968 insertions(+), 597 deletions(-) create mode 100644 public/brands/didar.png create mode 100644 src/features/assistant/components/StepTimeline.test.tsx create mode 100644 src/features/assistant/execution/ExecutionPanel.test.tsx create mode 100644 src/features/assistant/execution/SubagentLane.tsx create mode 100644 src/features/connectors/ConnectDidarModal.tsx create mode 100644 src/features/connectors/ConnectorsPage.test.tsx create mode 100644 src/features/connectors/ConnectorsPage.tsx create mode 100644 src/features/connectors/DidarMark.tsx create mode 100644 src/features/connectors/api.ts create mode 100644 src/features/data/ConnectionsPage.tsx create mode 100644 src/features/data/DataDashboardPage.tsx create mode 100644 src/features/data/DataHubLayout.test.tsx create mode 100644 src/features/data/DataHubLayout.tsx create mode 100644 src/features/data/SourcesPage.tsx delete mode 100644 src/pages/DatabasesPage.tsx diff --git a/nginx.conf b/nginx.conf index ba83fc9..3b41585 100644 --- a/nginx.conf +++ b/nginx.conf @@ -18,7 +18,9 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Connection ""; + # Variable proxy_pass plus keepalive reuses a socket that is still + # writing SSE. The next chat is written onto that socket and both die. + proxy_set_header Connection "close"; proxy_buffering off; proxy_cache off; proxy_read_timeout 3600s; diff --git a/public/brands/didar.png b/public/brands/didar.png new file mode 100644 index 0000000000000000000000000000000000000000..b619abd361cea1c2265299f0fef544496e085034 GIT binary patch literal 1856 zcmaJ?X;4#F6i$dNvWWp@snthNRV?T^ws@4b7^ne%<;+;hJ>FDooG z(8AQg6obK7@PoMF=p2H+HYP^s*CqI29Xi<~0YYRm6pLuYDiGr*g^Wq{K@YQiAU~fTd-k1PTxl16sunrJAW_6J~jt=vwb46M)$e zB%V$9Sd>r@25=x12>6g_LW5FAqdPQlQkL*N#jX^RB>b~gTWwEXk;3Vh(-|A$x1}5B`VcU za|~QiEm6r~L=GtdJ)<}VN<`QMwAhckP{5yQmFiEOLVHHmieWO9MA7eRE>Ix&|4@bE zbF>->2fx(&pTz2jWEdoegK8*IB|*kvXjN3zji2RxbzzjArJ7DtQtp<*6VAFuQA}eEwRz=jHo^mCj#_ z&%9VNS`1l#w*NZoITtD*{c={BdX=KaXZeFlRP!oS+!^v#9C~MJ_*}mTZO<*?wxA_` zc8|L=7e_30I1VDz`cmGVQ`cIC?{MlLnH=AD(A3@S#(w|i@V$IIx477n>v-p;TW4N$ zR*>6)<@l8S*`$KNU}AG0YuDKqeTVl>ls$Mp{A^JlJubEk`lIx{X2#wNpXFLz?Y?ju zg<>T)v0Jn(V$Ck!qVn}kmO#SU=tFHbZ1sAk7>z8^y~g?`#H~ z>1(g>LR@02aTyA~Z^22k;|7_#U4p0H;9v|rn?IfHY3bYf!4meymbPdf2G_1MHVHq^ z+Pu!fq)VzBY{pmZ0}dth{ESVdw_IlpEPa=>bqlkub&~L?waC2v<%RXh>)v{#KlV;} zW!!I&Y8IJlWOHN$&aDgKG}(;u-_~WF#1*D*S~@ZC#3Qk1)lY`6@(gkIL52X4 zv5quuLQe+Pxo5FxZ`fIroy9tY|3c7JnvU-`O}DIxCDq{ea!R1V4+6im5Wr4BzvE=dm4i1^Qv&1gn+;7cAqEX*wm}0B*UsZ^= zUftAr)2;mSz(p%!E`J;w`t+VdDV7RZyV4(%7Uv(z0G-|M3rXcqPsQ^zQx-tG6K`ha za6MT#{*qeaA)g34jX0Im=M~-WKJ|lhx1*zh`nu`DyDU=Qfoql?HWhz4=eLCqac<{K zEOf<@>>T%xl`$^Mdqo>-Wy@4TSBvEOOWEn)_d@4#VmVbjTidE^B{prtKx?*0gz3y_ z;ns{VH;NkhW!Sx9u(Xk2dnMT0p!;vz!J@jt=fkz4mcUJ3V^c??EX;P> ziTAMH&A9@Oahi literal 0 HcmV?d00001 diff --git a/src/app/nav.test.ts b/src/app/nav.test.ts index 0d8bc57..504634e 100644 --- a/src/app/nav.test.ts +++ b/src/app/nav.test.ts @@ -4,7 +4,12 @@ import { isNavActive, pageTitleFromPath, PRIMARY_NAV } from './nav' describe('app navigation', () => { it('names primary surfaces including the skills catalog', () => { expect(pageTitleFromPath('/assistant')).toBe('دستیار هوشمند') - expect(pageTitleFromPath('/databases')).toBe('پایگاه داده') + expect(pageTitleFromPath('/data')).toBe('داشبورد داده') + expect(pageTitleFromPath('/data/files')).toBe('فایل‌ها') + expect(pageTitleFromPath('/data/databases')).toBe('دیتابیس') + expect(pageTitleFromPath('/data/connectors')).toBe('اتصالات') + expect(pageTitleFromPath('/data/map')).toBe('نقشه سازمان') + expect(pageTitleFromPath('/databases')).toBe('دیتابیس') expect(pageTitleFromPath('/knowledge')).toBe('دانش') expect(pageTitleFromPath('/skills')).toBe('مهارت‌ها') expect(pageTitleFromPath('/settings')).toBe('تنظیمات') @@ -14,4 +19,15 @@ describe('app navigation', () => { expect(PRIMARY_NAV.some((item) => item.id === 'skills' && item.to === '/skills')).toBe(true) expect(isNavActive('/skills', { to: '/skills', match: ['/skills'] })).toBe(true) }) + + it('keeps sources, connections, and the data map under one داده item', () => { + const data = PRIMARY_NAV.find((item) => item.id === 'data') + expect(data?.to).toBe('/data') + expect(data && isNavActive('/data', data)).toBe(true) + expect(data && isNavActive('/data/files', data)).toBe(true) + expect(data && isNavActive('/data/databases', data)).toBe(true) + expect(data && isNavActive('/data/connectors', data)).toBe(true) + expect(data && isNavActive('/data/map', data)).toBe(true) + expect(data && isNavActive('/data-map', data)).toBe(true) + }) }) diff --git a/src/app/nav.ts b/src/app/nav.ts index c5931f9..fa51bc8 100644 --- a/src/app/nav.ts +++ b/src/app/nav.ts @@ -2,7 +2,7 @@ export const SIDEBAR_EXPANDED = 260 export const SIDEBAR_COLLAPSED = 52 export const SIDEBAR_COLLAPSED_STORAGE_KEY = 'kamidso.sidebarCollapsed' -export type NavId = 'assistant' | 'data-map' | 'databases' | 'knowledge' | 'skills' | 'settings' +export type NavId = 'assistant' | 'data' | 'knowledge' | 'skills' | 'settings' export type NavItem = { id: NavId @@ -14,8 +14,7 @@ export type NavItem = { export const PRIMARY_NAV: NavItem[] = [ { id: 'assistant', to: '/assistant', label: 'دستیار هوشمند', match: ['/assistant'] }, - { id: 'data-map', to: '/data-map', label: 'نقشه داده', match: ['/data-map'] }, - { id: 'databases', to: '/databases', label: 'پایگاه داده', match: ['/databases'] }, + { id: 'data', to: '/data', label: 'داده', match: ['/data', '/data-map', '/databases'] }, { id: 'knowledge', to: '/knowledge', label: 'دانش', match: ['/knowledge'] }, { id: 'skills', to: '/skills', label: 'مهارت‌ها', match: ['/skills'] }, ] @@ -29,8 +28,11 @@ export const SETTINGS_NAV: NavItem = { export function pageTitleFromPath(pathname: string): string { if (pathname === '/home' || pathname.startsWith('/assistant')) return 'دستیار هوشمند' - if (pathname.startsWith('/data-map')) return 'نقشه داده' - if (pathname.startsWith('/databases')) return 'پایگاه داده' + if (pathname.startsWith('/data/map') || pathname.startsWith('/data-map')) return 'نقشه سازمان' + if (pathname.startsWith('/data/files') || pathname.startsWith('/data/sources')) return 'فایل‌ها' + if (pathname.startsWith('/data/databases') || pathname.startsWith('/data/connections') || pathname.startsWith('/databases')) return 'دیتابیس' + if (pathname.startsWith('/data/connectors') || pathname.startsWith('/settings/connectors')) return 'اتصالات' + if (pathname.startsWith('/data')) return 'داشبورد داده' if (pathname.startsWith('/knowledge')) return 'دانش' if (pathname.startsWith('/skills')) return 'مهارت‌ها' if (pathname.startsWith('/settings')) return 'تنظیمات' diff --git a/src/app/providers/FeatureFlagProvider.tsx b/src/app/providers/FeatureFlagProvider.tsx index 6f4021b..6392ae1 100644 --- a/src/app/providers/FeatureFlagProvider.tsx +++ b/src/app/providers/FeatureFlagProvider.tsx @@ -23,6 +23,7 @@ export type FeatureFlags = { widgets: boolean radar: boolean new_agent_workspace: boolean + connectors: boolean } const DEFAULT_FLAGS: FeatureFlags = { @@ -38,6 +39,7 @@ const DEFAULT_FLAGS: FeatureFlags = { widgets: false, radar: false, new_agent_workspace: true, + connectors: false, } type FeatureFlagContextValue = { @@ -101,3 +103,7 @@ export function useFeatureFlags() { if (!ctx) throw new Error('useFeatureFlags must be used within FeatureFlagProvider') return ctx } + +export function useFeatureFlagsOptional() { + return useContext(FeatureFlagContext) +} diff --git a/src/app/router.tsx b/src/app/router.tsx index 975db78..9f6cae3 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -1,14 +1,18 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom' import { AppShell } from '../components/layout/AppShell' import { AssistantPage } from '../pages/AssistantPage' -import { DatabasesPage } from '../pages/DatabasesPage' import { DataMapPage } from '../features/data-map/DataMapPage' +import { ConnectionsPage } from '../features/data/ConnectionsPage' +import { DataDashboardPage } from '../features/data/DataDashboardPage' +import { DataHubLayout } from '../features/data/DataHubLayout' +import { SourcesPage } from '../features/data/SourcesPage' import { KnowledgePage } from '../pages/KnowledgePage' import { KnowledgeDetailPage } from '../pages/KnowledgeDetailPage' import { SkillsPage } from '../pages/SkillsPage' import { SettingsPage } from '../pages/SettingsPage' import { OrganizationProfilePage } from '../features/settings/OrganizationProfilePage' import { SkillSettingsPage } from '../features/settings/SkillSettingsPage' +import { ConnectorsPage } from '../features/connectors/ConnectorsPage' import { LandingPage } from '../features/landing/pages/LandingPage' import { ShareReportPage } from '../pages/ShareReportPage' import { AuthProvider } from '../features/auth/AuthContext' @@ -37,13 +41,23 @@ export function AppRouter() { } /> } /> - } /> - } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + } /> } /> } /> } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/layout/AppSidebar.tsx b/src/components/layout/AppSidebar.tsx index d4cb6a3..58a4703 100644 --- a/src/components/layout/AppSidebar.tsx +++ b/src/components/layout/AppSidebar.tsx @@ -3,7 +3,6 @@ import { useCallback, useEffect, useRef } from 'react' import { BookOpen, Database, - Network, Loader2, LogOut, MessageSquarePlus, @@ -19,8 +18,7 @@ import { useLogoutConfirm } from '../../features/auth/useLogoutConfirm' const ICONS = { assistant: Sparkles, - 'data-map': Network, - databases: Database, + data: Database, knowledge: BookOpen, skills: WandSparkles, } as const @@ -117,7 +115,10 @@ export function AppSidebar({ ) : null} { + event.preventDefault() + goNewChat() + }} className={`flex min-w-0 items-center gap-2.5 rounded-xl px-1 py-1 ${compact ? 'justify-center' : 'flex-1'}`} > diff --git a/src/components/layout/MobileNav.tsx b/src/components/layout/MobileNav.tsx index 414116d..667f6f2 100644 --- a/src/components/layout/MobileNav.tsx +++ b/src/components/layout/MobileNav.tsx @@ -1,25 +1,24 @@ -import { BookOpen, Database, House, Network, Settings, Sparkles, WandSparkles } from 'lucide-react' +import { BookOpen, Database, House, Settings, Sparkles, WandSparkles } from 'lucide-react' import { NavLink, useLocation } from 'react-router-dom' import { isNavActive, PRIMARY_NAV } from '../../app/nav' const ICONS = { home: House, assistant: Sparkles, - 'data-map': Network, - databases: Database, + data: Database, knowledge: BookOpen, skills: WandSparkles, settings: Settings, } as const -const MOBILE_IDS = ['assistant', 'data-map', 'databases', 'knowledge', 'skills'] as const +const MOBILE_IDS = ['assistant', 'data', 'knowledge', 'skills'] as const export function MobileNav() { const { pathname } = useLocation() const items = PRIMARY_NAV.filter((item) => MOBILE_IDS.includes(item.id as (typeof MOBILE_IDS)[number])) return ( -