@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
|
@@ -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
|
)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import '../ConfigView.scss'
|
|
2
|
+
|
|
3
|
+
import { Switch } from 'antd'
|
|
4
|
+
import { useAtom } from 'jotai'
|
|
5
|
+
|
|
6
|
+
import { showAnnouncementsAtom } from '#~/store/index.js'
|
|
7
|
+
|
|
8
|
+
import { FieldRow } from './ConfigFieldRow'
|
|
9
|
+
import type { TranslationFn } from './configUtils'
|
|
10
|
+
|
|
11
|
+
export function AppSettingsPanel({ t }: { t: TranslationFn }) {
|
|
12
|
+
const [showAnnouncements, setShowAnnouncements] = useAtom(showAnnouncementsAtom)
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className='config-view__editor-wrap'>
|
|
16
|
+
<div className='config-view__section-header'>
|
|
17
|
+
<div className='config-view__section-title'>
|
|
18
|
+
<span className='material-symbols-rounded config-view__section-icon'>tune</span>
|
|
19
|
+
<span>{t('config.sections.appearance')}</span>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div className='config-view__card'>
|
|
23
|
+
<FieldRow
|
|
24
|
+
title={t('config.appSettings.announcements.label')}
|
|
25
|
+
description={t('config.appSettings.announcements.desc')}
|
|
26
|
+
icon='campaign'
|
|
27
|
+
>
|
|
28
|
+
<Switch checked={showAnnouncements} onChange={setShowAnnouncements} />
|
|
29
|
+
</FieldRow>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -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
|
|
@@ -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
|
+
{entries.map(([key, itemValue]) => {
|
|
261
|
+
const recordValue = (itemValue != null && typeof itemValue === 'object')
|
|
262
|
+
? itemValue as ChannelRecordValue
|
|
263
|
+
: {}
|
|
264
|
+
const type = typeof recordValue.type === 'string' ? recordValue.type : undefined
|
|
265
|
+
const definition = getChannelDefinition(type)
|
|
266
|
+
const titleValue = typeof recordValue.title === 'string' ? recordValue.title : ''
|
|
267
|
+
const descriptionValue = typeof recordValue.description === 'string' ? recordValue.description : ''
|
|
268
|
+
const displayName = titleValue.trim() !== '' ? titleValue : key
|
|
269
|
+
const typeLabel = definition?.label ?? type ?? t('config.editor.unknownChannelType')
|
|
270
|
+
const isCollapsed = collapsedKeys[key] === true
|
|
271
|
+
const shape = definition ? getObjectShape(definition.configSchema) : {}
|
|
272
|
+
const fieldEntries = Object.entries(shape)
|
|
273
|
+
.filter(([fieldKey]) => fieldKey !== 'type')
|
|
274
|
+
.sort(([a], [b]) => {
|
|
275
|
+
const order = ['title', 'description', 'enabled', 'admins']
|
|
276
|
+
const aIndex = order.indexOf(a)
|
|
277
|
+
const bIndex = order.indexOf(b)
|
|
278
|
+
if (aIndex === -1 && bIndex === -1) return a.localeCompare(b)
|
|
279
|
+
if (aIndex === -1) return 1
|
|
280
|
+
if (bIndex === -1) return -1
|
|
281
|
+
return aIndex - bIndex
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div
|
|
286
|
+
key={key}
|
|
287
|
+
className={`config-view__record-card${isCollapsed ? ' config-view__record-card--collapsed' : ''}`}
|
|
288
|
+
>
|
|
289
|
+
<div className='config-view__record-title'>
|
|
290
|
+
<div className='config-view__record-title-left'>
|
|
291
|
+
<Tooltip title={isCollapsed ? t('config.editor.expand') : t('config.editor.collapse')}>
|
|
292
|
+
<Button
|
|
293
|
+
size='small'
|
|
294
|
+
type='text'
|
|
295
|
+
className='config-view__icon-button config-view__icon-button--compact'
|
|
296
|
+
aria-label={isCollapsed ? t('config.editor.expand') : t('config.editor.collapse')}
|
|
297
|
+
icon={
|
|
298
|
+
<span className='material-symbols-rounded'>{isCollapsed ? 'chevron_right' : 'expand_more'}</span>
|
|
299
|
+
}
|
|
300
|
+
onClick={() => {
|
|
301
|
+
setCollapsedKeys(prev => ({ ...prev, [key]: !isCollapsed }))
|
|
302
|
+
}}
|
|
303
|
+
/>
|
|
304
|
+
</Tooltip>
|
|
305
|
+
<div className='config-view__record-heading'>
|
|
306
|
+
<div>{displayName}</div>
|
|
307
|
+
<div className='config-view__record-subtitle'>
|
|
308
|
+
{key} · {typeLabel}
|
|
309
|
+
</div>
|
|
310
|
+
{descriptionValue !== '' && (
|
|
311
|
+
<div className='config-view__record-desc'>{descriptionValue}</div>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
<div className='config-view__record-actions'>
|
|
316
|
+
<Tooltip title={t('config.editor.remove')}>
|
|
317
|
+
<Button
|
|
318
|
+
size='small'
|
|
319
|
+
type='text'
|
|
320
|
+
danger
|
|
321
|
+
className='config-view__icon-button config-view__icon-button--compact'
|
|
322
|
+
aria-label={t('config.editor.remove')}
|
|
323
|
+
icon={<span className='material-symbols-rounded'>delete</span>}
|
|
324
|
+
onClick={() => {
|
|
325
|
+
const updated = { ...value }
|
|
326
|
+
delete updated[key]
|
|
327
|
+
onChange(updated)
|
|
328
|
+
}}
|
|
329
|
+
/>
|
|
330
|
+
</Tooltip>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
<div className='config-view__record-body'>
|
|
334
|
+
<div className='config-view__record-fields'>
|
|
335
|
+
<FieldRow
|
|
336
|
+
title={t('config.editor.channelType')}
|
|
337
|
+
description={definition?.description ?? ''}
|
|
338
|
+
icon={getTypeIcon('string')}
|
|
339
|
+
>
|
|
340
|
+
<Select
|
|
341
|
+
value={type}
|
|
342
|
+
options={typeOptions}
|
|
343
|
+
onChange={(nextType) => {
|
|
344
|
+
const nextDefinition = getChannelDefinition(nextType)
|
|
345
|
+
if (!nextDefinition) return
|
|
346
|
+
const defaults = buildRecordDefaults(nextDefinition)
|
|
347
|
+
const preserved = Object.fromEntries(
|
|
348
|
+
Object.entries(recordValue).filter(([fieldKey]) => baseKeys.has(fieldKey))
|
|
349
|
+
)
|
|
350
|
+
updateChannel(key, { ...defaults, ...preserved, type: nextType })
|
|
351
|
+
}}
|
|
352
|
+
/>
|
|
353
|
+
</FieldRow>
|
|
354
|
+
{fieldEntries.map(([fieldKey, fieldSchema]) => (
|
|
355
|
+
renderField(key, recordValue, fieldKey, fieldSchema)
|
|
356
|
+
))}
|
|
357
|
+
</div>
|
|
358
|
+
</div>
|
|
359
|
+
</div>
|
|
360
|
+
)
|
|
361
|
+
})}
|
|
362
|
+
<div className='config-view__record-add'>
|
|
363
|
+
<div className='config-view__record-add-inputs'>
|
|
364
|
+
<Input
|
|
365
|
+
value={newKey}
|
|
366
|
+
placeholder={keyPlaceholder}
|
|
367
|
+
onChange={(event) => setNewKey(event.target.value)}
|
|
368
|
+
/>
|
|
369
|
+
<Select
|
|
370
|
+
value={newType}
|
|
371
|
+
options={typeOptions}
|
|
372
|
+
onChange={(nextValue) => setNewType(nextValue)}
|
|
373
|
+
/>
|
|
374
|
+
<Tooltip title={t('common.confirm')}>
|
|
375
|
+
<Button
|
|
376
|
+
size='small'
|
|
377
|
+
type='primary'
|
|
378
|
+
className='config-view__icon-button'
|
|
379
|
+
aria-label={t('common.confirm')}
|
|
380
|
+
icon={<span className='material-symbols-rounded'>check</span>}
|
|
381
|
+
disabled={newKey.trim() === '' || Object.hasOwn(value, newKey) || newType === ''}
|
|
382
|
+
onClick={() => {
|
|
383
|
+
const definition = getChannelDefinition(newType)
|
|
384
|
+
if (!definition) return
|
|
385
|
+
onChange({
|
|
386
|
+
...value,
|
|
387
|
+
[newKey]: buildRecordDefaults(definition)
|
|
388
|
+
})
|
|
389
|
+
setNewKey('')
|
|
390
|
+
}}
|
|
391
|
+
/>
|
|
392
|
+
</Tooltip>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</div>
|
|
396
|
+
)
|
|
397
|
+
}
|
|
@@ -5,7 +5,8 @@ import React from 'react'
|
|
|
5
5
|
import { useTranslation } from 'react-i18next'
|
|
6
6
|
import useSWR from 'swr'
|
|
7
7
|
|
|
8
|
-
import type { EntitySummary, SpecSummary } from '#~/api.js'
|
|
8
|
+
import type { EntitySummary, RuleSummary, SpecSummary } from '#~/api.js'
|
|
9
|
+
import { useQueryParams } from '#~/hooks/useQueryParams.js'
|
|
9
10
|
import { EntitiesTab } from './components/EntitiesTab.js'
|
|
10
11
|
import { FlowsTab } from './components/FlowsTab.js'
|
|
11
12
|
import { KnowledgeBaseHeader } from './components/KnowledgeBaseHeader.js'
|
|
@@ -13,6 +14,10 @@ import { RulesTab } from './components/RulesTab.js'
|
|
|
13
14
|
import { SkillsTab } from './components/SkillsTab.js'
|
|
14
15
|
import { TabLabel } from './components/TabLabel.js'
|
|
15
16
|
|
|
17
|
+
interface KnowledgeQueryParams extends Record<string, string> {
|
|
18
|
+
kbTab: string
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export function KnowledgeBaseView() {
|
|
17
22
|
const { t } = useTranslation()
|
|
18
23
|
const { message } = App.useApp()
|
|
@@ -26,14 +31,28 @@ export function KnowledgeBaseView() {
|
|
|
26
31
|
isLoading: isEntitiesLoading,
|
|
27
32
|
mutate: mutateEntities
|
|
28
33
|
} = useSWR<{ entities: EntitySummary[] }>('/api/ai/entities')
|
|
34
|
+
const {
|
|
35
|
+
data: rulesRes,
|
|
36
|
+
isLoading: isRulesLoading,
|
|
37
|
+
mutate: mutateRules
|
|
38
|
+
} = useSWR<{ rules: RuleSummary[] }>('/api/ai/rules')
|
|
29
39
|
|
|
30
40
|
const specs = specsRes?.specs ?? []
|
|
31
41
|
const entities = entitiesRes?.entities ?? []
|
|
42
|
+
const rules = rulesRes?.rules ?? []
|
|
32
43
|
|
|
33
44
|
const [specQuery, setSpecQuery] = React.useState('')
|
|
34
45
|
const [specTagFilter, setSpecTagFilter] = React.useState<string[]>([])
|
|
35
46
|
const [entityQuery, setEntityQuery] = React.useState('')
|
|
36
47
|
const [entityTagFilter, setEntityTagFilter] = React.useState<string[]>([])
|
|
48
|
+
const [ruleQuery, setRuleQuery] = React.useState('')
|
|
49
|
+
|
|
50
|
+
const { values, update } = useQueryParams<KnowledgeQueryParams>({
|
|
51
|
+
keys: ['kbTab'],
|
|
52
|
+
defaults: {
|
|
53
|
+
kbTab: 'skills'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
37
56
|
|
|
38
57
|
const specTagOptions = React.useMemo(() => {
|
|
39
58
|
const tags = new Set<string>()
|
|
@@ -81,8 +100,18 @@ export function KnowledgeBaseView() {
|
|
|
81
100
|
})
|
|
82
101
|
}, [entityQuery, entityTagFilter, entities])
|
|
83
102
|
|
|
103
|
+
const filteredRules = React.useMemo(() => {
|
|
104
|
+
const query = ruleQuery.trim().toLowerCase()
|
|
105
|
+
return rules.filter(rule => {
|
|
106
|
+
if (query === '') return true
|
|
107
|
+
const globText = (rule.globs ?? []).join(' ')
|
|
108
|
+
const haystack = `${rule.name} ${rule.description} ${globText}`.toLowerCase()
|
|
109
|
+
return haystack.includes(query)
|
|
110
|
+
})
|
|
111
|
+
}, [ruleQuery, rules])
|
|
112
|
+
|
|
84
113
|
const handleRefresh = async () => {
|
|
85
|
-
await Promise.all([mutateSpecs(), mutateEntities()])
|
|
114
|
+
await Promise.all([mutateSpecs(), mutateEntities(), mutateRules()])
|
|
86
115
|
void message.success(t('knowledge.actions.refreshed'))
|
|
87
116
|
}
|
|
88
117
|
|
|
@@ -170,6 +199,11 @@ export function KnowledgeBaseView() {
|
|
|
170
199
|
label: <TabLabel icon='gavel' label={t('knowledge.tabs.rules')} />,
|
|
171
200
|
children: (
|
|
172
201
|
<RulesTab
|
|
202
|
+
rules={rules}
|
|
203
|
+
filteredRules={filteredRules}
|
|
204
|
+
isLoading={isRulesLoading}
|
|
205
|
+
query={ruleQuery}
|
|
206
|
+
onQueryChange={setRuleQuery}
|
|
173
207
|
onCreate={handleCreateRule}
|
|
174
208
|
onImport={handleImportRule}
|
|
175
209
|
/>
|
|
@@ -177,10 +211,24 @@ export function KnowledgeBaseView() {
|
|
|
177
211
|
}
|
|
178
212
|
]
|
|
179
213
|
|
|
214
|
+
const tabKeys = React.useMemo(() => tabs.map(tab => tab.key), [tabs])
|
|
215
|
+
const activeTab = tabKeys.includes(values.kbTab) ? values.kbTab : tabKeys[0]
|
|
216
|
+
|
|
217
|
+
React.useEffect(() => {
|
|
218
|
+
if (values.kbTab !== activeTab) {
|
|
219
|
+
update({ kbTab: activeTab })
|
|
220
|
+
}
|
|
221
|
+
}, [activeTab, update, values.kbTab])
|
|
222
|
+
|
|
180
223
|
return (
|
|
181
224
|
<div className='knowledge-base-view'>
|
|
182
225
|
<KnowledgeBaseHeader onRefresh={handleRefresh} />
|
|
183
|
-
<Tabs
|
|
226
|
+
<Tabs
|
|
227
|
+
className='knowledge-base-view__tabs'
|
|
228
|
+
items={tabs}
|
|
229
|
+
activeKey={activeTab}
|
|
230
|
+
onChange={(key) => update({ kbTab: key })}
|
|
231
|
+
/>
|
|
184
232
|
</div>
|
|
185
233
|
)
|
|
186
234
|
}
|