@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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type ReactNode, createElement } from 'react'
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
3
|
+
import useSWR from 'swr'
|
|
4
|
+
|
|
5
|
+
import { getConfig } from '#~/api.js'
|
|
6
|
+
import { getAdapterDisplay } from '#~/resources/adapters.js'
|
|
7
|
+
import type { ConfigResponse } from '@vibe-forge/core'
|
|
8
|
+
|
|
9
|
+
const ADAPTER_STORAGE_KEY = 'vf_chat_adapter'
|
|
10
|
+
|
|
11
|
+
export function useChatAdapter() {
|
|
12
|
+
const [selectedAdapter, setSelectedAdapter] = useState<string | undefined>(() => {
|
|
13
|
+
try {
|
|
14
|
+
const raw = localStorage.getItem(ADAPTER_STORAGE_KEY)
|
|
15
|
+
return raw == null || raw.trim() === '' ? undefined : raw
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const { data: configRes } = useSWR<ConfigResponse>('/api/config', getConfig)
|
|
22
|
+
|
|
23
|
+
const mergedAdapters = useMemo(() => {
|
|
24
|
+
return configRes?.sources?.merged?.adapters ?? {}
|
|
25
|
+
}, [configRes?.sources?.merged?.adapters])
|
|
26
|
+
|
|
27
|
+
const defaultAdapter = configRes?.sources?.merged?.general?.defaultAdapter
|
|
28
|
+
|
|
29
|
+
const resolveAdapter = (value?: string) => {
|
|
30
|
+
const normalizedValue = typeof value === 'string' ? value.trim() : ''
|
|
31
|
+
const keys = Object.keys(mergedAdapters)
|
|
32
|
+
if (keys.length === 0) return undefined
|
|
33
|
+
if (normalizedValue !== '' && keys.includes(normalizedValue)) return normalizedValue
|
|
34
|
+
if (defaultAdapter && keys.includes(defaultAdapter as string)) return defaultAdapter as string
|
|
35
|
+
return keys[0]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const updateSelectedAdapter = (value?: string) => {
|
|
39
|
+
setSelectedAdapter((prev) => {
|
|
40
|
+
const nextValue = resolveAdapter(value)
|
|
41
|
+
return nextValue === prev ? prev : nextValue
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const adapterOptions = useMemo<Array<{ value: string; label: ReactNode }>>(() => {
|
|
46
|
+
const keys = Object.keys(mergedAdapters)
|
|
47
|
+
return keys.map((key) => {
|
|
48
|
+
const display = getAdapterDisplay(key)
|
|
49
|
+
return {
|
|
50
|
+
value: key,
|
|
51
|
+
label: createElement('span', { className: 'adapter-option' }, [
|
|
52
|
+
display.icon != null
|
|
53
|
+
? createElement('img', {
|
|
54
|
+
key: 'icon',
|
|
55
|
+
className: 'adapter-option__icon',
|
|
56
|
+
src: display.icon,
|
|
57
|
+
alt: '',
|
|
58
|
+
'aria-hidden': true
|
|
59
|
+
})
|
|
60
|
+
: null,
|
|
61
|
+
createElement('span', { key: 'text', className: 'adapter-option__text' }, display.title)
|
|
62
|
+
])
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}, [mergedAdapters])
|
|
66
|
+
|
|
67
|
+
// Auto-select: use stored value if valid, else config default, else first available
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
const keys = Object.keys(mergedAdapters)
|
|
70
|
+
if (keys.length === 0) {
|
|
71
|
+
setSelectedAdapter(undefined)
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
setSelectedAdapter((prev) => resolveAdapter(prev))
|
|
75
|
+
}, [defaultAdapter, mergedAdapters])
|
|
76
|
+
|
|
77
|
+
// Persist to localStorage
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
try {
|
|
80
|
+
if (selectedAdapter == null || selectedAdapter.trim() === '') {
|
|
81
|
+
localStorage.removeItem(ADAPTER_STORAGE_KEY)
|
|
82
|
+
} else {
|
|
83
|
+
localStorage.setItem(ADAPTER_STORAGE_KEY, selectedAdapter)
|
|
84
|
+
}
|
|
85
|
+
} catch {}
|
|
86
|
+
}, [selectedAdapter])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
selectedAdapter,
|
|
90
|
+
setSelectedAdapter: updateSelectedAdapter,
|
|
91
|
+
adapterOptions
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -2,13 +2,21 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
import useSWR from 'swr'
|
|
4
4
|
|
|
5
|
-
import type { ConfigResponse, ModelServiceConfig, RecommendedModelConfig } from '@vibe-forge/core'
|
|
6
5
|
import { getConfig } from '#~/api.js'
|
|
6
|
+
import type { AdapterBuiltinModel, ConfigResponse, ModelServiceConfig, RecommendedModelConfig } from '@vibe-forge/core'
|
|
7
|
+
import {
|
|
8
|
+
buildServiceModelSelector,
|
|
9
|
+
listServiceModels,
|
|
10
|
+
resolveChatModelSelection,
|
|
11
|
+
resolveDefaultChatModelSelection,
|
|
12
|
+
resolveServiceModelSelector
|
|
13
|
+
} from './model-selector'
|
|
7
14
|
|
|
8
15
|
export interface ModelSelectOption {
|
|
9
16
|
value: string
|
|
10
17
|
label: React.ReactNode
|
|
11
18
|
searchText: string
|
|
19
|
+
displayLabel: string
|
|
12
20
|
}
|
|
13
21
|
|
|
14
22
|
export interface ModelSelectGroup {
|
|
@@ -16,7 +24,11 @@ export interface ModelSelectGroup {
|
|
|
16
24
|
options: ModelSelectOption[]
|
|
17
25
|
}
|
|
18
26
|
|
|
19
|
-
export function useChatModels(
|
|
27
|
+
export function useChatModels({
|
|
28
|
+
selectedAdapter
|
|
29
|
+
}: {
|
|
30
|
+
selectedAdapter?: string
|
|
31
|
+
} = {}) {
|
|
20
32
|
const { t } = useTranslation()
|
|
21
33
|
const [selectedModel, setSelectedModel] = useState<string | undefined>(() => {
|
|
22
34
|
try {
|
|
@@ -41,76 +53,95 @@ export function useChatModels() {
|
|
|
41
53
|
))
|
|
42
54
|
}, [configRes?.sources?.merged?.general?.recommendedModels])
|
|
43
55
|
|
|
44
|
-
const
|
|
56
|
+
const adapterBuiltinModels = useMemo(() => {
|
|
57
|
+
const raw = configRes?.sources?.merged?.adapterBuiltinModels
|
|
58
|
+
return (raw ?? {}) as Record<string, AdapterBuiltinModel[]>
|
|
59
|
+
}, [configRes?.sources?.merged?.adapterBuiltinModels])
|
|
45
60
|
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
const activeBuiltinModels = useMemo(() => {
|
|
62
|
+
if (selectedAdapter && adapterBuiltinModels[selectedAdapter]) {
|
|
63
|
+
return { [selectedAdapter]: adapterBuiltinModels[selectedAdapter] }
|
|
64
|
+
}
|
|
65
|
+
return adapterBuiltinModels
|
|
66
|
+
}, [adapterBuiltinModels, selectedAdapter])
|
|
67
|
+
|
|
68
|
+
const activeBuiltinModelValues = useMemo(() => (
|
|
69
|
+
Object.values(activeBuiltinModels).flat().map(model => model.value)
|
|
70
|
+
), [activeBuiltinModels])
|
|
71
|
+
|
|
72
|
+
const builtinModelSet = useMemo(() => {
|
|
73
|
+
const set = new Set<string>()
|
|
74
|
+
for (const models of Object.values(activeBuiltinModels)) {
|
|
75
|
+
for (const m of models) set.add(m.value)
|
|
57
76
|
}
|
|
58
|
-
return
|
|
59
|
-
}, [
|
|
77
|
+
return set
|
|
78
|
+
}, [activeBuiltinModels])
|
|
79
|
+
|
|
80
|
+
const modelServiceEntries = useMemo(() => Object.entries(mergedModelServices), [mergedModelServices])
|
|
60
81
|
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const availableModelSet = useMemo(() => new Set(availableModelValues), [availableModelKey])
|
|
64
|
-
const hasAvailableModels = availableModelValues.length > 0
|
|
82
|
+
const availableServiceModels = useMemo(() => listServiceModels(mergedModelServices), [mergedModelServices])
|
|
83
|
+
const hasAvailableModels = availableServiceModels.length > 0 || builtinModelSet.size > 0
|
|
65
84
|
const modelToService = useMemo(() => {
|
|
66
85
|
const map = new Map<string, { key: string; title: string }>()
|
|
67
|
-
for (const entry of
|
|
86
|
+
for (const entry of availableServiceModels) {
|
|
87
|
+
const serviceValue = mergedModelServices[entry.serviceKey]
|
|
88
|
+
const serviceTitle = serviceValue?.title?.trim() !== '' ? serviceValue?.title ?? '' : entry.serviceKey
|
|
68
89
|
if (!map.has(entry.model)) {
|
|
69
|
-
map.set(entry.model, { key: entry.serviceKey, title:
|
|
90
|
+
map.set(entry.model, { key: entry.serviceKey, title: serviceTitle })
|
|
70
91
|
}
|
|
71
92
|
}
|
|
72
93
|
return map
|
|
73
|
-
}, [
|
|
94
|
+
}, [availableServiceModels, mergedModelServices])
|
|
74
95
|
const defaultModelService = configRes?.sources?.merged?.general?.defaultModelService
|
|
75
96
|
const defaultModel = configRes?.sources?.merged?.general?.defaultModel
|
|
76
97
|
const formatModelWithService = useCallback((model: string | undefined) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
return resolveChatModelSelection({
|
|
99
|
+
value: model,
|
|
100
|
+
builtinModels: activeBuiltinModelValues,
|
|
101
|
+
serviceModels: availableServiceModels,
|
|
102
|
+
defaultModelService
|
|
103
|
+
})
|
|
104
|
+
}, [activeBuiltinModelValues, availableServiceModels, defaultModelService])
|
|
83
105
|
const resolvedDefaultModel = useMemo(() => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
return availableModelValues[0]
|
|
106
|
+
return resolveDefaultChatModelSelection({
|
|
107
|
+
defaultModel,
|
|
108
|
+
defaultModelService,
|
|
109
|
+
builtinModels: activeBuiltinModelValues,
|
|
110
|
+
serviceModels: availableServiceModels
|
|
111
|
+
})
|
|
92
112
|
}, [
|
|
93
|
-
|
|
94
|
-
|
|
113
|
+
activeBuiltinModelValues,
|
|
114
|
+
availableServiceModels,
|
|
95
115
|
defaultModel,
|
|
96
|
-
defaultModelService
|
|
97
|
-
hasAvailableModels,
|
|
98
|
-
mergedModelServices
|
|
116
|
+
defaultModelService
|
|
99
117
|
])
|
|
100
118
|
const selectedModelWithService = useMemo(() => (
|
|
101
|
-
formatModelWithService(selectedModel)
|
|
102
|
-
), [formatModelWithService, selectedModel])
|
|
119
|
+
formatModelWithService(selectedModel) ?? resolvedDefaultModel
|
|
120
|
+
), [formatModelWithService, resolvedDefaultModel, selectedModel])
|
|
121
|
+
|
|
122
|
+
const resolveSelectableModel = useCallback((value?: string) => {
|
|
123
|
+
return resolveChatModelSelection({
|
|
124
|
+
value,
|
|
125
|
+
builtinModels: activeBuiltinModelValues,
|
|
126
|
+
serviceModels: availableServiceModels,
|
|
127
|
+
defaultModelService
|
|
128
|
+
}) ?? resolvedDefaultModel
|
|
129
|
+
}, [activeBuiltinModelValues, availableServiceModels, defaultModelService, resolvedDefaultModel])
|
|
130
|
+
|
|
131
|
+
const updateSelectedModel = useCallback((value?: string) => {
|
|
132
|
+
setSelectedModel((prev) => {
|
|
133
|
+
const nextValue = resolveSelectableModel(value)
|
|
134
|
+
return nextValue === prev ? prev : nextValue
|
|
135
|
+
})
|
|
136
|
+
}, [resolveSelectableModel])
|
|
103
137
|
|
|
104
138
|
useEffect(() => {
|
|
105
139
|
if (!hasAvailableModels) {
|
|
106
140
|
setSelectedModel(undefined)
|
|
107
141
|
return
|
|
108
142
|
}
|
|
109
|
-
setSelectedModel((prev) =>
|
|
110
|
-
|
|
111
|
-
return resolvedDefaultModel
|
|
112
|
-
})
|
|
113
|
-
}, [availableModelSet, hasAvailableModels, resolvedDefaultModel])
|
|
143
|
+
setSelectedModel((prev) => resolveSelectableModel(prev))
|
|
144
|
+
}, [hasAvailableModels, resolveSelectableModel, selectedAdapter])
|
|
114
145
|
|
|
115
146
|
useEffect(() => {
|
|
116
147
|
try {
|
|
@@ -150,7 +181,8 @@ export function useChatModels() {
|
|
|
150
181
|
return {
|
|
151
182
|
value: params.value,
|
|
152
183
|
label,
|
|
153
|
-
searchText
|
|
184
|
+
searchText,
|
|
185
|
+
displayLabel: params.title
|
|
154
186
|
}
|
|
155
187
|
}
|
|
156
188
|
|
|
@@ -171,14 +203,16 @@ export function useChatModels() {
|
|
|
171
203
|
const serviceTitle = service?.title?.trim() !== '' ? service?.title ?? '' : serviceKey
|
|
172
204
|
const groupTitle = serviceTitle?.trim() !== '' ? serviceTitle : serviceKey
|
|
173
205
|
const serviceDescription = service?.description
|
|
174
|
-
const models = Array.isArray(service?.models)
|
|
206
|
+
const models = Array.isArray(service?.models)
|
|
207
|
+
? service.models.filter((item): item is string => typeof item === 'string')
|
|
208
|
+
: []
|
|
175
209
|
if (models.length === 0) return null
|
|
176
|
-
const options = models.map((model) => {
|
|
210
|
+
const options = models.map((model: string) => {
|
|
177
211
|
const alias = resolveFirstAlias(service?.modelsAlias as Record<string, string[]> | undefined, model)
|
|
178
212
|
const title = alias ?? model
|
|
179
213
|
const description = alias ? model : serviceTitle
|
|
180
214
|
return buildOption({
|
|
181
|
-
value: model,
|
|
215
|
+
value: buildServiceModelSelector(serviceKey, model),
|
|
182
216
|
title,
|
|
183
217
|
description,
|
|
184
218
|
serviceKey,
|
|
@@ -200,7 +234,11 @@ export function useChatModels() {
|
|
|
200
234
|
const recommendedOptions = recommendedModels
|
|
201
235
|
.filter((item) => {
|
|
202
236
|
if (item.placement && item.placement !== 'modelSelector') return false
|
|
203
|
-
return
|
|
237
|
+
return resolveServiceModelSelector({
|
|
238
|
+
value: item.service ? buildServiceModelSelector(item.service, item.model) : item.model,
|
|
239
|
+
serviceModels: availableServiceModels,
|
|
240
|
+
preferredServiceKey: item.service ?? defaultModelService
|
|
241
|
+
}) != null
|
|
204
242
|
})
|
|
205
243
|
.map((item) => {
|
|
206
244
|
const serviceInfo = item.service ? mergedModelServices[item.service] : undefined
|
|
@@ -214,8 +252,13 @@ export function useChatModels() {
|
|
|
214
252
|
const description = item.description?.trim() !== ''
|
|
215
253
|
? item.description
|
|
216
254
|
: serviceTitle
|
|
255
|
+
const value = resolveServiceModelSelector({
|
|
256
|
+
value: item.service ? buildServiceModelSelector(item.service, item.model) : item.model,
|
|
257
|
+
serviceModels: availableServiceModels,
|
|
258
|
+
preferredServiceKey: item.service ?? defaultModelService
|
|
259
|
+
}) ?? item.model
|
|
217
260
|
return buildOption({
|
|
218
|
-
value
|
|
261
|
+
value,
|
|
219
262
|
title,
|
|
220
263
|
description,
|
|
221
264
|
serviceKey: item.service ?? modelToService.get(item.model)?.key,
|
|
@@ -235,9 +278,35 @@ export function useChatModels() {
|
|
|
235
278
|
options: recommendedOptions
|
|
236
279
|
})
|
|
237
280
|
}
|
|
281
|
+
|
|
282
|
+
// Adapter builtin model groups (filtered to active adapter)
|
|
283
|
+
for (const [adapterKey, models] of Object.entries(activeBuiltinModels)) {
|
|
284
|
+
if (!Array.isArray(models) || models.length === 0) continue
|
|
285
|
+
const adapterTitle = t('chat.modelGroupBuiltin', {
|
|
286
|
+
adapter: adapterKey,
|
|
287
|
+
defaultValue: `${adapterKey} (Default)`
|
|
288
|
+
})
|
|
289
|
+
groups.push({
|
|
290
|
+
label: (
|
|
291
|
+
<div className='model-group-label'>
|
|
292
|
+
<div className='model-group-title'>{adapterTitle}</div>
|
|
293
|
+
</div>
|
|
294
|
+
),
|
|
295
|
+
options: models.map(m =>
|
|
296
|
+
buildOption({
|
|
297
|
+
value: m.value,
|
|
298
|
+
title: m.title,
|
|
299
|
+
description: m.description
|
|
300
|
+
})
|
|
301
|
+
)
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
238
305
|
return [...groups, ...serviceGroups]
|
|
239
306
|
}, [
|
|
240
|
-
|
|
307
|
+
activeBuiltinModels,
|
|
308
|
+
availableServiceModels,
|
|
309
|
+
defaultModelService,
|
|
241
310
|
modelToService,
|
|
242
311
|
mergedModelServices,
|
|
243
312
|
modelServiceEntries,
|
|
@@ -248,7 +317,7 @@ export function useChatModels() {
|
|
|
248
317
|
return {
|
|
249
318
|
selectedModel,
|
|
250
319
|
selectedModelWithService,
|
|
251
|
-
setSelectedModel,
|
|
320
|
+
setSelectedModel: updateSelectedModel,
|
|
252
321
|
modelOptions,
|
|
253
322
|
hasAvailableModels
|
|
254
323
|
}
|
|
@@ -5,22 +5,26 @@ export type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'b
|
|
|
5
5
|
|
|
6
6
|
const PERMISSION_MODE_STORAGE_KEY = 'vf_chat_permission_mode'
|
|
7
7
|
|
|
8
|
-
const isPermissionMode = (value: string): value is PermissionMode => {
|
|
9
|
-
return value === 'default'
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
export const isPermissionMode = (value: string): value is PermissionMode => {
|
|
9
|
+
return value === 'default' ||
|
|
10
|
+
value === 'acceptEdits' ||
|
|
11
|
+
value === 'plan' ||
|
|
12
|
+
value === 'dontAsk' ||
|
|
13
|
+
value === 'bypassPermissions'
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function useChatPermissionMode() {
|
|
17
17
|
const [permissionMode, setPermissionMode] = useState<PermissionMode>('default')
|
|
18
18
|
|
|
19
|
+
const updatePermissionMode = (value?: string) => {
|
|
20
|
+
setPermissionMode(isPermissionMode(value ?? '') ? value : 'default')
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
useEffect(() => {
|
|
20
24
|
try {
|
|
21
25
|
const raw = localStorage.getItem(PERMISSION_MODE_STORAGE_KEY)
|
|
22
26
|
if (raw && isPermissionMode(raw)) {
|
|
23
|
-
|
|
27
|
+
updatePermissionMode(raw)
|
|
24
28
|
}
|
|
25
29
|
} catch {}
|
|
26
30
|
}, [])
|
|
@@ -31,17 +35,17 @@ export function useChatPermissionMode() {
|
|
|
31
35
|
} catch {}
|
|
32
36
|
}, [permissionMode])
|
|
33
37
|
|
|
34
|
-
const permissionModeOptions = useMemo<Array<{ value: PermissionMode; label: ReactNode }>>(() =>
|
|
38
|
+
const permissionModeOptions = useMemo<Array<{ value: PermissionMode; label: ReactNode }>>(() => [
|
|
35
39
|
{ value: 'default', label: '默认' },
|
|
36
40
|
{ value: 'acceptEdits', label: '接受编辑' },
|
|
37
41
|
{ value: 'plan', label: '计划' },
|
|
38
42
|
{ value: 'dontAsk', label: '不询问' },
|
|
39
43
|
{ value: 'bypassPermissions', label: '跳过权限' }
|
|
40
|
-
]
|
|
44
|
+
], [])
|
|
41
45
|
|
|
42
46
|
return {
|
|
43
47
|
permissionMode,
|
|
44
|
-
setPermissionMode,
|
|
48
|
+
setPermissionMode: updatePermissionMode,
|
|
45
49
|
permissionModeOptions
|
|
46
50
|
}
|
|
47
51
|
}
|
|
@@ -4,9 +4,9 @@ import { useTranslation } from 'react-i18next'
|
|
|
4
4
|
import { useNavigate } from 'react-router-dom'
|
|
5
5
|
import { useSWRConfig } from 'swr'
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import { createSession } from '#~/api.js'
|
|
7
|
+
import { createSession, getApiErrorMessage } from '#~/api.js'
|
|
9
8
|
import { connectionManager } from '#~/connectionManager.js'
|
|
9
|
+
import type { ChatMessageContent, Session } from '@vibe-forge/core'
|
|
10
10
|
import type { PermissionMode } from './use-chat-permission-mode'
|
|
11
11
|
|
|
12
12
|
export function useChatSessionActions({
|
|
@@ -14,12 +14,14 @@ export function useChatSessionActions({
|
|
|
14
14
|
modelForQuery,
|
|
15
15
|
hasAvailableModels,
|
|
16
16
|
permissionMode,
|
|
17
|
+
adapter,
|
|
17
18
|
onClearMessages
|
|
18
19
|
}: {
|
|
19
20
|
session?: Session
|
|
20
21
|
modelForQuery?: string
|
|
21
22
|
hasAvailableModels: boolean
|
|
22
23
|
permissionMode: PermissionMode
|
|
24
|
+
adapter?: string
|
|
23
25
|
onClearMessages: () => void
|
|
24
26
|
}) {
|
|
25
27
|
const { message } = App.useApp()
|
|
@@ -30,17 +32,18 @@ export function useChatSessionActions({
|
|
|
30
32
|
const isThinking = isCreating || session?.status === 'running'
|
|
31
33
|
|
|
32
34
|
const send = useCallback(async (text: string) => {
|
|
33
|
-
if (text.trim() === '' || isThinking) return
|
|
35
|
+
if (text.trim() === '' || isThinking) return false
|
|
34
36
|
if (!hasAvailableModels) {
|
|
35
37
|
void message.warning(t('chat.modelConfigRequired'))
|
|
36
|
-
return
|
|
38
|
+
return false
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
if (!session?.id) {
|
|
40
42
|
setIsCreating(true)
|
|
41
43
|
try {
|
|
42
44
|
const { session: newSession } = await createSession(undefined, text.trim(), undefined, modelForQuery, {
|
|
43
|
-
permissionMode
|
|
45
|
+
permissionMode,
|
|
46
|
+
adapter
|
|
44
47
|
})
|
|
45
48
|
|
|
46
49
|
await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
|
|
@@ -52,19 +55,22 @@ export function useChatSessionActions({
|
|
|
52
55
|
}, false)
|
|
53
56
|
|
|
54
57
|
void navigate(`/session/${newSession.id}`)
|
|
58
|
+
return true
|
|
55
59
|
} catch (err) {
|
|
56
60
|
console.error(err)
|
|
57
61
|
setIsCreating(false)
|
|
58
|
-
void message.error('Failed to create session')
|
|
62
|
+
void message.error(getApiErrorMessage(err, 'Failed to create session'))
|
|
63
|
+
return false
|
|
59
64
|
}
|
|
60
|
-
return
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
connectionManager.send(session.id, {
|
|
64
68
|
type: 'user_message',
|
|
65
69
|
text: text.trim()
|
|
66
70
|
})
|
|
71
|
+
return true
|
|
67
72
|
}, [
|
|
73
|
+
adapter,
|
|
68
74
|
hasAvailableModels,
|
|
69
75
|
isThinking,
|
|
70
76
|
message,
|
|
@@ -77,17 +83,18 @@ export function useChatSessionActions({
|
|
|
77
83
|
])
|
|
78
84
|
|
|
79
85
|
const sendContent = useCallback(async (content: ChatMessageContent[]) => {
|
|
80
|
-
if (content.length === 0 || isThinking) return
|
|
86
|
+
if (content.length === 0 || isThinking) return false
|
|
81
87
|
if (!hasAvailableModels) {
|
|
82
88
|
void message.warning(t('chat.modelConfigRequired'))
|
|
83
|
-
return
|
|
89
|
+
return false
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
if (!session?.id) {
|
|
87
93
|
setIsCreating(true)
|
|
88
94
|
try {
|
|
89
95
|
const { session: newSession } = await createSession(undefined, undefined, content, modelForQuery, {
|
|
90
|
-
permissionMode
|
|
96
|
+
permissionMode,
|
|
97
|
+
adapter
|
|
91
98
|
})
|
|
92
99
|
|
|
93
100
|
await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
|
|
@@ -99,20 +106,22 @@ export function useChatSessionActions({
|
|
|
99
106
|
}, false)
|
|
100
107
|
|
|
101
108
|
void navigate(`/session/${newSession.id}`)
|
|
102
|
-
|
|
109
|
+
return true
|
|
103
110
|
} catch (err) {
|
|
104
111
|
console.error(err)
|
|
105
112
|
setIsCreating(false)
|
|
106
|
-
void message.error('Failed to create session')
|
|
113
|
+
void message.error(getApiErrorMessage(err, 'Failed to create session'))
|
|
114
|
+
return false
|
|
107
115
|
}
|
|
108
|
-
return
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
connectionManager.send(session.id, {
|
|
112
119
|
type: 'user_message',
|
|
113
120
|
content
|
|
114
121
|
})
|
|
122
|
+
return true
|
|
115
123
|
}, [
|
|
124
|
+
adapter,
|
|
116
125
|
hasAvailableModels,
|
|
117
126
|
isThinking,
|
|
118
127
|
message,
|