@vibe-forge/client 0.4.0 → 0.6.0
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/AGENTS.md +37 -0
- package/cli.cjs +1 -0
- package/dist/assets/{arc-DgIxeTMg.js → arc-CMAHd5G3.js} +1 -1
- package/dist/assets/{blockDiagram-c4efeb88-CEAob3X9.js → blockDiagram-c4efeb88-DKww-VCP.js} +1 -1
- package/dist/assets/{c4Diagram-c83219d4-DwIxpDKd.js → c4Diagram-c83219d4-DKrjVHyY.js} +1 -1
- package/dist/assets/channel-Bi4g8rj9.js +1 -0
- package/dist/assets/{classDiagram-beda092f-Cz1q8u_0.js → classDiagram-beda092f-BXx5rdo3.js} +1 -1
- package/dist/assets/{classDiagram-v2-2358418a-CImgTuwd.js → classDiagram-v2-2358418a-CnR3WLsr.js} +1 -1
- package/dist/assets/clone-DPrpP2ky.js +1 -0
- package/dist/assets/{createText-1719965b-C1_HJcCc.js → createText-1719965b-CmOsl1W7.js} +1 -1
- package/dist/assets/{edges-96097737-BU8qStzd.js → edges-96097737-CQeQgpjD.js} +1 -1
- package/dist/assets/{erDiagram-0228fc6a-DNA1Fz2L.js → erDiagram-0228fc6a-ZUNB-ucF.js} +1 -1
- package/dist/assets/{flowDb-c6c81e3f-DjiCStMN.js → flowDb-c6c81e3f-DuuKeSLX.js} +1 -1
- package/dist/assets/{flowDiagram-50d868cf-CSDi0-RD.js → flowDiagram-50d868cf-Bc6n85yR.js} +1 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-BZqaeqoh.js +1 -0
- package/dist/assets/{flowchart-elk-definition-6af322e1-DrhIMas7.js → flowchart-elk-definition-6af322e1-cAG5afW9.js} +1 -1
- package/dist/assets/{ganttDiagram-a2739b55-CTZnUP5z.js → ganttDiagram-a2739b55-Dp6xhY5I.js} +1 -1
- package/dist/assets/{gitGraphDiagram-82fe8481-COOW7jTi.js → gitGraphDiagram-82fe8481-MlIIRBdG.js} +1 -1
- package/dist/assets/{graph-CIkpD4Kx.js → graph-D7Es8jZ-.js} +1 -1
- package/dist/assets/{index-5325376f-aVVRRTIu.js → index-5325376f-DC18ottv.js} +1 -1
- package/dist/assets/index-D37AbgPQ.js +545 -0
- package/dist/assets/{index-D1giUI7r.css → index-fcJ9v94I.css} +1 -1
- package/dist/assets/{infoDiagram-8eee0895-DQpZ1LVD.js → infoDiagram-8eee0895-CXk21kFp.js} +1 -1
- package/dist/assets/{journeyDiagram-c64418c1-DoKguIuk.js → journeyDiagram-c64418c1-899BKBHL.js} +1 -1
- package/dist/assets/{layout-Tnmha8Nh.js → layout-DLaxdy48.js} +1 -1
- package/dist/assets/{line-BQR2SOyl.js → line-_lw5YbRM.js} +1 -1
- package/dist/assets/{linear-DlG0eemV.js → linear-D5iu84ui.js} +1 -1
- package/dist/assets/{mermaid.core-BnwYO0He.js → mermaid.core-C6sW3GFM.js} +4 -4
- package/dist/assets/{mindmap-definition-8da855dc-BllYwDID.js → mindmap-definition-8da855dc-BS9Xy9KN.js} +1 -1
- package/dist/assets/{pieDiagram-a8764435-DwCkhPVc.js → pieDiagram-a8764435-DZt9cEgs.js} +1 -1
- package/dist/assets/{quadrantDiagram-1e28029f-c40GKTU0.js → quadrantDiagram-1e28029f-BTIeHOgn.js} +1 -1
- package/dist/assets/{requirementDiagram-08caed73-DnQp2Tk6.js → requirementDiagram-08caed73-BHJAKD2g.js} +1 -1
- package/dist/assets/{sankeyDiagram-a04cb91d-CnJrs13b.js → sankeyDiagram-a04cb91d-DnAkVOK8.js} +1 -1
- package/dist/assets/{sequenceDiagram-c5b8d532-1YBwnpKu.js → sequenceDiagram-c5b8d532-92tE3oFv.js} +1 -1
- package/dist/assets/{stateDiagram-1ecb1508-BFBxQ6Fh.js → stateDiagram-1ecb1508-DG0ObiMg.js} +1 -1
- package/dist/assets/{stateDiagram-v2-c2b004d7-Dmechvv2.js → stateDiagram-v2-c2b004d7-BKoJx2ci.js} +1 -1
- package/dist/assets/{styles-b4e223ce-DWWfWX8O.js → styles-b4e223ce-Ba6G4ri9.js} +1 -1
- package/dist/assets/{styles-ca3715f6-CKKvZxaU.js → styles-ca3715f6-Bn6RIIVW.js} +1 -1
- package/dist/assets/{styles-d45a18b0-dKMOUh9p.js → styles-d45a18b0-_dELBUI6.js} +1 -1
- package/dist/assets/{svgDrawCommon-b86b1483-CBgjChPM.js → svgDrawCommon-b86b1483-CRK-ZoIs.js} +1 -1
- package/dist/assets/{timeline-definition-faaaa080-NCt-HHmb.js → timeline-definition-faaaa080-DvQ_RA_i.js} +1 -1
- package/dist/assets/{xychartDiagram-f5964ef8-BJhXS4dG.js → xychartDiagram-f5964ef8-CJxeDLfg.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +10 -7
- package/src/App.tsx +20 -168
- package/src/api/base.ts +116 -7
- package/src/api/sessions.ts +3 -1
- package/src/api.ts +3 -1
- package/src/components/ArchiveView.tsx +5 -5
- package/src/components/ConfigView.tsx +28 -28
- package/src/components/{AutomationView → automation-view}/index.tsx +10 -9
- package/src/components/{BenchmarkView → benchmark-view}/index.tsx +5 -4
- package/src/components/chat/ChatHeader.tsx +6 -6
- package/src/components/chat/ChatHistoryView.tsx +74 -16
- package/src/components/chat/ChatTimelineView.tsx +3 -3
- package/src/components/chat/CurrentTodoList.scss +56 -27
- package/src/components/chat/{Sender → sender}/Sender.scss +278 -85
- package/src/components/chat/{Sender → sender}/Sender.tsx +125 -41
- package/src/components/chat/tools/core/ToolGroup.tsx +1 -1
- package/src/components/config/ConfigSectionForm.tsx +1 -1
- package/src/components/config/ConfigSourceSwitch.tsx +12 -34
- package/src/components/config/channelDefinitions.ts +2 -2
- package/src/components/layout/AppShell.scss +19 -0
- package/src/components/layout/AppShell.tsx +45 -0
- package/src/components/sidebar/SessionItem.scss +17 -0
- package/src/components/sidebar/SessionItem.tsx +21 -13
- package/src/hooks/chat/model-selector.ts +150 -0
- package/src/hooks/chat/use-chat-adapter.ts +93 -0
- package/src/hooks/chat/use-chat-models.tsx +126 -57
- package/src/hooks/chat/use-chat-permission-mode.ts +14 -10
- package/src/hooks/chat/use-chat-session-actions.ts +22 -13
- package/src/hooks/chat/use-chat-session-messages.ts +62 -10
- package/src/hooks/chat/use-chat-session.ts +49 -2
- package/src/hooks/use-app-preferences.ts +41 -0
- package/src/hooks/use-session-subscription.ts +101 -0
- package/src/hooks/use-sidebar-navigation.ts +35 -0
- package/src/resources/adapters.ts +20 -0
- package/src/resources/locales/en.json +6 -0
- package/src/resources/locales/zh.json +6 -0
- package/src/routes/AppRoutes.tsx +22 -0
- package/src/routes/ArchiveRoute.tsx +5 -0
- package/src/routes/AutomationRoute.tsx +5 -0
- package/src/routes/BenchmarkRoute.tsx +5 -0
- package/src/{components/Chat.scss → routes/ChatRoute.scss} +35 -0
- package/src/routes/ChatRoute.tsx +132 -0
- package/src/routes/ConfigRoute.tsx +5 -0
- package/src/routes/KnowledgeRoute.tsx +5 -0
- package/dist/assets/channel-DhtnrNJ6.js +0 -1
- package/dist/assets/clone-7bHB6YkC.js +0 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-_13Sz5Wh.js +0 -1
- package/dist/assets/index-DRSI_ZIL.js +0 -514
- package/src/components/Chat.tsx +0 -100
- package/src/components/{AutomationView → automation-view}/RuleFormPanel.scss +0 -0
- package/src/components/{AutomationView → automation-view}/RuleFormPanel.tsx +0 -0
- package/src/components/{AutomationView → automation-view}/RuleSidebar.scss +0 -0
- package/src/components/{AutomationView → automation-view}/RuleSidebar.tsx +0 -0
- package/src/components/{AutomationView → automation-view}/RunHistoryPanel.scss +0 -0
- package/src/components/{AutomationView → automation-view}/RunHistoryPanel.tsx +0 -0
- package/src/components/{AutomationView → automation-view}/TaskList.scss +0 -0
- package/src/components/{AutomationView → automation-view}/TaskList.tsx +0 -0
- package/src/components/{AutomationView → automation-view}/TriggerList.scss +0 -0
- package/src/components/{AutomationView → automation-view}/TriggerList.tsx +0 -0
- package/src/components/{AutomationView/AutomationView.scss → automation-view/index.scss} +0 -0
- package/src/components/{AutomationView → automation-view}/types.ts +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/BenchmarkCasePanel.scss +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/BenchmarkCasePanel.tsx +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/BenchmarkSidebar.scss +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/BenchmarkSidebar.tsx +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/BenchmarkView.scss +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/types.ts +0 -0
- package/src/components/{BenchmarkView → benchmark-view}/utils.ts +0 -0
- package/src/components/chat/{Messages → messages}/MessageFooter.tsx +0 -0
- package/src/components/chat/{Messages → messages}/MessageItem.scss +0 -0
- package/src/components/chat/{Messages → messages}/MessageItem.tsx +0 -0
- package/src/components/chat/{Messages → messages}/message-utils.ts +0 -0
- package/src/components/chat/{Sender → sender}/CompletionMenu.scss +0 -0
- package/src/components/chat/{Sender → sender}/CompletionMenu.tsx +0 -0
- package/src/components/chat/{Sender → sender}/ThinkingStatus.scss +0 -0
- package/src/components/chat/{Sender → sender}/ThinkingStatus.tsx +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/EventList.scss +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/EventList.tsx +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/gantt.ts +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/git-graph.ts +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/index.scss +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/index.tsx +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/mermaid.ts +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/types.ts +0 -0
- package/src/components/chat/{SessionTimelinePanel → session-timeline-panel}/utils.ts +0 -0
- package/src/components/config/{recordEditors → record-editors}/BooleanRecordEditor.scss +0 -0
- package/src/components/config/{recordEditors → record-editors}/BooleanRecordEditor.tsx +0 -0
- package/src/components/config/{recordEditors → record-editors}/ChannelRecordEditor.scss +0 -0
- package/src/components/config/{recordEditors → record-editors}/ChannelRecordEditor.tsx +33 -33
- /package/src/components/config/{recordEditors → record-editors}/KeyValueEditor.scss +0 -0
- /package/src/components/config/{recordEditors → record-editors}/KeyValueEditor.tsx +0 -0
- /package/src/components/config/{recordEditors → record-editors}/McpServersRecordEditor.scss +0 -0
- /package/src/components/config/{recordEditors → record-editors}/McpServersRecordEditor.tsx +0 -0
- /package/src/components/config/{recordEditors → record-editors}/ModelServicesRecordEditor.scss +0 -0
- /package/src/components/config/{recordEditors → record-editors}/ModelServicesRecordEditor.tsx +0 -0
- /package/src/components/config/{recordEditors → record-editors}/RecordEditors.scss +0 -0
- /package/src/components/config/{recordEditors → record-editors}/RecordJsonEditor.scss +0 -0
- /package/src/components/config/{recordEditors → record-editors}/RecordJsonEditor.tsx +0 -0
- /package/src/components/config/{recordEditors → record-editors}/index.tsx +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import './Sender.scss'
|
|
2
2
|
|
|
3
|
-
import { App, Button, Input, Select, Tooltip } from 'antd'
|
|
3
|
+
import { App, Button, Cascader, Input, Select, Tooltip } from 'antd'
|
|
4
4
|
import type { TextAreaRef } from 'antd/es/input/TextArea'
|
|
5
|
-
import React, {
|
|
5
|
+
import React, { useRef, useState } from 'react'
|
|
6
6
|
import { useTranslation } from 'react-i18next'
|
|
7
7
|
import useSWR from 'swr'
|
|
8
8
|
|
|
9
|
+
import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
|
|
9
10
|
import type { AskUserQuestionParams, ChatMessageContent, SessionInfo, SessionStatus } from '@vibe-forge/core'
|
|
11
|
+
import { isShortcutMatch } from '../../../utils/shortcutUtils'
|
|
10
12
|
import type { CompletionItem } from './CompletionMenu'
|
|
11
|
-
import type { PermissionMode } from '#~/hooks/chat/use-chat-permission-mode'
|
|
12
13
|
import { CompletionMenu } from './CompletionMenu'
|
|
13
14
|
import { ThinkingStatus } from './ThinkingStatus'
|
|
14
|
-
import { isShortcutMatch } from '../../../utils/shortcutUtils'
|
|
15
15
|
|
|
16
16
|
const { TextArea } = Input
|
|
17
17
|
|
|
@@ -19,6 +19,7 @@ interface ModelSelectOption {
|
|
|
19
19
|
value: string
|
|
20
20
|
label: React.ReactNode
|
|
21
21
|
searchText: string
|
|
22
|
+
displayLabel: string
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
interface ModelSelectGroup {
|
|
@@ -34,13 +35,37 @@ interface PendingImage {
|
|
|
34
35
|
mimeType?: string
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
interface SenderToolGroup {
|
|
39
|
+
key: 'chrome-devtools' | 'system'
|
|
40
|
+
label: string
|
|
41
|
+
tools: string[]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SenderToolOption {
|
|
45
|
+
value: string
|
|
46
|
+
label: React.ReactNode
|
|
47
|
+
children?: SenderToolOption[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const formatToolLabel = (tool: string) => {
|
|
51
|
+
const parts = tool.split('__')
|
|
52
|
+
return parts[parts.length - 1] || tool
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const getToolGroupIcon = (groupKey: SenderToolGroup['key']) => {
|
|
56
|
+
return groupKey === 'chrome-devtools' ? 'web_traffic' : 'memory'
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
export function Sender({
|
|
38
60
|
onSend,
|
|
39
61
|
onSendContent,
|
|
62
|
+
adapterLocked = false,
|
|
40
63
|
sessionStatus,
|
|
41
64
|
onInterrupt,
|
|
42
65
|
onClear,
|
|
43
66
|
sessionInfo,
|
|
67
|
+
connectionError,
|
|
68
|
+
onRetryConnection,
|
|
44
69
|
interactionRequest,
|
|
45
70
|
onInteractionResponse,
|
|
46
71
|
placeholder,
|
|
@@ -50,14 +75,20 @@ export function Sender({
|
|
|
50
75
|
permissionMode,
|
|
51
76
|
permissionModeOptions,
|
|
52
77
|
onPermissionModeChange,
|
|
78
|
+
selectedAdapter,
|
|
79
|
+
adapterOptions,
|
|
80
|
+
onAdapterChange,
|
|
53
81
|
modelUnavailable
|
|
54
82
|
}: {
|
|
55
83
|
onSend: (text: string) => void
|
|
56
84
|
onSendContent: (content: ChatMessageContent[]) => void
|
|
85
|
+
adapterLocked?: boolean
|
|
57
86
|
sessionStatus?: SessionStatus
|
|
58
87
|
onInterrupt: () => void
|
|
59
88
|
onClear?: () => void
|
|
60
89
|
sessionInfo?: SessionInfo | null
|
|
90
|
+
connectionError?: string | null
|
|
91
|
+
onRetryConnection?: () => void
|
|
61
92
|
interactionRequest?: { id: string; payload: AskUserQuestionParams } | null
|
|
62
93
|
onInteractionResponse?: (id: string, data: string | string[]) => void
|
|
63
94
|
placeholder?: string
|
|
@@ -67,6 +98,9 @@ export function Sender({
|
|
|
67
98
|
permissionMode: PermissionMode
|
|
68
99
|
permissionModeOptions: Array<{ value: PermissionMode; label: React.ReactNode }>
|
|
69
100
|
onPermissionModeChange: (mode: PermissionMode) => void
|
|
101
|
+
selectedAdapter?: string
|
|
102
|
+
adapterOptions?: Array<{ value: string; label: React.ReactNode }>
|
|
103
|
+
onAdapterChange?: (adapter: string) => void
|
|
70
104
|
modelUnavailable?: boolean
|
|
71
105
|
}) {
|
|
72
106
|
const { t } = useTranslation()
|
|
@@ -79,7 +113,6 @@ export function Sender({
|
|
|
79
113
|
|
|
80
114
|
const [showToolsList, setShowToolsList] = useState(false)
|
|
81
115
|
const textareaRef = useRef<TextAreaRef>(null)
|
|
82
|
-
const toolsRef = useRef<HTMLDivElement>(null)
|
|
83
116
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
84
117
|
const isMac = navigator.platform.includes('Mac')
|
|
85
118
|
const [pendingImages, setPendingImages] = useState<PendingImage[]>([])
|
|
@@ -101,16 +134,39 @@ export function Sender({
|
|
|
101
134
|
: 'mod+enter'
|
|
102
135
|
|
|
103
136
|
const isThinking = sessionStatus === 'running'
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
137
|
+
const groupedTools: SenderToolGroup[] = sessionInfo != null && sessionInfo.type === 'init'
|
|
138
|
+
? [
|
|
139
|
+
{
|
|
140
|
+
key: 'chrome-devtools',
|
|
141
|
+
label: t('chat.toolGroupChromeDevtools'),
|
|
142
|
+
tools: sessionInfo.tools.filter(tool => tool.startsWith('mcp__ChromeDevtools__'))
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
key: 'system',
|
|
146
|
+
label: t('chat.toolGroupSystem'),
|
|
147
|
+
tools: sessionInfo.tools.filter(tool => !tool.startsWith('mcp__ChromeDevtools__'))
|
|
148
|
+
}
|
|
149
|
+
].filter(group => group.tools.length > 0)
|
|
150
|
+
: []
|
|
151
|
+
const toolCascaderOptions: SenderToolOption[] = groupedTools.map(group => ({
|
|
152
|
+
value: group.key,
|
|
153
|
+
label: (
|
|
154
|
+
<span className='sender-tool-group-option'>
|
|
155
|
+
<span className='sender-tool-group-option__icon material-symbols-rounded'>{getToolGroupIcon(group.key)}</span>
|
|
156
|
+
<span className='sender-tool-group-option__text'>{group.label}</span>
|
|
157
|
+
<span className='sender-tool-group-option__count'>{group.tools.length}</span>
|
|
158
|
+
</span>
|
|
159
|
+
),
|
|
160
|
+
children: group.tools.map(tool => ({
|
|
161
|
+
value: tool,
|
|
162
|
+
label: (
|
|
163
|
+
<span className='sender-tool-option'>
|
|
164
|
+
<span className='sender-tool-option__dot' />
|
|
165
|
+
<span className='sender-tool-option__text'>{formatToolLabel(tool)}</span>
|
|
166
|
+
</span>
|
|
167
|
+
)
|
|
168
|
+
}))
|
|
169
|
+
}))
|
|
114
170
|
|
|
115
171
|
const [historyIndex, setHistoryIndex] = useState(-1)
|
|
116
172
|
const [draft, setDraft] = useState('')
|
|
@@ -348,7 +404,9 @@ export function Sender({
|
|
|
348
404
|
handleSend()
|
|
349
405
|
return
|
|
350
406
|
}
|
|
351
|
-
if (
|
|
407
|
+
if (
|
|
408
|
+
clearInputShortcut != null && clearInputShortcut.trim() !== '' && isShortcutMatch(e, clearInputShortcut, isMac)
|
|
409
|
+
) {
|
|
352
410
|
e.preventDefault()
|
|
353
411
|
clearInputValue()
|
|
354
412
|
return
|
|
@@ -538,6 +596,20 @@ export function Sender({
|
|
|
538
596
|
</div>
|
|
539
597
|
)}
|
|
540
598
|
<div className='chat-input-container'>
|
|
599
|
+
{connectionError && connectionError.trim() !== '' && (
|
|
600
|
+
<div className='connection-error-banner'>
|
|
601
|
+
<div className='connection-error-content'>
|
|
602
|
+
<span className='material-symbols-rounded'>error</span>
|
|
603
|
+
<div className='connection-error-copy'>
|
|
604
|
+
<div className='connection-error-title'>{t('chat.connectionErrorTitle')}</div>
|
|
605
|
+
<div className='connection-error-message'>{connectionError}</div>
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
<Button size='small' onClick={onRetryConnection}>
|
|
609
|
+
{t('chat.retryConnection')}
|
|
610
|
+
</Button>
|
|
611
|
+
</div>
|
|
612
|
+
)}
|
|
541
613
|
{modelUnavailable && (
|
|
542
614
|
<div className='model-unavailable'>
|
|
543
615
|
{t('chat.modelConfigRequired')}
|
|
@@ -616,36 +688,46 @@ export function Sender({
|
|
|
616
688
|
</Tooltip>
|
|
617
689
|
|
|
618
690
|
{sessionInfo != null && sessionInfo.type === 'init' && (
|
|
619
|
-
<div className='session-info-toolbar'
|
|
620
|
-
<
|
|
621
|
-
|
|
622
|
-
|
|
691
|
+
<div className='session-info-toolbar'>
|
|
692
|
+
<Cascader
|
|
693
|
+
open={showToolsList}
|
|
694
|
+
options={toolCascaderOptions}
|
|
695
|
+
expandTrigger='hover'
|
|
696
|
+
placement='topLeft'
|
|
697
|
+
allowClear={false}
|
|
698
|
+
popupClassName='sender-tools-cascader-popup'
|
|
699
|
+
onOpenChange={setShowToolsList}
|
|
700
|
+
onChange={() => setShowToolsList(false)}
|
|
623
701
|
>
|
|
624
|
-
<
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
<div className='tools-list-popup'>
|
|
631
|
-
<div className='popup-header'>{t('chat.availableTools')}</div>
|
|
632
|
-
<div className='popup-content'>
|
|
633
|
-
<div className='tools-list'>
|
|
634
|
-
{sessionInfo.tools.map(tool => (
|
|
635
|
-
<div key={tool} className='tool-item'>
|
|
636
|
-
<span className='material-symbols-rounded'>check_circle</span>
|
|
637
|
-
<span className='tool-name'>{tool}</span>
|
|
638
|
-
</div>
|
|
639
|
-
))}
|
|
640
|
-
</div>
|
|
641
|
-
</div>
|
|
702
|
+
<div className={`info-item ${showToolsList ? 'active' : ''}`}>
|
|
703
|
+
<span className='info-item-leading'>
|
|
704
|
+
<span className='material-symbols-rounded'>build</span>
|
|
705
|
+
</span>
|
|
706
|
+
<span className='info-text'>{t('chat.toolsCount', { count: sessionInfo.tools.length })}</span>
|
|
707
|
+
<span className='material-symbols-rounded arrow-icon'>keyboard_arrow_up</span>
|
|
642
708
|
</div>
|
|
643
|
-
|
|
709
|
+
</Cascader>
|
|
644
710
|
</div>
|
|
645
711
|
)}
|
|
646
712
|
</div>
|
|
647
713
|
|
|
648
714
|
<div className='toolbar-right'>
|
|
715
|
+
{adapterOptions && adapterOptions.length > 1 && (
|
|
716
|
+
<Select
|
|
717
|
+
className='adapter-select'
|
|
718
|
+
classNames={{ popup: { root: 'adapter-select-popup' } }}
|
|
719
|
+
value={selectedAdapter}
|
|
720
|
+
options={adapterOptions}
|
|
721
|
+
showSearch={false}
|
|
722
|
+
allowClear={false}
|
|
723
|
+
disabled={adapterLocked || modelUnavailable || isThinking}
|
|
724
|
+
onChange={(value) => onAdapterChange?.(value)}
|
|
725
|
+
placeholder={t('chat.adapterSelectPlaceholder', { defaultValue: 'Adapter' })}
|
|
726
|
+
optionLabelProp='label'
|
|
727
|
+
popupMatchSelectWidth={false}
|
|
728
|
+
/>
|
|
729
|
+
)}
|
|
730
|
+
|
|
649
731
|
<Select
|
|
650
732
|
className='model-select'
|
|
651
733
|
classNames={{ popup: { root: 'model-select-popup' } }}
|
|
@@ -656,7 +738,7 @@ export function Sender({
|
|
|
656
738
|
disabled={modelUnavailable || isThinking}
|
|
657
739
|
onChange={(value) => onModelChange?.(value)}
|
|
658
740
|
placeholder={modelUnavailable ? t('chat.modelUnavailable') : t('chat.modelSelectPlaceholder')}
|
|
659
|
-
optionLabelProp='
|
|
741
|
+
optionLabelProp='displayLabel'
|
|
660
742
|
filterOption={(input, option) => {
|
|
661
743
|
const searchText = String((option as ModelSelectOption | undefined)?.searchText ?? '')
|
|
662
744
|
return searchText.toLowerCase().includes(input.toLowerCase())
|
|
@@ -679,7 +761,9 @@ export function Sender({
|
|
|
679
761
|
/>
|
|
680
762
|
|
|
681
763
|
<div
|
|
682
|
-
className={`chat-send-btn ${input.trim() !== '' && !modelUnavailable ? 'active' : ''} ${
|
|
764
|
+
className={`chat-send-btn ${input.trim() !== '' && !modelUnavailable ? 'active' : ''} ${
|
|
765
|
+
isThinking ? 'thinking' : ''
|
|
766
|
+
} ${modelUnavailable ? 'disabled' : ''}`}
|
|
683
767
|
onClick={modelUnavailable ? undefined : (isThinking ? onInterrupt : handleSend)}
|
|
684
768
|
>
|
|
685
769
|
<span className='material-symbols-rounded'>
|
|
@@ -3,7 +3,7 @@ import './ToolGroup.scss'
|
|
|
3
3
|
import type { ChatMessage, ChatMessageContent } from '@vibe-forge/core'
|
|
4
4
|
import React, { useState } from 'react'
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
|
-
import { MessageFooter } from '../../
|
|
6
|
+
import { MessageFooter } from '../../messages/MessageFooter'
|
|
7
7
|
import { ToolRenderer } from './ToolRenderer'
|
|
8
8
|
|
|
9
9
|
type ToolGroupProps = {
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import { Radio } from 'antd'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
2
3
|
|
|
3
4
|
import type { ConfigSource } from '@vibe-forge/core'
|
|
4
5
|
|
|
5
|
-
import type { TranslationFn } from './configUtils'
|
|
6
|
-
|
|
7
6
|
export function ConfigSourceSwitch({
|
|
8
7
|
value,
|
|
9
8
|
onChange,
|
|
10
|
-
|
|
11
|
-
t
|
|
9
|
+
options,
|
|
12
10
|
}: {
|
|
13
11
|
value: ConfigSource
|
|
14
12
|
onChange: (value: ConfigSource) => void
|
|
15
|
-
|
|
16
|
-
t: TranslationFn
|
|
13
|
+
options: Array<{ value: ConfigSource; icon: string; label: ReactNode }>
|
|
17
14
|
}) {
|
|
18
15
|
return (
|
|
19
16
|
<Radio.Group
|
|
@@ -24,34 +21,15 @@ export function ConfigSourceSwitch({
|
|
|
24
21
|
onChange={(event) => {
|
|
25
22
|
onChange(event.target.value as ConfigSource)
|
|
26
23
|
}}
|
|
27
|
-
options={
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</span>
|
|
37
|
-
</span>
|
|
38
|
-
),
|
|
39
|
-
value: 'project'
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
label: (
|
|
43
|
-
<span className='config-view__source-option'>
|
|
44
|
-
<span className='material-symbols-rounded'>person</span>
|
|
45
|
-
<span>
|
|
46
|
-
{configPresent?.user === true
|
|
47
|
-
? t('config.sources.user')
|
|
48
|
-
: t('config.sources.userMissing')}
|
|
49
|
-
</span>
|
|
50
|
-
</span>
|
|
51
|
-
),
|
|
52
|
-
value: 'user'
|
|
53
|
-
}
|
|
54
|
-
]}
|
|
24
|
+
options={options.map(opt => ({
|
|
25
|
+
value: opt.value,
|
|
26
|
+
label: (
|
|
27
|
+
<span className='config-view__source-option'>
|
|
28
|
+
<span className='material-symbols-rounded'>{opt.icon}</span>
|
|
29
|
+
<span>{opt.label}</span>
|
|
30
|
+
</span>
|
|
31
|
+
)
|
|
32
|
+
}))}
|
|
55
33
|
/>
|
|
56
34
|
)
|
|
57
35
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { channelDefinition } from '@vibe-forge/channel-lark'
|
|
2
2
|
import type { ChannelDescriptor } from '@vibe-forge/core/channel'
|
|
3
3
|
|
|
4
4
|
export const channelDefinitions: ChannelDescriptor[] = [
|
|
5
|
-
|
|
5
|
+
channelDefinition
|
|
6
6
|
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.app-shell {
|
|
2
|
+
height: 100vh;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: row;
|
|
5
|
+
overflow: hidden;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.app-shell__content {
|
|
9
|
+
flex: 1;
|
|
10
|
+
position: relative;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
background-color: #fff;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.app-shell--dark {
|
|
16
|
+
.app-shell__content {
|
|
17
|
+
background-color: #141414;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import './AppShell.scss'
|
|
2
|
+
|
|
3
|
+
import { Layout } from 'antd'
|
|
4
|
+
import type { PropsWithChildren } from 'react'
|
|
5
|
+
|
|
6
|
+
import type { Session } from '@vibe-forge/core'
|
|
7
|
+
|
|
8
|
+
import { NavRail } from '#~/components/NavRail'
|
|
9
|
+
import { Sidebar } from '#~/components/Sidebar'
|
|
10
|
+
|
|
11
|
+
type AppShellProps = PropsWithChildren<{
|
|
12
|
+
activeSessionId?: string
|
|
13
|
+
isDarkMode: boolean
|
|
14
|
+
onDeletedSession: (deletedId: string, nextId?: string) => void
|
|
15
|
+
onSelectSession: (session: Session, isNew?: boolean) => void
|
|
16
|
+
showSidebar: boolean
|
|
17
|
+
sidebarWidth: number
|
|
18
|
+
}>
|
|
19
|
+
|
|
20
|
+
export function AppShell({
|
|
21
|
+
activeSessionId,
|
|
22
|
+
children,
|
|
23
|
+
isDarkMode,
|
|
24
|
+
onDeletedSession,
|
|
25
|
+
onSelectSession,
|
|
26
|
+
showSidebar,
|
|
27
|
+
sidebarWidth
|
|
28
|
+
}: AppShellProps) {
|
|
29
|
+
return (
|
|
30
|
+
<Layout className={`app-shell ${isDarkMode ? 'app-shell--dark' : ''}`}>
|
|
31
|
+
<NavRail />
|
|
32
|
+
{showSidebar && (
|
|
33
|
+
<Sidebar
|
|
34
|
+
width={sidebarWidth}
|
|
35
|
+
activeId={activeSessionId}
|
|
36
|
+
onSelectSession={onSelectSession}
|
|
37
|
+
onDeletedSession={onDeletedSession}
|
|
38
|
+
/>
|
|
39
|
+
)}
|
|
40
|
+
<Layout.Content className='app-shell__content'>
|
|
41
|
+
{children}
|
|
42
|
+
</Layout.Content>
|
|
43
|
+
</Layout>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -163,11 +163,15 @@
|
|
|
163
163
|
align-items: center;
|
|
164
164
|
|
|
165
165
|
.session-tag {
|
|
166
|
+
max-width: 120px;
|
|
166
167
|
font-size: 10px;
|
|
167
168
|
margin: 0;
|
|
168
169
|
padding: 0 4px;
|
|
169
170
|
line-height: 14px;
|
|
170
171
|
border-radius: 2px;
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
white-space: nowrap;
|
|
171
175
|
|
|
172
176
|
&--automation {
|
|
173
177
|
padding: 0 6px;
|
|
@@ -176,6 +180,19 @@
|
|
|
176
180
|
&__link {
|
|
177
181
|
color: var(--primary-color);
|
|
178
182
|
text-decoration: none;
|
|
183
|
+
display: inline-block;
|
|
184
|
+
max-width: 100%;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
text-overflow: ellipsis;
|
|
187
|
+
white-space: nowrap;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
&__text {
|
|
191
|
+
display: inline-block;
|
|
192
|
+
max-width: 100%;
|
|
193
|
+
overflow: hidden;
|
|
194
|
+
text-overflow: ellipsis;
|
|
195
|
+
white-space: nowrap;
|
|
179
196
|
}
|
|
180
197
|
}
|
|
181
198
|
}
|
|
@@ -226,28 +226,36 @@ export function SessionItem({
|
|
|
226
226
|
if (automationTag) {
|
|
227
227
|
const href = `/automation?rule=${encodeURIComponent(automationTag.ruleId)}`
|
|
228
228
|
return (
|
|
229
|
-
<
|
|
229
|
+
<Tooltip
|
|
230
230
|
key={tag}
|
|
231
|
-
|
|
232
|
-
onClick={(event) => event.stopPropagation()}
|
|
231
|
+
title={automationTag.ruleTitle}
|
|
233
232
|
>
|
|
234
|
-
<
|
|
235
|
-
className='session-
|
|
236
|
-
href={href}
|
|
233
|
+
<Tag
|
|
234
|
+
className='session-tag session-tag--automation'
|
|
237
235
|
onClick={(event) => event.stopPropagation()}
|
|
238
236
|
>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
237
|
+
<a
|
|
238
|
+
className='session-tag__link'
|
|
239
|
+
href={href}
|
|
240
|
+
onClick={(event) => event.stopPropagation()}
|
|
241
|
+
>
|
|
242
|
+
{automationTag.ruleTitle}
|
|
243
|
+
</a>
|
|
244
|
+
</Tag>
|
|
245
|
+
</Tooltip>
|
|
242
246
|
)
|
|
243
247
|
}
|
|
244
248
|
return (
|
|
245
|
-
<
|
|
249
|
+
<Tooltip
|
|
246
250
|
key={tag}
|
|
247
|
-
|
|
251
|
+
title={tag}
|
|
248
252
|
>
|
|
249
|
-
|
|
250
|
-
|
|
253
|
+
<Tag className='session-tag'>
|
|
254
|
+
<span className='session-tag__text'>
|
|
255
|
+
{tag}
|
|
256
|
+
</span>
|
|
257
|
+
</Tag>
|
|
258
|
+
</Tooltip>
|
|
251
259
|
)
|
|
252
260
|
})}
|
|
253
261
|
</div>
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import type { ModelServiceConfig } from '@vibe-forge/core'
|
|
2
|
+
|
|
3
|
+
export interface ServiceModelEntry {
|
|
4
|
+
serviceKey: string
|
|
5
|
+
model: string
|
|
6
|
+
selectorValue: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const normalizeNonEmptyString = (value: string | undefined) => {
|
|
10
|
+
const normalized = typeof value === 'string' ? value.trim() : ''
|
|
11
|
+
return normalized === '' ? undefined : normalized
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const buildServiceModelSelector = (serviceKey: string, modelName: string) => `${serviceKey},${modelName}`
|
|
15
|
+
|
|
16
|
+
export const parseServiceModelSelector = (value: string | undefined) => {
|
|
17
|
+
const normalizedValue = normalizeNonEmptyString(value)
|
|
18
|
+
if (!normalizedValue || !normalizedValue.includes(',')) return undefined
|
|
19
|
+
|
|
20
|
+
const [serviceKey, modelName] = normalizedValue.split(/,(.+)/)
|
|
21
|
+
const normalizedServiceKey = normalizeNonEmptyString(serviceKey)
|
|
22
|
+
const normalizedModelName = normalizeNonEmptyString(modelName)
|
|
23
|
+
if (!normalizedServiceKey || !normalizedModelName) return undefined
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
serviceKey: normalizedServiceKey,
|
|
27
|
+
modelName: normalizedModelName,
|
|
28
|
+
selectorValue: buildServiceModelSelector(normalizedServiceKey, normalizedModelName)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const listServiceModels = (modelServices: Record<string, ModelServiceConfig>) => {
|
|
33
|
+
const list: ServiceModelEntry[] = []
|
|
34
|
+
|
|
35
|
+
for (const [serviceKey, serviceValue] of Object.entries(modelServices)) {
|
|
36
|
+
const normalizedServiceKey = normalizeNonEmptyString(serviceKey)
|
|
37
|
+
if (!normalizedServiceKey) continue
|
|
38
|
+
|
|
39
|
+
const service = (serviceValue != null && typeof serviceValue === 'object')
|
|
40
|
+
? serviceValue
|
|
41
|
+
: undefined
|
|
42
|
+
const models = Array.isArray(service?.models) ? service.models : []
|
|
43
|
+
|
|
44
|
+
for (const model of models) {
|
|
45
|
+
const normalizedModel = normalizeNonEmptyString(model)
|
|
46
|
+
if (!normalizedModel) continue
|
|
47
|
+
|
|
48
|
+
list.push({
|
|
49
|
+
serviceKey: normalizedServiceKey,
|
|
50
|
+
model: normalizedModel,
|
|
51
|
+
selectorValue: buildServiceModelSelector(normalizedServiceKey, normalizedModel)
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return list
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const findExactServiceModel = (serviceModels: ServiceModelEntry[], serviceKey: string, modelName: string) => (
|
|
60
|
+
serviceModels.find(entry => entry.serviceKey === serviceKey && entry.model === modelName)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
export const resolveServiceModelSelector = (params: {
|
|
64
|
+
value?: string
|
|
65
|
+
serviceModels: ServiceModelEntry[]
|
|
66
|
+
preferredServiceKey?: string
|
|
67
|
+
}) => {
|
|
68
|
+
const normalizedValue = normalizeNonEmptyString(params.value)
|
|
69
|
+
if (!normalizedValue) return undefined
|
|
70
|
+
|
|
71
|
+
const parsed = parseServiceModelSelector(normalizedValue)
|
|
72
|
+
if (parsed) {
|
|
73
|
+
const exactMatch = findExactServiceModel(params.serviceModels, parsed.serviceKey, parsed.modelName)
|
|
74
|
+
if (exactMatch) return exactMatch.selectorValue
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const modelName = parsed?.modelName ?? normalizedValue
|
|
78
|
+
const candidates = params.serviceModels.filter(entry => entry.model === modelName)
|
|
79
|
+
if (candidates.length === 0) return undefined
|
|
80
|
+
|
|
81
|
+
const preferredKeys = [parsed?.serviceKey, normalizeNonEmptyString(params.preferredServiceKey)]
|
|
82
|
+
.filter((value, index, array): value is string => Boolean(value) && array.indexOf(value) === index)
|
|
83
|
+
|
|
84
|
+
for (const preferredKey of preferredKeys) {
|
|
85
|
+
const candidate = candidates.find(entry => entry.serviceKey === preferredKey)
|
|
86
|
+
if (candidate) return candidate.selectorValue
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return candidates[0]?.selectorValue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export const resolveChatModelSelection = (params: {
|
|
93
|
+
value?: string
|
|
94
|
+
builtinModels?: Iterable<string>
|
|
95
|
+
serviceModels: ServiceModelEntry[]
|
|
96
|
+
defaultModelService?: string
|
|
97
|
+
}) => {
|
|
98
|
+
const normalizedValue = normalizeNonEmptyString(params.value)
|
|
99
|
+
if (!normalizedValue) return undefined
|
|
100
|
+
|
|
101
|
+
const builtinModelSet = new Set(
|
|
102
|
+
Array.from(params.builtinModels ?? [])
|
|
103
|
+
.map(item => normalizeNonEmptyString(item))
|
|
104
|
+
.filter((item): item is string => Boolean(item))
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if (builtinModelSet.has(normalizedValue)) return normalizedValue
|
|
108
|
+
|
|
109
|
+
return resolveServiceModelSelector({
|
|
110
|
+
value: normalizedValue,
|
|
111
|
+
serviceModels: params.serviceModels,
|
|
112
|
+
preferredServiceKey: params.defaultModelService
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const resolveDefaultChatModelSelection = (params: {
|
|
117
|
+
defaultModel?: string
|
|
118
|
+
defaultModelService?: string
|
|
119
|
+
builtinModels?: Iterable<string>
|
|
120
|
+
serviceModels: ServiceModelEntry[]
|
|
121
|
+
}) => {
|
|
122
|
+
const builtinModels = Array.from(params.builtinModels ?? [])
|
|
123
|
+
.map(item => normalizeNonEmptyString(item))
|
|
124
|
+
.filter((item): item is string => Boolean(item))
|
|
125
|
+
const builtinModelSet = new Set(builtinModels)
|
|
126
|
+
const normalizedDefaultModel = normalizeNonEmptyString(params.defaultModel)
|
|
127
|
+
|
|
128
|
+
if (normalizedDefaultModel) {
|
|
129
|
+
const parsed = parseServiceModelSelector(normalizedDefaultModel)
|
|
130
|
+
const resolvedServiceModel = resolveServiceModelSelector({
|
|
131
|
+
value: normalizedDefaultModel,
|
|
132
|
+
serviceModels: params.serviceModels,
|
|
133
|
+
preferredServiceKey: parsed?.serviceKey ?? params.defaultModelService
|
|
134
|
+
})
|
|
135
|
+
if (resolvedServiceModel) return resolvedServiceModel
|
|
136
|
+
|
|
137
|
+
if (builtinModelSet.has(normalizedDefaultModel)) return normalizedDefaultModel
|
|
138
|
+
if (parsed?.modelName && builtinModelSet.has(parsed.modelName)) return parsed.modelName
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const normalizedDefaultModelService = normalizeNonEmptyString(params.defaultModelService)
|
|
142
|
+
if (normalizedDefaultModelService) {
|
|
143
|
+
const defaultServiceModel = params.serviceModels.find(entry => entry.serviceKey === normalizedDefaultModelService)
|
|
144
|
+
if (defaultServiceModel) return defaultServiceModel.selectorValue
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (builtinModels.length > 0) return builtinModels[0]
|
|
148
|
+
|
|
149
|
+
return params.serviceModels[0]?.selectorValue
|
|
150
|
+
}
|