@shendeguize/dsh-agent-sidecar 0.1.0 → 0.2.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/README.md +78 -11
- package/lib/client.js +4708 -2932
- package/lib/client.js.map +1 -1
- package/package.json +3 -1
- package/src/client/analysis/AnalysisPanel.tsx +19 -27
- package/src/client/analysis/analysis.module.css +36 -97
- package/src/client/board/Board.tsx +255 -48
- package/src/client/board/board.module.css +113 -92
- package/src/client/board/logic.ts +99 -21
- package/src/client/board/project-view-logic.ts +27 -34
- package/src/client/board/project-view.module.css +46 -83
- package/src/client/board/project-view.tsx +50 -8
- package/src/client/board/strings.ts +70 -85
- package/src/client/commands.ts +30 -15
- package/src/client/controller.ts +27 -7
- package/src/client/detail/SessionDetail.tsx +234 -37
- package/src/client/detail/detail.module.css +100 -153
- package/src/client/detail/logic.ts +220 -29
- package/src/client/detail/strings.ts +66 -77
- package/src/client/detail-glue.ts +33 -0
- package/src/client/detail-view.module.css +43 -35
- package/src/client/detail-view.tsx +138 -118
- package/src/client/dsh-tools/LineageTree.tsx +22 -13
- package/src/client/dsh-tools/SearchPanel.tsx +18 -11
- package/src/client/dsh-tools/dsh-tools.module.css +53 -140
- package/src/client/dsh-tools/strings.ts +42 -77
- package/src/client/index.ts +285 -124
- package/src/client/inject/InjectPanel.tsx +81 -61
- package/src/client/inject/inject.module.css +97 -197
- package/src/client/inject/logic.ts +38 -0
- package/src/client/lifecycle/handoff.ts +83 -0
- package/src/client/locales/en.ts +91 -4
- package/src/client/locales/host.ts +131 -0
- package/src/client/locales/index.ts +196 -8
- package/src/client/locales/react.ts +10 -0
- package/src/client/locales/view.ts +38 -0
- package/src/client/locales/zh.ts +200 -125
- package/src/client/mount.tsx +75 -41
- package/src/client/navigation/CenterOverlay.tsx +40 -0
- package/src/client/navigation/center-overlay.module.css +51 -0
- package/src/client/navigation/center.ts +45 -0
- package/src/client/navigation/modal-isolation.ts +152 -0
- package/src/client/navigation/modal-surface-anchor.ts +72 -0
- package/src/client/navigation/sidebar-entry.module.css +73 -0
- package/src/client/navigation/sidebar-entry.ts +309 -0
- package/src/client/primitives/StaticPill.tsx +21 -0
- package/src/client/settings-card.module.css +35 -119
- package/src/client/settings-card.tsx +31 -153
- package/src/client/settings-fields.tsx +131 -0
- package/src/client/sidebar/SidebarTab.tsx +156 -0
- package/src/client/sidebar/model.ts +65 -0
- package/src/client/sidebar/sidebar-tab.module.css +118 -0
- package/src/client/sidebar-tab.tsx +46 -320
- package/src/client/theme/agsc.module.css +31 -0
- package/src/client/theme/parts.ts +56 -0
- package/src/client/ui-integration.ts +86 -0
- package/src/client/widget.tsx +42 -16
- package/src/client/inject/overlay.module.css +0 -22
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Session-detail container
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* stores:
|
|
2
|
+
* Session-detail React container. It composes the timeline, lineage,
|
|
3
|
+
* search, analysis, and optional injection surfaces over stores supplied
|
|
4
|
+
* by {@link DetailUiPort}.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* AI 分析 (AnalysisPanel over AnalysisStore; disabled with an honest
|
|
11
|
-
* hint while `analysis.enabled` is off);
|
|
12
|
-
* - dsh 会话专属区: LineageTree ← the DetailStore lineage slice (non-dsh
|
|
13
|
-
* sessions degrade client-side, no dialing) and SearchPanel ←
|
|
14
|
-
* SearchStore (full-text or filter-only degradation; a result click
|
|
15
|
-
* navigates the detail view to that session).
|
|
16
|
-
*
|
|
17
|
-
* The stores live in component state (one set per opened session id — the
|
|
18
|
-
* owner keys this component by session id) and are disposed on unmount.
|
|
19
|
-
* SSE coupling: the controller's subscribe seam notifies the DetailStore
|
|
20
|
-
* on every state frame (header refresh + listen-mode refetch trigger).
|
|
6
|
+
* Stores are scoped to one opened session and disposed on unmount. Each
|
|
7
|
+
* controller state frame refreshes the header hint and drives the
|
|
8
|
+
* detail store's bounded listen-mode refetch.
|
|
21
9
|
*
|
|
22
10
|
* @module
|
|
23
11
|
*/
|
|
24
12
|
|
|
13
|
+
import { Button, Modal } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
25
14
|
import { useEffect, useState, useSyncExternalStore } from 'react'
|
|
26
15
|
import type { ReactElement } from 'react'
|
|
27
16
|
import { SessionDetail } from './detail/SessionDetail.tsx'
|
|
@@ -29,58 +18,28 @@ import { LineageTree } from './dsh-tools/LineageTree.tsx'
|
|
|
29
18
|
import { SearchPanel } from './dsh-tools/SearchPanel.tsx'
|
|
30
19
|
import { AnalysisPanel } from './analysis/AnalysisPanel.tsx'
|
|
31
20
|
import { InjectPanel } from './inject/InjectPanel.tsx'
|
|
32
|
-
import {
|
|
33
|
-
import { SearchStore } from './search-glue.ts'
|
|
34
|
-
import { AnalysisStore } from './analysis-glue.ts'
|
|
35
|
-
import { ProjectsStore } from './project-glue.ts'
|
|
21
|
+
import { findCardHint, type DetailHeaderHint } from './detail-glue.ts'
|
|
36
22
|
import type { SidecarController } from './controller.ts'
|
|
37
|
-
import
|
|
23
|
+
import { isDeliveredResult } from './inject/logic.ts'
|
|
38
24
|
import type { InjectActions } from './inject-glue.ts'
|
|
25
|
+
import type {
|
|
26
|
+
AnalysisStorePort,
|
|
27
|
+
DetailStorePort,
|
|
28
|
+
DetailUiPort,
|
|
29
|
+
SearchStorePort,
|
|
30
|
+
} from './ui-integration.ts'
|
|
39
31
|
import { t } from './locales/index.ts'
|
|
32
|
+
import { surfaceProps } from './theme/parts.ts'
|
|
40
33
|
import css from './detail-view.module.css'
|
|
41
|
-
import overlay from './inject/overlay.module.css'
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
* Everything the integrated M3 surfaces need from the entry (index.ts
|
|
45
|
-
* builds one; mount.tsx and this container share it). Store factories are
|
|
46
|
-
* seams so tests/materialization can substitute injected transports.
|
|
47
|
-
*/
|
|
48
|
-
export interface SidecarUiIntegration {
|
|
49
|
-
/** M2 injection wiring; absent = injection not assembled (entry hidden). */
|
|
50
|
-
inject?: {
|
|
51
|
-
actions: InjectActions
|
|
52
|
-
getDefaultMode: () => InjectMode
|
|
53
|
-
}
|
|
54
|
-
/** Live `analysis.enabled` reader (settings scope box; default false). */
|
|
55
|
-
getAnalysisEnabled: () => boolean
|
|
56
|
-
createDetailStore: (sessionId: string, hint: DetailHeaderHint | null) => DetailStore
|
|
57
|
-
createSearchStore: () => SearchStore
|
|
58
|
-
createAnalysisStore: () => AnalysisStore
|
|
59
|
-
createProjectsStore: () => ProjectsStore
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Default integration over the real transports (analysis off until read). */
|
|
63
|
-
export function createDefaultIntegration(
|
|
64
|
-
base: Omit<
|
|
65
|
-
SidecarUiIntegration,
|
|
66
|
-
'createDetailStore' | 'createSearchStore' | 'createAnalysisStore' | 'createProjectsStore'
|
|
67
|
-
>,
|
|
68
|
-
): SidecarUiIntegration {
|
|
69
|
-
return {
|
|
70
|
-
...base,
|
|
71
|
-
createDetailStore: (sessionId, hint) => new DetailStore(sessionId, { hint }),
|
|
72
|
-
createSearchStore: () => new SearchStore(),
|
|
73
|
-
createAnalysisStore: () => new AnalysisStore(),
|
|
74
|
-
createProjectsStore: () => new ProjectsStore(),
|
|
75
|
-
}
|
|
76
|
-
}
|
|
35
|
+
const ANALYSIS_DISABLED_REASON_ID = 'agent-sidecar-analysis-disabled-reason'
|
|
77
36
|
|
|
78
37
|
export interface SidecarDetailViewProps {
|
|
79
38
|
sessionId: string
|
|
80
39
|
/** Header seed from the opening surface (board card / project row). */
|
|
81
40
|
hint: DetailHeaderHint | null
|
|
82
41
|
controller: SidecarController
|
|
83
|
-
integration:
|
|
42
|
+
integration: DetailUiPort
|
|
84
43
|
onClose: () => void
|
|
85
44
|
/** Provenance/search jump: navigate the detail view to another session. */
|
|
86
45
|
onSelectSession: (sessionId: string) => void
|
|
@@ -92,11 +51,14 @@ export interface SidecarDetailViewProps {
|
|
|
92
51
|
*/
|
|
93
52
|
export function SidecarDetailView(props: SidecarDetailViewProps): ReactElement {
|
|
94
53
|
const { controller, integration, sessionId } = props
|
|
95
|
-
const [detailStore] = useState(
|
|
96
|
-
|
|
97
|
-
|
|
54
|
+
const [detailStore] = useState<DetailStorePort>(
|
|
55
|
+
() => integration.createDetailStore(sessionId, props.hint),
|
|
56
|
+
)
|
|
57
|
+
const [searchStore] = useState<SearchStorePort>(() => integration.createSearchStore())
|
|
58
|
+
const [analysisStore] = useState<AnalysisStorePort>(() => integration.createAnalysisStore())
|
|
98
59
|
const [injectOpen, setInjectOpen] = useState(false)
|
|
99
60
|
const [analysisOpen, setAnalysisOpen] = useState(false)
|
|
61
|
+
const [toolsOpen, setToolsOpen] = useState(false)
|
|
100
62
|
|
|
101
63
|
useEffect(() => {
|
|
102
64
|
void detailStore.open()
|
|
@@ -126,33 +88,112 @@ export function SidecarDetailView(props: SidecarDetailViewProps): ReactElement {
|
|
|
126
88
|
)
|
|
127
89
|
|
|
128
90
|
const analysisEnabled = integration.getAnalysisEnabled()
|
|
91
|
+
const analysisDisabledHint =
|
|
92
|
+
analysisEnabled ? undefined : t('detail.actions.analyzeDisabledHint')
|
|
129
93
|
const injectIntegration = props.integration.inject
|
|
130
94
|
const closeInject = (): void => { setInjectOpen(false) }
|
|
131
95
|
const title = detail.header.title.trim()
|
|
132
96
|
|
|
97
|
+
// A delivered execute refreshes the newest timeline while preserving
|
|
98
|
+
// the integration-owned two-phase flow and delivery callback.
|
|
99
|
+
const injectActions: InjectActions | undefined =
|
|
100
|
+
injectIntegration === undefined
|
|
101
|
+
? undefined
|
|
102
|
+
: {
|
|
103
|
+
onPrepare: injectIntegration.actions.onPrepare,
|
|
104
|
+
onExecute: async (req) => {
|
|
105
|
+
const result = await injectIntegration.actions.onExecute(req)
|
|
106
|
+
if (isDeliveredResult(result)) void detailStore.refreshNewest()
|
|
107
|
+
return result
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const observeReaction = (): void => {
|
|
112
|
+
if (!detailStore.getState().listening) detailStore.toggleListen()
|
|
113
|
+
closeInject()
|
|
114
|
+
}
|
|
115
|
+
|
|
133
116
|
return (
|
|
134
|
-
<div
|
|
117
|
+
<div
|
|
118
|
+
{...surfaceProps('detail', css['detailRoot'])}
|
|
119
|
+
data-testid="agent-sidecar-detail-view"
|
|
120
|
+
>
|
|
135
121
|
<div className={css['actionsRow']}>
|
|
136
122
|
{injectIntegration !== undefined && (
|
|
137
|
-
<
|
|
138
|
-
|
|
139
|
-
|
|
123
|
+
<Button
|
|
124
|
+
size="sm"
|
|
125
|
+
variant="outline"
|
|
140
126
|
onClick={() => { setInjectOpen(true) }}
|
|
141
127
|
data-testid="agent-sidecar-detail-inject"
|
|
142
128
|
>
|
|
143
129
|
{t('detail.actions.inject')}
|
|
144
|
-
</
|
|
130
|
+
</Button>
|
|
145
131
|
)}
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
132
|
+
<Button
|
|
133
|
+
size="sm"
|
|
134
|
+
variant="outline"
|
|
149
135
|
disabled={!analysisEnabled}
|
|
150
|
-
title={
|
|
136
|
+
title={analysisDisabledHint}
|
|
137
|
+
aria-describedby={analysisEnabled ? undefined : ANALYSIS_DISABLED_REASON_ID}
|
|
151
138
|
onClick={() => { setAnalysisOpen(true) }}
|
|
152
139
|
data-testid="agent-sidecar-detail-analyze"
|
|
153
140
|
>
|
|
154
141
|
{t('detail.actions.analyze')}
|
|
155
|
-
</
|
|
142
|
+
</Button>
|
|
143
|
+
{!analysisEnabled && (
|
|
144
|
+
<span
|
|
145
|
+
id={ANALYSIS_DISABLED_REASON_ID}
|
|
146
|
+
className={css['analysisDisabledReason']}
|
|
147
|
+
>
|
|
148
|
+
{analysisDisabledHint}
|
|
149
|
+
</span>
|
|
150
|
+
)}
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<div
|
|
154
|
+
{...surfaceProps('dsh-tools', css['toolsSection'])}
|
|
155
|
+
data-testid="agent-sidecar-detail-tools"
|
|
156
|
+
>
|
|
157
|
+
<Button
|
|
158
|
+
size="sm"
|
|
159
|
+
variant="ghost"
|
|
160
|
+
className={css['toolsToggle']}
|
|
161
|
+
aria-expanded={toolsOpen}
|
|
162
|
+
onClick={() => { setToolsOpen((open) => !open) }}
|
|
163
|
+
>
|
|
164
|
+
<span className={css['toolsToggleGlyph']} aria-hidden>
|
|
165
|
+
{toolsOpen ? '▾' : '▸'}
|
|
166
|
+
</span>
|
|
167
|
+
{t('detail.tools.title')}
|
|
168
|
+
<span className={css['toolsToggleGlyph']}>
|
|
169
|
+
{toolsOpen ? t('detail.tools.hide') : t('detail.tools.show')}
|
|
170
|
+
</span>
|
|
171
|
+
</Button>
|
|
172
|
+
{toolsOpen && (
|
|
173
|
+
<>
|
|
174
|
+
<LineageTree
|
|
175
|
+
trace={detail.lineage.trace}
|
|
176
|
+
available={detail.lineage.available}
|
|
177
|
+
reason={detail.lineage.reason}
|
|
178
|
+
detail={detail.lineage.detail}
|
|
179
|
+
currentSessionId={sessionId}
|
|
180
|
+
onSelectSession={props.onSelectSession}
|
|
181
|
+
loading={detail.lineage.loading}
|
|
182
|
+
error={detail.lineage.error}
|
|
183
|
+
/>
|
|
184
|
+
<SearchPanel
|
|
185
|
+
query={search.query}
|
|
186
|
+
project={search.project}
|
|
187
|
+
mode={search.mode}
|
|
188
|
+
items={search.items}
|
|
189
|
+
loading={search.loading}
|
|
190
|
+
error={search.error}
|
|
191
|
+
onQueryChange={(query) => { searchStore.setQuery(query) }}
|
|
192
|
+
onSubmit={() => { void searchStore.submit() }}
|
|
193
|
+
onSelectSession={props.onSelectSession}
|
|
194
|
+
/>
|
|
195
|
+
</>
|
|
196
|
+
)}
|
|
156
197
|
</div>
|
|
157
198
|
|
|
158
199
|
<SessionDetail
|
|
@@ -163,8 +204,10 @@ export function SidecarDetailView(props: SidecarDetailViewProps): ReactElement {
|
|
|
163
204
|
error={detail.error}
|
|
164
205
|
hasMore={detail.hasMore}
|
|
165
206
|
listening={detail.listening}
|
|
207
|
+
refreshing={detail.refreshing}
|
|
166
208
|
onLoadMore={() => { void detailStore.loadMore() }}
|
|
167
209
|
onToggleListen={() => { detailStore.toggleListen() }}
|
|
210
|
+
onRefresh={() => { void detailStore.refreshNewest() }}
|
|
168
211
|
onClose={props.onClose}
|
|
169
212
|
/>
|
|
170
213
|
|
|
@@ -181,52 +224,29 @@ export function SidecarDetailView(props: SidecarDetailViewProps): ReactElement {
|
|
|
181
224
|
/>
|
|
182
225
|
)}
|
|
183
226
|
|
|
184
|
-
|
|
185
|
-
<
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{injectIntegration !== undefined && injectOpen && (
|
|
209
|
-
<div className={overlay['backdrop']} role="presentation" onClick={closeInject}>
|
|
210
|
-
<div
|
|
211
|
-
className={overlay['dialog']}
|
|
212
|
-
role="dialog"
|
|
213
|
-
aria-modal="true"
|
|
214
|
-
onClick={(event) => { event.stopPropagation() }}
|
|
215
|
-
>
|
|
216
|
-
<InjectPanel
|
|
217
|
-
capability={{ inject: view.injectCapability }}
|
|
218
|
-
target={{
|
|
219
|
-
agent: detail.header.agent,
|
|
220
|
-
sessionId,
|
|
221
|
-
...(title !== '' ? { title } : {}),
|
|
222
|
-
}}
|
|
223
|
-
defaultMode={injectIntegration.getDefaultMode()}
|
|
224
|
-
onPrepare={injectIntegration.actions.onPrepare}
|
|
225
|
-
onExecute={injectIntegration.actions.onExecute}
|
|
226
|
-
onClose={closeInject}
|
|
227
|
-
/>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
227
|
+
{injectIntegration !== undefined && injectActions !== undefined && (
|
|
228
|
+
<Modal
|
|
229
|
+
open={injectOpen}
|
|
230
|
+
onClose={closeInject}
|
|
231
|
+
title={t('inject.title')}
|
|
232
|
+
closeLabel={t('inject.close')}
|
|
233
|
+
className={css['injectDialog']}
|
|
234
|
+
headless
|
|
235
|
+
>
|
|
236
|
+
<InjectPanel
|
|
237
|
+
capability={{ inject: view.injectCapability }}
|
|
238
|
+
target={{
|
|
239
|
+
agent: detail.header.agent,
|
|
240
|
+
sessionId,
|
|
241
|
+
...(title !== '' ? { title } : {}),
|
|
242
|
+
}}
|
|
243
|
+
defaultMode={injectIntegration.getDefaultMode()}
|
|
244
|
+
onPrepare={injectActions.onPrepare}
|
|
245
|
+
onExecute={injectActions.onExecute}
|
|
246
|
+
onClose={closeInject}
|
|
247
|
+
onObserve={observeReaction}
|
|
248
|
+
/>
|
|
249
|
+
</Modal>
|
|
230
250
|
)}
|
|
231
251
|
</div>
|
|
232
252
|
)
|
|
@@ -13,8 +13,11 @@
|
|
|
13
13
|
* ./logic.ts and is unit-tested there.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { Button, Pill } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
16
17
|
import { useState } from 'react'
|
|
17
18
|
import type { ReactElement } from 'react'
|
|
19
|
+
import { StaticPill } from '../primitives/StaticPill.tsx'
|
|
20
|
+
import { surfaceProps } from '../theme/parts.ts'
|
|
18
21
|
import css from './dsh-tools.module.css'
|
|
19
22
|
import {
|
|
20
23
|
deriveLineageView,
|
|
@@ -64,20 +67,24 @@ function TreeRow(props: {
|
|
|
64
67
|
data-role={node.role}
|
|
65
68
|
>
|
|
66
69
|
{node.hasChildren ? (
|
|
67
|
-
<
|
|
70
|
+
<Button
|
|
68
71
|
type="button"
|
|
72
|
+
size="sm"
|
|
73
|
+
variant="ghost"
|
|
69
74
|
className={css['toggle']}
|
|
70
75
|
aria-expanded={!collapsed}
|
|
71
76
|
aria-label={collapsed ? S.expand : S.collapse}
|
|
72
77
|
onClick={() => props.onToggle(node.id)}
|
|
73
78
|
>
|
|
74
79
|
{collapsed ? '▸' : '▾'}
|
|
75
|
-
</
|
|
80
|
+
</Button>
|
|
76
81
|
) : (
|
|
77
82
|
<span className={css['toggleSpacer']} aria-hidden />
|
|
78
83
|
)}
|
|
79
|
-
<
|
|
84
|
+
<Button
|
|
80
85
|
type="button"
|
|
86
|
+
size="sm"
|
|
87
|
+
variant="ghost"
|
|
81
88
|
className={css['node']}
|
|
82
89
|
data-current={node.isCurrent ? 'true' : 'false'}
|
|
83
90
|
aria-current={node.isCurrent ? 'true' : undefined}
|
|
@@ -91,17 +98,17 @@ function TreeRow(props: {
|
|
|
91
98
|
{node.shortId}
|
|
92
99
|
</span>
|
|
93
100
|
{node.isCurrent && (
|
|
94
|
-
<
|
|
101
|
+
<StaticPill className={css['nodeBadge']} data-kind="current">
|
|
95
102
|
{S.currentBadge}
|
|
96
|
-
</
|
|
103
|
+
</StaticPill>
|
|
97
104
|
)}
|
|
98
105
|
{node.live && (
|
|
99
|
-
<
|
|
106
|
+
<StaticPill className={css['nodeBadge']} data-kind="live">
|
|
100
107
|
{S.liveBadge}
|
|
101
|
-
</
|
|
108
|
+
</StaticPill>
|
|
102
109
|
)}
|
|
103
|
-
{!node.persisted && <
|
|
104
|
-
</
|
|
110
|
+
{!node.persisted && <Pill className={css['nodeBadge']}>{S.notPersistedBadge}</Pill>}
|
|
111
|
+
</Button>
|
|
105
112
|
</div>
|
|
106
113
|
)
|
|
107
114
|
}
|
|
@@ -163,17 +170,19 @@ export function LineageTree(props: LineageTreeProps): ReactElement {
|
|
|
163
170
|
})
|
|
164
171
|
|
|
165
172
|
return (
|
|
166
|
-
<section
|
|
173
|
+
<section {...surfaceProps('dsh-tools', css['panel'])} data-testid="agent-sidecar-lineage">
|
|
167
174
|
<div className={css['panelHead']}>
|
|
168
175
|
<span className={css['panelTitle']}>{S.title}</span>
|
|
169
176
|
{view.kind === 'tree' && (
|
|
170
|
-
<
|
|
177
|
+
<Pill className={css['panelCount']}>
|
|
171
178
|
{formatTemplate(S.nodeCount, { n: view.tree.nodeCount })}
|
|
172
|
-
</
|
|
179
|
+
</Pill>
|
|
173
180
|
)}
|
|
174
181
|
</div>
|
|
175
182
|
|
|
176
|
-
{view.kind === 'loading' &&
|
|
183
|
+
{view.kind === 'loading' && (
|
|
184
|
+
<div className={css['mutedLine']} role="status">{view.text}</div>
|
|
185
|
+
)}
|
|
177
186
|
|
|
178
187
|
{view.kind === 'error' && (
|
|
179
188
|
<div className={css['errorCard']} role="alert">
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
* injection); full-text hits carry them, filter hits render without.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
17
18
|
import type { ReactElement } from 'react'
|
|
19
|
+
import { StaticPill } from '../primitives/StaticPill.tsx'
|
|
20
|
+
import { surfaceProps } from '../theme/parts.ts'
|
|
18
21
|
import css from './dsh-tools.module.css'
|
|
19
22
|
import {
|
|
20
23
|
deriveSearchView,
|
|
@@ -50,8 +53,10 @@ function ResultItem(props: {
|
|
|
50
53
|
}): ReactElement {
|
|
51
54
|
const { item } = props
|
|
52
55
|
return (
|
|
53
|
-
<
|
|
56
|
+
<Button
|
|
54
57
|
type="button"
|
|
58
|
+
size="sm"
|
|
59
|
+
variant="ghost"
|
|
55
60
|
className={css['resultItem']}
|
|
56
61
|
onClick={() => props.onSelectSession(item.sessionId)}
|
|
57
62
|
data-testid="agent-sidecar-search-item"
|
|
@@ -61,9 +66,9 @@ function ResultItem(props: {
|
|
|
61
66
|
<span className={css['resultTitle']} title={item.title}>
|
|
62
67
|
{item.titleLabel}
|
|
63
68
|
</span>
|
|
64
|
-
<
|
|
69
|
+
<StaticPill className={css['matchTag']} data-kind={item.matchedBy}>
|
|
65
70
|
{item.matchedByLabel}
|
|
66
|
-
</
|
|
71
|
+
</StaticPill>
|
|
67
72
|
</span>
|
|
68
73
|
<span className={css['resultMeta']}>
|
|
69
74
|
<span className={css['resultProject']} title={item.project}>
|
|
@@ -86,7 +91,7 @@ function ResultItem(props: {
|
|
|
86
91
|
)}
|
|
87
92
|
</span>
|
|
88
93
|
)}
|
|
89
|
-
</
|
|
94
|
+
</Button>
|
|
90
95
|
)
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -102,7 +107,7 @@ export function SearchPanel(props: SearchPanelProps): ReactElement {
|
|
|
102
107
|
const project = props.project ?? null
|
|
103
108
|
|
|
104
109
|
return (
|
|
105
|
-
<section
|
|
110
|
+
<section {...surfaceProps('dsh-tools', css['panel'])} data-testid="agent-sidecar-search">
|
|
106
111
|
<div className={css['panelHead']}>
|
|
107
112
|
<span className={css['panelTitle']}>{S.title}</span>
|
|
108
113
|
</div>
|
|
@@ -114,22 +119,22 @@ export function SearchPanel(props: SearchPanelProps): ReactElement {
|
|
|
114
119
|
props.onSubmit()
|
|
115
120
|
}}
|
|
116
121
|
>
|
|
117
|
-
<
|
|
122
|
+
<Input
|
|
118
123
|
type="search"
|
|
119
124
|
className={css['searchInput']}
|
|
120
125
|
value={props.query}
|
|
121
126
|
placeholder={S.placeholder}
|
|
122
127
|
onChange={(ev) => props.onQueryChange(ev.target.value)}
|
|
123
128
|
/>
|
|
124
|
-
<
|
|
129
|
+
<Button type="submit" size="sm" variant="outline">
|
|
125
130
|
{S.submit}
|
|
126
|
-
</
|
|
131
|
+
</Button>
|
|
127
132
|
</form>
|
|
128
133
|
|
|
129
134
|
{project !== null && project !== '' && (
|
|
130
|
-
<
|
|
135
|
+
<StaticPill className={css['projectChip']} title={project}>
|
|
131
136
|
{formatTemplate(S.projectFilter, { project })}
|
|
132
|
-
</
|
|
137
|
+
</StaticPill>
|
|
133
138
|
)}
|
|
134
139
|
|
|
135
140
|
{view.notice !== null && (
|
|
@@ -142,7 +147,9 @@ export function SearchPanel(props: SearchPanelProps): ReactElement {
|
|
|
142
147
|
</div>
|
|
143
148
|
)}
|
|
144
149
|
|
|
145
|
-
{view.body === 'loading' &&
|
|
150
|
+
{view.body === 'loading' && (
|
|
151
|
+
<div className={css['mutedLine']} role="status">{view.text}</div>
|
|
152
|
+
)}
|
|
146
153
|
|
|
147
154
|
{view.body === 'error' && (
|
|
148
155
|
<div className={css['errorCard']} role="alert">
|