@vibe-forge/client 2.0.1 → 3.0.0-alpha.1
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/dist/assets/{arc-CqviK3HX.js → arc-CLPB686k.js} +1 -1
- package/dist/assets/{blockDiagram-c4efeb88-BEp50UHp.js → blockDiagram-c4efeb88-DMStYteY.js} +1 -1
- package/dist/assets/{c4Diagram-c83219d4-C5w55JzM.js → c4Diagram-c83219d4-rf9gMSjH.js} +1 -1
- package/dist/assets/channel-DVzVHik1.js +1 -0
- package/dist/assets/{classDiagram-beda092f-CQJVtHEy.js → classDiagram-beda092f-C4vQa5QZ.js} +1 -1
- package/dist/assets/{classDiagram-v2-2358418a-B37Xl9jB.js → classDiagram-v2-2358418a-f9zo4MCR.js} +1 -1
- package/dist/assets/clone-Bqdku2Xw.js +1 -0
- package/dist/assets/{createText-1719965b-9YwvWMdV.js → createText-1719965b-D_7VWOAv.js} +1 -1
- package/dist/assets/{cssMode-BX88r5f4.js → cssMode-TBU0lKLl.js} +1 -1
- package/dist/assets/{edges-96097737-CNHoXVrD.js → edges-96097737-C4aqXnw4.js} +1 -1
- package/dist/assets/{erDiagram-0228fc6a-BoYldy0g.js → erDiagram-0228fc6a-CUOFDP88.js} +1 -1
- package/dist/assets/{flowDb-c6c81e3f-CoPw_R-Q.js → flowDb-c6c81e3f-CtPmxsKO.js} +1 -1
- package/dist/assets/{flowDiagram-50d868cf-nCqbSXd-.js → flowDiagram-50d868cf-BxJQf6xZ.js} +1 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-UwlDcmES.js +1 -0
- package/dist/assets/{flowchart-elk-definition-6af322e1-BwMuPTrV.js → flowchart-elk-definition-6af322e1-Dit-sZjo.js} +1 -1
- package/dist/assets/{freemarker2-DUFDSvgj.js → freemarker2-CTXFNqpX.js} +1 -1
- package/dist/assets/{ganttDiagram-a2739b55-CLNH3S_C.js → ganttDiagram-a2739b55-CULsxs9m.js} +1 -1
- package/dist/assets/{gitGraphDiagram-82fe8481-uDu1ectX.js → gitGraphDiagram-82fe8481-C6B6ctTM.js} +1 -1
- package/dist/assets/{graph-DuC4kt4I.js → graph-E5s7kY4o.js} +1 -1
- package/dist/assets/{handlebars-BSd4a6l9.js → handlebars-VV2sVWpA.js} +1 -1
- package/dist/assets/{html-H48gEjQd.js → html-BaQZhNyu.js} +1 -1
- package/dist/assets/{htmlMode-Nqw7-Nqh.js → htmlMode-DC-T7U3Z.js} +1 -1
- package/dist/assets/{index-5325376f-rnz0GXAT.js → index-5325376f-BcHntDDH.js} +1 -1
- package/dist/assets/{index-DeQLT67a.js → index-Dc8IL8wB.js} +322 -322
- package/dist/assets/{index-DiOCtPLP.css → index-MWOwVzqE.css} +1 -1
- package/dist/assets/{infoDiagram-8eee0895-BsGB550b.js → infoDiagram-8eee0895-DBhMGcJZ.js} +1 -1
- package/dist/assets/{javascript-0g2herYV.js → javascript-D1mh-XtA.js} +1 -1
- package/dist/assets/{journeyDiagram-c64418c1-DLldlz0H.js → journeyDiagram-c64418c1-C6ccHNG_.js} +1 -1
- package/dist/assets/{jsonMode-CN5ZURMh.js → jsonMode-BcVpgG1G.js} +1 -1
- package/dist/assets/{layout-QKUiDNJK.js → layout-DQUeOk09.js} +1 -1
- package/dist/assets/{line-CeP3XWjD.js → line-BNSZ8woB.js} +1 -1
- package/dist/assets/{linear-74cQVgWT.js → linear-FDIGQp-F.js} +1 -1
- package/dist/assets/{liquid-B6cRrfrb.js → liquid-Yp7v-HuI.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-C5ogOh5E.js → lspLanguageFeatures-y-HB9mm1.js} +1 -1
- package/dist/assets/{mdx-BBIy-KRj.js → mdx-BzbOmj2n.js} +1 -1
- package/dist/assets/{mermaid.core-BhdbV0mr.js → mermaid.core-BZWBFlxL.js} +4 -4
- package/dist/assets/{mindmap-definition-8da855dc-B67VKJuD.js → mindmap-definition-8da855dc-Bx0W62wP.js} +1 -1
- package/dist/assets/{pieDiagram-a8764435-Cxv9WY_E.js → pieDiagram-a8764435-ICiizDcw.js} +1 -1
- package/dist/assets/{python-CBdGo8__.js → python--itpZziZ.js} +1 -1
- package/dist/assets/{quadrantDiagram-1e28029f-BTkj65P_.js → quadrantDiagram-1e28029f-DPLXwibA.js} +1 -1
- package/dist/assets/{razor-azKH0Dwj.js → razor-BY3vF3Z2.js} +1 -1
- package/dist/assets/{requirementDiagram-08caed73-D4jVXpOT.js → requirementDiagram-08caed73-Czz2Ayg6.js} +1 -1
- package/dist/assets/{sankeyDiagram-a04cb91d-CXhutIA1.js → sankeyDiagram-a04cb91d-D82XBO4T.js} +1 -1
- package/dist/assets/{sequenceDiagram-c5b8d532-B56TTZlx.js → sequenceDiagram-c5b8d532-BiyoxjF7.js} +1 -1
- package/dist/assets/{stateDiagram-1ecb1508-Cs0plMcS.js → stateDiagram-1ecb1508-BtohcrjI.js} +1 -1
- package/dist/assets/{stateDiagram-v2-c2b004d7-LSJaXPJN.js → stateDiagram-v2-c2b004d7-Bte1ATNh.js} +1 -1
- package/dist/assets/{styles-b4e223ce-UdXfHMuu.js → styles-b4e223ce-BB-1jLao.js} +1 -1
- package/dist/assets/{styles-ca3715f6-EuRy_hTu.js → styles-ca3715f6-Dv3R8M8l.js} +1 -1
- package/dist/assets/{styles-d45a18b0-B24zVoK3.js → styles-d45a18b0-CslmN8E7.js} +1 -1
- package/dist/assets/{svgDrawCommon-b86b1483-B2S0NW3K.js → svgDrawCommon-b86b1483-C9wC1rgS.js} +1 -1
- package/dist/assets/{timeline-definition-faaaa080-DFWKh9mU.js → timeline-definition-faaaa080-DUFt2nhX.js} +1 -1
- package/dist/assets/{tsMode-FZsHWiOn.js → tsMode-xH26JMT6.js} +1 -1
- package/dist/assets/{typescript-CYdJ3s3D.js → typescript-DBlHiZkC.js} +1 -1
- package/dist/assets/{xml-C16X_hpZ.js → xml-DvpPym76.js} +1 -1
- package/dist/assets/{xychartDiagram-f5964ef8-DyBiBYci.js → xychartDiagram-f5964ef8-Brmas6JU.js} +1 -1
- package/dist/assets/{yaml-CRjA4-Rj.js → yaml-BGpvKN7u.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +13 -13
- package/src/components/ConfigView.scss +7 -6
- package/src/components/ConfigView.tsx +2 -1
- package/src/components/Sidebar.tsx +1 -0
- package/src/components/automation-view/@components/AutomationTaskComposer.tsx +10 -8
- package/src/components/automation-view/AutomationEmptyLanding.tsx +12 -0
- package/src/components/automation-view/TaskList.scss +5 -1
- package/src/components/chat/ChatHistoryView.tsx +5 -3
- package/src/components/chat/NewSessionGuide.tsx +16 -10
- package/src/components/chat/NewSessionGuideStarterList.tsx +4 -1
- package/src/components/chat/NewSessionGuideStarterSection.tsx +2 -1
- package/src/components/chat/messages/MessageItem.tsx +19 -5
- package/src/components/chat/messages/message-action-utils.ts +11 -0
- package/src/components/chat/sender/Sender.scss +12 -0
- package/src/components/config/AppSettingsPanel.tsx +84 -30
- package/src/components/knowledge-base/components/@hooks/use-skills-cli-modal-controller.ts +157 -0
- package/src/components/knowledge-base/components/@hooks/use-skills-tab-actions.ts +63 -0
- package/src/components/knowledge-base/components/SkillsTab.tsx +40 -193
- package/src/components/sidebar/SidebarHeader.tsx +44 -26
- package/src/components/sidebar/sidebar-search-visibility.ts +18 -0
- package/src/hooks/useQueryParams.ts +91 -23
- package/src/resources/locales/en.json +12 -0
- package/src/resources/locales/zh.json +12 -0
- package/src/routes/ChatRoute.scss +50 -45
- package/src/store/index.ts +111 -3
- package/dist/assets/channel-C6LTxxLg.js +0 -1
- package/dist/assets/clone-CG6ZcokX.js +0 -1
- package/dist/assets/flowDiagram-v2-4f6560a1-CSZTI7GQ.js +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import './NewSessionGuide.scss'
|
|
2
2
|
|
|
3
|
-
import { useAtom } from 'jotai'
|
|
3
|
+
import { useAtom, useAtomValue } from 'jotai'
|
|
4
4
|
|
|
5
5
|
import type { ConversationStarterConfig } from '@vibe-forge/types'
|
|
6
6
|
|
|
7
7
|
import { MarkdownContent } from '#~/components/MarkdownContent'
|
|
8
|
-
import { showAnnouncementsAtom } from '#~/store/index.js'
|
|
8
|
+
import { showAnnouncementsAtom, showNewSessionStarterListAtom } from '#~/store/index.js'
|
|
9
9
|
|
|
10
10
|
import { NewSessionGuideStarterList } from './NewSessionGuideStarterList'
|
|
11
11
|
|
|
@@ -21,8 +21,12 @@ export function NewSessionGuide({
|
|
|
21
21
|
onApplyStarter: (starter: ConversationStarterConfig) => void
|
|
22
22
|
}) {
|
|
23
23
|
const [showAnnouncements, setShowAnnouncements] = useAtom(showAnnouncementsAtom)
|
|
24
|
+
const showNewSessionStarterList = useAtomValue(showNewSessionStarterListAtom)
|
|
24
25
|
const visibleAnnouncements = showAnnouncements ? announcements : []
|
|
25
|
-
const
|
|
26
|
+
const visibleStartupPresets = showNewSessionStarterList ? startupPresets : []
|
|
27
|
+
const visibleBuiltinActions = showNewSessionStarterList ? builtinActions : []
|
|
28
|
+
const hasStarterList = visibleStartupPresets.length > 0 || visibleBuiltinActions.length > 0
|
|
29
|
+
const hasGuideContent = visibleAnnouncements.length > 0 || hasStarterList
|
|
26
30
|
|
|
27
31
|
if (!hasGuideContent) {
|
|
28
32
|
return null
|
|
@@ -51,13 +55,15 @@ export function NewSessionGuide({
|
|
|
51
55
|
</button>
|
|
52
56
|
</div>
|
|
53
57
|
)}
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
{hasStarterList && (
|
|
59
|
+
<div className='new-session-guide__main'>
|
|
60
|
+
<NewSessionGuideStarterList
|
|
61
|
+
startupPresets={visibleStartupPresets}
|
|
62
|
+
builtinActions={visibleBuiltinActions}
|
|
63
|
+
onApplyStarter={onApplyStarter}
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
61
67
|
</div>
|
|
62
68
|
)
|
|
63
69
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { ConversationStarterConfig } from '@vibe-forge/types'
|
|
2
1
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
3
2
|
import { useTranslation } from 'react-i18next'
|
|
3
|
+
|
|
4
|
+
import type { ConversationStarterConfig } from '@vibe-forge/types'
|
|
5
|
+
|
|
4
6
|
import { NewSessionGuideStarterSection } from './NewSessionGuideStarterSection'
|
|
5
7
|
import { buildConversationStarterListItems, partitionConversationStarterListItems } from './new-session-guide-config'
|
|
8
|
+
|
|
6
9
|
const FAVORITE_STARTER_STORAGE_KEY = 'vf_new_session_guide_favorites'
|
|
7
10
|
const RECENT_STARTER_STORAGE_KEY = 'vf_new_session_guide_recent'
|
|
8
11
|
const DEFAULT_VISIBLE_STARTER_COUNT = 8
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { ConversationStarterConfig } from '@vibe-forge/types'
|
|
2
1
|
import { Tooltip } from 'antd'
|
|
3
2
|
import { useTranslation } from 'react-i18next'
|
|
4
3
|
|
|
4
|
+
import type { ConversationStarterConfig } from '@vibe-forge/types'
|
|
5
|
+
|
|
5
6
|
import type { ConversationStarterListItem } from './new-session-guide-config'
|
|
6
7
|
import { normalizeConversationStarterMode } from './new-session-guide-config'
|
|
7
8
|
|
|
@@ -29,6 +29,7 @@ interface MessageItemProps {
|
|
|
29
29
|
sessionId?: string
|
|
30
30
|
sessionInfo?: SessionInfo | null
|
|
31
31
|
isSessionBusy: boolean
|
|
32
|
+
hideActionButtons: boolean
|
|
32
33
|
isEditing: boolean
|
|
33
34
|
isCompactLayout: boolean
|
|
34
35
|
isTouchInteraction: boolean
|
|
@@ -54,6 +55,7 @@ function MessageItemComponent({
|
|
|
54
55
|
sessionId,
|
|
55
56
|
sessionInfo,
|
|
56
57
|
isSessionBusy,
|
|
58
|
+
hideActionButtons,
|
|
57
59
|
isEditing,
|
|
58
60
|
isCompactLayout,
|
|
59
61
|
isTouchInteraction,
|
|
@@ -83,9 +85,11 @@ function MessageItemComponent({
|
|
|
83
85
|
const canRecall = isPersistedMessage && !isSessionBusy && isUser
|
|
84
86
|
const canFork = isPersistedMessage && !isSessionBusy && isUser
|
|
85
87
|
const canCopy = copyableText != null
|
|
86
|
-
const shouldShowAssistantActions = !isUser && showAssistantActions
|
|
88
|
+
const shouldShowAssistantActions = !hideActionButtons && !isUser && showAssistantActions
|
|
87
89
|
const showCompactActionMenu = isCompactLayout || isTouchInteraction
|
|
88
|
-
const shouldShowCompactActionMenu =
|
|
90
|
+
const shouldShowCompactActionMenu = !hideActionButtons &&
|
|
91
|
+
showCompactActionMenu &&
|
|
92
|
+
(isUser || shouldShowAssistantActions)
|
|
89
93
|
|
|
90
94
|
useEffect(() => {
|
|
91
95
|
setIsSubmitting(false)
|
|
@@ -115,6 +119,15 @@ function MessageItemComponent({
|
|
|
115
119
|
}
|
|
116
120
|
}, [isActionsVisible])
|
|
117
121
|
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!hideActionButtons) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setIsActionsVisible(false)
|
|
128
|
+
setPendingConfirmAction(null)
|
|
129
|
+
}, [hideActionButtons])
|
|
130
|
+
|
|
118
131
|
useEffect(() => {
|
|
119
132
|
if (pendingConfirmAction == null) {
|
|
120
133
|
return
|
|
@@ -277,7 +290,7 @@ function MessageItemComponent({
|
|
|
277
290
|
}
|
|
278
291
|
|
|
279
292
|
const handleActionsPointerEnter = () => {
|
|
280
|
-
if (!isUser || isEditing) {
|
|
293
|
+
if (!isUser || isEditing || hideActionButtons) {
|
|
281
294
|
return
|
|
282
295
|
}
|
|
283
296
|
|
|
@@ -297,7 +310,7 @@ function MessageItemComponent({
|
|
|
297
310
|
}
|
|
298
311
|
|
|
299
312
|
const handleActionsPointerLeave = () => {
|
|
300
|
-
if (!isUser) {
|
|
313
|
+
if (!isUser || hideActionButtons) {
|
|
301
314
|
return
|
|
302
315
|
}
|
|
303
316
|
|
|
@@ -448,7 +461,7 @@ function MessageItemComponent({
|
|
|
448
461
|
onPointerLeave={handleActionsPointerLeave}
|
|
449
462
|
>
|
|
450
463
|
<div className={`message-body-container ${isEditing ? 'is-editing' : ''}`}>
|
|
451
|
-
{isUser && !isEditing && !showCompactActionMenu && (
|
|
464
|
+
{isUser && !isEditing && !showCompactActionMenu && !hideActionButtons && (
|
|
452
465
|
<div className='message-side-actions'>
|
|
453
466
|
{actionButtons}
|
|
454
467
|
</div>
|
|
@@ -496,6 +509,7 @@ const areMessageItemPropsEqual = (prev: MessageItemProps, next: MessageItemProps
|
|
|
496
509
|
prev.isFirstInGroup === next.isFirstInGroup &&
|
|
497
510
|
prev.isTargeted === next.isTargeted &&
|
|
498
511
|
prev.isSessionBusy === next.isSessionBusy &&
|
|
512
|
+
prev.hideActionButtons === next.hideActionButtons &&
|
|
499
513
|
prev.isEditing === next.isEditing &&
|
|
500
514
|
prev.isCompactLayout === next.isCompactLayout &&
|
|
501
515
|
prev.isTouchInteraction === next.isTouchInteraction &&
|
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import type { ChatRenderItem } from './message-utils'
|
|
2
2
|
|
|
3
|
+
export function getLastMessageAnchorId(renderItems: ChatRenderItem[]) {
|
|
4
|
+
for (let index = renderItems.length - 1; index >= 0; index -= 1) {
|
|
5
|
+
const item = renderItems[index]
|
|
6
|
+
if (item?.type === 'message') {
|
|
7
|
+
return item.anchorId
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return null
|
|
12
|
+
}
|
|
13
|
+
|
|
3
14
|
export function getLastAssistantActionAnchorId(renderItems: ChatRenderItem[]) {
|
|
4
15
|
const lastItem = renderItems[renderItems.length - 1]
|
|
5
16
|
if (lastItem?.type === 'tool-group') {
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
.sender-container {
|
|
2
|
+
flex: 0;
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
gap: 8px;
|
|
6
|
+
min-width: 0;
|
|
7
|
+
justify-content: flex-end;
|
|
8
|
+
transition:
|
|
9
|
+
flex .4s cubic-bezier(.4, 0, .2, 1),
|
|
10
|
+
padding-bottom .4s cubic-bezier(.4, 0, .2, 1);
|
|
11
|
+
}
|
|
12
|
+
|
|
1
13
|
.chat-input-wrapper {
|
|
2
14
|
width: 100%;
|
|
3
15
|
margin: 0;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import '../ConfigView.scss'
|
|
2
2
|
|
|
3
|
-
import { Select, Switch } from 'antd'
|
|
3
|
+
import { InputNumber, Select, Switch } from 'antd'
|
|
4
4
|
import { useAtom } from 'jotai'
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
6
|
+
import {
|
|
7
|
+
senderHeaderDisplayAtom,
|
|
8
|
+
sessionListSearchThresholdAtom,
|
|
9
|
+
showAnnouncementsAtom,
|
|
10
|
+
showNewSessionStarterListAtom,
|
|
11
|
+
themeAtom
|
|
12
|
+
} from '#~/store/index.js'
|
|
13
|
+
import type { SenderHeaderDisplayMode, ThemeMode } from '#~/store/index.js'
|
|
8
14
|
|
|
9
15
|
import { FieldRow } from './ConfigFieldRow'
|
|
10
16
|
import type { TranslationFn } from './configUtils'
|
|
11
17
|
|
|
12
18
|
export function AppSettingsPanel({ t }: { t: TranslationFn }) {
|
|
19
|
+
const [themeMode, setThemeMode] = useAtom(themeAtom)
|
|
13
20
|
const [showAnnouncements, setShowAnnouncements] = useAtom(showAnnouncementsAtom)
|
|
21
|
+
const [showNewSessionStarterList, setShowNewSessionStarterList] = useAtom(showNewSessionStarterListAtom)
|
|
14
22
|
const [senderHeaderDisplay, setSenderHeaderDisplay] = useAtom(senderHeaderDisplayAtom)
|
|
23
|
+
const [sessionListSearchThreshold, setSessionListSearchThreshold] = useAtom(sessionListSearchThresholdAtom)
|
|
15
24
|
|
|
16
25
|
return (
|
|
17
26
|
<div className='config-view__editor-wrap'>
|
|
@@ -22,33 +31,78 @@ export function AppSettingsPanel({ t }: { t: TranslationFn }) {
|
|
|
22
31
|
</div>
|
|
23
32
|
</div>
|
|
24
33
|
<div className='config-view__card'>
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
34
|
+
<div className='config-view__app-settings-list'>
|
|
35
|
+
<FieldRow
|
|
36
|
+
title={t('config.appSettings.themeMode.label')}
|
|
37
|
+
description={t('config.appSettings.themeMode.desc')}
|
|
38
|
+
icon='dark_mode'
|
|
39
|
+
>
|
|
40
|
+
<Select<ThemeMode>
|
|
41
|
+
value={themeMode}
|
|
42
|
+
onChange={setThemeMode}
|
|
43
|
+
options={[
|
|
44
|
+
{
|
|
45
|
+
value: 'light',
|
|
46
|
+
label: t('common.themeLight')
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
value: 'dark',
|
|
50
|
+
label: t('common.themeDark')
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
value: 'system',
|
|
54
|
+
label: t('common.themeSystem')
|
|
55
|
+
}
|
|
56
|
+
]}
|
|
57
|
+
/>
|
|
58
|
+
</FieldRow>
|
|
59
|
+
<FieldRow
|
|
60
|
+
title={t('config.appSettings.senderHeaderDisplay.label')}
|
|
61
|
+
description={t('config.appSettings.senderHeaderDisplay.desc')}
|
|
62
|
+
icon='unfold_more'
|
|
63
|
+
>
|
|
64
|
+
<Select<SenderHeaderDisplayMode>
|
|
65
|
+
value={senderHeaderDisplay}
|
|
66
|
+
onChange={setSenderHeaderDisplay}
|
|
67
|
+
options={[
|
|
68
|
+
{
|
|
69
|
+
value: 'expanded',
|
|
70
|
+
label: t('config.appSettings.senderHeaderDisplay.expanded')
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
value: 'collapsed',
|
|
74
|
+
label: t('config.appSettings.senderHeaderDisplay.collapsed')
|
|
75
|
+
}
|
|
76
|
+
]}
|
|
77
|
+
/>
|
|
78
|
+
</FieldRow>
|
|
79
|
+
<FieldRow
|
|
80
|
+
title={t('config.appSettings.sessionListSearchThreshold.label')}
|
|
81
|
+
description={t('config.appSettings.sessionListSearchThreshold.desc')}
|
|
82
|
+
icon='search'
|
|
83
|
+
>
|
|
84
|
+
<InputNumber
|
|
85
|
+
min={0}
|
|
86
|
+
precision={0}
|
|
87
|
+
value={sessionListSearchThreshold}
|
|
88
|
+
onChange={(value) => setSessionListSearchThreshold(value ?? 0)}
|
|
89
|
+
/>
|
|
90
|
+
</FieldRow>
|
|
91
|
+
<FieldRow
|
|
92
|
+
title={t('config.appSettings.announcements.label')}
|
|
93
|
+
description={t('config.appSettings.announcements.desc')}
|
|
94
|
+
icon='campaign'
|
|
95
|
+
>
|
|
96
|
+
<Switch checked={showAnnouncements} onChange={setShowAnnouncements} />
|
|
97
|
+
</FieldRow>
|
|
98
|
+
<FieldRow
|
|
99
|
+
title={t('config.appSettings.recommendedActions.label')}
|
|
100
|
+
description={t('config.appSettings.recommendedActions.desc')}
|
|
101
|
+
icon='tips_and_updates'
|
|
102
|
+
>
|
|
103
|
+
<Switch checked={showNewSessionStarterList} onChange={setShowNewSessionStarterList} />
|
|
104
|
+
</FieldRow>
|
|
105
|
+
</div>
|
|
52
106
|
</div>
|
|
53
107
|
</div>
|
|
54
108
|
)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Form } from 'antd'
|
|
4
|
+
import type { MessageInstance } from 'antd/es/message/interface'
|
|
5
|
+
import type { TFunction } from 'i18next'
|
|
6
|
+
|
|
7
|
+
import type { SkillHubItem } from '#~/api.js'
|
|
8
|
+
import { getApiErrorMessage, installSkillsCliItem, searchSkillsCli } from '#~/api.js'
|
|
9
|
+
import type { SkillsCliFormValues } from '../SkillsCliModal'
|
|
10
|
+
|
|
11
|
+
const SKILLS_CLI_INITIAL_LIMIT = 100
|
|
12
|
+
const SKILLS_CLI_LIMIT_STEP = 100
|
|
13
|
+
const SKILLS_CLI_MAX_LIMIT = 500
|
|
14
|
+
|
|
15
|
+
const trimOptionalString = (value: string | undefined) => {
|
|
16
|
+
const normalizedValue = value?.trim()
|
|
17
|
+
return normalizedValue == null || normalizedValue === '' ? undefined : normalizedValue
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const useSkillsCliModalController = (params: {
|
|
21
|
+
message: MessageInstance
|
|
22
|
+
mutateSkills: () => Promise<unknown>
|
|
23
|
+
t: TFunction
|
|
24
|
+
}) => {
|
|
25
|
+
const [open, setOpen] = React.useState(false)
|
|
26
|
+
const [searching, setSearching] = React.useState(false)
|
|
27
|
+
const [loadingMore, setLoadingMore] = React.useState(false)
|
|
28
|
+
const [installingId, setInstallingId] = React.useState<string | null>(null)
|
|
29
|
+
const [items, setItems] = React.useState<SkillHubItem[]>([])
|
|
30
|
+
const [error, setError] = React.useState<string | null>(null)
|
|
31
|
+
const [hasMore, setHasMore] = React.useState(false)
|
|
32
|
+
const [hasSearched, setHasSearched] = React.useState(false)
|
|
33
|
+
const [limit, setLimit] = React.useState(SKILLS_CLI_INITIAL_LIMIT)
|
|
34
|
+
const [resetKey, setResetKey] = React.useState('')
|
|
35
|
+
const [form] = Form.useForm<SkillsCliFormValues>()
|
|
36
|
+
|
|
37
|
+
const resolveRequest = React.useCallback(async () => {
|
|
38
|
+
try {
|
|
39
|
+
await form.validateFields(['source'])
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const values = form.getFieldsValue()
|
|
45
|
+
const source = values.source.trim()
|
|
46
|
+
return {
|
|
47
|
+
source,
|
|
48
|
+
query: trimOptionalString(values.query),
|
|
49
|
+
registry: trimOptionalString(values.registry)
|
|
50
|
+
}
|
|
51
|
+
}, [form])
|
|
52
|
+
|
|
53
|
+
const runSearch = React.useCallback(async (nextLimit: number) => {
|
|
54
|
+
const request = await resolveRequest()
|
|
55
|
+
if (request == null) return false
|
|
56
|
+
|
|
57
|
+
const result = await searchSkillsCli({
|
|
58
|
+
limit: nextLimit,
|
|
59
|
+
source: request.source,
|
|
60
|
+
...(request.query != null ? { query: request.query } : {}),
|
|
61
|
+
...(request.registry != null ? { registry: request.registry } : {})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
setHasSearched(true)
|
|
65
|
+
setLimit(nextLimit)
|
|
66
|
+
setItems(result.items)
|
|
67
|
+
setError(result.error ?? null)
|
|
68
|
+
setHasMore(result.hasMore === true && nextLimit < SKILLS_CLI_MAX_LIMIT)
|
|
69
|
+
setResetKey([request.source, request.query ?? '', request.registry ?? '', String(nextLimit)].join('\0'))
|
|
70
|
+
return true
|
|
71
|
+
}, [resolveRequest])
|
|
72
|
+
|
|
73
|
+
const handleSearch = React.useCallback(async () => {
|
|
74
|
+
setSearching(true)
|
|
75
|
+
setLoadingMore(false)
|
|
76
|
+
try {
|
|
77
|
+
await runSearch(SKILLS_CLI_INITIAL_LIMIT)
|
|
78
|
+
} catch (error) {
|
|
79
|
+
void params.message.error(getApiErrorMessage(error, params.t('knowledge.skills.skillsCliSearchFailed')))
|
|
80
|
+
} finally {
|
|
81
|
+
setSearching(false)
|
|
82
|
+
}
|
|
83
|
+
}, [params, runSearch])
|
|
84
|
+
|
|
85
|
+
const handleLoadMore = React.useCallback(async () => {
|
|
86
|
+
const nextLimit = Math.min(limit + SKILLS_CLI_LIMIT_STEP, SKILLS_CLI_MAX_LIMIT)
|
|
87
|
+
if (nextLimit === limit) return
|
|
88
|
+
|
|
89
|
+
setLoadingMore(true)
|
|
90
|
+
try {
|
|
91
|
+
await runSearch(nextLimit)
|
|
92
|
+
} catch (error) {
|
|
93
|
+
void params.message.error(getApiErrorMessage(error, params.t('knowledge.skills.skillsCliSearchFailed')))
|
|
94
|
+
} finally {
|
|
95
|
+
setLoadingMore(false)
|
|
96
|
+
}
|
|
97
|
+
}, [limit, params, runSearch])
|
|
98
|
+
|
|
99
|
+
const handleInstall = React.useCallback(async (item: SkillHubItem) => {
|
|
100
|
+
const request = await resolveRequest()
|
|
101
|
+
if (request == null) return
|
|
102
|
+
|
|
103
|
+
setInstallingId(item.id)
|
|
104
|
+
try {
|
|
105
|
+
await installSkillsCliItem({
|
|
106
|
+
source: request.source,
|
|
107
|
+
skill: item.installRef ?? item.name,
|
|
108
|
+
force: item.installed,
|
|
109
|
+
...(request.registry != null ? { registry: request.registry } : {})
|
|
110
|
+
})
|
|
111
|
+
await params.mutateSkills()
|
|
112
|
+
try {
|
|
113
|
+
await runSearch(limit)
|
|
114
|
+
} catch {
|
|
115
|
+
// Keep the install success state even if refreshing the source list fails afterwards.
|
|
116
|
+
}
|
|
117
|
+
void params.message.success(params.t('knowledge.skills.installSuccess'))
|
|
118
|
+
} catch (error) {
|
|
119
|
+
void params.message.error(getApiErrorMessage(error, params.t('knowledge.skills.installFailed')))
|
|
120
|
+
} finally {
|
|
121
|
+
setInstallingId(null)
|
|
122
|
+
}
|
|
123
|
+
}, [limit, params, resolveRequest, runSearch])
|
|
124
|
+
|
|
125
|
+
const handleClose = React.useCallback(() => {
|
|
126
|
+
setOpen(false)
|
|
127
|
+
setSearching(false)
|
|
128
|
+
setLoadingMore(false)
|
|
129
|
+
setInstallingId(null)
|
|
130
|
+
setItems([])
|
|
131
|
+
setError(null)
|
|
132
|
+
setHasMore(false)
|
|
133
|
+
setHasSearched(false)
|
|
134
|
+
setLimit(SKILLS_CLI_INITIAL_LIMIT)
|
|
135
|
+
setResetKey('')
|
|
136
|
+
form.resetFields()
|
|
137
|
+
}, [form])
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
form,
|
|
141
|
+
hasMore,
|
|
142
|
+
hasSearched,
|
|
143
|
+
installingId,
|
|
144
|
+
items,
|
|
145
|
+
limit,
|
|
146
|
+
loadingMore,
|
|
147
|
+
open,
|
|
148
|
+
resetKey,
|
|
149
|
+
searching,
|
|
150
|
+
searchError: error,
|
|
151
|
+
setOpen,
|
|
152
|
+
handleClose,
|
|
153
|
+
handleInstall,
|
|
154
|
+
handleLoadMore,
|
|
155
|
+
handleSearch
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import type { MessageInstance } from 'antd/es/message/interface'
|
|
4
|
+
import type { TFunction } from 'i18next'
|
|
5
|
+
|
|
6
|
+
import type { SkillHubItem } from '#~/api.js'
|
|
7
|
+
import { getApiErrorMessage, importSkillArchive, installSkillHubItem } from '#~/api.js'
|
|
8
|
+
|
|
9
|
+
export const useSkillsTabActions = (params: {
|
|
10
|
+
marketMutate: () => Promise<unknown>
|
|
11
|
+
message: MessageInstance
|
|
12
|
+
mutateConfig: () => Promise<unknown>
|
|
13
|
+
mutateSkills: () => Promise<unknown>
|
|
14
|
+
onRefresh: () => void | Promise<void>
|
|
15
|
+
t: TFunction
|
|
16
|
+
}) => {
|
|
17
|
+
const importInputRef = React.useRef<HTMLInputElement | null>(null)
|
|
18
|
+
const [installingId, setInstallingId] = React.useState<string | null>(null)
|
|
19
|
+
const [importing, setImporting] = React.useState(false)
|
|
20
|
+
|
|
21
|
+
const handleRefresh = React.useCallback(async () => {
|
|
22
|
+
await Promise.all([params.mutateSkills(), params.marketMutate(), params.mutateConfig(), params.onRefresh()])
|
|
23
|
+
}, [params])
|
|
24
|
+
|
|
25
|
+
const handleInstall = React.useCallback(async (item: SkillHubItem) => {
|
|
26
|
+
setInstallingId(item.id)
|
|
27
|
+
try {
|
|
28
|
+
await installSkillHubItem({
|
|
29
|
+
registry: item.registry,
|
|
30
|
+
plugin: item.installRef ?? item.name,
|
|
31
|
+
force: item.installed
|
|
32
|
+
})
|
|
33
|
+
await Promise.all([params.marketMutate(), params.mutateSkills()])
|
|
34
|
+
void params.message.success(params.t('knowledge.skills.installSuccess'))
|
|
35
|
+
} catch (error) {
|
|
36
|
+
void params.message.error(getApiErrorMessage(error, params.t('knowledge.skills.installFailed')))
|
|
37
|
+
} finally {
|
|
38
|
+
setInstallingId(null)
|
|
39
|
+
}
|
|
40
|
+
}, [params])
|
|
41
|
+
|
|
42
|
+
const handleImportArchive = React.useCallback(async (file: File) => {
|
|
43
|
+
setImporting(true)
|
|
44
|
+
try {
|
|
45
|
+
const result = await importSkillArchive(file)
|
|
46
|
+
await Promise.all([params.mutateSkills(), params.onRefresh()])
|
|
47
|
+
void params.message.success(params.t('knowledge.skills.importSuccess', { count: result.fileCount }))
|
|
48
|
+
} catch (error) {
|
|
49
|
+
void params.message.error(getApiErrorMessage(error, params.t('knowledge.skills.importFailed')))
|
|
50
|
+
} finally {
|
|
51
|
+
setImporting(false)
|
|
52
|
+
}
|
|
53
|
+
}, [params])
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
importInputRef,
|
|
57
|
+
importing,
|
|
58
|
+
installingId,
|
|
59
|
+
handleImportArchive,
|
|
60
|
+
handleInstall,
|
|
61
|
+
handleRefresh
|
|
62
|
+
}
|
|
63
|
+
}
|