@docyrus/ui-pro-ai-assistant 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +26161 -0
- package/dist/index.js.map +1 -0
- package/package.json +155 -0
- package/src/components/assistant-animations.tsx +29 -0
- package/src/components/assistant-dialogs.tsx +235 -0
- package/src/components/code-view.tsx +278 -0
- package/src/components/create-agent-task.tsx +104 -0
- package/src/components/create-new-work-version.tsx +30 -0
- package/src/components/extract-web.tsx +160 -0
- package/src/components/forward-to-agent.tsx +90 -0
- package/src/components/generate-chart.tsx +101 -0
- package/src/components/generative-action-button.tsx +122 -0
- package/src/components/generative-tool.tsx +685 -0
- package/src/components/generative-ui-object.tsx +210 -0
- package/src/components/input-area.tsx +1209 -0
- package/src/components/json-schema-layout.tsx +326 -0
- package/src/components/list-item-card.tsx +92 -0
- package/src/components/mermaid-diagram.tsx +192 -0
- package/src/components/preview-image.tsx +47 -0
- package/src/components/request-approval.tsx +48 -0
- package/src/components/request-user-input.tsx +270 -0
- package/src/components/search-web.tsx +319 -0
- package/src/components/sheet-command.tsx +88 -0
- package/src/components/shell-canvas.tsx +122 -0
- package/src/components/show-advanced-data-table.tsx +352 -0
- package/src/components/show-generated-content-options.tsx +93 -0
- package/src/components/show-people-cards.tsx +180 -0
- package/src/components/subagent-tool.tsx +180 -0
- package/src/components/text-editor-tool.tsx +328 -0
- package/src/components/work-canvas.tsx +88 -0
- package/src/components/work-card.tsx +42 -0
- package/src/declarations.d.ts +1 -0
- package/src/docy-assistant.tsx +1962 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-assistant-api.ts +507 -0
- package/src/hooks/use-deployment-data.ts +162 -0
- package/src/hooks/use-project-state.ts +347 -0
- package/src/hooks/use-session-state.ts +207 -0
- package/src/hooks/use-speech-recognition.ts +137 -0
- package/src/hooks/use-ui-state.ts +185 -0
- package/src/hooks/use-works-state.ts +146 -0
- package/src/i18n/context.tsx +253 -0
- package/src/i18n/index.ts +19 -0
- package/src/i18n/locales/de.json +198 -0
- package/src/i18n/locales/el.json +198 -0
- package/src/i18n/locales/en.json +226 -0
- package/src/i18n/locales/es.json +198 -0
- package/src/i18n/locales/fr.json +198 -0
- package/src/i18n/locales/it.json +198 -0
- package/src/i18n/locales/pt.json +198 -0
- package/src/i18n/locales/sl.json +198 -0
- package/src/i18n/locales/tr.json +211 -0
- package/src/i18n/types.ts +23 -0
- package/src/i18n/use-translation.ts +17 -0
- package/src/index.ts +18 -0
- package/src/internal/plate-editor/editor/auth-context.ts +11 -0
- package/src/internal/plate-editor/editor/editor-base-kit.tsx +39 -0
- package/src/internal/plate-editor/editor/editor-kit.tsx +89 -0
- package/src/internal/plate-editor/editor/plate-editor.tsx +75 -0
- package/src/internal/plate-editor/editor/plate-types.ts +126 -0
- package/src/internal/plate-editor/editor/plugins/ai-kit.tsx +172 -0
- package/src/internal/plate-editor/editor/plugins/autoformat-kit.tsx +211 -0
- package/src/internal/plate-editor/editor/plugins/basic-blocks-base-kit.tsx +26 -0
- package/src/internal/plate-editor/editor/plugins/basic-blocks-kit.tsx +51 -0
- package/src/internal/plate-editor/editor/plugins/basic-marks-base-kit.tsx +24 -0
- package/src/internal/plate-editor/editor/plugins/basic-marks-kit.tsx +38 -0
- package/src/internal/plate-editor/editor/plugins/basic-nodes-kit.tsx +6 -0
- package/src/internal/plate-editor/editor/plugins/block-menu-kit.tsx +14 -0
- package/src/internal/plate-editor/editor/plugins/block-placeholder-kit.tsx +17 -0
- package/src/internal/plate-editor/editor/plugins/block-selection-kit.tsx +31 -0
- package/src/internal/plate-editor/editor/plugins/callout-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/callout-kit.tsx +7 -0
- package/src/internal/plate-editor/editor/plugins/code-block-base-kit.tsx +23 -0
- package/src/internal/plate-editor/editor/plugins/code-block-kit.tsx +26 -0
- package/src/internal/plate-editor/editor/plugins/column-base-kit.tsx +8 -0
- package/src/internal/plate-editor/editor/plugins/column-kit.tsx +7 -0
- package/src/internal/plate-editor/editor/plugins/comment-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/comment-kit.tsx +174 -0
- package/src/internal/plate-editor/editor/plugins/copilot-kit.tsx +68 -0
- package/src/internal/plate-editor/editor/plugins/cursor-overlay-kit.tsx +13 -0
- package/src/internal/plate-editor/editor/plugins/date-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/date-kit.tsx +7 -0
- package/src/internal/plate-editor/editor/plugins/discussion-kit.tsx +36 -0
- package/src/internal/plate-editor/editor/plugins/dnd-kit.tsx +27 -0
- package/src/internal/plate-editor/editor/plugins/docx-export-kit.tsx +43 -0
- package/src/internal/plate-editor/editor/plugins/docx-kit.tsx +6 -0
- package/src/internal/plate-editor/editor/plugins/emoji-kit.tsx +15 -0
- package/src/internal/plate-editor/editor/plugins/exit-break-kit.tsx +12 -0
- package/src/internal/plate-editor/editor/plugins/floating-toolbar-kit.tsx +19 -0
- package/src/internal/plate-editor/editor/plugins/font-base-kit.tsx +36 -0
- package/src/internal/plate-editor/editor/plugins/font-kit.tsx +47 -0
- package/src/internal/plate-editor/editor/plugins/indent-base-kit.tsx +19 -0
- package/src/internal/plate-editor/editor/plugins/indent-kit.tsx +22 -0
- package/src/internal/plate-editor/editor/plugins/link-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/link-kit.tsx +35 -0
- package/src/internal/plate-editor/editor/plugins/list-base-kit.tsx +24 -0
- package/src/internal/plate-editor/editor/plugins/list-kit.tsx +27 -0
- package/src/internal/plate-editor/editor/plugins/markdown-kit.tsx +18 -0
- package/src/internal/plate-editor/editor/plugins/math-base-kit.tsx +8 -0
- package/src/internal/plate-editor/editor/plugins/math-kit.tsx +10 -0
- package/src/internal/plate-editor/editor/plugins/media-base-kit.tsx +37 -0
- package/src/internal/plate-editor/editor/plugins/media-kit.tsx +53 -0
- package/src/internal/plate-editor/editor/plugins/mention-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/mention-kit.tsx +36 -0
- package/src/internal/plate-editor/editor/plugins/slash-kit.tsx +17 -0
- package/src/internal/plate-editor/editor/plugins/suggestion-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/suggestion-kit.tsx +95 -0
- package/src/internal/plate-editor/editor/plugins/table-base-kit.tsx +20 -0
- package/src/internal/plate-editor/editor/plugins/table-kit.tsx +22 -0
- package/src/internal/plate-editor/editor/plugins/toc-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/toc-kit.tsx +14 -0
- package/src/internal/plate-editor/editor/plugins/toggle-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/toggle-kit.tsx +9 -0
- package/src/internal/plate-editor/editor/transforms.ts +165 -0
- package/src/internal/plate-editor/editor/use-chat.ts +152 -0
- package/src/internal/plate-editor/hooks/index.ts +3 -0
- package/src/internal/plate-editor/hooks/use-copy-to-clipboard.ts +31 -0
- package/src/internal/plate-editor/hooks/use-is-touch-device.ts +26 -0
- package/src/internal/plate-editor/hooks/use-lock-scroll.ts +21 -0
- package/src/internal/plate-editor/hooks/use-media-query.ts +44 -0
- package/src/internal/plate-editor/hooks/use-mounted.ts +18 -0
- package/src/internal/plate-editor/hooks/use-on-click-outside.ts +114 -0
- package/src/internal/plate-editor/hooks/use-upload-file.ts +81 -0
- package/src/internal/plate-editor/i18n/context.tsx +58 -0
- package/src/internal/plate-editor/i18n/index.ts +3 -0
- package/src/internal/plate-editor/i18n/locales/de.json +57 -0
- package/src/internal/plate-editor/i18n/locales/el.json +57 -0
- package/src/internal/plate-editor/i18n/locales/en.json +57 -0
- package/src/internal/plate-editor/i18n/locales/es.json +57 -0
- package/src/internal/plate-editor/i18n/locales/fr.json +57 -0
- package/src/internal/plate-editor/i18n/locales/it.json +57 -0
- package/src/internal/plate-editor/i18n/locales/pt.json +57 -0
- package/src/internal/plate-editor/i18n/locales/sl.json +57 -0
- package/src/internal/plate-editor/i18n/locales/tr.json +57 -0
- package/src/internal/plate-editor/i18n/types.ts +59 -0
- package/src/internal/plate-editor/i18n/use-translation.ts +22 -0
- package/src/internal/plate-editor/index.ts +39 -0
- package/src/internal/plate-editor/lib/ai-output-converter.ts +153 -0
- package/src/internal/plate-editor/lib/download-file.ts +17 -0
- package/src/internal/plate-editor/plate-ui/ai-chat-editor.tsx +26 -0
- package/src/internal/plate-editor/plate-ui/ai-menu.tsx +828 -0
- package/src/internal/plate-editor/plate-ui/ai-node.tsx +41 -0
- package/src/internal/plate-editor/plate-ui/ai-toolbar-button.tsx +27 -0
- package/src/internal/plate-editor/plate-ui/alert-dialog.tsx +147 -0
- package/src/internal/plate-editor/plate-ui/align-toolbar-button.tsx +90 -0
- package/src/internal/plate-editor/plate-ui/avatar.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/block-context-menu.tsx +106 -0
- package/src/internal/plate-editor/plate-ui/block-discussion.tsx +364 -0
- package/src/internal/plate-editor/plate-ui/block-draggable.tsx +556 -0
- package/src/internal/plate-editor/plate-ui/block-list-static.tsx +78 -0
- package/src/internal/plate-editor/plate-ui/block-list.tsx +85 -0
- package/src/internal/plate-editor/plate-ui/block-menu.tsx +557 -0
- package/src/internal/plate-editor/plate-ui/block-selection.tsx +47 -0
- package/src/internal/plate-editor/plate-ui/block-suggestion.tsx +469 -0
- package/src/internal/plate-editor/plate-ui/blockquote-node-static.tsx +10 -0
- package/src/internal/plate-editor/plate-ui/blockquote-node.tsx +11 -0
- package/src/internal/plate-editor/plate-ui/button.tsx +190 -0
- package/src/internal/plate-editor/plate-ui/calendar.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/callout-node-static.tsx +76 -0
- package/src/internal/plate-editor/plate-ui/callout-node.tsx +54 -0
- package/src/internal/plate-editor/plate-ui/caption.tsx +48 -0
- package/src/internal/plate-editor/plate-ui/checkbox.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/code-block-node-static.tsx +172 -0
- package/src/internal/plate-editor/plate-ui/code-block-node.tsx +228 -0
- package/src/internal/plate-editor/plate-ui/code-node-static.tsx +11 -0
- package/src/internal/plate-editor/plate-ui/code-node.tsx +12 -0
- package/src/internal/plate-editor/plate-ui/column-node-static.tsx +65 -0
- package/src/internal/plate-editor/plate-ui/column-node.tsx +24 -0
- package/src/internal/plate-editor/plate-ui/command.tsx +204 -0
- package/src/internal/plate-editor/plate-ui/comment-node-static.tsx +12 -0
- package/src/internal/plate-editor/plate-ui/comment-node.tsx +45 -0
- package/src/internal/plate-editor/plate-ui/comment-toolbar-button.tsx +24 -0
- package/src/internal/plate-editor/plate-ui/comment.tsx +619 -0
- package/src/internal/plate-editor/plate-ui/cursor-overlay.tsx +85 -0
- package/src/internal/plate-editor/plate-ui/date-node-static.tsx +43 -0
- package/src/internal/plate-editor/plate-ui/date-node.tsx +56 -0
- package/src/internal/plate-editor/plate-ui/dialog.tsx +426 -0
- package/src/internal/plate-editor/plate-ui/dropdown-menu.tsx +266 -0
- package/src/internal/plate-editor/plate-ui/editor-static.tsx +40 -0
- package/src/internal/plate-editor/plate-ui/editor.tsx +148 -0
- package/src/internal/plate-editor/plate-ui/emoji-node.tsx +48 -0
- package/src/internal/plate-editor/plate-ui/emoji-toolbar-button.tsx +626 -0
- package/src/internal/plate-editor/plate-ui/equation-node-static.tsx +155 -0
- package/src/internal/plate-editor/plate-ui/equation-node.tsx +226 -0
- package/src/internal/plate-editor/plate-ui/equation-toolbar-button.tsx +26 -0
- package/src/internal/plate-editor/plate-ui/export-toolbar-button.tsx +208 -0
- package/src/internal/plate-editor/plate-ui/fixed-toolbar-buttons.tsx +157 -0
- package/src/internal/plate-editor/plate-ui/fixed-toolbar.tsx +27 -0
- package/src/internal/plate-editor/plate-ui/floating-discussion.tsx +1129 -0
- package/src/internal/plate-editor/plate-ui/floating-toolbar-buttons.tsx +129 -0
- package/src/internal/plate-editor/plate-ui/floating-toolbar.tsx +99 -0
- package/src/internal/plate-editor/plate-ui/font-color-toolbar-button.tsx +211 -0
- package/src/internal/plate-editor/plate-ui/font-size-toolbar-button.tsx +154 -0
- package/src/internal/plate-editor/plate-ui/ghost-text.tsx +20 -0
- package/src/internal/plate-editor/plate-ui/heading-node-static.tsx +52 -0
- package/src/internal/plate-editor/plate-ui/heading-node.tsx +56 -0
- package/src/internal/plate-editor/plate-ui/highlight-node-static.tsx +9 -0
- package/src/internal/plate-editor/plate-ui/highlight-node.tsx +11 -0
- package/src/internal/plate-editor/plate-ui/history-toolbar-button.tsx +52 -0
- package/src/internal/plate-editor/plate-ui/hover-card.tsx +7 -0
- package/src/internal/plate-editor/plate-ui/hr-node-static.tsx +18 -0
- package/src/internal/plate-editor/plate-ui/hr-node.tsx +28 -0
- package/src/internal/plate-editor/plate-ui/import-toolbar-button.tsx +124 -0
- package/src/internal/plate-editor/plate-ui/indent-toolbar-button.tsx +34 -0
- package/src/internal/plate-editor/plate-ui/inline-combobox.tsx +409 -0
- package/src/internal/plate-editor/plate-ui/input.tsx +39 -0
- package/src/internal/plate-editor/plate-ui/insert-toolbar-button.tsx +260 -0
- package/src/internal/plate-editor/plate-ui/label.tsx +1 -0
- package/src/internal/plate-editor/plate-ui/line-height-toolbar-button.tsx +71 -0
- package/src/internal/plate-editor/plate-ui/link-node-static.tsx +15 -0
- package/src/internal/plate-editor/plate-ui/link-node.tsx +33 -0
- package/src/internal/plate-editor/plate-ui/link-toolbar-button.tsx +30 -0
- package/src/internal/plate-editor/plate-ui/link-toolbar.tsx +149 -0
- package/src/internal/plate-editor/plate-ui/list-toolbar-button.tsx +179 -0
- package/src/internal/plate-editor/plate-ui/mark-toolbar-button.tsx +36 -0
- package/src/internal/plate-editor/plate-ui/media-audio-node-static.tsx +21 -0
- package/src/internal/plate-editor/plate-ui/media-audio-node.tsx +32 -0
- package/src/internal/plate-editor/plate-ui/media-embed-node.tsx +103 -0
- package/src/internal/plate-editor/plate-ui/media-file-node-static.tsx +30 -0
- package/src/internal/plate-editor/plate-ui/media-file-node.tsx +52 -0
- package/src/internal/plate-editor/plate-ui/media-image-node-static.tsx +37 -0
- package/src/internal/plate-editor/plate-ui/media-image-node.tsx +183 -0
- package/src/internal/plate-editor/plate-ui/media-placeholder-node.tsx +441 -0
- package/src/internal/plate-editor/plate-ui/media-preview-dialog.tsx +127 -0
- package/src/internal/plate-editor/plate-ui/media-toolbar-button.tsx +229 -0
- package/src/internal/plate-editor/plate-ui/media-toolbar.tsx +216 -0
- package/src/internal/plate-editor/plate-ui/media-upload-toast.tsx +73 -0
- package/src/internal/plate-editor/plate-ui/media-video-node-static.tsx +35 -0
- package/src/internal/plate-editor/plate-ui/media-video-node.tsx +119 -0
- package/src/internal/plate-editor/plate-ui/mention-node-static.tsx +46 -0
- package/src/internal/plate-editor/plate-ui/mention-node.tsx +79 -0
- package/src/internal/plate-editor/plate-ui/menu.tsx +532 -0
- package/src/internal/plate-editor/plate-ui/mode-toolbar-button.tsx +126 -0
- package/src/internal/plate-editor/plate-ui/more-toolbar-button.tsx +34 -0
- package/src/internal/plate-editor/plate-ui/paragraph-node-static.tsx +15 -0
- package/src/internal/plate-editor/plate-ui/paragraph-node.tsx +16 -0
- package/src/internal/plate-editor/plate-ui/popover.tsx +77 -0
- package/src/internal/plate-editor/plate-ui/progress.tsx +1 -0
- package/src/internal/plate-editor/plate-ui/remote-cursor-overlay.tsx +81 -0
- package/src/internal/plate-editor/plate-ui/resize-handle.tsx +88 -0
- package/src/internal/plate-editor/plate-ui/separator.tsx +43 -0
- package/src/internal/plate-editor/plate-ui/slash-node.tsx +435 -0
- package/src/internal/plate-editor/plate-ui/spinner.tsx +1 -0
- package/src/internal/plate-editor/plate-ui/suggestion-node-static.tsx +35 -0
- package/src/internal/plate-editor/plate-ui/suggestion-node.tsx +168 -0
- package/src/internal/plate-editor/plate-ui/suggestion-toolbar-button.tsx +24 -0
- package/src/internal/plate-editor/plate-ui/table-node-static.tsx +85 -0
- package/src/internal/plate-editor/plate-ui/table-node.tsx +285 -0
- package/src/internal/plate-editor/plate-ui/table-toolbar-button.tsx +254 -0
- package/src/internal/plate-editor/plate-ui/tabs.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/textarea.tsx +58 -0
- package/src/internal/plate-editor/plate-ui/toc-node-static.tsx +142 -0
- package/src/internal/plate-editor/plate-ui/toc-node.tsx +57 -0
- package/src/internal/plate-editor/plate-ui/toc-sidebar.tsx +50 -0
- package/src/internal/plate-editor/plate-ui/toggle-node-static.tsx +18 -0
- package/src/internal/plate-editor/plate-ui/toggle-node.tsx +33 -0
- package/src/internal/plate-editor/plate-ui/toggle-toolbar-button.tsx +26 -0
- package/src/internal/plate-editor/plate-ui/toggle.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/toolbar.tsx +380 -0
- package/src/internal/plate-editor/plate-ui/tooltip.tsx +149 -0
- package/src/internal/plate-editor/plate-ui/turn-into-toolbar-button.tsx +177 -0
- package/src/internal/plate-editor/types/index.ts +22 -0
- package/src/internal/plate-editor/vite.ts +284 -0
- package/src/internal/sheets/components/univer-sheets.tsx +1104 -0
- package/src/internal/sheets/i18n/context.tsx +183 -0
- package/src/internal/sheets/i18n/index.ts +19 -0
- package/src/internal/sheets/i18n/locales/de.json +21 -0
- package/src/internal/sheets/i18n/locales/el.json +21 -0
- package/src/internal/sheets/i18n/locales/en.json +21 -0
- package/src/internal/sheets/i18n/locales/es.json +21 -0
- package/src/internal/sheets/i18n/locales/fr.json +21 -0
- package/src/internal/sheets/i18n/locales/it.json +21 -0
- package/src/internal/sheets/i18n/locales/pt.json +21 -0
- package/src/internal/sheets/i18n/locales/sl.json +21 -0
- package/src/internal/sheets/i18n/locales/tr.json +21 -0
- package/src/internal/sheets/i18n/types.ts +23 -0
- package/src/internal/sheets/i18n/use-translation.ts +17 -0
- package/src/internal/sheets/index.ts +14 -0
- package/src/internal/sheets/types/css.d.ts +11 -0
- package/src/internal/sheets/types/index.ts +260 -0
- package/src/internal/sheets/xlsx.ts +1169 -0
- package/src/lib/api-client.ts +77 -0
- package/src/lib/assistant-api-actions.ts +549 -0
- package/src/lib/assistant-config.tsx +71 -0
- package/src/lib/class-utils.ts +40 -0
- package/src/lib/index.ts +7 -0
- package/src/lib/message-utils.ts +131 -0
- package/src/tools/tools-schema.json +512 -0
- package/src/types/index.ts +235 -0
- package/src/views/assistant-view.tsx +1137 -0
- package/src/views/canvas-app.tsx +839 -0
- package/src/views/canvas-code.tsx +93 -0
- package/src/views/canvas-deep-research.tsx +44 -0
- package/src/views/canvas-image.tsx +25 -0
- package/src/views/canvas-record-view.tsx +285 -0
- package/src/views/canvas-spreadsheet.tsx +125 -0
- package/src/views/canvas-text.tsx +52 -0
- package/src/views/canvas.tsx +274 -0
- package/src/views/chat-panel.tsx +149 -0
- package/src/views/index.ts +20 -0
- package/src/views/memories-panel.tsx +365 -0
- package/src/views/message-list.tsx +370 -0
- package/src/views/project-detail.tsx +257 -0
- package/src/views/projects-panel.tsx +131 -0
- package/src/views/sessions-list.tsx +98 -0
- package/src/views/sidebar-content.tsx +256 -0
- package/src/views/work-detail.tsx +98 -0
- package/src/vite.ts +284 -0
|
@@ -0,0 +1,1962 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent, type FormEvent, type KeyboardEvent, type Ref, type RefObject
|
|
5
|
+
} from 'react';
|
|
6
|
+
|
|
7
|
+
import { useChat } from '@ai-sdk/react';
|
|
8
|
+
import { Button } from '@docyrus/ui-pro-shared/components/button';
|
|
9
|
+
import { toast } from 'sonner';
|
|
10
|
+
import {
|
|
11
|
+
Dialog, DialogContent, DialogTitle
|
|
12
|
+
} from '@docyrus/ui-pro-shared/components/dialog';
|
|
13
|
+
import {
|
|
14
|
+
DropdownMenu,
|
|
15
|
+
DropdownMenuContent,
|
|
16
|
+
DropdownMenuItem,
|
|
17
|
+
DropdownMenuSeparator,
|
|
18
|
+
DropdownMenuSub,
|
|
19
|
+
DropdownMenuSubContent,
|
|
20
|
+
DropdownMenuSubTrigger,
|
|
21
|
+
DropdownMenuTrigger
|
|
22
|
+
} from '@docyrus/ui-pro-shared/components/dropdown-menu';
|
|
23
|
+
import { Textarea } from '@docyrus/ui-pro-shared/components/textarea';
|
|
24
|
+
import { Input } from '@docyrus/ui-pro-shared/components/input';
|
|
25
|
+
import {
|
|
26
|
+
Tabs, TabsContent, TabsList, TabsTrigger
|
|
27
|
+
} from '@docyrus/ui-pro-shared/components/tabs';
|
|
28
|
+
import { cn } from '@docyrus/ui-pro-shared/lib/utils';
|
|
29
|
+
import {
|
|
30
|
+
DefaultChatTransport,
|
|
31
|
+
type FinishReason,
|
|
32
|
+
lastAssistantMessageIsCompleteWithToolCalls,
|
|
33
|
+
type UIMessage
|
|
34
|
+
} from 'ai';
|
|
35
|
+
import {
|
|
36
|
+
Archive,
|
|
37
|
+
ArrowLeft,
|
|
38
|
+
ArrowRight,
|
|
39
|
+
Edit,
|
|
40
|
+
FolderInput,
|
|
41
|
+
Maximize2,
|
|
42
|
+
Minimize2,
|
|
43
|
+
MoreHorizontal,
|
|
44
|
+
PanelLeft,
|
|
45
|
+
Plus,
|
|
46
|
+
Search,
|
|
47
|
+
Trash,
|
|
48
|
+
X
|
|
49
|
+
} from 'lucide-react';
|
|
50
|
+
|
|
51
|
+
import { AssistantDialogs } from './components/assistant-dialogs';
|
|
52
|
+
import { WorkCard } from './components/work-card';
|
|
53
|
+
import {
|
|
54
|
+
useAssistantApi, useProjectState, useSessionState, useSpeechRecognition, useUIState, useWorksState
|
|
55
|
+
} from './hooks';
|
|
56
|
+
import { AssistantCanvasView } from './views/canvas';
|
|
57
|
+
import { ConversationView, InlineView } from './views/assistant-view';
|
|
58
|
+
import { MemoriesPanel } from './views/memories-panel';
|
|
59
|
+
import { AssistantProjectDetailView } from './views/project-detail';
|
|
60
|
+
import { AssistantProjectsPanel } from './views/projects-panel';
|
|
61
|
+
import { SessionsListView } from './views/sessions-list';
|
|
62
|
+
import { SidebarContent } from './views/sidebar-content';
|
|
63
|
+
import { AssistantWorkDetailView } from './views/work-detail';
|
|
64
|
+
import { useAssistantTranslation } from './i18n';
|
|
65
|
+
import { useApiClient } from './lib/api-client';
|
|
66
|
+
import {
|
|
67
|
+
createThread,
|
|
68
|
+
deleteProject as deleteProjectApi,
|
|
69
|
+
deleteSession as deleteSessionApi,
|
|
70
|
+
fetchAgentThreads,
|
|
71
|
+
fetchProjectThreads as fetchProjectThreadsApi,
|
|
72
|
+
fetchProjects as fetchProjectsApi,
|
|
73
|
+
fetchProjectWorks as fetchProjectWorksApi,
|
|
74
|
+
fetchWorks as fetchWorksApi,
|
|
75
|
+
loadThreadMessages as loadThreadMessagesApi,
|
|
76
|
+
updateProject as updateProjectApi,
|
|
77
|
+
updateProjectInstructions as updateProjectInstructionsApi,
|
|
78
|
+
updateSession as updateSessionApi,
|
|
79
|
+
uploadThreadFile
|
|
80
|
+
} from './lib/assistant-api-actions';
|
|
81
|
+
import { useAssistantConfig } from './lib/assistant-config';
|
|
82
|
+
import { getContainerClassName, getDialogClassName } from './lib/class-utils';
|
|
83
|
+
|
|
84
|
+
import {
|
|
85
|
+
type AssistantSession,
|
|
86
|
+
type DocyAssistantProps,
|
|
87
|
+
type Project,
|
|
88
|
+
type Work
|
|
89
|
+
} from './types';
|
|
90
|
+
|
|
91
|
+
interface ThreadHeaderInlineProps {
|
|
92
|
+
session: AssistantSession;
|
|
93
|
+
projects: Project[];
|
|
94
|
+
projectContext: { id: string; name: string } | null;
|
|
95
|
+
onSaveTitle: (newTitle: string) => Promise<void>;
|
|
96
|
+
onDelete: () => void;
|
|
97
|
+
onArchive: () => Promise<void>;
|
|
98
|
+
onMoveToProject: (projectId: string) => Promise<void>;
|
|
99
|
+
onProjectContextClick?: () => void;
|
|
100
|
+
isFullscreen?: boolean;
|
|
101
|
+
t: (key: string) => string;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function ThreadHeaderInline({
|
|
105
|
+
session,
|
|
106
|
+
projects,
|
|
107
|
+
projectContext,
|
|
108
|
+
onSaveTitle,
|
|
109
|
+
onDelete,
|
|
110
|
+
onArchive,
|
|
111
|
+
onMoveToProject,
|
|
112
|
+
onProjectContextClick,
|
|
113
|
+
isFullscreen = false,
|
|
114
|
+
t
|
|
115
|
+
}: ThreadHeaderInlineProps) {
|
|
116
|
+
const dropdownZClass = isFullscreen ? 'z-[10000]' : '';
|
|
117
|
+
const [editValue, setEditValue] = useState<string | null>(null);
|
|
118
|
+
|
|
119
|
+
const handleBlur = async () => {
|
|
120
|
+
if (editValue === null) return;
|
|
121
|
+
const trimmed = editValue.trim();
|
|
122
|
+
|
|
123
|
+
if (trimmed && trimmed !== session.title) {
|
|
124
|
+
await onSaveTitle(trimmed);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setEditValue(null);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const handleKeyDown = async (e: KeyboardEvent<HTMLInputElement>) => {
|
|
131
|
+
if (e.key === 'Enter') {
|
|
132
|
+
e.currentTarget.blur();
|
|
133
|
+
} else if (e.key === 'Escape') {
|
|
134
|
+
setEditValue(null);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<div className="flex-none h-10 flex bg-background items-center justify-between border-b border-x border-border px-3 mx-3 rounded-b-md shrink-0">
|
|
140
|
+
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
141
|
+
{projectContext?.id && onProjectContextClick && (
|
|
142
|
+
<Button
|
|
143
|
+
variant="ghost"
|
|
144
|
+
onClick={onProjectContextClick}
|
|
145
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 h-auto rounded-full bg-sky-100 text-sky-700 dark:bg-sky-900/40 dark:text-sky-300 text-xs font-medium flex-none hover:bg-sky-200 transition-colors">
|
|
146
|
+
{projectContext.name}
|
|
147
|
+
</Button>
|
|
148
|
+
)}
|
|
149
|
+
{editValue !== null ? (
|
|
150
|
+
<input
|
|
151
|
+
autoFocus
|
|
152
|
+
value={editValue}
|
|
153
|
+
onChange={e => setEditValue(e.target.value)}
|
|
154
|
+
onBlur={handleBlur}
|
|
155
|
+
onKeyDown={handleKeyDown}
|
|
156
|
+
className="text-sm font-medium text-foreground bg-transparent border-b border-border focus:outline-none min-w-0 flex-1" />
|
|
157
|
+
) : (
|
|
158
|
+
<div className="group/title flex items-center gap-1 min-w-0">
|
|
159
|
+
<span className="text-sm font-medium text-foreground truncate min-w-0">
|
|
160
|
+
{session.title || t('common.untitled')}
|
|
161
|
+
</span>
|
|
162
|
+
<Button
|
|
163
|
+
variant="ghost"
|
|
164
|
+
size="icon"
|
|
165
|
+
onClick={() => setEditValue(session.title || '')}
|
|
166
|
+
className="opacity-0 group-hover/title:opacity-100 transition-opacity shrink-0 h-5 w-5 text-muted-foreground hover:text-foreground">
|
|
167
|
+
<Edit className="w-3.5 h-3.5" />
|
|
168
|
+
</Button>
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
<div className="flex items-center flex-none">
|
|
173
|
+
<DropdownMenu>
|
|
174
|
+
<DropdownMenuTrigger asChild>
|
|
175
|
+
<Button variant="ghost" size="icon" className="h-7 w-7 text-muted-foreground hover:text-foreground">
|
|
176
|
+
<MoreHorizontal className="w-4 h-4" />
|
|
177
|
+
</Button>
|
|
178
|
+
</DropdownMenuTrigger>
|
|
179
|
+
<DropdownMenuContent align="end" className={cn('w-48', dropdownZClass)}>
|
|
180
|
+
<DropdownMenuSub>
|
|
181
|
+
<DropdownMenuSubTrigger className="cursor-pointer">
|
|
182
|
+
<FolderInput className="w-4 h-4 mr-2" />
|
|
183
|
+
{t('actions.move_to_project')}
|
|
184
|
+
</DropdownMenuSubTrigger>
|
|
185
|
+
<DropdownMenuSubContent className={cn('w-48', dropdownZClass)}>
|
|
186
|
+
{projects.length === 0 ? (
|
|
187
|
+
<DropdownMenuItem disabled className="text-muted-foreground text-xs">
|
|
188
|
+
{t('messages.no_projects_yet')}
|
|
189
|
+
</DropdownMenuItem>
|
|
190
|
+
) : (
|
|
191
|
+
projects.map(project => (
|
|
192
|
+
<DropdownMenuItem
|
|
193
|
+
key={project.id}
|
|
194
|
+
className="cursor-pointer"
|
|
195
|
+
onClick={() => onMoveToProject(project.id)}>
|
|
196
|
+
{project.name}
|
|
197
|
+
</DropdownMenuItem>
|
|
198
|
+
))
|
|
199
|
+
)}
|
|
200
|
+
</DropdownMenuSubContent>
|
|
201
|
+
</DropdownMenuSub>
|
|
202
|
+
<DropdownMenuItem
|
|
203
|
+
className="cursor-pointer"
|
|
204
|
+
onClick={onArchive}>
|
|
205
|
+
<Archive className="w-4 h-4 mr-2" />
|
|
206
|
+
{t('actions.archive')}
|
|
207
|
+
</DropdownMenuItem>
|
|
208
|
+
<DropdownMenuSeparator />
|
|
209
|
+
<DropdownMenuItem
|
|
210
|
+
className="text-destructive focus:text-destructive cursor-pointer"
|
|
211
|
+
onClick={onDelete}>
|
|
212
|
+
<Trash className="w-4 h-4 mr-2" />
|
|
213
|
+
{t('common.delete')}
|
|
214
|
+
</DropdownMenuItem>
|
|
215
|
+
</DropdownMenuContent>
|
|
216
|
+
</DropdownMenu>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function dataUrlToFile(dataUrl: string, filename: string): File {
|
|
223
|
+
const arr = dataUrl.split(',');
|
|
224
|
+
const mime = arr[0].match(/:(.*?);/)?.[1] || 'application/octet-stream';
|
|
225
|
+
const bstr = atob(arr[1]);
|
|
226
|
+
let n = bstr.length;
|
|
227
|
+
const u8arr = new Uint8Array(n);
|
|
228
|
+
|
|
229
|
+
while (n--) {
|
|
230
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return new File([u8arr], filename, { type: mime });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const DocyAssistant = (
|
|
237
|
+
{
|
|
238
|
+
ref, open, onOpenChange, supportWebSearch = false, supportThinking = false, supportFiles = false, supportDocumentSearch = false, supportDeepResearch = false, supportMultiModels = false, supportWorkCanvas = false, apiEndpoint = '/ai/agents/:agentId/chat', title: titleProp, description: descriptionProp, placeholder: placeholderProp, logo, footerText: footerTextProp, variant = 'default', renderMode = 'modal', enableSidebar = true, enableNavDropdown = false, enableVoice = false, enableMicrophone = true, tenantAiAgentId, deploymentId, onMessageSend, onVoiceStart, onVoiceEnd, className, defaultFullscreen = false, hideExpand = false, agentSelectorUrl, baseAgentSelectorUrl, onAgentChange, ...props
|
|
239
|
+
}: DocyAssistantProps & { ref?: RefObject<HTMLDivElement | null> }
|
|
240
|
+
) => {
|
|
241
|
+
const config = useAssistantConfig();
|
|
242
|
+
const { apiBaseUrl: baseUrl, getAuthToken, user: configUser } = config;
|
|
243
|
+
const apiClient = useApiClient();
|
|
244
|
+
const { t } = useAssistantTranslation();
|
|
245
|
+
|
|
246
|
+
// Localized default values
|
|
247
|
+
const title = titleProp || 'DocyAssistant';
|
|
248
|
+
const description = descriptionProp || t('descriptions.default');
|
|
249
|
+
const placeholder = placeholderProp || t('placeholders.type_message');
|
|
250
|
+
const footerText = footerTextProp || t('descriptions.footer_default');
|
|
251
|
+
|
|
252
|
+
// Custom hooks for grouped state management
|
|
253
|
+
const { state: uiState, actions: uiActions } = useUIState(variant, renderMode);
|
|
254
|
+
const { state: sessionState, actions: sessionActions } = useSessionState();
|
|
255
|
+
const { state: projectState, actions: projectActions } = useProjectState();
|
|
256
|
+
const { state: worksState, actions: worksActions } = useWorksState();
|
|
257
|
+
|
|
258
|
+
// Remaining local state
|
|
259
|
+
const [input, setInput] = useState('');
|
|
260
|
+
const currentUserId = configUser?.id || null;
|
|
261
|
+
const [projectSearchQuery, setProjectSearchQuery] = useState('');
|
|
262
|
+
const [isInlineFullscreen, setIsInlineFullscreen] = useState(defaultFullscreen);
|
|
263
|
+
|
|
264
|
+
// Speech recognition hook
|
|
265
|
+
const { isRecording, recognition, handleMicrophoneClick } = useSpeechRecognition({
|
|
266
|
+
enabled: enableMicrophone,
|
|
267
|
+
onTranscript: transcript => setInput(prev => prev + (prev ? ' ' : '') + transcript),
|
|
268
|
+
onStart: onVoiceStart,
|
|
269
|
+
onEnd: onVoiceEnd
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Refs
|
|
273
|
+
const selectedSessionIdRef = useRef<string | null>(null);
|
|
274
|
+
const sidebarCloseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
275
|
+
const rightSidebarCloseTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
276
|
+
const authTokenRef = useRef<string>('');
|
|
277
|
+
const messageOptionsRef = useRef<any>(null);
|
|
278
|
+
|
|
279
|
+
// Keep refs in sync with state
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
selectedSessionIdRef.current = sessionState.selectedSessionId;
|
|
282
|
+
}, [sessionState.selectedSessionId]);
|
|
283
|
+
|
|
284
|
+
// Get and maintain auth token in ref
|
|
285
|
+
useEffect(() => {
|
|
286
|
+
getAuthToken().then((token: string) => {
|
|
287
|
+
if (token) {
|
|
288
|
+
authTokenRef.current = token;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}, [getAuthToken]);
|
|
292
|
+
|
|
293
|
+
// Create transport with headers and body using functions for dynamic values
|
|
294
|
+
const transport = useMemo(() => {
|
|
295
|
+
// Build URL from apiEndpoint template, replacing :agentId and optionally adding deployment
|
|
296
|
+
const endpointPath = apiEndpoint.replace(':agentId', tenantAiAgentId);
|
|
297
|
+
const apiUrl = deploymentId ? `${baseUrl}${endpointPath.replace('/chat', `/deployments/${deploymentId}/chat`)}` : `${baseUrl}${endpointPath}`;
|
|
298
|
+
|
|
299
|
+
return new DefaultChatTransport({
|
|
300
|
+
api: apiUrl,
|
|
301
|
+
prepareSendMessagesRequest: ({ messages, body }: { messages: any[]; body: any }) => {
|
|
302
|
+
const options = messageOptionsRef.current;
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
body: {
|
|
306
|
+
...body,
|
|
307
|
+
agentId: tenantAiAgentId,
|
|
308
|
+
threadId: selectedSessionIdRef.current, // Use ref for immediate access
|
|
309
|
+
messages: messages.slice(-10), // Only send last 10 messages
|
|
310
|
+
modelId: options?.modelId,
|
|
311
|
+
supportMultipleModels: options?.supportMultipleModels,
|
|
312
|
+
supportFiles: options?.supportFiles,
|
|
313
|
+
supportWebSearch: options?.supportWebSearch,
|
|
314
|
+
supportDeepResearch: options?.supportDeepResearch,
|
|
315
|
+
supportDocumentSearch: options?.supportDocumentSearch,
|
|
316
|
+
supportThinking: options?.supportThinking,
|
|
317
|
+
supportWorkCanvas: options?.supportWorkCanvas,
|
|
318
|
+
...(options?.filePaths?.length ? { files: options.filePaths } : {})
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
headers: () => {
|
|
323
|
+
const token = authTokenRef.current;
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
'Content-Type': 'application/json',
|
|
327
|
+
...(token && { Authorization: `Bearer ${token}` })
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
body: () => ({
|
|
331
|
+
agentId: tenantAiAgentId,
|
|
332
|
+
threadId: selectedSessionIdRef.current
|
|
333
|
+
})
|
|
334
|
+
});
|
|
335
|
+
}, [
|
|
336
|
+
baseUrl,
|
|
337
|
+
apiEndpoint,
|
|
338
|
+
tenantAiAgentId,
|
|
339
|
+
deploymentId
|
|
340
|
+
]);
|
|
341
|
+
|
|
342
|
+
// Use the AI SDK's useChat hook with agent-specific endpoint
|
|
343
|
+
const {
|
|
344
|
+
messages = [],
|
|
345
|
+
status,
|
|
346
|
+
sendMessage,
|
|
347
|
+
setMessages,
|
|
348
|
+
stop,
|
|
349
|
+
addToolOutput
|
|
350
|
+
} = useChat({
|
|
351
|
+
id: `docy-assistant:${tenantAiAgentId}`,
|
|
352
|
+
transport,
|
|
353
|
+
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
|
|
354
|
+
onFinish: (
|
|
355
|
+
message: UIMessage,
|
|
356
|
+
options?: {
|
|
357
|
+
usage?: {
|
|
358
|
+
inputTokens?: number;
|
|
359
|
+
outputTokens?: number;
|
|
360
|
+
totalTokens?: number;
|
|
361
|
+
cacheReadTokens?: number;
|
|
362
|
+
cacheWriteTokens?: number;
|
|
363
|
+
};
|
|
364
|
+
finishReason?: FinishReason;
|
|
365
|
+
}
|
|
366
|
+
) => {
|
|
367
|
+
const { usage, finishReason } = options || {};
|
|
368
|
+
|
|
369
|
+
if (typeof process !== 'undefined' && process.env.NODE_ENV === 'development') {
|
|
370
|
+
console.info('[AI] Message finished:', {
|
|
371
|
+
messageId: message.id,
|
|
372
|
+
role: message.role,
|
|
373
|
+
finishReason,
|
|
374
|
+
inputTokens: usage?.inputTokens,
|
|
375
|
+
outputTokens: usage?.outputTokens,
|
|
376
|
+
totalTokens: usage?.totalTokens,
|
|
377
|
+
cacheReadTokens: usage?.cacheReadTokens,
|
|
378
|
+
cacheWriteTokens: usage?.cacheWriteTokens
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
onError: (error: Error) => {
|
|
383
|
+
console.error('[AI] Chat error:', error);
|
|
384
|
+
}
|
|
385
|
+
} as any);
|
|
386
|
+
|
|
387
|
+
// AI SDK v6 status handling
|
|
388
|
+
const isStreaming = status === 'streaming';
|
|
389
|
+
const isSubmitting = status === 'submitted';
|
|
390
|
+
const isLoading = isStreaming || isSubmitting;
|
|
391
|
+
|
|
392
|
+
const handleToolAction = useCallback((event: {
|
|
393
|
+
tool: string;
|
|
394
|
+
toolCallId: string;
|
|
395
|
+
decision: string;
|
|
396
|
+
input: any;
|
|
397
|
+
}) => {
|
|
398
|
+
switch (event.decision) {
|
|
399
|
+
case 'approve':
|
|
400
|
+
|
|
401
|
+
case 'continue':
|
|
402
|
+
addToolOutput({
|
|
403
|
+
tool: event.tool as any,
|
|
404
|
+
toolCallId: event.toolCallId,
|
|
405
|
+
output: { action: event.decision }
|
|
406
|
+
});
|
|
407
|
+
break;
|
|
408
|
+
|
|
409
|
+
case 'submit':
|
|
410
|
+
addToolOutput({
|
|
411
|
+
tool: event.tool as any,
|
|
412
|
+
toolCallId: event.toolCallId,
|
|
413
|
+
output: event.input
|
|
414
|
+
});
|
|
415
|
+
break;
|
|
416
|
+
|
|
417
|
+
default:
|
|
418
|
+
addToolOutput({
|
|
419
|
+
tool: event.tool as any,
|
|
420
|
+
toolCallId: event.toolCallId,
|
|
421
|
+
output: { decision: event.decision }
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}, [addToolOutput]);
|
|
425
|
+
|
|
426
|
+
// Compute user display info from config
|
|
427
|
+
const userDisplayName = configUser ? `${configUser.firstname || ''} ${configUser.lastname || ''}`.trim() || configUser.email?.split('@')[0] || 'User' : 'User';
|
|
428
|
+
const userPhoto = configUser?.photo || '';
|
|
429
|
+
|
|
430
|
+
// Computed values from state
|
|
431
|
+
const isInlineMode = uiState.currentRenderMode === 'inline';
|
|
432
|
+
|
|
433
|
+
// Auto-open create view when no projects
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
if (
|
|
436
|
+
uiState.activeTab === 2
|
|
437
|
+
&& projectState.projectsLoaded
|
|
438
|
+
&& projectState.projects.length === 0
|
|
439
|
+
&& projectState.view === 'list'
|
|
440
|
+
) {
|
|
441
|
+
projectActions.setView('create');
|
|
442
|
+
}
|
|
443
|
+
}, [
|
|
444
|
+
uiState.activeTab,
|
|
445
|
+
projectState.projectsLoaded,
|
|
446
|
+
projectState.projects.length,
|
|
447
|
+
projectState.view,
|
|
448
|
+
projectActions
|
|
449
|
+
]);
|
|
450
|
+
|
|
451
|
+
/*
|
|
452
|
+
* Fetch agent details with request deduplication
|
|
453
|
+
* Uses module-level cache to prevent multiple requests across component instances
|
|
454
|
+
*/
|
|
455
|
+
const apiClientRef = useRef(apiClient);
|
|
456
|
+
|
|
457
|
+
apiClientRef.current = apiClient;
|
|
458
|
+
|
|
459
|
+
// Use the assistant API hook for agent details
|
|
460
|
+
const { agentDetails, isLoadingAgentDetails } = useAssistantApi({
|
|
461
|
+
tenantAiAgentId,
|
|
462
|
+
deploymentId,
|
|
463
|
+
logo,
|
|
464
|
+
title,
|
|
465
|
+
t
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const fetchThreads = useCallback(async () => {
|
|
469
|
+
await fetchAgentThreads(apiClient, tenantAiAgentId, sessionActions.setSessions, currentUserId, deploymentId);
|
|
470
|
+
}, [
|
|
471
|
+
apiClient,
|
|
472
|
+
tenantAiAgentId,
|
|
473
|
+
sessionActions,
|
|
474
|
+
currentUserId,
|
|
475
|
+
deploymentId
|
|
476
|
+
]);
|
|
477
|
+
|
|
478
|
+
const fetchProjectThreads = useCallback(async (projectId: string) => {
|
|
479
|
+
await fetchProjectThreadsApi(apiClient, projectId, projectActions.setProjectThreads);
|
|
480
|
+
}, [apiClient, projectActions]);
|
|
481
|
+
|
|
482
|
+
const fetchProjectWorks = useCallback(async (projectId: string) => {
|
|
483
|
+
await fetchProjectWorksApi(apiClient, projectId, projectActions.setProjectWorks, tenantAiAgentId, currentUserId);
|
|
484
|
+
}, [
|
|
485
|
+
apiClient,
|
|
486
|
+
projectActions,
|
|
487
|
+
tenantAiAgentId,
|
|
488
|
+
currentUserId
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
const fetchProjects = useCallback(async () => {
|
|
492
|
+
await fetchProjectsApi(
|
|
493
|
+
apiClient,
|
|
494
|
+
configUser?.id || null,
|
|
495
|
+
projectActions.setProjects,
|
|
496
|
+
(project) => {
|
|
497
|
+
fetchProjectThreads(project.id);
|
|
498
|
+
fetchProjectWorks(project.id);
|
|
499
|
+
},
|
|
500
|
+
() => projectActions.setProjectsLoaded(true),
|
|
501
|
+
tenantAiAgentId
|
|
502
|
+
);
|
|
503
|
+
}, [
|
|
504
|
+
apiClient,
|
|
505
|
+
configUser?.id,
|
|
506
|
+
projectActions,
|
|
507
|
+
fetchProjectThreads,
|
|
508
|
+
fetchProjectWorks,
|
|
509
|
+
tenantAiAgentId
|
|
510
|
+
]);
|
|
511
|
+
|
|
512
|
+
const fetchWorks = useCallback(async () => {
|
|
513
|
+
await fetchWorksApi(apiClient, worksActions.setWorks, tenantAiAgentId, currentUserId);
|
|
514
|
+
}, [
|
|
515
|
+
apiClient,
|
|
516
|
+
worksActions,
|
|
517
|
+
tenantAiAgentId,
|
|
518
|
+
currentUserId
|
|
519
|
+
]);
|
|
520
|
+
|
|
521
|
+
// Fetch threads from API
|
|
522
|
+
useEffect(() => {
|
|
523
|
+
if (open && (enableSidebar || enableNavDropdown)) {
|
|
524
|
+
fetchThreads();
|
|
525
|
+
}
|
|
526
|
+
}, [
|
|
527
|
+
open,
|
|
528
|
+
enableSidebar,
|
|
529
|
+
enableNavDropdown,
|
|
530
|
+
fetchThreads
|
|
531
|
+
]);
|
|
532
|
+
|
|
533
|
+
// Fetch projects when projects tab is active
|
|
534
|
+
useEffect(() => {
|
|
535
|
+
if (open && (enableSidebar || enableNavDropdown) && uiState.activeTab === 3) {
|
|
536
|
+
fetchWorks();
|
|
537
|
+
} else if (open && (enableSidebar || enableNavDropdown)) {
|
|
538
|
+
projectActions.setProjectsLoaded(false);
|
|
539
|
+
fetchProjects();
|
|
540
|
+
}
|
|
541
|
+
}, [
|
|
542
|
+
open,
|
|
543
|
+
enableSidebar,
|
|
544
|
+
enableNavDropdown,
|
|
545
|
+
uiState.activeTab,
|
|
546
|
+
projectActions,
|
|
547
|
+
fetchProjects,
|
|
548
|
+
fetchWorks
|
|
549
|
+
]);
|
|
550
|
+
|
|
551
|
+
// Fetch works when right sidebar opens
|
|
552
|
+
useEffect(() => {
|
|
553
|
+
if (open && uiState.isRightSidebarOpen) {
|
|
554
|
+
fetchWorks();
|
|
555
|
+
}
|
|
556
|
+
}, [open, uiState.isRightSidebarOpen, fetchWorks]);
|
|
557
|
+
|
|
558
|
+
const updateProject = async () => {
|
|
559
|
+
await updateProjectApi(apiClient, projectState, projectActions, fetchProjects, t);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const deleteProject = async () => {
|
|
563
|
+
await deleteProjectApi(apiClient, projectState, projectActions, fetchProjects, t);
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
const loadThreadMessages = async (threadId: string) => {
|
|
567
|
+
await loadThreadMessagesApi(apiClient, threadId, setMessages);
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
const handleSessionClick = async (session: AssistantSession) => {
|
|
571
|
+
sessionActions.selectSessionId(session.id);
|
|
572
|
+
sessionActions.selectSession(session);
|
|
573
|
+
uiActions.setActiveTab(0); // Switch to Home tab when a session is clicked
|
|
574
|
+
await loadThreadMessages(session.id);
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const createNewThread = () => {
|
|
578
|
+
sessionActions.selectSessionId(null);
|
|
579
|
+
sessionActions.selectSession(null);
|
|
580
|
+
uiActions.setActiveTab(0); // Switch to Home tab when creating a new thread
|
|
581
|
+
setMessages([]); // Clear messages for new thread
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const handleSendMessage = async (e?: FormEvent, options?: any) => {
|
|
585
|
+
e?.preventDefault();
|
|
586
|
+
if (!input.trim()) return;
|
|
587
|
+
|
|
588
|
+
// Store options in ref for access in prepareSendMessagesRequest
|
|
589
|
+
messageOptionsRef.current = options;
|
|
590
|
+
|
|
591
|
+
const messageText = input.trim();
|
|
592
|
+
let currentThreadId = selectedSessionIdRef.current;
|
|
593
|
+
|
|
594
|
+
// If no thread ID exists, create a new one
|
|
595
|
+
if (!currentThreadId) {
|
|
596
|
+
const subject = messageText.substring(0, 100);
|
|
597
|
+
|
|
598
|
+
const newThread = await createThread(apiClient, {
|
|
599
|
+
subject,
|
|
600
|
+
body_text: messageText,
|
|
601
|
+
sender_name: userDisplayName,
|
|
602
|
+
deploymentId,
|
|
603
|
+
tenantAiAgentId,
|
|
604
|
+
projectId: projectState.projectContext?.id
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
if (newThread) {
|
|
608
|
+
currentThreadId = newThread.id;
|
|
609
|
+
|
|
610
|
+
// Update the selected session ID
|
|
611
|
+
sessionActions.selectSessionId(currentThreadId);
|
|
612
|
+
selectedSessionIdRef.current = currentThreadId;
|
|
613
|
+
|
|
614
|
+
// Add the new session to the list
|
|
615
|
+
const session: AssistantSession = {
|
|
616
|
+
id: newThread.id,
|
|
617
|
+
title: newThread.subject || subject,
|
|
618
|
+
messages: [],
|
|
619
|
+
createdAt: new Date(newThread.created_on || new Date()),
|
|
620
|
+
updatedAt: new Date(newThread.last_modified_on || new Date())
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
sessionActions.addSession(session);
|
|
624
|
+
|
|
625
|
+
fetchThreads();
|
|
626
|
+
|
|
627
|
+
if (projectState.projectContext?.id) {
|
|
628
|
+
fetchProjectThreads(projectState.projectContext.id);
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
onMessageSend?.(messageText);
|
|
636
|
+
setInput('');
|
|
637
|
+
|
|
638
|
+
// Upload files to thread storage via REST API
|
|
639
|
+
const rawFiles: { url: string; mediaType: string; filename: string }[] = options?.files || [];
|
|
640
|
+
const filePaths: string[] = [];
|
|
641
|
+
|
|
642
|
+
if (rawFiles.length > 0 && currentThreadId) {
|
|
643
|
+
for (const rawFile of rawFiles) {
|
|
644
|
+
try {
|
|
645
|
+
const file = dataUrlToFile(rawFile.url, rawFile.filename);
|
|
646
|
+
const path = await uploadThreadFile(apiClient, currentThreadId, file);
|
|
647
|
+
|
|
648
|
+
if (path) {
|
|
649
|
+
filePaths.push(path);
|
|
650
|
+
}
|
|
651
|
+
} catch (error) {
|
|
652
|
+
console.error('[FILE_UPLOAD] Error:', error);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Store file paths in ref for prepareSendMessagesRequest
|
|
658
|
+
messageOptionsRef.current = {
|
|
659
|
+
...options,
|
|
660
|
+
filePaths: filePaths.length > 0 ? filePaths : undefined
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
await sendMessage({
|
|
664
|
+
text: messageText,
|
|
665
|
+
metadata: {
|
|
666
|
+
modelId: options?.modelId,
|
|
667
|
+
supportMultipleModels: options?.supportMultipleModels,
|
|
668
|
+
supportFiles: options?.supportFiles,
|
|
669
|
+
supportWebSearch: options?.supportWebSearch,
|
|
670
|
+
supportDeepResearch: options?.supportDeepResearch,
|
|
671
|
+
supportDocumentSearch: options?.supportDocumentSearch,
|
|
672
|
+
supportThinking: options?.supportThinking,
|
|
673
|
+
supportWorkCanvas: options?.supportWorkCanvas
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const handleInputChange = useCallback((e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
679
|
+
setInput(e.target.value);
|
|
680
|
+
}, []);
|
|
681
|
+
|
|
682
|
+
const handleProjectMessage = async (e?: FormEvent) => {
|
|
683
|
+
e?.preventDefault();
|
|
684
|
+
if (!input.trim() || !projectState.selectedProject) return;
|
|
685
|
+
|
|
686
|
+
// Clear everything and switch to home tab
|
|
687
|
+
sessionActions.selectSessionId(null);
|
|
688
|
+
selectedSessionIdRef.current = null;
|
|
689
|
+
setMessages([]);
|
|
690
|
+
uiActions.setActiveTab(0);
|
|
691
|
+
|
|
692
|
+
const originalMessage = input.trim();
|
|
693
|
+
|
|
694
|
+
// Store project context for breadcrumb
|
|
695
|
+
projectActions.setContext({
|
|
696
|
+
name: projectState.selectedProject.name,
|
|
697
|
+
id: projectState.selectedProject.id,
|
|
698
|
+
message: originalMessage
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Set input and use existing handleSendMessage logic
|
|
702
|
+
setInput(originalMessage);
|
|
703
|
+
|
|
704
|
+
// Small delay to ensure state updates, then send message
|
|
705
|
+
setTimeout(async () => {
|
|
706
|
+
// Double-check that session is cleared before sending
|
|
707
|
+
sessionActions.selectSessionId(null);
|
|
708
|
+
selectedSessionIdRef.current = null;
|
|
709
|
+
await handleSendMessage();
|
|
710
|
+
}, 200);
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
const handleDockLeft = () => {
|
|
714
|
+
// If in inline mode, switch to modal mode when docking
|
|
715
|
+
if (isInlineMode) {
|
|
716
|
+
uiActions.setRenderMode('modal');
|
|
717
|
+
uiActions.setDocked(true);
|
|
718
|
+
uiActions.setExpanded(false);
|
|
719
|
+
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// If currently docked and original renderMode was inline, return to inline
|
|
723
|
+
if (uiState.isDocked && renderMode === 'inline') {
|
|
724
|
+
uiActions.setRenderMode('inline');
|
|
725
|
+
uiActions.setDocked(false);
|
|
726
|
+
uiActions.setExpanded(false);
|
|
727
|
+
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
uiActions.setDocked(!uiState.isDocked);
|
|
731
|
+
uiActions.setExpanded(false);
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const handleExpand = () => {
|
|
735
|
+
// In inline mode, always toggle fullscreen overlay (InlineView handles it via portal)
|
|
736
|
+
if (isInlineMode) {
|
|
737
|
+
setIsInlineFullscreen(prev => !prev);
|
|
738
|
+
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
// If currently expanded and original renderMode was inline, return to inline
|
|
742
|
+
if (uiState.isExpanded && renderMode === 'inline') {
|
|
743
|
+
uiActions.setRenderMode('inline');
|
|
744
|
+
uiActions.setExpanded(false);
|
|
745
|
+
uiActions.setDocked(false);
|
|
746
|
+
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
uiActions.setExpanded(!uiState.isExpanded);
|
|
750
|
+
uiActions.setDocked(false);
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
const handleCreateProject = async (e: FormEvent) => {
|
|
754
|
+
e.preventDefault();
|
|
755
|
+
|
|
756
|
+
if (!projectState.form.name.trim()) return;
|
|
757
|
+
|
|
758
|
+
projectActions.setCreating(true);
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
const response: any = await apiClient.post('/ai/projects', {
|
|
762
|
+
name: projectState.form.name,
|
|
763
|
+
tenant_ai_agent_id: tenantAiAgentId,
|
|
764
|
+
description: projectState.form.description || undefined
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
if (response.success && response.data) {
|
|
768
|
+
const newProject = response.data;
|
|
769
|
+
|
|
770
|
+
// Set the selected project
|
|
771
|
+
projectActions.selectProject(newProject);
|
|
772
|
+
|
|
773
|
+
// Reset form
|
|
774
|
+
projectActions.setForm({ name: '', description: '' });
|
|
775
|
+
|
|
776
|
+
// Navigate to conversation view
|
|
777
|
+
projectActions.setView('conversation');
|
|
778
|
+
|
|
779
|
+
// Refresh projects list
|
|
780
|
+
fetchProjects();
|
|
781
|
+
}
|
|
782
|
+
} catch (error) {
|
|
783
|
+
console.error('Failed to create project:', error);
|
|
784
|
+
// Could add error handling/toast here
|
|
785
|
+
} finally {
|
|
786
|
+
projectActions.setCreating(false);
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
|
|
790
|
+
const updateSession = async () => {
|
|
791
|
+
await updateSessionApi(
|
|
792
|
+
apiClient,
|
|
793
|
+
sessionState,
|
|
794
|
+
sessionActions,
|
|
795
|
+
fetchThreads,
|
|
796
|
+
fetchProjectThreads,
|
|
797
|
+
projectState.projectContext?.id || null,
|
|
798
|
+
projectState.selectedProject?.id || null,
|
|
799
|
+
t
|
|
800
|
+
);
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
const deleteSession = async () => {
|
|
804
|
+
await deleteSessionApi(
|
|
805
|
+
apiClient,
|
|
806
|
+
sessionState,
|
|
807
|
+
sessionActions,
|
|
808
|
+
fetchThreads,
|
|
809
|
+
fetchProjectThreads,
|
|
810
|
+
projectState.projectContext?.id || null,
|
|
811
|
+
projectState.selectedProject?.id || null,
|
|
812
|
+
t
|
|
813
|
+
);
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const updateProjectInstructions = async () => {
|
|
817
|
+
await updateProjectInstructionsApi(apiClient, projectState, projectActions, fetchProjects, t);
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
// Filter sessions based on search query
|
|
821
|
+
const filteredSessions = sessionState.sessions.filter((session) => {
|
|
822
|
+
if (!sessionState.searchQuery.trim()) return true;
|
|
823
|
+
|
|
824
|
+
const query = sessionState.searchQuery.toLowerCase();
|
|
825
|
+
|
|
826
|
+
return session.title.toLowerCase().includes(query);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Sidebar content render helper
|
|
830
|
+
const renderSidebarContent = () => (
|
|
831
|
+
<SidebarContent
|
|
832
|
+
sessions={sessionState.sessions}
|
|
833
|
+
works={worksState.works}
|
|
834
|
+
projects={projectState.projects}
|
|
835
|
+
selectedSessionId={sessionState.selectedSessionId}
|
|
836
|
+
selectedWorkId={worksState.selectedWork?.id}
|
|
837
|
+
activeTab={uiState.activeTab}
|
|
838
|
+
onNewChat={() => {
|
|
839
|
+
createNewThread();
|
|
840
|
+
projectActions.setContext(null);
|
|
841
|
+
}}
|
|
842
|
+
onTabChange={uiActions.setActiveTab}
|
|
843
|
+
onSessionClick={handleSessionClick}
|
|
844
|
+
onDeleteSession={(session) => {
|
|
845
|
+
sessionActions.selectSession(session);
|
|
846
|
+
sessionActions.setDeleteDialogOpen(true);
|
|
847
|
+
}}
|
|
848
|
+
onWorkClick={async (work) => {
|
|
849
|
+
if (work.base_thread_id) {
|
|
850
|
+
sessionActions.selectSessionId(work.base_thread_id);
|
|
851
|
+
uiActions.setActiveTab(0);
|
|
852
|
+
await loadThreadMessages(work.base_thread_id);
|
|
853
|
+
}
|
|
854
|
+
}}
|
|
855
|
+
onProjectClick={(project) => {
|
|
856
|
+
projectActions.selectProject(project);
|
|
857
|
+
projectActions.setView('conversation');
|
|
858
|
+
uiActions.setActiveTab(2);
|
|
859
|
+
}}
|
|
860
|
+
onShowMoreProjects={() => {
|
|
861
|
+
uiActions.setActiveTab(2);
|
|
862
|
+
projectActions.setView('list');
|
|
863
|
+
}}
|
|
864
|
+
onNewProject={() => {
|
|
865
|
+
uiActions.setActiveTab(2);
|
|
866
|
+
projectActions.setView('create');
|
|
867
|
+
}}
|
|
868
|
+
onToggleSidebar={uiState.isExpanded ? undefined : () => uiActions.setSidebarOpen(false)}
|
|
869
|
+
t={t} />
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
const handleWorkNavigation = (work: Work | null) => {
|
|
873
|
+
worksActions.selectWork(work);
|
|
874
|
+
uiActions.setRightSidebarView(work ? 'detail' : 'list');
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
// Inline mode: render directly without Dialog wrapper
|
|
878
|
+
if (isInlineMode) {
|
|
879
|
+
return (
|
|
880
|
+
<>
|
|
881
|
+
<InlineView
|
|
882
|
+
ref={ref as Ref<HTMLDivElement>}
|
|
883
|
+
open={open}
|
|
884
|
+
onOpenChange={onOpenChange}
|
|
885
|
+
messages={messages}
|
|
886
|
+
isLoading={isLoading}
|
|
887
|
+
input={input}
|
|
888
|
+
onInputChange={handleInputChange}
|
|
889
|
+
onSendMessage={handleSendMessage}
|
|
890
|
+
onStop={stop}
|
|
891
|
+
title={agentDetails?.name ?? title}
|
|
892
|
+
description={description}
|
|
893
|
+
welcomeMessage={agentDetails?.welcomeMessage}
|
|
894
|
+
isLoadingAgent={isLoadingAgentDetails}
|
|
895
|
+
logo={agentDetails?.logo ?? logo}
|
|
896
|
+
placeholder={placeholder}
|
|
897
|
+
footerText={footerText}
|
|
898
|
+
className={className}
|
|
899
|
+
userPhoto={userPhoto}
|
|
900
|
+
userDisplayName={userDisplayName}
|
|
901
|
+
enableSidebar={isInlineFullscreen ? true : enableSidebar}
|
|
902
|
+
supportFiles={supportFiles}
|
|
903
|
+
supportWebSearch={supportWebSearch}
|
|
904
|
+
supportDocumentSearch={supportDocumentSearch}
|
|
905
|
+
supportDeepResearch={supportDeepResearch}
|
|
906
|
+
supportThinking={supportThinking}
|
|
907
|
+
supportWorkCanvas={supportWorkCanvas}
|
|
908
|
+
supportMultiModels={supportMultiModels}
|
|
909
|
+
deploymentId={deploymentId}
|
|
910
|
+
tenantAiAgentId={tenantAiAgentId}
|
|
911
|
+
enableMicrophone={enableMicrophone}
|
|
912
|
+
enableVoice={enableVoice}
|
|
913
|
+
isRecording={isRecording}
|
|
914
|
+
recognition={recognition}
|
|
915
|
+
onMicrophoneClick={handleMicrophoneClick}
|
|
916
|
+
isSidebarOpen={uiState.isSidebarOpen}
|
|
917
|
+
onToggleSidebar={() => uiActions.setSidebarOpen(!uiState.isSidebarOpen)}
|
|
918
|
+
renderSidebar={renderSidebarContent}
|
|
919
|
+
activeTab={uiState.activeTab}
|
|
920
|
+
onTabChange={uiActions.setActiveTab}
|
|
921
|
+
onNewChat={() => {
|
|
922
|
+
createNewThread();
|
|
923
|
+
projectActions.setContext(null);
|
|
924
|
+
}}
|
|
925
|
+
enableNavDropdown={isInlineFullscreen ? false : enableNavDropdown}
|
|
926
|
+
isFullscreen={isInlineFullscreen}
|
|
927
|
+
onExpand={hideExpand ? undefined : handleExpand}
|
|
928
|
+
renderThreadHeader={sessionState.selectedSession && sessionState.selectedSession.id === sessionState.selectedSessionId ? () => (
|
|
929
|
+
<ThreadHeaderInline
|
|
930
|
+
session={sessionState.selectedSession!}
|
|
931
|
+
projects={projectState.projects}
|
|
932
|
+
projectContext={projectState.projectContext}
|
|
933
|
+
isFullscreen={isInlineFullscreen}
|
|
934
|
+
onSaveTitle={async (newTitle) => {
|
|
935
|
+
await updateSessionApi(
|
|
936
|
+
apiClient,
|
|
937
|
+
{ ...sessionState, name: newTitle },
|
|
938
|
+
sessionActions,
|
|
939
|
+
fetchThreads,
|
|
940
|
+
fetchProjectThreads,
|
|
941
|
+
projectState.projectContext?.id || null,
|
|
942
|
+
projectState.selectedProject?.id || null,
|
|
943
|
+
t
|
|
944
|
+
);
|
|
945
|
+
sessionActions.updateSession({ ...sessionState.selectedSession!, title: newTitle });
|
|
946
|
+
}}
|
|
947
|
+
onDelete={() => sessionActions.setDeleteDialogOpen(true)}
|
|
948
|
+
onArchive={async () => {
|
|
949
|
+
try {
|
|
950
|
+
await apiClient.patch(
|
|
951
|
+
`/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
|
|
952
|
+
{ archived: true }
|
|
953
|
+
);
|
|
954
|
+
sessionActions.removeSession(sessionState.selectedSession!.id);
|
|
955
|
+
sessionActions.selectSession(null);
|
|
956
|
+
sessionActions.selectSessionId(null);
|
|
957
|
+
toast.success(t('toast.session_archived_success'));
|
|
958
|
+
} catch {
|
|
959
|
+
toast.error(t('toast.failed_archive_session'));
|
|
960
|
+
}
|
|
961
|
+
}}
|
|
962
|
+
onMoveToProject={async (projectId) => {
|
|
963
|
+
try {
|
|
964
|
+
await apiClient.patch(
|
|
965
|
+
`/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
|
|
966
|
+
{ tenant_ai_project_id: projectId }
|
|
967
|
+
);
|
|
968
|
+
sessionActions.removeSession(sessionState.selectedSession!.id);
|
|
969
|
+
sessionActions.selectSession(null);
|
|
970
|
+
sessionActions.selectSessionId(null);
|
|
971
|
+
toast.success(t('toast.session_moved_success'));
|
|
972
|
+
} catch {
|
|
973
|
+
toast.error(t('toast.failed_move_session'));
|
|
974
|
+
}
|
|
975
|
+
}}
|
|
976
|
+
onProjectContextClick={() => {
|
|
977
|
+
const project = projectState.projects.find(p => p.id === projectState.projectContext!.id);
|
|
978
|
+
|
|
979
|
+
if (project) {
|
|
980
|
+
projectActions.selectProject(project);
|
|
981
|
+
projectActions.setView('conversation');
|
|
982
|
+
uiActions.setActiveTab(2);
|
|
983
|
+
projectActions.setContext(null);
|
|
984
|
+
}
|
|
985
|
+
}}
|
|
986
|
+
t={t} />
|
|
987
|
+
) : undefined}
|
|
988
|
+
renderSessionsView={() => (
|
|
989
|
+
<SessionsListView
|
|
990
|
+
sessions={sessionState.sessions}
|
|
991
|
+
filteredSessions={filteredSessions}
|
|
992
|
+
searchQuery={sessionState.searchQuery}
|
|
993
|
+
onSearchChange={sessionActions.setSearchQuery}
|
|
994
|
+
onNewSession={createNewThread}
|
|
995
|
+
onSessionClick={handleSessionClick}
|
|
996
|
+
onEditSession={(session) => {
|
|
997
|
+
sessionActions.selectSession(session);
|
|
998
|
+
sessionActions.setName(session.title || '');
|
|
999
|
+
sessionActions.startEditing();
|
|
1000
|
+
}}
|
|
1001
|
+
onDeleteSession={(session) => {
|
|
1002
|
+
sessionActions.selectSession(session);
|
|
1003
|
+
sessionActions.setDeleteDialogOpen(true);
|
|
1004
|
+
}}
|
|
1005
|
+
t={t} />
|
|
1006
|
+
)}
|
|
1007
|
+
renderProjectsView={() => (
|
|
1008
|
+
projectState.view === 'create' ? (
|
|
1009
|
+
<div className="flex-1 flex flex-col p-6">
|
|
1010
|
+
<div className="flex items-center gap-4 mb-6">
|
|
1011
|
+
{projectState.projects && projectState.projects.length > 0 && (
|
|
1012
|
+
<Button
|
|
1013
|
+
variant="ghost"
|
|
1014
|
+
size="icon"
|
|
1015
|
+
onClick={() => projectActions.setView('list')}
|
|
1016
|
+
className="text-muted-foreground cursor-pointer hover:text-foreground">
|
|
1017
|
+
<ArrowLeft className="w-5 h-5" />
|
|
1018
|
+
</Button>
|
|
1019
|
+
)}
|
|
1020
|
+
</div>
|
|
1021
|
+
<div className="max-w-lg mx-auto w-full">
|
|
1022
|
+
<h1 className="text-2xl font-semibold mb-4 ps-2">{t('dialogs.create_project')}</h1>
|
|
1023
|
+
<form onSubmit={handleCreateProject} className="grid grid-cols-1 gap-4">
|
|
1024
|
+
<div>
|
|
1025
|
+
<label className="text-sm ps-2 mb-1 block font-base">
|
|
1026
|
+
{t('placeholders.what_are_you_working')}
|
|
1027
|
+
</label>
|
|
1028
|
+
<Input
|
|
1029
|
+
value={projectState.form.name}
|
|
1030
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => projectActions.setForm({ ...projectState.form, name: e.target.value })}
|
|
1031
|
+
className="border border-border-300 hover:border-border-200 transition-colors font-large h-11 px-3 rounded-[0.6rem] w-full"
|
|
1032
|
+
placeholder={t('placeholders.name_your_project')}
|
|
1033
|
+
required />
|
|
1034
|
+
</div>
|
|
1035
|
+
<div>
|
|
1036
|
+
<label className="text-sm ps-2 mb-1 block font-base">
|
|
1037
|
+
{t('placeholders.what_are_you_trying')}
|
|
1038
|
+
</label>
|
|
1039
|
+
<Textarea
|
|
1040
|
+
value={projectState.form.description}
|
|
1041
|
+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => projectActions.setForm({ ...projectState.form, description: e.target.value })}
|
|
1042
|
+
className="border border-border-300 min-h-25 resize-none p-2 hover:border-border-200 transition-colors font-large px-3 rounded-[0.6rem] w-full"
|
|
1043
|
+
placeholder={t('placeholders.describe_project')} />
|
|
1044
|
+
</div>
|
|
1045
|
+
<div className="mt-2 flex justify-end gap-2">
|
|
1046
|
+
<Button
|
|
1047
|
+
type="button"
|
|
1048
|
+
variant="outline"
|
|
1049
|
+
onClick={() => projectActions.setView('list')}>
|
|
1050
|
+
{t('buttons.cancel')}
|
|
1051
|
+
</Button>
|
|
1052
|
+
<Button
|
|
1053
|
+
type="submit"
|
|
1054
|
+
disabled={projectState.isCreating || !projectState.form.name.trim()}>
|
|
1055
|
+
{projectState.isCreating ? t('buttons.creating') : t('buttons.create_project')}
|
|
1056
|
+
</Button>
|
|
1057
|
+
</div>
|
|
1058
|
+
</form>
|
|
1059
|
+
</div>
|
|
1060
|
+
</div>
|
|
1061
|
+
) : projectState.view === 'conversation' && projectState.selectedProject ? (
|
|
1062
|
+
<AssistantProjectDetailView
|
|
1063
|
+
project={projectState.selectedProject}
|
|
1064
|
+
projectThreads={projectState.projectThreads}
|
|
1065
|
+
projectWorks={projectState.projectWorks}
|
|
1066
|
+
onBack={() => projectActions.setView('list')}
|
|
1067
|
+
onEditInstructions={(instructions) => {
|
|
1068
|
+
projectActions.setInstructions(instructions);
|
|
1069
|
+
projectActions.startEditingInstructions();
|
|
1070
|
+
}}
|
|
1071
|
+
onProjectMessage={handleProjectMessage}
|
|
1072
|
+
onThreadClick={(thread) => {
|
|
1073
|
+
sessionActions.selectSessionId(thread.id);
|
|
1074
|
+
sessionActions.selectSession(thread);
|
|
1075
|
+
projectActions.setContext({
|
|
1076
|
+
name: projectState.selectedProject!.name,
|
|
1077
|
+
id: projectState.selectedProject!.id,
|
|
1078
|
+
message: thread.title || 'Untitled'
|
|
1079
|
+
});
|
|
1080
|
+
loadThreadMessages(thread.id);
|
|
1081
|
+
uiActions.setActiveTab(0);
|
|
1082
|
+
}}
|
|
1083
|
+
onEditSession={(thread) => {
|
|
1084
|
+
sessionActions.selectSession(thread);
|
|
1085
|
+
sessionActions.setName(thread.title || '');
|
|
1086
|
+
sessionActions.startEditing();
|
|
1087
|
+
}}
|
|
1088
|
+
onDeleteSession={(thread) => {
|
|
1089
|
+
sessionActions.selectSession(thread);
|
|
1090
|
+
sessionActions.setDeleteDialogOpen(true);
|
|
1091
|
+
}}
|
|
1092
|
+
input={input}
|
|
1093
|
+
onChange={handleInputChange}
|
|
1094
|
+
isLoading={isLoading}
|
|
1095
|
+
supportFiles={supportFiles}
|
|
1096
|
+
supportWebSearch={supportWebSearch}
|
|
1097
|
+
supportDocumentSearch={supportDocumentSearch}
|
|
1098
|
+
supportDeepResearch={supportDeepResearch}
|
|
1099
|
+
supportThinking={supportThinking}
|
|
1100
|
+
supportWorkCanvas={supportWorkCanvas}
|
|
1101
|
+
supportMultiModels={supportMultiModels}
|
|
1102
|
+
deploymentId={deploymentId}
|
|
1103
|
+
enableMicrophone={enableMicrophone}
|
|
1104
|
+
enableVoice={enableVoice}
|
|
1105
|
+
isRecording={isRecording}
|
|
1106
|
+
recognition={recognition}
|
|
1107
|
+
onMicrophoneClick={handleMicrophoneClick} />
|
|
1108
|
+
) : (
|
|
1109
|
+
<AssistantProjectsPanel
|
|
1110
|
+
projects={projectState.projects}
|
|
1111
|
+
searchQuery={projectSearchQuery}
|
|
1112
|
+
onSearchChange={setProjectSearchQuery}
|
|
1113
|
+
onNewProject={() => projectActions.setView('create')}
|
|
1114
|
+
onProjectClick={(project) => {
|
|
1115
|
+
projectActions.selectProject(project);
|
|
1116
|
+
projectActions.setView('conversation');
|
|
1117
|
+
}}
|
|
1118
|
+
onEditProject={(project) => {
|
|
1119
|
+
projectActions.selectProject(project);
|
|
1120
|
+
projectActions.setName(project.name || '');
|
|
1121
|
+
projectActions.setDescription(project.description || '');
|
|
1122
|
+
projectActions.startEditing();
|
|
1123
|
+
}}
|
|
1124
|
+
onDeleteProject={(project) => {
|
|
1125
|
+
projectActions.selectProject(project);
|
|
1126
|
+
projectActions.setDeleteDialogOpen(true);
|
|
1127
|
+
}}
|
|
1128
|
+
compact={enableNavDropdown}
|
|
1129
|
+
t={t} />
|
|
1130
|
+
)
|
|
1131
|
+
)}
|
|
1132
|
+
renderMemoriesView={() => <MemoriesPanel t={t} />}
|
|
1133
|
+
renderWorksView={() => (
|
|
1134
|
+
worksState.detailViewWork ? (
|
|
1135
|
+
<div className="flex-1 flex flex-col overflow-y-auto">
|
|
1136
|
+
<AssistantWorkDetailView
|
|
1137
|
+
work={worksState.detailViewWork}
|
|
1138
|
+
onClose={() => worksActions.setDetailViewWork(null)}
|
|
1139
|
+
onBack={() => worksActions.setDetailViewWork(null)} />
|
|
1140
|
+
</div>
|
|
1141
|
+
) : (
|
|
1142
|
+
<div className="flex-1 flex flex-col overflow-y-auto p-6">
|
|
1143
|
+
{/* Header */}
|
|
1144
|
+
<div className="flex items-center justify-between mb-6">
|
|
1145
|
+
<div className="flex flex-col min-w-0">
|
|
1146
|
+
<h1 className="text-2xl font-semibold ps-2">{t('tabs.works')}</h1>
|
|
1147
|
+
<div className="text-sm text-muted-foreground ps-2">{t('descriptions.manage_works')}</div>
|
|
1148
|
+
</div>
|
|
1149
|
+
<Button size="sm" className="shrink-0">
|
|
1150
|
+
<Plus className="w-4 h-4" />
|
|
1151
|
+
{t('buttons.new_work')}
|
|
1152
|
+
</Button>
|
|
1153
|
+
</div>
|
|
1154
|
+
|
|
1155
|
+
{/* Search */}
|
|
1156
|
+
<div className="flex items-center gap-4 mb-6">
|
|
1157
|
+
<div className="relative flex-1">
|
|
1158
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
1159
|
+
<Input
|
|
1160
|
+
type="text"
|
|
1161
|
+
placeholder={t('placeholders.search_works')}
|
|
1162
|
+
value={worksState.searchQuery}
|
|
1163
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => worksActions.setSearchQuery(e.target.value)}
|
|
1164
|
+
className="pl-10 pr-4 py-2 w-full border border-border rounded-lg focus:outline-none" />
|
|
1165
|
+
</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
|
|
1168
|
+
{/* Works Tabs */}
|
|
1169
|
+
<Tabs value={worksState.tabValue} onValueChange={worksActions.setTabValue}>
|
|
1170
|
+
<TabsList className="mb-4 p-1">
|
|
1171
|
+
<TabsTrigger className="py-2" value="1">
|
|
1172
|
+
{t('tabs.my_works')}
|
|
1173
|
+
</TabsTrigger>
|
|
1174
|
+
<TabsTrigger className="py-2" value="2">
|
|
1175
|
+
{t('tabs.shared_to_me')}
|
|
1176
|
+
</TabsTrigger>
|
|
1177
|
+
<TabsTrigger className="py-2" value="3">
|
|
1178
|
+
{t('tabs.library')}
|
|
1179
|
+
</TabsTrigger>
|
|
1180
|
+
</TabsList>
|
|
1181
|
+
<TabsContent value="1">
|
|
1182
|
+
{worksState.works.length === 0 ? (
|
|
1183
|
+
<div className="text-center py-8 text-muted-foreground">{t('messages.no_works_start')}</div>
|
|
1184
|
+
) : (
|
|
1185
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
1186
|
+
{worksState.works
|
|
1187
|
+
.filter(
|
|
1188
|
+
work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1189
|
+
|| work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1190
|
+
)
|
|
1191
|
+
.map(work => (
|
|
1192
|
+
<WorkCard
|
|
1193
|
+
key={work.id}
|
|
1194
|
+
work={work}
|
|
1195
|
+
onClick={async () => {
|
|
1196
|
+
if (work.base_thread_id) {
|
|
1197
|
+
sessionActions.selectSessionId(work.base_thread_id);
|
|
1198
|
+
uiActions.setActiveTab(0);
|
|
1199
|
+
await loadThreadMessages(work.base_thread_id);
|
|
1200
|
+
}
|
|
1201
|
+
}}
|
|
1202
|
+
t={t} />
|
|
1203
|
+
))}
|
|
1204
|
+
</div>
|
|
1205
|
+
)}
|
|
1206
|
+
</TabsContent>
|
|
1207
|
+
<TabsContent value="2">
|
|
1208
|
+
{(() => {
|
|
1209
|
+
const sharedWorks = worksState.works.filter(
|
|
1210
|
+
work => work.shared_to
|
|
1211
|
+
&& Array.isArray(work.shared_to)
|
|
1212
|
+
&& currentUserId
|
|
1213
|
+
&& work.shared_to.includes(currentUserId)
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1216
|
+
return sharedWorks.length === 0 ? (
|
|
1217
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
1218
|
+
{t('messages.no_works_shared')}
|
|
1219
|
+
</div>
|
|
1220
|
+
) : (
|
|
1221
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
1222
|
+
{worksState.works
|
|
1223
|
+
.filter((work) => {
|
|
1224
|
+
const isSharedToMe
|
|
1225
|
+
= work.shared_to
|
|
1226
|
+
&& Array.isArray(work.shared_to)
|
|
1227
|
+
&& currentUserId
|
|
1228
|
+
&& work.shared_to.includes(currentUserId);
|
|
1229
|
+
const matchesSearch
|
|
1230
|
+
= work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1231
|
+
|| work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase());
|
|
1232
|
+
|
|
1233
|
+
return isSharedToMe && matchesSearch;
|
|
1234
|
+
})
|
|
1235
|
+
.map(work => (
|
|
1236
|
+
<WorkCard
|
|
1237
|
+
key={work.id}
|
|
1238
|
+
work={work}
|
|
1239
|
+
onClick={async () => {
|
|
1240
|
+
if (work.base_thread_id) {
|
|
1241
|
+
sessionActions.selectSessionId(work.base_thread_id);
|
|
1242
|
+
uiActions.setActiveTab(0);
|
|
1243
|
+
await loadThreadMessages(work.base_thread_id);
|
|
1244
|
+
}
|
|
1245
|
+
}}
|
|
1246
|
+
t={t} />
|
|
1247
|
+
))}
|
|
1248
|
+
</div>
|
|
1249
|
+
);
|
|
1250
|
+
})()}
|
|
1251
|
+
</TabsContent>
|
|
1252
|
+
<TabsContent value="3">
|
|
1253
|
+
{worksState.works.length === 0 ? (
|
|
1254
|
+
<div className="text-center py-8 text-muted-foreground">{t('messages.no_works_found')}</div>
|
|
1255
|
+
) : (
|
|
1256
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
1257
|
+
{worksState.works
|
|
1258
|
+
.filter(
|
|
1259
|
+
work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1260
|
+
|| work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1261
|
+
)
|
|
1262
|
+
.map(work => (
|
|
1263
|
+
<WorkCard
|
|
1264
|
+
key={work.id}
|
|
1265
|
+
work={work}
|
|
1266
|
+
onClick={() => worksActions.setDetailViewWork(work)}
|
|
1267
|
+
t={t} />
|
|
1268
|
+
))}
|
|
1269
|
+
</div>
|
|
1270
|
+
)}
|
|
1271
|
+
</TabsContent>
|
|
1272
|
+
</Tabs>
|
|
1273
|
+
</div>
|
|
1274
|
+
)
|
|
1275
|
+
)}
|
|
1276
|
+
onToolAction={handleToolAction}
|
|
1277
|
+
threadId={sessionState.selectedSessionId ?? undefined}
|
|
1278
|
+
agentSelectorUrl={agentSelectorUrl}
|
|
1279
|
+
baseAgentSelectorUrl={baseAgentSelectorUrl}
|
|
1280
|
+
onAgentChange={(agentId, agentType) => {
|
|
1281
|
+
createNewThread();
|
|
1282
|
+
onAgentChange?.(agentId, agentType);
|
|
1283
|
+
}}
|
|
1284
|
+
t={t} />
|
|
1285
|
+
<AssistantDialogs
|
|
1286
|
+
projectState={projectState}
|
|
1287
|
+
projectActions={projectActions}
|
|
1288
|
+
sessionState={sessionState}
|
|
1289
|
+
sessionActions={sessionActions}
|
|
1290
|
+
onUpdateProject={updateProject}
|
|
1291
|
+
onDeleteProject={deleteProject}
|
|
1292
|
+
onUpdateSession={updateSession}
|
|
1293
|
+
onDeleteSession={deleteSession}
|
|
1294
|
+
onUpdateProjectInstructions={updateProjectInstructions}
|
|
1295
|
+
t={t} />
|
|
1296
|
+
</>
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Modal mode: render with Dialog wrapper (original implementation)
|
|
1301
|
+
return (
|
|
1302
|
+
<>
|
|
1303
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
1304
|
+
<DialogTitle className="sr-only">DocyAssistant</DialogTitle>
|
|
1305
|
+
<DialogContent
|
|
1306
|
+
className={cn(getDialogClassName({ isExpanded: uiState.isExpanded, isDocked: uiState.isDocked }), className, '@container')}
|
|
1307
|
+
data-docked={uiState.isDocked}
|
|
1308
|
+
data-expanded={uiState.isExpanded}
|
|
1309
|
+
data-container-query="true"
|
|
1310
|
+
showCloseButton={!uiState.isRightSidebarOpen}
|
|
1311
|
+
style={{
|
|
1312
|
+
position: 'fixed',
|
|
1313
|
+
...(uiState.isExpanded && {
|
|
1314
|
+
top: 0,
|
|
1315
|
+
left: 0,
|
|
1316
|
+
right: 0,
|
|
1317
|
+
bottom: 0,
|
|
1318
|
+
width: '100vw',
|
|
1319
|
+
height: '100vh',
|
|
1320
|
+
transform: 'none',
|
|
1321
|
+
maxWidth: 'none',
|
|
1322
|
+
maxHeight: 'none'
|
|
1323
|
+
}),
|
|
1324
|
+
...(uiState.isDocked && {
|
|
1325
|
+
top: 0,
|
|
1326
|
+
left: 0,
|
|
1327
|
+
width: '700px',
|
|
1328
|
+
height: '100vh',
|
|
1329
|
+
transform: 'none',
|
|
1330
|
+
maxWidth: 'none',
|
|
1331
|
+
maxHeight: 'none'
|
|
1332
|
+
})
|
|
1333
|
+
}}
|
|
1334
|
+
{...props}
|
|
1335
|
+
ref={ref}>
|
|
1336
|
+
{/* Floating Sidebar with Overlay - shown only when container < 3xl */}
|
|
1337
|
+
{enableSidebar && uiState.isSidebarOpen && (
|
|
1338
|
+
<>
|
|
1339
|
+
{/* Background Mask */}
|
|
1340
|
+
<div
|
|
1341
|
+
className={cn(
|
|
1342
|
+
'absolute inset-0 bg-black/30 z-99',
|
|
1343
|
+
// Show only when container is smaller than 3xl
|
|
1344
|
+
'@3xl:hidden'
|
|
1345
|
+
)}
|
|
1346
|
+
onClick={() => uiActions.setSidebarOpen(false)} />
|
|
1347
|
+
|
|
1348
|
+
{/* Floating Sidebar */}
|
|
1349
|
+
<div
|
|
1350
|
+
className={cn(
|
|
1351
|
+
'absolute left-0 top-0 h-full w-72 rounded-lg z-100 shadow-xl',
|
|
1352
|
+
// Show floating sidebar when container is smaller than 3xl
|
|
1353
|
+
'@3xl:hidden'
|
|
1354
|
+
)}
|
|
1355
|
+
onMouseLeave={() => {
|
|
1356
|
+
// Auto-close when mouse leaves sidebar (not in fullscreen/expanded mode)
|
|
1357
|
+
if (uiState.isExpanded) return;
|
|
1358
|
+
if (sidebarCloseTimeoutRef.current) {
|
|
1359
|
+
clearTimeout(sidebarCloseTimeoutRef.current);
|
|
1360
|
+
}
|
|
1361
|
+
sidebarCloseTimeoutRef.current = setTimeout(() => {
|
|
1362
|
+
uiActions.setSidebarOpen(false);
|
|
1363
|
+
}, 300);
|
|
1364
|
+
}}
|
|
1365
|
+
onMouseEnter={() => {
|
|
1366
|
+
// Clear any pending close timeout when mouse enters
|
|
1367
|
+
if (sidebarCloseTimeoutRef.current) {
|
|
1368
|
+
clearTimeout(sidebarCloseTimeoutRef.current);
|
|
1369
|
+
sidebarCloseTimeoutRef.current = null;
|
|
1370
|
+
}
|
|
1371
|
+
}}>
|
|
1372
|
+
<div className="w-full h-full flex flex-col">
|
|
1373
|
+
{/* Header with Close Button */}
|
|
1374
|
+
<div className="flex items-center justify-end h-10 px-2 border-b">
|
|
1375
|
+
{/* Close Button */}
|
|
1376
|
+
<Button
|
|
1377
|
+
variant="ghost"
|
|
1378
|
+
size="icon"
|
|
1379
|
+
onClick={() => uiActions.setSidebarOpen(false)}
|
|
1380
|
+
className="h-8 w-8 text-muted-foreground hover:text-foreground">
|
|
1381
|
+
<X className="w-4 h-4" />
|
|
1382
|
+
</Button>
|
|
1383
|
+
</div>
|
|
1384
|
+
|
|
1385
|
+
<div className="flex-1 p-4 flex flex-col min-h-0">
|
|
1386
|
+
{renderSidebarContent()}
|
|
1387
|
+
</div>
|
|
1388
|
+
</div>
|
|
1389
|
+
</div>
|
|
1390
|
+
</>
|
|
1391
|
+
)}
|
|
1392
|
+
|
|
1393
|
+
<div className={getContainerClassName({ isExpanded: uiState.isExpanded, isDocked: uiState.isDocked })}>
|
|
1394
|
+
{/* Top Left Controls - Sidebar Toggle & New Thread */}
|
|
1395
|
+
{enableSidebar && (
|
|
1396
|
+
<div className="absolute z-10 top-2.5 left-3 flex items-center gap-2">
|
|
1397
|
+
{/* Sidebar Toggle */}
|
|
1398
|
+
<Button
|
|
1399
|
+
variant="ghost"
|
|
1400
|
+
size="icon"
|
|
1401
|
+
onClick={() => uiActions.setSidebarOpen(!uiState.isSidebarOpen)}
|
|
1402
|
+
data-sidebar-open={uiState.isSidebarOpen}
|
|
1403
|
+
className={cn(
|
|
1404
|
+
'h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-all duration-300',
|
|
1405
|
+
// Hide button when sidebar is open
|
|
1406
|
+
uiState.isSidebarOpen && 'opacity-0 pointer-events-none'
|
|
1407
|
+
)}>
|
|
1408
|
+
<PanelLeft
|
|
1409
|
+
className={cn(
|
|
1410
|
+
'w-5 h-5 transition-transform duration-300',
|
|
1411
|
+
uiState.isSidebarOpen && 'scale-x-[-1]'
|
|
1412
|
+
)} />
|
|
1413
|
+
</Button>
|
|
1414
|
+
|
|
1415
|
+
{/* New Thread Button */}
|
|
1416
|
+
{!uiState.isSidebarOpen && (
|
|
1417
|
+
<Button
|
|
1418
|
+
variant="ghost"
|
|
1419
|
+
size="icon"
|
|
1420
|
+
onClick={createNewThread}
|
|
1421
|
+
className="h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-all duration-300"
|
|
1422
|
+
title={t('buttons.start_new_thread')}>
|
|
1423
|
+
<Edit className="w-5 h-5" />
|
|
1424
|
+
</Button>
|
|
1425
|
+
)}
|
|
1426
|
+
</div>
|
|
1427
|
+
)}
|
|
1428
|
+
|
|
1429
|
+
{/* Top Right Controls - Dock & Expand */}
|
|
1430
|
+
{!uiState.isRightSidebarOpen && (
|
|
1431
|
+
<div className="absolute top-2 right-12 flex items-center gap-2 z-50">
|
|
1432
|
+
{!uiState.isExpanded && (
|
|
1433
|
+
<Button
|
|
1434
|
+
variant="ghost"
|
|
1435
|
+
size="icon"
|
|
1436
|
+
className="h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
|
1437
|
+
onClick={handleDockLeft}
|
|
1438
|
+
title={uiState.isDocked ? t('actions.undock') : t('actions.dock_left')}>
|
|
1439
|
+
{uiState.isDocked ? <ArrowRight className="w-5 h-5" /> : <ArrowLeft className="w-5 h-5" />}
|
|
1440
|
+
</Button>
|
|
1441
|
+
)}
|
|
1442
|
+
|
|
1443
|
+
{!uiState.isDocked && (
|
|
1444
|
+
<Button
|
|
1445
|
+
variant="ghost"
|
|
1446
|
+
size="icon"
|
|
1447
|
+
className="h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
|
1448
|
+
onClick={handleExpand}
|
|
1449
|
+
title={uiState.isExpanded ? t('actions.minimize') : t('actions.expand')}>
|
|
1450
|
+
{uiState.isExpanded ? <Minimize2 className="w-5 h-5" /> : <Maximize2 className="w-5 h-5" />}
|
|
1451
|
+
</Button>
|
|
1452
|
+
)}
|
|
1453
|
+
</div>
|
|
1454
|
+
)}
|
|
1455
|
+
|
|
1456
|
+
{/* Inline Sidebar - shown only when container >= 3xl */}
|
|
1457
|
+
{enableSidebar && (
|
|
1458
|
+
<div
|
|
1459
|
+
className={cn(
|
|
1460
|
+
'relative flex-none transition-all duration-300',
|
|
1461
|
+
// Show inline sidebar when container is 3xl or larger (48rem/768px)
|
|
1462
|
+
'hidden @3xl:block',
|
|
1463
|
+
uiState.isSidebarOpen ? 'w-72' : 'w-0 overflow-hidden'
|
|
1464
|
+
)}>
|
|
1465
|
+
<div className="w-72 h-full flex flex-col">
|
|
1466
|
+
<div className="flex-1 p-4 flex flex-col min-h-0">
|
|
1467
|
+
{renderSidebarContent()}
|
|
1468
|
+
</div>
|
|
1469
|
+
</div>
|
|
1470
|
+
</div>
|
|
1471
|
+
)}
|
|
1472
|
+
|
|
1473
|
+
{/* Main Content Area */}
|
|
1474
|
+
<div className={cn('flex-1 flex flex-col bg-background', uiState.isDocked ? 'min-w-0' : 'min-w-96')}>
|
|
1475
|
+
{/* Conditionally render content based on activeTab */}
|
|
1476
|
+
{uiState.activeTab === 1 ? (
|
|
1477
|
+
<SessionsListView
|
|
1478
|
+
sessions={sessionState.sessions}
|
|
1479
|
+
filteredSessions={filteredSessions}
|
|
1480
|
+
searchQuery={sessionState.searchQuery}
|
|
1481
|
+
onSearchChange={sessionActions.setSearchQuery}
|
|
1482
|
+
onNewSession={createNewThread}
|
|
1483
|
+
onSessionClick={handleSessionClick}
|
|
1484
|
+
onEditSession={(session) => {
|
|
1485
|
+
sessionActions.selectSession(session);
|
|
1486
|
+
sessionActions.setName(session.title || '');
|
|
1487
|
+
sessionActions.startEditing();
|
|
1488
|
+
}}
|
|
1489
|
+
onDeleteSession={(session) => {
|
|
1490
|
+
sessionActions.selectSession(session);
|
|
1491
|
+
sessionActions.setDeleteDialogOpen(true);
|
|
1492
|
+
}}
|
|
1493
|
+
t={t} />
|
|
1494
|
+
) : uiState.activeTab === 2 ? (
|
|
1495
|
+
/* Projects View */
|
|
1496
|
+
projectState.view === 'list' ? (
|
|
1497
|
+
<AssistantProjectsPanel
|
|
1498
|
+
projects={projectState.projects}
|
|
1499
|
+
searchQuery={projectSearchQuery}
|
|
1500
|
+
onSearchChange={setProjectSearchQuery}
|
|
1501
|
+
onNewProject={() => projectActions.setView('create')}
|
|
1502
|
+
onProjectClick={(project) => {
|
|
1503
|
+
projectActions.selectProject(project);
|
|
1504
|
+
projectActions.setView('conversation');
|
|
1505
|
+
}}
|
|
1506
|
+
onEditProject={(project) => {
|
|
1507
|
+
projectActions.selectProject(project);
|
|
1508
|
+
projectActions.setName(project.name || '');
|
|
1509
|
+
projectActions.setDescription(project.description || '');
|
|
1510
|
+
projectActions.startEditing();
|
|
1511
|
+
}}
|
|
1512
|
+
onDeleteProject={(project) => {
|
|
1513
|
+
projectActions.selectProject(project);
|
|
1514
|
+
projectActions.setDeleteDialogOpen(true);
|
|
1515
|
+
}}
|
|
1516
|
+
compact={enableNavDropdown}
|
|
1517
|
+
t={t} />
|
|
1518
|
+
) : projectState.view === 'create' ? (
|
|
1519
|
+
/* Create Project Form */
|
|
1520
|
+
<div className="flex-1 flex flex-col p-6">
|
|
1521
|
+
{/* Header with Back Button */}
|
|
1522
|
+
<div className="flex items-center gap-4 mb-6">
|
|
1523
|
+
{projectState.projects && projectState.projects.length > 0 && (
|
|
1524
|
+
<Button
|
|
1525
|
+
variant="ghost"
|
|
1526
|
+
size="icon"
|
|
1527
|
+
onClick={() => projectActions.setView('list')}
|
|
1528
|
+
className="text-muted-foreground cursor-pointer hover:text-foreground">
|
|
1529
|
+
<ArrowLeft className="w-5 h-5" />
|
|
1530
|
+
</Button>
|
|
1531
|
+
)}
|
|
1532
|
+
</div>
|
|
1533
|
+
|
|
1534
|
+
<div className="max-w-lg mx-auto w-full">
|
|
1535
|
+
<h1 className="text-2xl font-semibold mb-4 ps-2">{t('dialogs.create_project')}</h1>
|
|
1536
|
+
<form onSubmit={handleCreateProject} className="grid grid-cols-1 gap-4">
|
|
1537
|
+
<div>
|
|
1538
|
+
<label className="text-sm ps-2 mb-1 block font-base">
|
|
1539
|
+
{t('placeholders.what_are_you_working')}
|
|
1540
|
+
</label>
|
|
1541
|
+
<Input
|
|
1542
|
+
value={projectState.form.name}
|
|
1543
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => projectActions.setForm({ ...projectState.form, name: e.target.value })}
|
|
1544
|
+
className="border border-border-300 hover:border-border-200 transition-colors font-large h-11 px-3 rounded-[0.6rem] w-full"
|
|
1545
|
+
placeholder={t('placeholders.name_your_project')}
|
|
1546
|
+
required />
|
|
1547
|
+
</div>
|
|
1548
|
+
|
|
1549
|
+
<div>
|
|
1550
|
+
<label className="text-sm ps-2 mb-1 block font-base">
|
|
1551
|
+
{t('placeholders.what_are_you_trying')}
|
|
1552
|
+
</label>
|
|
1553
|
+
<Textarea
|
|
1554
|
+
value={projectState.form.description}
|
|
1555
|
+
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => projectActions.setForm({ ...projectState.form, description: e.target.value })}
|
|
1556
|
+
className="border border-border-300 min-h-25 resize-none p-2 hover:border-border-200 transition-colors font-large px-3 rounded-[0.6rem] w-full"
|
|
1557
|
+
placeholder={t('placeholders.describe_project')} />
|
|
1558
|
+
</div>
|
|
1559
|
+
|
|
1560
|
+
<div className="mt-2 flex justify-end gap-2">
|
|
1561
|
+
<Button
|
|
1562
|
+
type="button"
|
|
1563
|
+
variant="outline"
|
|
1564
|
+
onClick={() => projectActions.setView('list')}>
|
|
1565
|
+
{t('buttons.cancel')}
|
|
1566
|
+
</Button>
|
|
1567
|
+
<Button
|
|
1568
|
+
type="submit"
|
|
1569
|
+
disabled={projectState.isCreating || !projectState.form.name.trim()}>
|
|
1570
|
+
{projectState.isCreating ? t('buttons.creating') : t('buttons.create_project')}
|
|
1571
|
+
</Button>
|
|
1572
|
+
</div>
|
|
1573
|
+
</form>
|
|
1574
|
+
</div>
|
|
1575
|
+
</div>
|
|
1576
|
+
) : (
|
|
1577
|
+
/* Project Conversation View */
|
|
1578
|
+
projectState.selectedProject && (
|
|
1579
|
+
<AssistantProjectDetailView
|
|
1580
|
+
project={projectState.selectedProject}
|
|
1581
|
+
projectThreads={projectState.projectThreads}
|
|
1582
|
+
projectWorks={projectState.projectWorks}
|
|
1583
|
+
onBack={() => projectActions.setView('list')}
|
|
1584
|
+
onEditInstructions={(instructions) => {
|
|
1585
|
+
projectActions.setInstructions(instructions);
|
|
1586
|
+
projectActions.startEditingInstructions();
|
|
1587
|
+
}}
|
|
1588
|
+
onProjectMessage={handleProjectMessage}
|
|
1589
|
+
onThreadClick={(thread) => {
|
|
1590
|
+
sessionActions.selectSessionId(thread.id);
|
|
1591
|
+
sessionActions.selectSession(thread);
|
|
1592
|
+
projectActions.setContext({
|
|
1593
|
+
name: projectState.selectedProject!.name,
|
|
1594
|
+
id: projectState.selectedProject!.id,
|
|
1595
|
+
message: thread.title || 'Untitled'
|
|
1596
|
+
});
|
|
1597
|
+
loadThreadMessages(thread.id);
|
|
1598
|
+
uiActions.setActiveTab(0); // Switch to Home tab
|
|
1599
|
+
}}
|
|
1600
|
+
onEditSession={(thread) => {
|
|
1601
|
+
sessionActions.selectSession(thread);
|
|
1602
|
+
sessionActions.setName(thread.title || '');
|
|
1603
|
+
sessionActions.startEditing();
|
|
1604
|
+
}}
|
|
1605
|
+
onDeleteSession={(thread) => {
|
|
1606
|
+
sessionActions.selectSession(thread);
|
|
1607
|
+
sessionActions.setDeleteDialogOpen(true);
|
|
1608
|
+
}}
|
|
1609
|
+
input={input}
|
|
1610
|
+
onChange={handleInputChange}
|
|
1611
|
+
isLoading={isLoading}
|
|
1612
|
+
supportFiles={supportFiles}
|
|
1613
|
+
supportWebSearch={supportWebSearch}
|
|
1614
|
+
supportDocumentSearch={supportDocumentSearch}
|
|
1615
|
+
supportDeepResearch={supportDeepResearch}
|
|
1616
|
+
supportThinking={supportThinking}
|
|
1617
|
+
supportWorkCanvas={supportWorkCanvas}
|
|
1618
|
+
supportMultiModels={supportMultiModels}
|
|
1619
|
+
deploymentId={deploymentId}
|
|
1620
|
+
enableMicrophone={enableMicrophone}
|
|
1621
|
+
enableVoice={enableVoice}
|
|
1622
|
+
isRecording={isRecording}
|
|
1623
|
+
recognition={recognition}
|
|
1624
|
+
onMicrophoneClick={handleMicrophoneClick} />
|
|
1625
|
+
)
|
|
1626
|
+
)
|
|
1627
|
+
) : uiState.activeTab === 3 ? (
|
|
1628
|
+
/* Works View */
|
|
1629
|
+
worksState.detailViewWork ? (
|
|
1630
|
+
/* Work Detail View */
|
|
1631
|
+
<div className="flex-1 flex flex-col overflow-y-auto">
|
|
1632
|
+
<AssistantWorkDetailView
|
|
1633
|
+
work={worksState.detailViewWork}
|
|
1634
|
+
onClose={() => worksActions.setDetailViewWork(null)}
|
|
1635
|
+
onBack={() => worksActions.setDetailViewWork(null)} />
|
|
1636
|
+
</div>
|
|
1637
|
+
) : (
|
|
1638
|
+
<div className="flex-1 flex flex-col overflow-y-auto p-6">
|
|
1639
|
+
{/* Header */}
|
|
1640
|
+
<div className="flex items-center justify-between mb-6">
|
|
1641
|
+
<div className="flex flex-col min-w-0">
|
|
1642
|
+
<h1 className="text-2xl font-semibold ps-2">{t('tabs.works')}</h1>
|
|
1643
|
+
<div className="text-sm text-muted-foreground ps-2">{t('descriptions.manage_works')}</div>
|
|
1644
|
+
</div>
|
|
1645
|
+
<Button size="sm" className="shrink-0">
|
|
1646
|
+
<Plus className="w-4 h-4" />
|
|
1647
|
+
{t('buttons.new_work')}
|
|
1648
|
+
</Button>
|
|
1649
|
+
</div>
|
|
1650
|
+
|
|
1651
|
+
{/* Search */}
|
|
1652
|
+
<div className="flex items-center gap-4 mb-6">
|
|
1653
|
+
<div className="relative flex-1">
|
|
1654
|
+
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
1655
|
+
<Input
|
|
1656
|
+
type="text"
|
|
1657
|
+
placeholder={t('placeholders.search_works')}
|
|
1658
|
+
value={worksState.searchQuery}
|
|
1659
|
+
onChange={(e: ChangeEvent<HTMLInputElement>) => worksActions.setSearchQuery(e.target.value)}
|
|
1660
|
+
className="pl-10 pr-4 py-2 w-full border border-border rounded-lg focus:outline-none" />
|
|
1661
|
+
</div>
|
|
1662
|
+
</div>
|
|
1663
|
+
|
|
1664
|
+
{/* Works Tabs */}
|
|
1665
|
+
<Tabs value={worksState.tabValue} onValueChange={worksActions.setTabValue}>
|
|
1666
|
+
<TabsList className="mb-4 p-1">
|
|
1667
|
+
<TabsTrigger className="py-2" value="1">
|
|
1668
|
+
{t('tabs.my_works')}
|
|
1669
|
+
</TabsTrigger>
|
|
1670
|
+
<TabsTrigger className="py-2" value="2">
|
|
1671
|
+
{t('tabs.shared_to_me')}
|
|
1672
|
+
</TabsTrigger>
|
|
1673
|
+
<TabsTrigger className="py-2" value="3">
|
|
1674
|
+
{t('tabs.library')}
|
|
1675
|
+
</TabsTrigger>
|
|
1676
|
+
</TabsList>
|
|
1677
|
+
<TabsContent value="1">
|
|
1678
|
+
{worksState.works.length === 0 ? (
|
|
1679
|
+
<div className="text-center py-8 text-muted-foreground">{t('messages.no_works_start')}</div>
|
|
1680
|
+
) : (
|
|
1681
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
1682
|
+
{worksState.works
|
|
1683
|
+
.filter(
|
|
1684
|
+
work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1685
|
+
|| work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1686
|
+
)
|
|
1687
|
+
.map(work => (
|
|
1688
|
+
<WorkCard
|
|
1689
|
+
key={work.id}
|
|
1690
|
+
work={work}
|
|
1691
|
+
onClick={async () => {
|
|
1692
|
+
if (work.base_thread_id) {
|
|
1693
|
+
sessionActions.selectSessionId(work.base_thread_id);
|
|
1694
|
+
uiActions.setActiveTab(0);
|
|
1695
|
+
await loadThreadMessages(work.base_thread_id);
|
|
1696
|
+
}
|
|
1697
|
+
}}
|
|
1698
|
+
t={t} />
|
|
1699
|
+
))}
|
|
1700
|
+
</div>
|
|
1701
|
+
)}
|
|
1702
|
+
</TabsContent>
|
|
1703
|
+
<TabsContent value="2">
|
|
1704
|
+
{(() => {
|
|
1705
|
+
const sharedWorks = worksState.works.filter(
|
|
1706
|
+
work => work.shared_to
|
|
1707
|
+
&& Array.isArray(work.shared_to)
|
|
1708
|
+
&& currentUserId
|
|
1709
|
+
&& work.shared_to.includes(currentUserId)
|
|
1710
|
+
);
|
|
1711
|
+
|
|
1712
|
+
return sharedWorks.length === 0 ? (
|
|
1713
|
+
<div className="text-center py-8 text-muted-foreground">
|
|
1714
|
+
{t('messages.no_works_shared')}
|
|
1715
|
+
</div>
|
|
1716
|
+
) : (
|
|
1717
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
1718
|
+
{worksState.works
|
|
1719
|
+
.filter((work) => {
|
|
1720
|
+
// Filter by shared_to field containing current user ID
|
|
1721
|
+
const isSharedToMe
|
|
1722
|
+
= work.shared_to
|
|
1723
|
+
&& Array.isArray(work.shared_to)
|
|
1724
|
+
&& currentUserId
|
|
1725
|
+
&& work.shared_to.includes(currentUserId);
|
|
1726
|
+
// Also filter by search query
|
|
1727
|
+
const matchesSearch
|
|
1728
|
+
= work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1729
|
+
|| work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase());
|
|
1730
|
+
|
|
1731
|
+
return isSharedToMe && matchesSearch;
|
|
1732
|
+
})
|
|
1733
|
+
.map(work => (
|
|
1734
|
+
<WorkCard
|
|
1735
|
+
key={work.id}
|
|
1736
|
+
work={work}
|
|
1737
|
+
onClick={async () => {
|
|
1738
|
+
if (work.base_thread_id) {
|
|
1739
|
+
sessionActions.selectSessionId(work.base_thread_id);
|
|
1740
|
+
uiActions.setActiveTab(0);
|
|
1741
|
+
await loadThreadMessages(work.base_thread_id);
|
|
1742
|
+
}
|
|
1743
|
+
}}
|
|
1744
|
+
t={t} />
|
|
1745
|
+
))}
|
|
1746
|
+
</div>
|
|
1747
|
+
);
|
|
1748
|
+
})()}
|
|
1749
|
+
</TabsContent>
|
|
1750
|
+
<TabsContent value="3">
|
|
1751
|
+
{worksState.works.length === 0 ? (
|
|
1752
|
+
<div className="text-center py-8 text-muted-foreground">{t('messages.no_works_found')}</div>
|
|
1753
|
+
) : (
|
|
1754
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
|
1755
|
+
{worksState.works
|
|
1756
|
+
.filter(
|
|
1757
|
+
work => work.title.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1758
|
+
|| work.content_text?.toLowerCase().includes(worksState.searchQuery.toLowerCase())
|
|
1759
|
+
)
|
|
1760
|
+
.map(work => (
|
|
1761
|
+
<WorkCard
|
|
1762
|
+
key={work.id}
|
|
1763
|
+
work={work}
|
|
1764
|
+
onClick={() => worksActions.setDetailViewWork(work)}
|
|
1765
|
+
t={t} />
|
|
1766
|
+
))}
|
|
1767
|
+
</div>
|
|
1768
|
+
)}
|
|
1769
|
+
</TabsContent>
|
|
1770
|
+
</Tabs>
|
|
1771
|
+
</div>
|
|
1772
|
+
)
|
|
1773
|
+
) : uiState.activeTab === 4 ? (
|
|
1774
|
+
/* Memories View */
|
|
1775
|
+
<MemoriesPanel t={t} />
|
|
1776
|
+
) : (
|
|
1777
|
+
<>
|
|
1778
|
+
{sessionState.selectedSession && sessionState.selectedSession.id === sessionState.selectedSessionId && (
|
|
1779
|
+
<ThreadHeaderInline
|
|
1780
|
+
session={sessionState.selectedSession}
|
|
1781
|
+
projects={projectState.projects}
|
|
1782
|
+
projectContext={projectState.projectContext}
|
|
1783
|
+
onSaveTitle={async (newTitle) => {
|
|
1784
|
+
await updateSessionApi(
|
|
1785
|
+
apiClient,
|
|
1786
|
+
{ ...sessionState, name: newTitle },
|
|
1787
|
+
sessionActions,
|
|
1788
|
+
fetchThreads,
|
|
1789
|
+
fetchProjectThreads,
|
|
1790
|
+
projectState.projectContext?.id || null,
|
|
1791
|
+
projectState.selectedProject?.id || null,
|
|
1792
|
+
t
|
|
1793
|
+
);
|
|
1794
|
+
sessionActions.updateSession({ ...sessionState.selectedSession!, title: newTitle });
|
|
1795
|
+
}}
|
|
1796
|
+
onDelete={() => sessionActions.setDeleteDialogOpen(true)}
|
|
1797
|
+
onArchive={async () => {
|
|
1798
|
+
try {
|
|
1799
|
+
await apiClient.patch(
|
|
1800
|
+
`/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
|
|
1801
|
+
{ archived: true }
|
|
1802
|
+
);
|
|
1803
|
+
sessionActions.removeSession(sessionState.selectedSession!.id);
|
|
1804
|
+
sessionActions.selectSession(null);
|
|
1805
|
+
sessionActions.selectSessionId(null);
|
|
1806
|
+
toast.success(t('toast.session_archived_success'));
|
|
1807
|
+
} catch {
|
|
1808
|
+
toast.error(t('toast.failed_archive_session'));
|
|
1809
|
+
}
|
|
1810
|
+
}}
|
|
1811
|
+
onMoveToProject={async (projectId) => {
|
|
1812
|
+
try {
|
|
1813
|
+
await apiClient.patch(
|
|
1814
|
+
`/apps/base/data-sources/thread/items/${sessionState.selectedSession!.id}`,
|
|
1815
|
+
{ tenant_ai_project_id: projectId }
|
|
1816
|
+
);
|
|
1817
|
+
sessionActions.removeSession(sessionState.selectedSession!.id);
|
|
1818
|
+
sessionActions.selectSession(null);
|
|
1819
|
+
sessionActions.selectSessionId(null);
|
|
1820
|
+
toast.success(t('toast.session_moved_success'));
|
|
1821
|
+
} catch {
|
|
1822
|
+
toast.error(t('toast.failed_move_session'));
|
|
1823
|
+
}
|
|
1824
|
+
}}
|
|
1825
|
+
onProjectContextClick={() => {
|
|
1826
|
+
const project = projectState.projects.find(p => p.id === projectState.projectContext!.id);
|
|
1827
|
+
|
|
1828
|
+
if (project) {
|
|
1829
|
+
projectActions.selectProject(project);
|
|
1830
|
+
projectActions.setView('conversation');
|
|
1831
|
+
uiActions.setActiveTab(2);
|
|
1832
|
+
projectActions.setContext(null);
|
|
1833
|
+
}
|
|
1834
|
+
}}
|
|
1835
|
+
t={t} />
|
|
1836
|
+
)}
|
|
1837
|
+
<ConversationView
|
|
1838
|
+
messages={messages}
|
|
1839
|
+
isLoading={isLoading}
|
|
1840
|
+
input={input}
|
|
1841
|
+
onInputChange={handleInputChange}
|
|
1842
|
+
onSendMessage={handleSendMessage}
|
|
1843
|
+
onStop={stop}
|
|
1844
|
+
logo={logo}
|
|
1845
|
+
userPhoto={userPhoto}
|
|
1846
|
+
userDisplayName={userDisplayName}
|
|
1847
|
+
description={description}
|
|
1848
|
+
title={agentDetails?.name ?? title}
|
|
1849
|
+
welcomeMessage={agentDetails?.welcomeMessage}
|
|
1850
|
+
isLoadingAgent={isLoadingAgentDetails}
|
|
1851
|
+
placeholder={placeholder}
|
|
1852
|
+
footerText={footerText}
|
|
1853
|
+
onWorkSelect={(work) => {
|
|
1854
|
+
worksActions.updateWork(work);
|
|
1855
|
+
worksActions.selectWork(work);
|
|
1856
|
+
}}
|
|
1857
|
+
onOpenRightSidebar={() => {
|
|
1858
|
+
uiActions.setRightSidebarOpen(true);
|
|
1859
|
+
uiActions.setRightSidebarView('detail');
|
|
1860
|
+
}}
|
|
1861
|
+
supportFiles={supportFiles}
|
|
1862
|
+
supportWebSearch={supportWebSearch}
|
|
1863
|
+
supportDocumentSearch={supportDocumentSearch}
|
|
1864
|
+
supportDeepResearch={supportDeepResearch}
|
|
1865
|
+
supportThinking={supportThinking}
|
|
1866
|
+
supportWorkCanvas={supportWorkCanvas}
|
|
1867
|
+
supportMultiModels={supportMultiModels}
|
|
1868
|
+
deploymentId={deploymentId}
|
|
1869
|
+
tenantAiAgentId={tenantAiAgentId}
|
|
1870
|
+
enableMicrophone={enableMicrophone}
|
|
1871
|
+
enableVoice={enableVoice}
|
|
1872
|
+
isRecording={isRecording}
|
|
1873
|
+
recognition={recognition}
|
|
1874
|
+
onMicrophoneClick={handleMicrophoneClick}
|
|
1875
|
+
onToolAction={handleToolAction}
|
|
1876
|
+
threadId={sessionState.selectedSessionId ?? undefined} />
|
|
1877
|
+
</>
|
|
1878
|
+
)}
|
|
1879
|
+
</div>
|
|
1880
|
+
|
|
1881
|
+
{/* Floating Right Sidebar with Overlay - shown only when container < 3xl */}
|
|
1882
|
+
{uiState.isRightSidebarOpen && (
|
|
1883
|
+
<>
|
|
1884
|
+
{/* Background Mask */}
|
|
1885
|
+
<div
|
|
1886
|
+
className={cn(
|
|
1887
|
+
'absolute inset-0 bg-black/30 z-99',
|
|
1888
|
+
// Show only when container is smaller than 3xl
|
|
1889
|
+
'@3xl:hidden'
|
|
1890
|
+
)}
|
|
1891
|
+
onClick={() => uiActions.setRightSidebarOpen(false)} />
|
|
1892
|
+
|
|
1893
|
+
{/* Floating Right Sidebar */}
|
|
1894
|
+
<div
|
|
1895
|
+
className={cn(
|
|
1896
|
+
'absolute right-0 top-0 h-full w-80 rounded-lg bg-card z-100 shadow-xl',
|
|
1897
|
+
// Show floating sidebar when container is smaller than 3xl
|
|
1898
|
+
'@3xl:hidden'
|
|
1899
|
+
)}
|
|
1900
|
+
onMouseLeave={() => {
|
|
1901
|
+
// Auto-close when mouse leaves sidebar
|
|
1902
|
+
if (rightSidebarCloseTimeoutRef.current) {
|
|
1903
|
+
clearTimeout(rightSidebarCloseTimeoutRef.current);
|
|
1904
|
+
}
|
|
1905
|
+
rightSidebarCloseTimeoutRef.current = setTimeout(() => {
|
|
1906
|
+
uiActions.setRightSidebarOpen(false);
|
|
1907
|
+
}, 300);
|
|
1908
|
+
}}
|
|
1909
|
+
onMouseEnter={() => {
|
|
1910
|
+
// Clear any pending close timeout when mouse enters
|
|
1911
|
+
if (rightSidebarCloseTimeoutRef.current) {
|
|
1912
|
+
clearTimeout(rightSidebarCloseTimeoutRef.current);
|
|
1913
|
+
rightSidebarCloseTimeoutRef.current = null;
|
|
1914
|
+
}
|
|
1915
|
+
}}>
|
|
1916
|
+
<AssistantCanvasView
|
|
1917
|
+
works={worksState.works}
|
|
1918
|
+
selectedWork={worksState.selectedWork}
|
|
1919
|
+
rightSidebarView={uiState.rightSidebarView}
|
|
1920
|
+
onWorkSelect={handleWorkNavigation}
|
|
1921
|
+
onBackToList={() => handleWorkNavigation(null)}
|
|
1922
|
+
onClose={() => uiActions.setRightSidebarOpen(false)} />
|
|
1923
|
+
</div>
|
|
1924
|
+
</>
|
|
1925
|
+
)}
|
|
1926
|
+
|
|
1927
|
+
{/* Inline Right Sidebar - shown only when container >= 3xl */}
|
|
1928
|
+
<div
|
|
1929
|
+
className={cn(
|
|
1930
|
+
'relative bg-card flex-none transition-all duration-300 border-l',
|
|
1931
|
+
// Show inline sidebar when container is 3xl or larger
|
|
1932
|
+
'hidden @3xl:block',
|
|
1933
|
+
uiState.isRightSidebarOpen ? 'w-80' : 'w-0 overflow-hidden'
|
|
1934
|
+
)}>
|
|
1935
|
+
<AssistantCanvasView
|
|
1936
|
+
works={worksState.works}
|
|
1937
|
+
selectedWork={worksState.selectedWork}
|
|
1938
|
+
rightSidebarView={uiState.rightSidebarView}
|
|
1939
|
+
onWorkSelect={handleWorkNavigation}
|
|
1940
|
+
onBackToList={() => handleWorkNavigation(null)}
|
|
1941
|
+
onClose={() => uiActions.setRightSidebarOpen(false)} />
|
|
1942
|
+
</div>
|
|
1943
|
+
</div>
|
|
1944
|
+
</DialogContent>
|
|
1945
|
+
</Dialog>
|
|
1946
|
+
|
|
1947
|
+
{/* CRUD Dialogs */}
|
|
1948
|
+
<AssistantDialogs
|
|
1949
|
+
projectState={projectState}
|
|
1950
|
+
projectActions={projectActions}
|
|
1951
|
+
sessionState={sessionState}
|
|
1952
|
+
sessionActions={sessionActions}
|
|
1953
|
+
onUpdateProject={updateProject}
|
|
1954
|
+
onDeleteProject={deleteProject}
|
|
1955
|
+
onUpdateSession={updateSession}
|
|
1956
|
+
onDeleteSession={deleteSession}
|
|
1957
|
+
onUpdateProjectInstructions={updateProjectInstructions}
|
|
1958
|
+
t={t} />
|
|
1959
|
+
|
|
1960
|
+
</>
|
|
1961
|
+
);
|
|
1962
|
+
};
|