@vibe-forge/client 0.3.0 → 0.5.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 +2 -1
- package/dist/assets/{arc-CwMXUVsq.js → arc-C4ymrcSQ.js} +1 -1
- package/dist/assets/{blockDiagram-c4efeb88-CGxJV7KJ.js → blockDiagram-c4efeb88-CeB7-kgP.js} +1 -1
- package/dist/assets/{c4Diagram-c83219d4-BKhin7cY.js → c4Diagram-c83219d4-C935Im8S.js} +1 -1
- package/dist/assets/channel-84s1ACzD.js +1 -0
- package/dist/assets/{classDiagram-beda092f-BASmn22R.js → classDiagram-beda092f-B9IV13KI.js} +1 -1
- package/dist/assets/{classDiagram-v2-2358418a-BUk9rNBX.js → classDiagram-v2-2358418a-CXF_K4fE.js} +1 -1
- package/dist/assets/clone-B2E8tddE.js +1 -0
- package/dist/assets/{createText-1719965b-2XqnWjQY.js → createText-1719965b-DwX8iC5F.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-B7e32Jeg.js → edges-96097737-9P1uH1RE.js} +1 -1
- package/dist/assets/{erDiagram-0228fc6a-CCR2or72.js → erDiagram-0228fc6a-ixeGTFvg.js} +1 -1
- package/dist/assets/{flowDb-c6c81e3f-B72HWT9x.js → flowDb-c6c81e3f-G1gSTTBI.js} +1 -1
- package/dist/assets/{flowDiagram-50d868cf-WOi0KARY.js → flowDiagram-50d868cf-CzrG99nD.js} +1 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-CJfJYbME.js +1 -0
- package/dist/assets/{flowchart-elk-definition-6af322e1-i_Yd0LCE.js → flowchart-elk-definition-6af322e1-sFCoysWa.js} +1 -1
- package/dist/assets/{ganttDiagram-a2739b55-CFH9zF14.js → ganttDiagram-a2739b55-Ccsk_Lru.js} +1 -1
- package/dist/assets/{gitGraphDiagram-82fe8481-DglKfMze.js → gitGraphDiagram-82fe8481-CwathJ6H.js} +1 -1
- package/dist/assets/{graph-BKbBNGPf.js → graph-DRCU-8Rz.js} +1 -1
- package/dist/assets/{index-5325376f-BK7F9nSl.js → index-5325376f-Bq-fg2i_.js} +1 -1
- package/dist/assets/index-CHMuZ5-1.css +1 -0
- package/dist/assets/index-cGZvDhhU.js +542 -0
- package/dist/assets/{infoDiagram-8eee0895-BLFL77_D.js → infoDiagram-8eee0895-JBcUkJ6T.js} +1 -1
- package/dist/assets/{journeyDiagram-c64418c1-CS9XctDL.js → journeyDiagram-c64418c1-DsdQU-R8.js} +1 -1
- package/dist/assets/{layout-By3JZZGt.js → layout-s0slG1OL.js} +1 -1
- package/dist/assets/{line-9GUsXbwv.js → line-CymFqgW6.js} +1 -1
- package/dist/assets/{linear-DzGV4E9N.js → linear-lDQVZ6aQ.js} +1 -1
- package/dist/assets/{mermaid.core-CG3Ib42Q.js → mermaid.core-Cmlqga_E.js} +6 -6
- package/dist/assets/{mindmap-definition-8da855dc-WQ3LPKJU.js → mindmap-definition-8da855dc-CqqTDJn_.js} +1 -1
- package/dist/assets/{pieDiagram-a8764435-DHVIUZiN.js → pieDiagram-a8764435-BL2Ajx7Z.js} +1 -1
- package/dist/assets/{quadrantDiagram-1e28029f-C3G9Ye8-.js → quadrantDiagram-1e28029f-ClL_3ASt.js} +1 -1
- package/dist/assets/{requirementDiagram-08caed73-C9ES1D5G.js → requirementDiagram-08caed73-CB1RgE3K.js} +1 -1
- package/dist/assets/{sankeyDiagram-a04cb91d-B4BKXclQ.js → sankeyDiagram-a04cb91d-tgleEYiD.js} +1 -1
- package/dist/assets/{sequenceDiagram-c5b8d532-DrgEb25G.js → sequenceDiagram-c5b8d532-DlatQT5R.js} +1 -1
- package/dist/assets/{stateDiagram-1ecb1508-CF1XWARJ.js → stateDiagram-1ecb1508-B--MLqRs.js} +1 -1
- package/dist/assets/{stateDiagram-v2-c2b004d7-IO3i3yXv.js → stateDiagram-v2-c2b004d7-CRMZ6Dpx.js} +1 -1
- package/dist/assets/{styles-b4e223ce-DACN9aSc.js → styles-b4e223ce-CPiYHfUz.js} +1 -1
- package/dist/assets/{styles-ca3715f6-bekm2WLP.js → styles-ca3715f6-B9UKPAzX.js} +1 -1
- package/dist/assets/{styles-d45a18b0-OzTDVBb8.js → styles-d45a18b0-BC1Ak1So.js} +1 -1
- package/dist/assets/{svgDrawCommon-b86b1483-BWroJerr.js → svgDrawCommon-b86b1483-DV8R0g-n.js} +1 -1
- package/dist/assets/{timeline-definition-faaaa080-CCfRNigO.js → timeline-definition-faaaa080-CiqGS5DC.js} +1 -1
- package/dist/assets/{xychartDiagram-f5964ef8-C3cbfVqN.js → xychartDiagram-f5964ef8-h6VSD3GE.js} +1 -1
- package/dist/index.html +2 -7
- package/index.html +0 -5
- package/package.json +12 -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 +84 -0
- package/src/api/types.ts +20 -0
- package/src/api.ts +44 -269
- 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 +43 -29
- package/src/components/{chat/CodeBlock.tsx → CodeBlock.tsx} +3 -1
- package/src/components/ConfigView.tsx +32 -25
- 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 +99 -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/{Sender.scss → Sender/Sender.scss} +146 -3
- package/src/components/chat/{Sender.tsx → Sender/Sender.tsx} +183 -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/ConfigSectionForm.tsx +12 -1
- package/src/components/config/ConfigSourceSwitch.tsx +12 -34
- 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/components/RuleItem.tsx +1 -1
- package/src/components/knowledge-base/components/SpecItem.tsx +1 -1
- package/src/components/sidebar/SessionItem.scss +17 -0
- package/src/components/sidebar/SessionItem.tsx +21 -13
- package/src/hooks/chat/use-chat-adapter.ts +81 -0
- package/src/hooks/chat/use-chat-interaction.ts +26 -0
- package/src/{components/chat/useChatModels.tsx → hooks/chat/use-chat-models.tsx} +117 -22
- 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 +153 -0
- package/src/hooks/chat/use-chat-session-messages.ts +262 -0
- package/src/hooks/chat/use-chat-session.ts +63 -0
- package/src/hooks/chat/use-chat-view.ts +39 -0
- package/src/main.tsx +10 -13
- package/src/resources/adapters.ts +20 -0
- package/src/resources/locales/en.json +66 -0
- package/src/resources/locales/zh.json +66 -0
- package/src/runtime-config.ts +52 -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-jbCEHqbG.js +0 -1
- package/dist/assets/clone-CCRKqS4L.js +0 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-Baslbgn4.js +0 -1
- package/dist/assets/index-B0qfCb1G.css +0 -1
- package/dist/assets/index-CNo75dYr.js +0 -497
- 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
|
@@ -6,7 +6,7 @@ import { useNavigate } from 'react-router-dom'
|
|
|
6
6
|
import type { ChatMessage, WSEvent } from '@vibe-forge/core'
|
|
7
7
|
|
|
8
8
|
import { connectionManager } from '#~/connectionManager.js'
|
|
9
|
-
import { CodeBlock } from '
|
|
9
|
+
import { CodeBlock } from '#~/components/CodeBlock'
|
|
10
10
|
|
|
11
11
|
export interface TaskToolCardProps {
|
|
12
12
|
description?: string
|
|
@@ -154,11 +154,13 @@ export function TaskToolCard({
|
|
|
154
154
|
))}
|
|
155
155
|
</div>
|
|
156
156
|
)}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
{logText !== '' && (
|
|
158
|
+
<CodeBlock
|
|
159
|
+
hideHeader
|
|
160
|
+
code={logText}
|
|
161
|
+
lang='md'
|
|
162
|
+
/>
|
|
163
|
+
)}
|
|
162
164
|
</div>
|
|
163
165
|
</div>
|
|
164
166
|
)
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import type { TranslationFn } from './configUtils'
|
|
21
21
|
import {
|
|
22
22
|
BooleanRecordEditor,
|
|
23
|
+
ChannelRecordEditor,
|
|
23
24
|
KeyValueEditor,
|
|
24
25
|
McpServersRecordEditor,
|
|
25
26
|
ModelServicesRecordEditor,
|
|
@@ -49,7 +50,7 @@ export const SectionForm = ({
|
|
|
49
50
|
if (fields.length === 0) {
|
|
50
51
|
return <Empty description={t('common.noData')} image={null} />
|
|
51
52
|
}
|
|
52
|
-
const directRecordSections = new Set(['modelServices', 'adapters', 'plugins', 'mcp'])
|
|
53
|
+
const directRecordSections = new Set(['modelServices', 'channels', 'adapters', 'plugins', 'mcp'])
|
|
53
54
|
|
|
54
55
|
const modelServiceEntries = Object.entries(mergedModelServices)
|
|
55
56
|
const modelServiceOptions: Array<{ value: string; label: ReactNode }> = modelServiceEntries.map(([key, entry]) => {
|
|
@@ -97,6 +98,7 @@ export const SectionForm = ({
|
|
|
97
98
|
|
|
98
99
|
const getRecordKeyPlaceholder = (field: FieldSpec) => {
|
|
99
100
|
if (sectionKey === 'modelServices') return t('config.editor.newModelServiceName')
|
|
101
|
+
if (sectionKey === 'channels') return t('config.editor.newChannelName')
|
|
100
102
|
if (sectionKey === 'adapters') return t('config.editor.newAdapterName')
|
|
101
103
|
if (sectionKey === 'plugins') {
|
|
102
104
|
if (field.path.join('.') === 'extraKnownMarketplaces') return t('config.editor.newMarketplaceName')
|
|
@@ -247,6 +249,15 @@ export const SectionForm = ({
|
|
|
247
249
|
keyPlaceholder={getRecordKeyPlaceholder(field)}
|
|
248
250
|
/>
|
|
249
251
|
)
|
|
252
|
+
} else if (field.recordKind === 'channels') {
|
|
253
|
+
control = (
|
|
254
|
+
<ChannelRecordEditor
|
|
255
|
+
value={recordValue}
|
|
256
|
+
onChange={handleValueChange}
|
|
257
|
+
t={t}
|
|
258
|
+
keyPlaceholder={getRecordKeyPlaceholder(field)}
|
|
259
|
+
/>
|
|
260
|
+
)
|
|
250
261
|
} else if (field.recordKind === 'mcpServers') {
|
|
251
262
|
control = (
|
|
252
263
|
<McpServersRecordEditor
|
|
@@ -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
|
}
|
|
@@ -9,7 +9,7 @@ export type FieldValueType =
|
|
|
9
9
|
| 'record'
|
|
10
10
|
| 'shortcut'
|
|
11
11
|
|
|
12
|
-
export type RecordKind = 'json' | 'modelServices' | 'mcpServers' | 'boolean' | 'keyValue'
|
|
12
|
+
export type RecordKind = 'json' | 'modelServices' | 'mcpServers' | 'boolean' | 'keyValue' | 'channels'
|
|
13
13
|
|
|
14
14
|
export interface FieldOption {
|
|
15
15
|
value: string
|
|
@@ -260,6 +260,15 @@ export const configSchema: Record<string, FieldSpec[]> = {
|
|
|
260
260
|
icon: 'hub'
|
|
261
261
|
}
|
|
262
262
|
],
|
|
263
|
+
channels: [
|
|
264
|
+
{
|
|
265
|
+
path: [],
|
|
266
|
+
type: 'record',
|
|
267
|
+
recordKind: 'channels',
|
|
268
|
+
defaultValue: {},
|
|
269
|
+
icon: 'campaign'
|
|
270
|
+
}
|
|
271
|
+
],
|
|
263
272
|
adapters: [
|
|
264
273
|
{
|
|
265
274
|
path: [],
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@use './RecordEditors.scss';
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import './ChannelRecordEditor.scss'
|
|
2
|
+
|
|
3
|
+
import { Button, Input, InputNumber, Select, Switch, Tooltip } from 'antd'
|
|
4
|
+
import { useEffect, useMemo, useState } from 'react'
|
|
5
|
+
import type { ZodRawShape, ZodTypeAny } from 'zod'
|
|
6
|
+
import { z } from 'zod'
|
|
7
|
+
|
|
8
|
+
import type { ChannelDescriptor } from '@vibe-forge/core/channel'
|
|
9
|
+
import { channelBaseSchema } from '@vibe-forge/core/channel'
|
|
10
|
+
|
|
11
|
+
import { ComplexTextEditor, StringArrayEditor } from '../ConfigEditors'
|
|
12
|
+
import { FieldRow } from '../ConfigFieldRow'
|
|
13
|
+
import { channelDefinitions } from '../channelDefinitions'
|
|
14
|
+
import { getTypeIcon, isSensitiveKey } from '../configUtils'
|
|
15
|
+
import type { TranslationFn } from '../configUtils'
|
|
16
|
+
|
|
17
|
+
type ChannelRecordValue = Record<string, unknown>
|
|
18
|
+
|
|
19
|
+
const getObjectShape = (schema: ZodTypeAny): ZodRawShape => {
|
|
20
|
+
if (schema instanceof z.ZodObject) return schema.shape
|
|
21
|
+
if (schema instanceof z.ZodEffects) return getObjectShape(schema.innerType())
|
|
22
|
+
if (schema instanceof z.ZodOptional) return getObjectShape(schema.unwrap())
|
|
23
|
+
if (schema instanceof z.ZodDefault) return getObjectShape(schema.removeDefault())
|
|
24
|
+
if (schema instanceof z.ZodNullable) return getObjectShape(schema.unwrap())
|
|
25
|
+
return {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const unwrapSchema = (schema: ZodTypeAny): ZodTypeAny => {
|
|
29
|
+
if (schema instanceof z.ZodEffects) return unwrapSchema(schema.innerType())
|
|
30
|
+
if (schema instanceof z.ZodOptional) return unwrapSchema(schema.unwrap())
|
|
31
|
+
if (schema instanceof z.ZodDefault) return unwrapSchema(schema.removeDefault())
|
|
32
|
+
if (schema instanceof z.ZodNullable) return unwrapSchema(schema.unwrap())
|
|
33
|
+
return schema
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const getDefaultValue = (schema: ZodTypeAny): unknown => {
|
|
37
|
+
if (schema instanceof z.ZodDefault) return schema._def.defaultValue()
|
|
38
|
+
if (schema instanceof z.ZodOptional) return undefined
|
|
39
|
+
if (schema instanceof z.ZodNullable) return null
|
|
40
|
+
if (schema instanceof z.ZodLiteral) return schema.value
|
|
41
|
+
if (schema instanceof z.ZodEnum) return schema.options[0]
|
|
42
|
+
if (schema instanceof z.ZodNativeEnum) {
|
|
43
|
+
const values = Object.values(schema.enum)
|
|
44
|
+
return values.length > 0 ? values[0] : undefined
|
|
45
|
+
}
|
|
46
|
+
if (schema instanceof z.ZodString) return ''
|
|
47
|
+
if (schema instanceof z.ZodNumber) return 0
|
|
48
|
+
if (schema instanceof z.ZodBoolean) return false
|
|
49
|
+
if (schema instanceof z.ZodArray) return []
|
|
50
|
+
if (schema instanceof z.ZodObject) {
|
|
51
|
+
const shape = getObjectShape(schema)
|
|
52
|
+
return Object.fromEntries(Object.entries(shape).map(([key, value]) => [key, getDefaultValue(value)]))
|
|
53
|
+
}
|
|
54
|
+
if (schema instanceof z.ZodRecord) return {}
|
|
55
|
+
if (schema instanceof z.ZodEffects) return getDefaultValue(schema.innerType())
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const toLabel = (key: string) =>
|
|
60
|
+
key
|
|
61
|
+
.replace(/_/g, ' ')
|
|
62
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
63
|
+
.replace(/\b\w/g, char => char.toUpperCase())
|
|
64
|
+
|
|
65
|
+
const buildRecordDefaults = (definition: ChannelDescriptor) => {
|
|
66
|
+
const shape = getObjectShape(definition.configSchema)
|
|
67
|
+
const entries = Object.entries(shape)
|
|
68
|
+
return entries.reduce<Record<string, unknown>>((acc, [key, schema]) => {
|
|
69
|
+
acc[key] = getDefaultValue(schema)
|
|
70
|
+
return acc
|
|
71
|
+
}, {})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const getChannelDefinition = (type: string | undefined) =>
|
|
75
|
+
channelDefinitions.find(definition => definition.type === type)
|
|
76
|
+
|
|
77
|
+
export const ChannelRecordEditor = ({
|
|
78
|
+
value,
|
|
79
|
+
onChange,
|
|
80
|
+
t,
|
|
81
|
+
keyPlaceholder
|
|
82
|
+
}: {
|
|
83
|
+
value: Record<string, unknown>
|
|
84
|
+
onChange: (nextValue: Record<string, unknown>) => void
|
|
85
|
+
t: TranslationFn
|
|
86
|
+
keyPlaceholder: string
|
|
87
|
+
}) => {
|
|
88
|
+
const [newKey, setNewKey] = useState('')
|
|
89
|
+
const [newType, setNewType] = useState(channelDefinitions[0]?.type ?? '')
|
|
90
|
+
const entries = useMemo(() => Object.entries(value), [value])
|
|
91
|
+
const [collapsedKeys, setCollapsedKeys] = useState<Record<string, boolean>>(() => (
|
|
92
|
+
Object.fromEntries(entries.map(([key]) => [key, true]))
|
|
93
|
+
))
|
|
94
|
+
const baseKeys = useMemo(() => new Set(Object.keys(getObjectShape(channelBaseSchema))), [])
|
|
95
|
+
const typeOptions = useMemo(() =>
|
|
96
|
+
channelDefinitions.map(definition => ({
|
|
97
|
+
value: definition.type,
|
|
98
|
+
label: definition.label
|
|
99
|
+
})), [])
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
setCollapsedKeys(prev => {
|
|
103
|
+
const next: Record<string, boolean> = {}
|
|
104
|
+
for (const [key] of entries) {
|
|
105
|
+
next[key] = prev[key] ?? true
|
|
106
|
+
}
|
|
107
|
+
return next
|
|
108
|
+
})
|
|
109
|
+
}, [entries])
|
|
110
|
+
|
|
111
|
+
const updateChannel = (key: string, nextValue: ChannelRecordValue) => {
|
|
112
|
+
onChange({ ...value, [key]: nextValue })
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const renderField = (
|
|
116
|
+
channelKey: string,
|
|
117
|
+
recordValue: ChannelRecordValue,
|
|
118
|
+
fieldKey: string,
|
|
119
|
+
fieldSchema: ZodTypeAny
|
|
120
|
+
) => {
|
|
121
|
+
const rawSchema = unwrapSchema(fieldSchema)
|
|
122
|
+
const description = rawSchema.description ?? ''
|
|
123
|
+
const label = toLabel(fieldKey)
|
|
124
|
+
const currentValue = recordValue[fieldKey]
|
|
125
|
+
const valueToUse = currentValue !== undefined ? currentValue : getDefaultValue(fieldSchema)
|
|
126
|
+
|
|
127
|
+
if (rawSchema instanceof z.ZodString) {
|
|
128
|
+
const isSensitive = isSensitiveKey(fieldKey)
|
|
129
|
+
return (
|
|
130
|
+
<FieldRow key={fieldKey} title={label} description={description} icon={getTypeIcon('string')}>
|
|
131
|
+
{isSensitive
|
|
132
|
+
? (
|
|
133
|
+
<Input.Password
|
|
134
|
+
value={typeof valueToUse === 'string' ? valueToUse : ''}
|
|
135
|
+
onChange={(event) => {
|
|
136
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: event.target.value })
|
|
137
|
+
}}
|
|
138
|
+
placeholder={t('config.editor.secretPlaceholder')}
|
|
139
|
+
/>
|
|
140
|
+
)
|
|
141
|
+
: (
|
|
142
|
+
<Input
|
|
143
|
+
value={typeof valueToUse === 'string' ? valueToUse : ''}
|
|
144
|
+
onChange={(event) => {
|
|
145
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: event.target.value })
|
|
146
|
+
}}
|
|
147
|
+
/>
|
|
148
|
+
)}
|
|
149
|
+
</FieldRow>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (rawSchema instanceof z.ZodNumber) {
|
|
154
|
+
return (
|
|
155
|
+
<FieldRow key={fieldKey} title={label} description={description} icon={getTypeIcon('number')}>
|
|
156
|
+
<InputNumber
|
|
157
|
+
value={typeof valueToUse === 'number' ? valueToUse : 0}
|
|
158
|
+
onChange={(nextValue) => {
|
|
159
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: nextValue ?? 0 })
|
|
160
|
+
}}
|
|
161
|
+
/>
|
|
162
|
+
</FieldRow>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (rawSchema instanceof z.ZodBoolean) {
|
|
167
|
+
return (
|
|
168
|
+
<FieldRow key={fieldKey} title={label} description={description} icon={getTypeIcon('boolean')}>
|
|
169
|
+
<Switch
|
|
170
|
+
checked={Boolean(valueToUse)}
|
|
171
|
+
onChange={(checked) => {
|
|
172
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: checked })
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
</FieldRow>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (rawSchema instanceof z.ZodEnum || rawSchema instanceof z.ZodNativeEnum) {
|
|
180
|
+
const optionValues: string[] = (
|
|
181
|
+
rawSchema instanceof z.ZodEnum
|
|
182
|
+
? rawSchema.options
|
|
183
|
+
: Object.values(rawSchema.enum)
|
|
184
|
+
).map((option: string | number) => String(option))
|
|
185
|
+
return (
|
|
186
|
+
<FieldRow key={fieldKey} title={label} description={description} icon={getTypeIcon('string')}>
|
|
187
|
+
<Select
|
|
188
|
+
value={typeof valueToUse === 'string' ? valueToUse : undefined}
|
|
189
|
+
options={optionValues.map((option) => ({ value: option, label: option }))}
|
|
190
|
+
onChange={(nextValue) => {
|
|
191
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: nextValue })
|
|
192
|
+
}}
|
|
193
|
+
/>
|
|
194
|
+
</FieldRow>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (rawSchema instanceof z.ZodArray) {
|
|
199
|
+
const inner = unwrapSchema(rawSchema.element)
|
|
200
|
+
if (inner instanceof z.ZodString) {
|
|
201
|
+
const items = Array.isArray(valueToUse)
|
|
202
|
+
? valueToUse.filter(item => typeof item === 'string')
|
|
203
|
+
: []
|
|
204
|
+
return (
|
|
205
|
+
<FieldRow
|
|
206
|
+
key={fieldKey}
|
|
207
|
+
title={label}
|
|
208
|
+
description={description}
|
|
209
|
+
icon={getTypeIcon('array')}
|
|
210
|
+
layout='stacked'
|
|
211
|
+
>
|
|
212
|
+
<StringArrayEditor
|
|
213
|
+
value={items}
|
|
214
|
+
onChange={(nextValue) => {
|
|
215
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: nextValue })
|
|
216
|
+
}}
|
|
217
|
+
t={t}
|
|
218
|
+
/>
|
|
219
|
+
</FieldRow>
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
return (
|
|
223
|
+
<FieldRow
|
|
224
|
+
key={fieldKey}
|
|
225
|
+
title={label}
|
|
226
|
+
description={description}
|
|
227
|
+
icon={getTypeIcon('array')}
|
|
228
|
+
layout='stacked'
|
|
229
|
+
>
|
|
230
|
+
<ComplexTextEditor
|
|
231
|
+
value={Array.isArray(valueToUse) ? valueToUse : []}
|
|
232
|
+
onChange={(nextValue) => {
|
|
233
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: nextValue })
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
236
|
+
</FieldRow>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<FieldRow
|
|
242
|
+
key={fieldKey}
|
|
243
|
+
title={label}
|
|
244
|
+
description={description}
|
|
245
|
+
icon={getTypeIcon('object')}
|
|
246
|
+
layout='stacked'
|
|
247
|
+
>
|
|
248
|
+
<ComplexTextEditor
|
|
249
|
+
value={valueToUse ?? {}}
|
|
250
|
+
onChange={(nextValue) => {
|
|
251
|
+
updateChannel(channelKey, { ...recordValue, [fieldKey]: nextValue })
|
|
252
|
+
}}
|
|
253
|
+
/>
|
|
254
|
+
</FieldRow>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return (
|
|
259
|
+
<div className='config-view__record-list'>
|
|
260
|
+
<div className='config-view__record-add'>
|
|
261
|
+
<div className='config-view__record-add-inputs'>
|
|
262
|
+
<Input
|
|
263
|
+
value={newKey}
|
|
264
|
+
placeholder={keyPlaceholder}
|
|
265
|
+
onChange={(event) => setNewKey(event.target.value)}
|
|
266
|
+
/>
|
|
267
|
+
<Select
|
|
268
|
+
value={newType}
|
|
269
|
+
options={typeOptions}
|
|
270
|
+
onChange={(nextValue) => setNewType(nextValue)}
|
|
271
|
+
/>
|
|
272
|
+
<Tooltip title={t('common.confirm')}>
|
|
273
|
+
<Button
|
|
274
|
+
size='small'
|
|
275
|
+
type='primary'
|
|
276
|
+
className='config-view__icon-button'
|
|
277
|
+
aria-label={t('common.confirm')}
|
|
278
|
+
icon={<span className='material-symbols-rounded'>check</span>}
|
|
279
|
+
disabled={newKey.trim() === '' || Object.hasOwn(value, newKey) || newType === ''}
|
|
280
|
+
onClick={() => {
|
|
281
|
+
const definition = getChannelDefinition(newType)
|
|
282
|
+
if (!definition) return
|
|
283
|
+
onChange({
|
|
284
|
+
...value,
|
|
285
|
+
[newKey]: buildRecordDefaults(definition)
|
|
286
|
+
})
|
|
287
|
+
setNewKey('')
|
|
288
|
+
}}
|
|
289
|
+
/>
|
|
290
|
+
</Tooltip>
|
|
291
|
+
</div>
|
|
292
|
+
</div>
|
|
293
|
+
{entries.map(([key, itemValue]) => {
|
|
294
|
+
const recordValue = (itemValue != null && typeof itemValue === 'object')
|
|
295
|
+
? itemValue as ChannelRecordValue
|
|
296
|
+
: {}
|
|
297
|
+
const type = typeof recordValue.type === 'string' ? recordValue.type : undefined
|
|
298
|
+
const definition = getChannelDefinition(type)
|
|
299
|
+
const titleValue = typeof recordValue.title === 'string' ? recordValue.title : ''
|
|
300
|
+
const descriptionValue = typeof recordValue.description === 'string' ? recordValue.description : ''
|
|
301
|
+
const displayName = titleValue.trim() !== '' ? titleValue : key
|
|
302
|
+
const typeLabel = definition?.label ?? type ?? t('config.editor.unknownChannelType')
|
|
303
|
+
const isCollapsed = collapsedKeys[key] === true
|
|
304
|
+
const shape = definition ? getObjectShape(definition.configSchema) : {}
|
|
305
|
+
const fieldEntries = Object.entries(shape)
|
|
306
|
+
.filter(([fieldKey]) => fieldKey !== 'type')
|
|
307
|
+
.sort(([a], [b]) => {
|
|
308
|
+
const order = ['title', 'description', 'enabled', 'admins']
|
|
309
|
+
const aIndex = order.indexOf(a)
|
|
310
|
+
const bIndex = order.indexOf(b)
|
|
311
|
+
if (aIndex === -1 && bIndex === -1) return a.localeCompare(b)
|
|
312
|
+
if (aIndex === -1) return 1
|
|
313
|
+
if (bIndex === -1) return -1
|
|
314
|
+
return aIndex - bIndex
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<div
|
|
319
|
+
key={key}
|
|
320
|
+
className={`config-view__record-card${isCollapsed ? ' config-view__record-card--collapsed' : ''}`}
|
|
321
|
+
>
|
|
322
|
+
<div className='config-view__record-title'>
|
|
323
|
+
<div className='config-view__record-title-left'>
|
|
324
|
+
<Tooltip title={isCollapsed ? t('config.editor.expand') : t('config.editor.collapse')}>
|
|
325
|
+
<Button
|
|
326
|
+
size='small'
|
|
327
|
+
type='text'
|
|
328
|
+
className='config-view__icon-button config-view__icon-button--compact'
|
|
329
|
+
aria-label={isCollapsed ? t('config.editor.expand') : t('config.editor.collapse')}
|
|
330
|
+
icon={
|
|
331
|
+
<span className='material-symbols-rounded'>{isCollapsed ? 'chevron_right' : 'expand_more'}</span>
|
|
332
|
+
}
|
|
333
|
+
onClick={() => {
|
|
334
|
+
setCollapsedKeys(prev => ({ ...prev, [key]: !isCollapsed }))
|
|
335
|
+
}}
|
|
336
|
+
/>
|
|
337
|
+
</Tooltip>
|
|
338
|
+
<div className='config-view__record-heading'>
|
|
339
|
+
<div>{displayName}</div>
|
|
340
|
+
<div className='config-view__record-subtitle'>
|
|
341
|
+
{key} · {typeLabel}
|
|
342
|
+
</div>
|
|
343
|
+
{descriptionValue !== '' && (
|
|
344
|
+
<div className='config-view__record-desc'>{descriptionValue}</div>
|
|
345
|
+
)}
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
<div className='config-view__record-actions'>
|
|
349
|
+
<Tooltip title={t('config.editor.remove')}>
|
|
350
|
+
<Button
|
|
351
|
+
size='small'
|
|
352
|
+
type='text'
|
|
353
|
+
danger
|
|
354
|
+
className='config-view__icon-button config-view__icon-button--compact'
|
|
355
|
+
aria-label={t('config.editor.remove')}
|
|
356
|
+
icon={<span className='material-symbols-rounded'>delete</span>}
|
|
357
|
+
onClick={() => {
|
|
358
|
+
const updated = { ...value }
|
|
359
|
+
delete updated[key]
|
|
360
|
+
onChange(updated)
|
|
361
|
+
}}
|
|
362
|
+
/>
|
|
363
|
+
</Tooltip>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
<div className='config-view__record-body'>
|
|
367
|
+
<div className='config-view__record-fields'>
|
|
368
|
+
<FieldRow
|
|
369
|
+
title={t('config.editor.channelType')}
|
|
370
|
+
description={definition?.description ?? ''}
|
|
371
|
+
icon={getTypeIcon('string')}
|
|
372
|
+
>
|
|
373
|
+
<Select
|
|
374
|
+
value={type}
|
|
375
|
+
options={typeOptions}
|
|
376
|
+
onChange={(nextType) => {
|
|
377
|
+
const nextDefinition = getChannelDefinition(nextType)
|
|
378
|
+
if (!nextDefinition) return
|
|
379
|
+
const defaults = buildRecordDefaults(nextDefinition)
|
|
380
|
+
const preserved = Object.fromEntries(
|
|
381
|
+
Object.entries(recordValue).filter(([fieldKey]) => baseKeys.has(fieldKey))
|
|
382
|
+
)
|
|
383
|
+
updateChannel(key, { ...defaults, ...preserved, type: nextType })
|
|
384
|
+
}}
|
|
385
|
+
/>
|
|
386
|
+
</FieldRow>
|
|
387
|
+
{fieldEntries.map(([fieldKey, fieldSchema]) => (
|
|
388
|
+
renderField(key, recordValue, fieldKey, fieldSchema)
|
|
389
|
+
))}
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
)
|
|
394
|
+
})}
|
|
395
|
+
</div>
|
|
396
|
+
)
|
|
397
|
+
}
|
|
@@ -5,7 +5,7 @@ import useSWR from 'swr'
|
|
|
5
5
|
|
|
6
6
|
import type { RuleDetail, RuleSummary } from '#~/api.js'
|
|
7
7
|
import { getRuleDetail } from '#~/api.js'
|
|
8
|
-
import { MarkdownContent } from '#~/components/
|
|
8
|
+
import { MarkdownContent } from '#~/components/MarkdownContent'
|
|
9
9
|
import { LoadingState } from './LoadingState'
|
|
10
10
|
|
|
11
11
|
type RuleItemProps = {
|
|
@@ -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
|
|
|
@@ -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>
|