@vibe-forge/client 0.2.0-alpha.9 → 0.4.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/cli.cjs +1 -1
- package/dist/assets/{arc-CybT1Fs2.js → arc-DgIxeTMg.js} +1 -1
- package/dist/assets/{blockDiagram-c4efeb88-BY5Aoa-D.js → blockDiagram-c4efeb88-CEAob3X9.js} +1 -1
- package/dist/assets/{c4Diagram-c83219d4-F42hTbzS.js → c4Diagram-c83219d4-DwIxpDKd.js} +1 -1
- package/dist/assets/channel-DhtnrNJ6.js +1 -0
- package/dist/assets/{classDiagram-beda092f-D-tIPp-3.js → classDiagram-beda092f-Cz1q8u_0.js} +1 -1
- package/dist/assets/{classDiagram-v2-2358418a-J57aCe6u.js → classDiagram-v2-2358418a-CImgTuwd.js} +1 -1
- package/dist/assets/clone-7bHB6YkC.js +1 -0
- package/dist/assets/{createText-1719965b-ByfEqOF-.js → createText-1719965b-C1_HJcCc.js} +1 -1
- package/dist/assets/devicon-BWlTeAUU.woff +0 -0
- package/dist/assets/devicon-CirD-cQx.ttf +0 -0
- package/dist/assets/devicon-Dg8iWy0i.svg +1211 -0
- package/dist/assets/devicon-TqfHp33-.eot +0 -0
- package/dist/assets/{edges-96097737-CMEArkOa.js → edges-96097737-BU8qStzd.js} +1 -1
- package/dist/assets/{erDiagram-0228fc6a-Cf8mX2aj.js → erDiagram-0228fc6a-DNA1Fz2L.js} +1 -1
- package/dist/assets/{flowDb-c6c81e3f-DG6WKyo7.js → flowDb-c6c81e3f-DjiCStMN.js} +1 -1
- package/dist/assets/{flowDiagram-50d868cf-CstUxz-w.js → flowDiagram-50d868cf-CSDi0-RD.js} +1 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-_13Sz5Wh.js +1 -0
- package/dist/assets/{flowchart-elk-definition-6af322e1--4CRoQ-H.js → flowchart-elk-definition-6af322e1-DrhIMas7.js} +1 -1
- package/dist/assets/{ganttDiagram-a2739b55-DYgHcKd-.js → ganttDiagram-a2739b55-CTZnUP5z.js} +1 -1
- package/dist/assets/{gitGraphDiagram-82fe8481-DDSVpfsd.js → gitGraphDiagram-82fe8481-COOW7jTi.js} +1 -1
- package/dist/assets/{graph-CRWF39gX.js → graph-CIkpD4Kx.js} +1 -1
- package/dist/assets/{index-5325376f-W1hft795.js → index-5325376f-aVVRRTIu.js} +1 -1
- package/dist/assets/index-D1giUI7r.css +1 -0
- package/dist/assets/index-DRSI_ZIL.js +514 -0
- package/dist/assets/{infoDiagram-8eee0895-D4SHcix6.js → infoDiagram-8eee0895-DQpZ1LVD.js} +1 -1
- package/dist/assets/{journeyDiagram-c64418c1-MWgCkVoE.js → journeyDiagram-c64418c1-DoKguIuk.js} +1 -1
- package/dist/assets/{layout-C88ObkCf.js → layout-Tnmha8Nh.js} +1 -1
- package/dist/assets/{line-C7WAYMt5.js → line-BQR2SOyl.js} +1 -1
- package/dist/assets/{linear-C4msxfcU.js → linear-DlG0eemV.js} +1 -1
- package/dist/assets/{mermaid.core-Cabag9SZ.js → mermaid.core-BnwYO0He.js} +6 -6
- package/dist/assets/{mindmap-definition-8da855dc-CeS8ETXx.js → mindmap-definition-8da855dc-BllYwDID.js} +1 -1
- package/dist/assets/{pieDiagram-a8764435-BvjyKnq5.js → pieDiagram-a8764435-DwCkhPVc.js} +1 -1
- package/dist/assets/{quadrantDiagram-1e28029f-DzYvpbNM.js → quadrantDiagram-1e28029f-c40GKTU0.js} +1 -1
- package/dist/assets/{requirementDiagram-08caed73-DHIoDbyo.js → requirementDiagram-08caed73-DnQp2Tk6.js} +1 -1
- package/dist/assets/{sankeyDiagram-a04cb91d-BFSGnQGs.js → sankeyDiagram-a04cb91d-CnJrs13b.js} +1 -1
- package/dist/assets/{sequenceDiagram-c5b8d532-_LM3BJ5-.js → sequenceDiagram-c5b8d532-1YBwnpKu.js} +1 -1
- package/dist/assets/{stateDiagram-1ecb1508-DwORjOzl.js → stateDiagram-1ecb1508-BFBxQ6Fh.js} +1 -1
- package/dist/assets/{stateDiagram-v2-c2b004d7-B4cAWWz1.js → stateDiagram-v2-c2b004d7-Dmechvv2.js} +1 -1
- package/dist/assets/{styles-b4e223ce-D_rmV3B_.js → styles-b4e223ce-DWWfWX8O.js} +1 -1
- package/dist/assets/{styles-ca3715f6-BFx4VuFc.js → styles-ca3715f6-CKKvZxaU.js} +1 -1
- package/dist/assets/{styles-d45a18b0-BE3106vL.js → styles-d45a18b0-dKMOUh9p.js} +1 -1
- package/dist/assets/{svgDrawCommon-b86b1483-DwDTO1op.js → svgDrawCommon-b86b1483-CBgjChPM.js} +1 -1
- package/dist/assets/{timeline-definition-faaaa080-C4b8qUQZ.js → timeline-definition-faaaa080-NCt-HHmb.js} +1 -1
- package/dist/assets/{xychartDiagram-f5964ef8-BRJ9Z4u-.js → xychartDiagram-f5964ef8-BJhXS4dG.js} +1 -1
- package/dist/index.html +2 -7
- package/index.html +0 -5
- package/package.json +11 -6
- package/src/App.tsx +2 -0
- package/src/api/README.md +26 -0
- package/src/api/automation.ts +88 -0
- package/src/api/base.ts +54 -0
- package/src/api/benchmark.ts +45 -0
- package/src/api/config.ts +24 -0
- package/src/api/knowledge.ts +72 -0
- package/src/api/projects.ts +15 -0
- package/src/api/sessions.ts +82 -0
- package/src/api/types.ts +20 -0
- package/src/api.ts +44 -241
- package/src/components/AutomationView/AutomationView.scss +5 -1
- package/src/components/AutomationView/RuleFormPanel.tsx +3 -2
- package/src/components/AutomationView/TaskList.scss +4 -6
- package/src/components/AutomationView/TaskList.tsx +2 -1
- package/src/components/AutomationView/TriggerList.scss +4 -1
- package/src/components/BenchmarkView/BenchmarkCasePanel.scss +267 -0
- package/src/components/BenchmarkView/BenchmarkCasePanel.tsx +309 -0
- package/src/components/BenchmarkView/BenchmarkSidebar.scss +182 -0
- package/src/components/BenchmarkView/BenchmarkSidebar.tsx +262 -0
- package/src/components/BenchmarkView/BenchmarkView.scss +78 -0
- package/src/components/BenchmarkView/index.tsx +197 -0
- package/src/components/BenchmarkView/types.ts +10 -0
- package/src/components/BenchmarkView/utils.ts +21 -0
- package/src/components/Chat.tsx +37 -29
- package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
- package/src/components/ConfigView.tsx +13 -1
- package/src/components/{chat/MarkdownContent.tsx → MarkdownContent.tsx} +1 -1
- package/src/components/NavRail.tsx +7 -0
- package/src/components/chat/ChatHeader.scss +37 -19
- package/src/components/chat/ChatHeader.tsx +6 -9
- package/src/components/chat/ChatHistoryView.tsx +89 -45
- package/src/components/chat/CurrentTodoList.tsx +10 -9
- package/src/components/chat/{MessageItem.scss → Messages/MessageItem.scss} +14 -0
- package/src/components/chat/{MessageItem.tsx → Messages/MessageItem.tsx} +30 -8
- package/src/components/chat/{messageUtils.ts → Messages/message-utils.ts} +1 -1
- package/src/components/chat/NewSessionGuide.scss +35 -13
- package/src/components/chat/NewSessionGuide.tsx +20 -10
- package/src/components/chat/{Sender.scss → Sender/Sender.scss} +80 -0
- package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +161 -5
- package/src/components/chat/tools/DefaultTool.tsx +184 -21
- package/src/components/chat/tools/adapter-claude/BashTool.scss +67 -51
- package/src/components/chat/tools/adapter-claude/BashTool.tsx +83 -49
- package/src/components/chat/tools/adapter-claude/GlobTool.scss +0 -79
- package/src/components/chat/tools/adapter-claude/GlobTool.tsx +16 -36
- package/src/components/chat/tools/adapter-claude/GrepTool.scss +0 -87
- package/src/components/chat/tools/adapter-claude/GrepTool.tsx +22 -41
- package/src/components/chat/tools/adapter-claude/LSTool.scss +0 -79
- package/src/components/chat/tools/adapter-claude/LSTool.tsx +15 -15
- package/src/components/chat/tools/adapter-claude/ReadTool.scss +0 -55
- package/src/components/chat/tools/adapter-claude/ReadTool.tsx +20 -42
- package/src/components/chat/tools/adapter-claude/TodoTool.scss +8 -23
- package/src/components/chat/tools/adapter-claude/TodoTool.tsx +24 -11
- package/src/components/chat/tools/adapter-claude/WriteTool.scss +21 -69
- package/src/components/chat/tools/adapter-claude/WriteTool.tsx +22 -58
- package/src/components/chat/tools/adapter-claude/index.ts +4 -10
- package/src/components/chat/tools/adapter-claude/utils.ts +54 -0
- package/src/components/chat/tools/core/ToolCallBox.scss +356 -0
- package/src/components/chat/{ToolGroup.tsx → tools/core/ToolGroup.tsx} +26 -7
- package/src/components/chat/{ToolRenderer.tsx → tools/core/ToolRenderer.tsx} +6 -4
- package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.scss +11 -0
- package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +75 -0
- package/src/components/chat/tools/plugin-chrome-devtools/index.ts +45 -0
- package/src/components/chat/tools/task/GetTaskInfoTool.scss +2 -27
- package/src/components/chat/tools/task/GetTaskInfoTool.tsx +48 -38
- package/src/components/chat/tools/task/ListTasksTool.scss +3 -28
- package/src/components/chat/tools/task/ListTasksTool.tsx +11 -8
- package/src/components/chat/tools/task/StartTasksTool.scss +3 -28
- package/src/components/chat/tools/task/StartTasksTool.tsx +14 -17
- package/src/components/chat/tools/task/components/TaskRow.scss +105 -0
- package/src/components/chat/tools/task/components/TaskRow.tsx +163 -0
- package/src/components/chat/tools/task/components/TaskToolCard.scss +15 -15
- package/src/components/chat/tools/task/components/TaskToolCard.tsx +8 -6
- package/src/components/config/AppSettingsPanel.tsx +33 -0
- package/src/components/config/ConfigSectionForm.tsx +12 -1
- package/src/components/config/channelDefinitions.ts +6 -0
- package/src/components/config/configSchema.ts +10 -1
- package/src/components/config/recordEditors/ChannelRecordEditor.scss +1 -0
- package/src/components/config/recordEditors/ChannelRecordEditor.tsx +397 -0
- package/src/components/config/recordEditors/index.tsx +1 -0
- package/src/components/knowledge-base/KnowledgeBaseView.tsx +51 -3
- package/src/components/knowledge-base/components/RuleItem.tsx +79 -0
- package/src/components/knowledge-base/components/RuleList.scss +5 -0
- package/src/components/knowledge-base/components/RuleList.tsx +70 -0
- package/src/components/knowledge-base/components/RulesTab.tsx +32 -7
- package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
- package/src/hooks/chat/use-chat-interaction.ts +26 -0
- package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +65 -16
- package/src/hooks/chat/use-chat-permission-mode.ts +47 -0
- package/src/hooks/chat/use-chat-scroll.ts +51 -0
- package/src/hooks/chat/use-chat-session-actions.ts +147 -0
- package/src/hooks/chat/use-chat-session-messages.ts +250 -0
- package/src/hooks/chat/use-chat-session.ts +57 -0
- package/src/hooks/chat/use-chat-view.ts +39 -0
- package/src/main.tsx +10 -13
- package/src/resources/locales/en.json +73 -0
- package/src/resources/locales/zh.json +73 -0
- package/src/runtime-config.ts +52 -0
- package/src/store/index.ts +2 -0
- package/src/vite-env.d.ts +11 -0
- package/src/ws.ts +5 -3
- package/vite.config.ts +12 -4
- package/dist/assets/channel-DrWdSpqV.js +0 -1
- package/dist/assets/clone-D0cC8LLB.js +0 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-Bf_DH7dp.js +0 -1
- package/dist/assets/index-CNMzWvKV.js +0 -497
- package/dist/assets/index-PEmISxiy.css +0 -1
- package/src/components/chat/ToolCallBox.scss +0 -137
- package/src/components/chat/useChatSession.ts +0 -370
- /package/src/components/{chat/CodeBlock.scss → CodeBlock.scss} +0 -0
- /package/src/components/chat/{MessageFooter.tsx → Messages/MessageFooter.tsx} +0 -0
- /package/src/components/chat/{CompletionMenu.scss → Sender/CompletionMenu.scss} +0 -0
- /package/src/components/chat/{CompletionMenu.tsx → Sender/CompletionMenu.tsx} +0 -0
- /package/src/components/chat/{ThinkingStatus.scss → Sender/ThinkingStatus.scss} +0 -0
- /package/src/components/chat/{ThinkingStatus.tsx → Sender/ThinkingStatus.tsx} +0 -0
- /package/src/components/chat/{ToolCallBox.tsx → tools/core/ToolCallBox.tsx} +0 -0
- /package/src/components/chat/{ToolGroup.scss → tools/core/ToolGroup.scss} +0 -0
- /package/src/{components/chat/safeSerialize.ts → utils/safe-serialize.ts} +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Button, Empty, Tag, Tooltip } from 'antd'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import useSWR from 'swr'
|
|
5
|
+
|
|
6
|
+
import type { RuleDetail, RuleSummary } from '#~/api.js'
|
|
7
|
+
import { getRuleDetail } from '#~/api.js'
|
|
8
|
+
import { MarkdownContent } from '#~/components/MarkdownContent'
|
|
9
|
+
import { LoadingState } from './LoadingState'
|
|
10
|
+
|
|
11
|
+
type RuleItemProps = {
|
|
12
|
+
rule: RuleSummary
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function RuleItem({ rule }: RuleItemProps) {
|
|
16
|
+
const { t } = useTranslation()
|
|
17
|
+
const [expanded, setExpanded] = React.useState(false)
|
|
18
|
+
const { data, isLoading } = useSWR<{ rule: RuleDetail }>(
|
|
19
|
+
expanded ? ['rule-detail', rule.id] : null,
|
|
20
|
+
() => getRuleDetail(rule.id)
|
|
21
|
+
)
|
|
22
|
+
const detail = data?.rule
|
|
23
|
+
const body = detail?.body ?? ''
|
|
24
|
+
const globList = rule.globs ?? []
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className='knowledge-base-view__item'>
|
|
28
|
+
<div className='knowledge-base-view__item-row'>
|
|
29
|
+
<div className='knowledge-base-view__item-main'>
|
|
30
|
+
<div className='knowledge-base-view__item-title'>
|
|
31
|
+
<span className='material-symbols-rounded knowledge-base-view__item-icon'>gavel</span>
|
|
32
|
+
<span>{rule.name}</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div className='knowledge-base-view__item-desc'>{rule.description}</div>
|
|
35
|
+
{globList.length > 0 && (
|
|
36
|
+
<div className='knowledge-base-view__tag-list'>
|
|
37
|
+
{globList.map(glob => (
|
|
38
|
+
<Tag key={glob} className='knowledge-base-view__tag'>
|
|
39
|
+
<span className='material-symbols-rounded knowledge-base-view__tag-icon'>folder</span>
|
|
40
|
+
<span>{glob}</span>
|
|
41
|
+
</Tag>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
<div className='knowledge-base-view__item-meta'>
|
|
47
|
+
{rule.always && (
|
|
48
|
+
<Tag color='blue'>{t('knowledge.meta.always')}</Tag>
|
|
49
|
+
)}
|
|
50
|
+
<Tooltip title={expanded ? t('knowledge.actions.collapse') : t('knowledge.actions.expand')}>
|
|
51
|
+
<Button
|
|
52
|
+
type='text'
|
|
53
|
+
className='knowledge-base-view__icon-button'
|
|
54
|
+
onClick={() => setExpanded(prev => !prev)}
|
|
55
|
+
icon={
|
|
56
|
+
<span className='material-symbols-rounded'>
|
|
57
|
+
{expanded ? 'expand_less' : 'expand_more'}
|
|
58
|
+
</span>
|
|
59
|
+
}
|
|
60
|
+
/>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
{expanded && (
|
|
65
|
+
<div className='knowledge-base-view__detail'>
|
|
66
|
+
{isLoading && <LoadingState />}
|
|
67
|
+
{!isLoading && body.trim() === '' && (
|
|
68
|
+
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={t('knowledge.rules.empty')} />
|
|
69
|
+
)}
|
|
70
|
+
{!isLoading && body.trim() !== '' && (
|
|
71
|
+
<div className='knowledge-base-view__markdown'>
|
|
72
|
+
<MarkdownContent content={body} />
|
|
73
|
+
</div>
|
|
74
|
+
)}
|
|
75
|
+
</div>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import './RuleList.scss'
|
|
2
|
+
|
|
3
|
+
import { List } from 'antd'
|
|
4
|
+
import { useTranslation } from 'react-i18next'
|
|
5
|
+
|
|
6
|
+
import type { RuleSummary } from '#~/api.js'
|
|
7
|
+
import { EmptyState } from './EmptyState'
|
|
8
|
+
import { KnowledgeList } from './KnowledgeList'
|
|
9
|
+
import { LoadingState } from './LoadingState'
|
|
10
|
+
import { RuleItem } from './RuleItem'
|
|
11
|
+
|
|
12
|
+
type RuleListProps = {
|
|
13
|
+
isLoading: boolean
|
|
14
|
+
rules: RuleSummary[]
|
|
15
|
+
filteredRules: RuleSummary[]
|
|
16
|
+
onCreate: () => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function RuleList({
|
|
20
|
+
isLoading,
|
|
21
|
+
rules,
|
|
22
|
+
filteredRules,
|
|
23
|
+
onCreate
|
|
24
|
+
}: RuleListProps) {
|
|
25
|
+
const { t } = useTranslation()
|
|
26
|
+
|
|
27
|
+
if (isLoading) {
|
|
28
|
+
return (
|
|
29
|
+
<div className='knowledge-base-view__rule-list'>
|
|
30
|
+
<LoadingState />
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (rules.length === 0) {
|
|
36
|
+
return (
|
|
37
|
+
<div className='knowledge-base-view__rule-list'>
|
|
38
|
+
<EmptyState
|
|
39
|
+
description={t('knowledge.rules.empty')}
|
|
40
|
+
actionLabel={t('knowledge.rules.create')}
|
|
41
|
+
onAction={onCreate}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (filteredRules.length === 0) {
|
|
48
|
+
return (
|
|
49
|
+
<div className='knowledge-base-view__rule-list'>
|
|
50
|
+
<EmptyState
|
|
51
|
+
description={t('knowledge.filters.noResults')}
|
|
52
|
+
variant='simple'
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div className='knowledge-base-view__rule-list'>
|
|
60
|
+
<KnowledgeList
|
|
61
|
+
data={filteredRules}
|
|
62
|
+
renderItem={(rule) => (
|
|
63
|
+
<List.Item className='knowledge-base-view__list-item'>
|
|
64
|
+
<RuleItem rule={rule} />
|
|
65
|
+
</List.Item>
|
|
66
|
+
)}
|
|
67
|
+
/>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
import './RulesTab.scss'
|
|
2
2
|
|
|
3
|
-
import { Space } from 'antd'
|
|
3
|
+
import { Input, Space } from 'antd'
|
|
4
4
|
import { useTranslation } from 'react-i18next'
|
|
5
5
|
|
|
6
|
+
import type { RuleSummary } from '#~/api.js'
|
|
6
7
|
import { ActionButton } from './ActionButton'
|
|
7
|
-
import {
|
|
8
|
+
import { RuleList } from './RuleList'
|
|
8
9
|
import { SectionHeader } from './SectionHeader'
|
|
9
10
|
import { TabContent } from './TabContent'
|
|
10
11
|
|
|
11
12
|
type RulesTabProps = {
|
|
13
|
+
rules: RuleSummary[]
|
|
14
|
+
filteredRules: RuleSummary[]
|
|
15
|
+
isLoading: boolean
|
|
16
|
+
query: string
|
|
17
|
+
onQueryChange: (value: string) => void
|
|
12
18
|
onCreate: () => void
|
|
13
19
|
onImport: () => void
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
export function RulesTab({
|
|
22
|
+
export function RulesTab({
|
|
23
|
+
rules,
|
|
24
|
+
filteredRules,
|
|
25
|
+
isLoading,
|
|
26
|
+
query,
|
|
27
|
+
onQueryChange,
|
|
28
|
+
onCreate,
|
|
29
|
+
onImport
|
|
30
|
+
}: RulesTabProps) {
|
|
17
31
|
const { t } = useTranslation()
|
|
18
32
|
|
|
19
33
|
return (
|
|
@@ -39,10 +53,21 @@ export function RulesTab({ onCreate, onImport }: RulesTabProps) {
|
|
|
39
53
|
</Space>
|
|
40
54
|
)}
|
|
41
55
|
/>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
<div className='knowledge-base-view__filters'>
|
|
57
|
+
<Input
|
|
58
|
+
className='knowledge-base-view__filter-input'
|
|
59
|
+
prefix={<span className='material-symbols-rounded knowledge-base-view__filter-icon'>search</span>}
|
|
60
|
+
placeholder={t('knowledge.filters.search')}
|
|
61
|
+
allowClear
|
|
62
|
+
value={query}
|
|
63
|
+
onChange={(e) => onQueryChange(e.target.value)}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
<RuleList
|
|
67
|
+
isLoading={isLoading}
|
|
68
|
+
rules={rules}
|
|
69
|
+
filteredRules={filteredRules}
|
|
70
|
+
onCreate={onCreate}
|
|
46
71
|
/>
|
|
47
72
|
</TabContent>
|
|
48
73
|
)
|
|
@@ -7,7 +7,7 @@ import useSWR from 'swr'
|
|
|
7
7
|
|
|
8
8
|
import type { SpecDetail, SpecSummary } from '#~/api.js'
|
|
9
9
|
import { getSpecDetail } from '#~/api.js'
|
|
10
|
-
import { MarkdownContent } from '#~/components/
|
|
10
|
+
import { MarkdownContent } from '#~/components/MarkdownContent'
|
|
11
11
|
import { LoadingState } from './LoadingState'
|
|
12
12
|
import { MetaList } from './MetaList'
|
|
13
13
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { AskUserQuestionParams } from '@vibe-forge/core'
|
|
4
|
+
import { connectionManager } from '#~/connectionManager.js'
|
|
5
|
+
|
|
6
|
+
export function useChatInteraction({ sessionId }: { sessionId?: string }) {
|
|
7
|
+
const [interactionRequest, setInteractionRequest] = useState<{ id: string; payload: AskUserQuestionParams } | null>(
|
|
8
|
+
null
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
const handleInteractionResponse = useCallback((id: string, data: string | string[]) => {
|
|
12
|
+
if (!sessionId) return
|
|
13
|
+
connectionManager.send(sessionId, {
|
|
14
|
+
type: 'interaction_response',
|
|
15
|
+
id,
|
|
16
|
+
data
|
|
17
|
+
})
|
|
18
|
+
setInteractionRequest(null)
|
|
19
|
+
}, [sessionId])
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
interactionRequest,
|
|
23
|
+
setInteractionRequest,
|
|
24
|
+
handleInteractionResponse
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react'
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import { useTranslation } from 'react-i18next'
|
|
3
3
|
import useSWR from 'swr'
|
|
4
4
|
|
|
5
5
|
import type { ConfigResponse, ModelServiceConfig, RecommendedModelConfig } from '@vibe-forge/core'
|
|
6
|
-
import { getConfig } from '
|
|
6
|
+
import { getConfig } from '#~/api.js'
|
|
7
7
|
|
|
8
|
-
interface ModelSelectOption {
|
|
8
|
+
export interface ModelSelectOption {
|
|
9
9
|
value: string
|
|
10
10
|
label: React.ReactNode
|
|
11
11
|
searchText: string
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
interface ModelSelectGroup {
|
|
14
|
+
export interface ModelSelectGroup {
|
|
15
15
|
label: React.ReactNode
|
|
16
16
|
options: ModelSelectOption[]
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export function useChatModels() {
|
|
20
20
|
const { t } = useTranslation()
|
|
21
|
-
const [selectedModel, setSelectedModel] = useState<string | undefined>(
|
|
21
|
+
const [selectedModel, setSelectedModel] = useState<string | undefined>(() => {
|
|
22
|
+
try {
|
|
23
|
+
const raw = localStorage.getItem('vf_chat_selected_model')
|
|
24
|
+
return raw == null || raw.trim() === '' ? undefined : raw
|
|
25
|
+
} catch {
|
|
26
|
+
return undefined
|
|
27
|
+
}
|
|
28
|
+
})
|
|
22
29
|
const { data: configRes } = useSWR<ConfigResponse>('/api/config', getConfig)
|
|
23
30
|
|
|
24
31
|
const mergedModelServices = useMemo(() => {
|
|
@@ -39,7 +46,9 @@ export function useChatModels() {
|
|
|
39
46
|
const availableModels = useMemo(() => {
|
|
40
47
|
const list: Array<{ model: string; serviceKey: string; serviceTitle: string }> = []
|
|
41
48
|
for (const [serviceKey, serviceValue] of modelServiceEntries) {
|
|
42
|
-
const service = (serviceValue != null && typeof serviceValue === 'object')
|
|
49
|
+
const service = (serviceValue != null && typeof serviceValue === 'object')
|
|
50
|
+
? serviceValue as ModelServiceConfig
|
|
51
|
+
: undefined
|
|
43
52
|
const serviceTitle = service?.title?.trim() !== '' ? service?.title ?? '' : serviceKey
|
|
44
53
|
const models = Array.isArray(service?.models) ? service?.models.filter(item => typeof item === 'string') : []
|
|
45
54
|
for (const model of models) {
|
|
@@ -53,8 +62,24 @@ export function useChatModels() {
|
|
|
53
62
|
const availableModelKey = useMemo(() => availableModelValues.join('|'), [availableModelValues])
|
|
54
63
|
const availableModelSet = useMemo(() => new Set(availableModelValues), [availableModelKey])
|
|
55
64
|
const hasAvailableModels = availableModelValues.length > 0
|
|
65
|
+
const modelToService = useMemo(() => {
|
|
66
|
+
const map = new Map<string, { key: string; title: string }>()
|
|
67
|
+
for (const entry of availableModels) {
|
|
68
|
+
if (!map.has(entry.model)) {
|
|
69
|
+
map.set(entry.model, { key: entry.serviceKey, title: entry.serviceTitle })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return map
|
|
73
|
+
}, [availableModels])
|
|
56
74
|
const defaultModelService = configRes?.sources?.merged?.general?.defaultModelService
|
|
57
75
|
const defaultModel = configRes?.sources?.merged?.general?.defaultModel
|
|
76
|
+
const formatModelWithService = useCallback((model: string | undefined) => {
|
|
77
|
+
const normalizedModel = typeof model === 'string' ? model.trim() : ''
|
|
78
|
+
if (normalizedModel === '') return undefined
|
|
79
|
+
if (normalizedModel.includes(',')) return normalizedModel
|
|
80
|
+
const resolvedService = modelToService.get(normalizedModel)?.key ?? defaultModelService
|
|
81
|
+
return resolvedService ? `${resolvedService},${normalizedModel}` : normalizedModel
|
|
82
|
+
}, [defaultModelService, modelToService])
|
|
58
83
|
const resolvedDefaultModel = useMemo(() => {
|
|
59
84
|
if (!hasAvailableModels) return undefined
|
|
60
85
|
if (defaultModel && availableModelSet.has(defaultModel)) return defaultModel
|
|
@@ -64,7 +89,17 @@ export function useChatModels() {
|
|
|
64
89
|
if (models.length > 0) return models[0]
|
|
65
90
|
}
|
|
66
91
|
return availableModelValues[0]
|
|
67
|
-
}, [
|
|
92
|
+
}, [
|
|
93
|
+
availableModelSet,
|
|
94
|
+
availableModelValues,
|
|
95
|
+
defaultModel,
|
|
96
|
+
defaultModelService,
|
|
97
|
+
hasAvailableModels,
|
|
98
|
+
mergedModelServices
|
|
99
|
+
])
|
|
100
|
+
const selectedModelWithService = useMemo(() => (
|
|
101
|
+
formatModelWithService(selectedModel)
|
|
102
|
+
), [formatModelWithService, selectedModel])
|
|
68
103
|
|
|
69
104
|
useEffect(() => {
|
|
70
105
|
if (!hasAvailableModels) {
|
|
@@ -77,6 +112,17 @@ export function useChatModels() {
|
|
|
77
112
|
})
|
|
78
113
|
}, [availableModelSet, hasAvailableModels, resolvedDefaultModel])
|
|
79
114
|
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
try {
|
|
117
|
+
if (selectedModel == null || selectedModel.trim() === '') {
|
|
118
|
+
localStorage.removeItem('vf_chat_selected_model')
|
|
119
|
+
} else {
|
|
120
|
+
localStorage.setItem('vf_chat_selected_model', selectedModel)
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}, [selectedModel])
|
|
125
|
+
|
|
80
126
|
const modelOptions = useMemo<ModelSelectGroup[]>(() => {
|
|
81
127
|
const buildOption = (params: {
|
|
82
128
|
value: string
|
|
@@ -108,13 +154,6 @@ export function useChatModels() {
|
|
|
108
154
|
}
|
|
109
155
|
}
|
|
110
156
|
|
|
111
|
-
const modelToService = new Map<string, { key: string; title: string }>()
|
|
112
|
-
for (const entry of availableModels) {
|
|
113
|
-
if (!modelToService.has(entry.model)) {
|
|
114
|
-
modelToService.set(entry.model, { key: entry.serviceKey, title: entry.serviceTitle })
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
157
|
const resolveFirstAlias = (modelsAlias: Record<string, string[]> | undefined, model: string) => {
|
|
119
158
|
if (!modelsAlias) return undefined
|
|
120
159
|
for (const [alias, aliasModels] of Object.entries(modelsAlias)) {
|
|
@@ -126,7 +165,9 @@ export function useChatModels() {
|
|
|
126
165
|
|
|
127
166
|
const serviceGroups = modelServiceEntries
|
|
128
167
|
.map(([serviceKey, serviceValue]) => {
|
|
129
|
-
const service = (serviceValue != null && typeof serviceValue === 'object')
|
|
168
|
+
const service = (serviceValue != null && typeof serviceValue === 'object')
|
|
169
|
+
? serviceValue as ModelServiceConfig
|
|
170
|
+
: undefined
|
|
130
171
|
const serviceTitle = service?.title?.trim() !== '' ? service?.title ?? '' : serviceKey
|
|
131
172
|
const groupTitle = serviceTitle?.trim() !== '' ? serviceTitle : serviceKey
|
|
132
173
|
const serviceDescription = service?.description
|
|
@@ -195,10 +236,18 @@ export function useChatModels() {
|
|
|
195
236
|
})
|
|
196
237
|
}
|
|
197
238
|
return [...groups, ...serviceGroups]
|
|
198
|
-
}, [
|
|
239
|
+
}, [
|
|
240
|
+
availableModelSet,
|
|
241
|
+
modelToService,
|
|
242
|
+
mergedModelServices,
|
|
243
|
+
modelServiceEntries,
|
|
244
|
+
recommendedModels,
|
|
245
|
+
t
|
|
246
|
+
])
|
|
199
247
|
|
|
200
248
|
return {
|
|
201
249
|
selectedModel,
|
|
250
|
+
selectedModelWithService,
|
|
202
251
|
setSelectedModel,
|
|
203
252
|
modelOptions,
|
|
204
253
|
hasAvailableModels
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
export type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'dontAsk' | 'bypassPermissions'
|
|
5
|
+
|
|
6
|
+
const PERMISSION_MODE_STORAGE_KEY = 'vf_chat_permission_mode'
|
|
7
|
+
|
|
8
|
+
const isPermissionMode = (value: string): value is PermissionMode => {
|
|
9
|
+
return value === 'default'
|
|
10
|
+
|| value === 'acceptEdits'
|
|
11
|
+
|| value === 'plan'
|
|
12
|
+
|| value === 'dontAsk'
|
|
13
|
+
|| value === 'bypassPermissions'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useChatPermissionMode() {
|
|
17
|
+
const [permissionMode, setPermissionMode] = useState<PermissionMode>('default')
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
try {
|
|
21
|
+
const raw = localStorage.getItem(PERMISSION_MODE_STORAGE_KEY)
|
|
22
|
+
if (raw && isPermissionMode(raw)) {
|
|
23
|
+
setPermissionMode(raw)
|
|
24
|
+
}
|
|
25
|
+
} catch {}
|
|
26
|
+
}, [])
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
try {
|
|
30
|
+
localStorage.setItem(PERMISSION_MODE_STORAGE_KEY, permissionMode)
|
|
31
|
+
} catch {}
|
|
32
|
+
}, [permissionMode])
|
|
33
|
+
|
|
34
|
+
const permissionModeOptions = useMemo<Array<{ value: PermissionMode; label: ReactNode }>>(() => ([
|
|
35
|
+
{ value: 'default', label: '默认' },
|
|
36
|
+
{ value: 'acceptEdits', label: '接受编辑' },
|
|
37
|
+
{ value: 'plan', label: '计划' },
|
|
38
|
+
{ value: 'dontAsk', label: '不询问' },
|
|
39
|
+
{ value: 'bypassPermissions', label: '跳过权限' }
|
|
40
|
+
]), [])
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
permissionMode,
|
|
44
|
+
setPermissionMode,
|
|
45
|
+
permissionModeOptions
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const SCROLL_THRESHOLD = 80
|
|
4
|
+
|
|
5
|
+
export function useChatScroll({ messagesLength }: { messagesLength: number }) {
|
|
6
|
+
const messagesEndRef = useRef<HTMLDivElement>(null)
|
|
7
|
+
const messagesContainerRef = useRef<HTMLDivElement>(null)
|
|
8
|
+
const messagesContentRef = useRef<HTMLDivElement>(null)
|
|
9
|
+
const [showScrollBottom, setShowScrollBottom] = useState(false)
|
|
10
|
+
|
|
11
|
+
const updateScrollState = useCallback(() => {
|
|
12
|
+
const container = messagesContainerRef.current
|
|
13
|
+
if (!container) return
|
|
14
|
+
const distanceToBottom = container.scrollHeight - (container.scrollTop + container.clientHeight)
|
|
15
|
+
setShowScrollBottom(distanceToBottom > SCROLL_THRESHOLD)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {
|
|
19
|
+
setTimeout(() => {
|
|
20
|
+
if (messagesContainerRef.current) {
|
|
21
|
+
messagesContainerRef.current.scrollTo({
|
|
22
|
+
top: messagesContainerRef.current.scrollHeight,
|
|
23
|
+
behavior
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
}, 50)
|
|
27
|
+
}, [])
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const container = messagesContainerRef.current
|
|
31
|
+
if (!container) return
|
|
32
|
+
updateScrollState()
|
|
33
|
+
const handleScroll = () => updateScrollState()
|
|
34
|
+
container.addEventListener('scroll', handleScroll, { passive: true })
|
|
35
|
+
return () => {
|
|
36
|
+
container.removeEventListener('scroll', handleScroll)
|
|
37
|
+
}
|
|
38
|
+
}, [updateScrollState])
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
updateScrollState()
|
|
42
|
+
}, [updateScrollState, messagesLength])
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
messagesEndRef,
|
|
46
|
+
messagesContainerRef,
|
|
47
|
+
messagesContentRef,
|
|
48
|
+
showScrollBottom,
|
|
49
|
+
scrollToBottom
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { App } from 'antd'
|
|
2
|
+
import { useCallback, useState } from 'react'
|
|
3
|
+
import { useTranslation } from 'react-i18next'
|
|
4
|
+
import { useNavigate } from 'react-router-dom'
|
|
5
|
+
import { useSWRConfig } from 'swr'
|
|
6
|
+
|
|
7
|
+
import type { ChatMessageContent, Session } from '@vibe-forge/core'
|
|
8
|
+
import { createSession } from '#~/api.js'
|
|
9
|
+
import { connectionManager } from '#~/connectionManager.js'
|
|
10
|
+
import type { PermissionMode } from './use-chat-permission-mode'
|
|
11
|
+
|
|
12
|
+
export function useChatSessionActions({
|
|
13
|
+
session,
|
|
14
|
+
modelForQuery,
|
|
15
|
+
hasAvailableModels,
|
|
16
|
+
permissionMode,
|
|
17
|
+
onClearMessages
|
|
18
|
+
}: {
|
|
19
|
+
session?: Session
|
|
20
|
+
modelForQuery?: string
|
|
21
|
+
hasAvailableModels: boolean
|
|
22
|
+
permissionMode: PermissionMode
|
|
23
|
+
onClearMessages: () => void
|
|
24
|
+
}) {
|
|
25
|
+
const { message } = App.useApp()
|
|
26
|
+
const { t } = useTranslation()
|
|
27
|
+
const navigate = useNavigate()
|
|
28
|
+
const { mutate } = useSWRConfig()
|
|
29
|
+
const [isCreating, setIsCreating] = useState(false)
|
|
30
|
+
const isThinking = isCreating || session?.status === 'running'
|
|
31
|
+
|
|
32
|
+
const send = useCallback(async (text: string) => {
|
|
33
|
+
if (text.trim() === '' || isThinking) return
|
|
34
|
+
if (!hasAvailableModels) {
|
|
35
|
+
void message.warning(t('chat.modelConfigRequired'))
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!session?.id) {
|
|
40
|
+
setIsCreating(true)
|
|
41
|
+
try {
|
|
42
|
+
const { session: newSession } = await createSession(undefined, text.trim(), undefined, modelForQuery, {
|
|
43
|
+
permissionMode
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
|
|
47
|
+
if (!prev?.sessions) return { sessions: [newSession] }
|
|
48
|
+
return {
|
|
49
|
+
...prev,
|
|
50
|
+
sessions: [newSession, ...prev.sessions]
|
|
51
|
+
}
|
|
52
|
+
}, false)
|
|
53
|
+
|
|
54
|
+
void navigate(`/session/${newSession.id}`)
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(err)
|
|
57
|
+
setIsCreating(false)
|
|
58
|
+
void message.error('Failed to create session')
|
|
59
|
+
}
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
connectionManager.send(session.id, {
|
|
64
|
+
type: 'user_message',
|
|
65
|
+
text: text.trim()
|
|
66
|
+
})
|
|
67
|
+
}, [
|
|
68
|
+
hasAvailableModels,
|
|
69
|
+
isThinking,
|
|
70
|
+
message,
|
|
71
|
+
mutate,
|
|
72
|
+
navigate,
|
|
73
|
+
permissionMode,
|
|
74
|
+
modelForQuery,
|
|
75
|
+
session?.id,
|
|
76
|
+
t
|
|
77
|
+
])
|
|
78
|
+
|
|
79
|
+
const sendContent = useCallback(async (content: ChatMessageContent[]) => {
|
|
80
|
+
if (content.length === 0 || isThinking) return
|
|
81
|
+
if (!hasAvailableModels) {
|
|
82
|
+
void message.warning(t('chat.modelConfigRequired'))
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!session?.id) {
|
|
87
|
+
setIsCreating(true)
|
|
88
|
+
try {
|
|
89
|
+
const { session: newSession } = await createSession(undefined, undefined, content, modelForQuery, {
|
|
90
|
+
permissionMode
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
await mutate('/api/sessions', (prev: { sessions: Session[] } | undefined) => {
|
|
94
|
+
if (!prev?.sessions) return { sessions: [newSession] }
|
|
95
|
+
return {
|
|
96
|
+
...prev,
|
|
97
|
+
sessions: [newSession, ...prev.sessions]
|
|
98
|
+
}
|
|
99
|
+
}, false)
|
|
100
|
+
|
|
101
|
+
void navigate(`/session/${newSession.id}`)
|
|
102
|
+
setIsCreating(false)
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(err)
|
|
105
|
+
setIsCreating(false)
|
|
106
|
+
void message.error('Failed to create session')
|
|
107
|
+
}
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
connectionManager.send(session.id, {
|
|
112
|
+
type: 'user_message',
|
|
113
|
+
content
|
|
114
|
+
})
|
|
115
|
+
}, [
|
|
116
|
+
hasAvailableModels,
|
|
117
|
+
isThinking,
|
|
118
|
+
message,
|
|
119
|
+
mutate,
|
|
120
|
+
navigate,
|
|
121
|
+
permissionMode,
|
|
122
|
+
modelForQuery,
|
|
123
|
+
session?.id,
|
|
124
|
+
t
|
|
125
|
+
])
|
|
126
|
+
|
|
127
|
+
const interrupt = useCallback(() => {
|
|
128
|
+
if (!session?.id || isThinking === false) return
|
|
129
|
+
connectionManager.send(session.id, {
|
|
130
|
+
type: 'interrupt'
|
|
131
|
+
})
|
|
132
|
+
}, [isThinking, session?.id])
|
|
133
|
+
|
|
134
|
+
const clearMessages = useCallback(() => {
|
|
135
|
+
onClearMessages()
|
|
136
|
+
void message.success('Messages cleared')
|
|
137
|
+
}, [message, onClearMessages])
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
isCreating,
|
|
141
|
+
isThinking,
|
|
142
|
+
send,
|
|
143
|
+
sendContent,
|
|
144
|
+
interrupt,
|
|
145
|
+
clearMessages
|
|
146
|
+
}
|
|
147
|
+
}
|