@nextclaw/ui 0.12.34 → 0.12.35-beta.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/CHANGELOG.md +127 -0
- package/dist/assets/{api-rASx2-N4.js → api-De1PAch0.js} +3 -3
- package/dist/assets/{book-open-BcnAiKde.js → book-open-825Pcd0z.js} +1 -1
- package/dist/assets/channels-list-page-CGjXDSD-.js +8 -0
- package/dist/assets/chat-page-RsUTK_bY.js +105 -0
- package/dist/assets/{config-split-page-CcrEUtwu.js → config-split-page-BvfxFQSb.js} +1 -1
- package/dist/assets/{createLucideIcon-DzY6wN61.js → createLucideIcon-CeNHY6BW.js} +1 -1
- package/dist/assets/desktop-update-config-B_YIv5ZJ.js +1 -0
- package/dist/assets/{dist-NBjcwlsw.js → dist-uH7tiW6E.js} +1 -1
- package/dist/assets/{doc-browser-D2pq4IHR.js → doc-browser-CDe9yz9n.js} +1 -1
- package/dist/assets/doc-browser-DwT64GuF.js +1 -0
- package/dist/assets/{doc-browser-context-BPeeiNJ_.js → doc-browser-context-CQAx_92e.js} +1 -1
- package/dist/assets/{es2015-CVAuMqlM.js → es2015-c9XYpli5.js} +1 -1
- package/dist/assets/{external-link-CpEvG65F.js → external-link-BE1deZ_c.js} +1 -1
- package/dist/assets/i18n-DnubSHoK.js +1 -0
- package/dist/assets/index-6WZ6JjaU.css +1 -0
- package/dist/assets/index-BjH_p3R6.js +103 -0
- package/dist/assets/{key-round-DUq47t0P.js → key-round-CHo3gqpO.js} +1 -1
- package/dist/assets/mcp-marketplace-page-0C214hGi.js +40 -0
- package/dist/assets/mcp-marketplace-page-amWcMncu.js +1 -0
- package/dist/assets/model-config-B8zNrkag.js +1 -0
- package/dist/assets/notice-card-DO33_LzD.js +5 -0
- package/dist/assets/play-CqlWbVNo.js +1 -0
- package/dist/assets/plus-p61tQh2-.js +1 -0
- package/dist/assets/provider-scoped-model-input-B0_r_4EY.js +1 -0
- package/dist/assets/providers-list-B6v3foi7.js +1 -0
- package/dist/assets/remote-k671rLmY.js +1 -0
- package/dist/assets/{rotate-cw-BN9yjccP.js → rotate-cw-CYUByuqo.js} +1 -1
- package/dist/assets/runtime-config-page-DgH-XJwK.js +1 -0
- package/dist/assets/{save-CO_4qf6b.js → save-B6Ga8JIq.js} +1 -1
- package/dist/assets/{search-CRtQwr-h.js → search-5kKpgNoP.js} +1 -1
- package/dist/assets/search-config-DPZ6d9y6.js +1 -0
- package/dist/assets/{secrets-config-DFbHCb9n.js → secrets-config-CrXA11bo.js} +2 -2
- package/dist/assets/{select-HI5kiwX_.js → select-C0hGOCOK.js} +1 -1
- package/dist/assets/{setting-row-B4QnghjV.js → setting-row-xsM_0nTd.js} +1 -1
- package/dist/assets/{tabs-custom-D48zdZoc.js → tabs-custom-Bnqfhe4p.js} +1 -1
- package/dist/assets/{tag-chip-BPgMfp9l.js → tag-chip-cktJJMzh.js} +1 -1
- package/dist/assets/x-DDQw-C2x.js +1 -0
- package/dist/index.html +17 -29
- package/package.json +9 -9
- package/src/app/components/layout/app-layout.test.tsx +49 -4
- package/src/app/components/layout/app-layout.tsx +4 -7
- package/src/app/components/layout/settings-entry-page.test.tsx +9 -0
- package/src/app/components/layout/sidebar.layout.test.tsx +2 -2
- package/src/app/components/layout/sidebar.tsx +3 -3
- package/src/app/configs/app-navigation.config.ts +0 -12
- package/src/app/hooks/use-app-event-consumers.ts +1 -6
- package/src/app/hooks/use-viewport-layout.ts +0 -1
- package/src/app/index.tsx +2 -10
- package/src/features/channels/components/config/channel-form.tsx +85 -47
- package/src/features/channels/components/config/weixin-channel-auth-section.test.tsx +45 -1
- package/src/features/channels/components/config/weixin-channel-auth-section.tsx +119 -1
- package/src/features/channels/hooks/use-channel-auth.ts +8 -1
- package/src/features/channels/index.ts +1 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.test.tsx +2 -2
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +2 -7
- package/src/features/chat/components/layout/chat-sidebar.test.tsx +27 -3
- package/src/features/chat/components/layout/chat-sidebar.tsx +2 -3
- package/src/features/chat/managers/chat-session-list.manager.test.ts +12 -11
- package/src/features/chat/managers/chat-session-list.manager.ts +2 -1
- package/src/features/chat/managers/chat-ui.manager.ts +2 -2
- package/src/features/chat/managers/ncp-chat-input.manager.test.ts +3 -3
- package/src/features/chat/pages/ncp-chat-page.test.ts +3 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +19 -19
- package/src/features/chat/utils/chat-session-route.utils.test.ts +14 -0
- package/src/features/chat/utils/chat-session-route.utils.ts +3 -1
- package/src/features/chat/utils/ncp-chat-runtime-availability.utils.test.ts +6 -6
- package/src/features/marketplace/components/marketplace-list-card.tsx +4 -36
- package/src/features/marketplace/components/marketplace-page-detail.test.tsx +0 -1
- package/src/features/marketplace/components/marketplace-page.test.tsx +9 -128
- package/src/features/marketplace/components/marketplace-page.tsx +16 -72
- package/src/features/marketplace/hooks/use-marketplace-curated-scene-route.ts +1 -1
- package/src/features/marketplace/hooks/use-marketplace.ts +1 -10
- package/src/features/marketplace/utils/marketplace-installed-cache.utils.test.ts +23 -21
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +12 -12
- package/src/features/system-status/managers/system-status.manager.test.ts +6 -6
- package/src/index.css +1 -1
- package/src/platforms/desktop/components/desktop-app-shell.test.tsx +29 -13
- package/src/platforms/desktop/components/desktop-app-shell.tsx +14 -16
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +2 -3
- package/src/platforms/mobile/components/mobile-app-shell.tsx +3 -1
- package/src/platforms/mobile/components/mobile-topbar.tsx +6 -5
- package/src/platforms/mobile/index.ts +1 -0
- package/src/shared/components/config/provider-form-support.ts +51 -33
- package/src/shared/components/config/provider-form.tsx +68 -35
- package/src/shared/components/config/provider-models-section.tsx +19 -4
- package/src/shared/components/model-config.test.tsx +2 -0
- package/src/shared/components/ui/button.tsx +1 -1
- package/src/shared/lib/api/channel-auth.types.ts +8 -0
- package/src/shared/lib/api/types.ts +17 -20
- package/src/shared/lib/api/utils/channel-auth.utils.ts +10 -1
- package/src/shared/lib/api/utils/marketplace.utils.ts +0 -5
- package/src/shared/lib/i18n/channel-auth.constants.ts +24 -10
- package/src/shared/lib/i18n/chat-labels.utils.ts +2 -2
- package/src/shared/lib/i18n/index.ts +2 -0
- package/src/shared/lib/i18n/marketplace.ts +0 -16
- package/src/shared/lib/provider-models/index.ts +41 -13
- package/src/shared/lib/ui-document-title/index.ts +0 -1
- package/dist/assets/app-manager-provider-CUkcbdWk.js +0 -1
- package/dist/assets/channels-list-page-ZWYqfcCe.js +0 -8
- package/dist/assets/chat-page-ACm58eUO.js +0 -1
- package/dist/assets/cpu-DKc3bQic.js +0 -3
- package/dist/assets/desktop-C-cDRCBY.js +0 -3
- package/dist/assets/desktop-update-config-DAD9JN9Q.js +0 -1
- package/dist/assets/dialog-CR80q3Rm.js +0 -5
- package/dist/assets/doc-browser-DhIkcy0-.js +0 -1
- package/dist/assets/doc-browser-fLs0TcdS.js +0 -1
- package/dist/assets/download-CMM8po31.js +0 -1
- package/dist/assets/i18n-D1144VAA.js +0 -1
- package/dist/assets/index-DV57tBmg.css +0 -1
- package/dist/assets/index-Db9Cufsy.js +0 -100
- package/dist/assets/marketplace-page-CAp_gpNe.js +0 -105
- package/dist/assets/marketplace-page-PPY5uMHK.js +0 -1
- package/dist/assets/mcp-marketplace-page-87__92b1.js +0 -1
- package/dist/assets/mcp-marketplace-page-C6HENB6V.js +0 -40
- package/dist/assets/model-config-CK3a8DWH.js +0 -1
- package/dist/assets/notice-card-CF4AJPM3.js +0 -1
- package/dist/assets/play-AqrNslHI.js +0 -1
- package/dist/assets/plus-B-YHtTNC.js +0 -1
- package/dist/assets/popover-DeAliE_4.js +0 -1
- package/dist/assets/provider-scoped-model-input-B8LJ6RCt.js +0 -1
- package/dist/assets/providers-list-BTPjqNdK.js +0 -1
- package/dist/assets/refresh-cw-CrbD8EkT.js +0 -1
- package/dist/assets/remote-DiPWGRZc.js +0 -1
- package/dist/assets/runtime-config-page-Bb9he7t8.js +0 -1
- package/dist/assets/search-config-BkbSXa9d.js +0 -1
- package/dist/assets/settings-MWL2SMyk.js +0 -1
- package/dist/assets/sparkles-BmgOD4nY.js +0 -1
- package/dist/assets/status-dot-l3kPFdq_.js +0 -1
- package/dist/assets/use-config-BFqzVF8Y.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-DXhw9W4l.js +0 -1
- package/dist/assets/x-BeyYA_h6.js +0 -1
- /package/dist/assets/{config-hints-BNfpOL4J.js → config-hints-DGfwbumO.js} +0 -0
|
@@ -10,7 +10,7 @@ import { nextclawClient } from '@/shared/lib/api';
|
|
|
10
10
|
import { useConfig, useConfigMeta, useConfigSchema, useExecuteConfigAction, useUpdateChannel } from '@/shared/hooks/use-config';
|
|
11
11
|
import type { ConfigActionManifest, ConfigUiHints } from '@/shared/lib/api';
|
|
12
12
|
import { ChannelFormFieldsSection } from '@/features/channels/components/channel-form-fields-section';
|
|
13
|
-
import { QrChannelAuthSection } from '@/features/channels/components/config/weixin-channel-auth-section';
|
|
13
|
+
import { FeishuChannelAuthSection, QrChannelAuthSection } from '@/features/channels/components/config/weixin-channel-auth-section';
|
|
14
14
|
import { buildChannelFormDefinitions, type ChannelField, type ChannelFormBlock, type ChannelFormFieldSection } from '@/features/channels/utils/channel-form-fields.utils';
|
|
15
15
|
import { ConfigSplitDetailPane, ConfigSplitEmptyPane, ConfigSplitPaneBody, ConfigSplitPaneFooter, ConfigSplitPaneHeader } from '@/shared/components/config-split-page';
|
|
16
16
|
import { hintForPath } from '@/shared/lib/config-hints';
|
|
@@ -121,7 +121,13 @@ function buildChannelApplyStatusView(channelApplyState: ChannelApplyState) {
|
|
|
121
121
|
return { className: 'text-red-600', label: `${t('channelConfigApplyFailed')}${channelApplyState.message ? `: ${channelApplyState.message}` : ''}` };
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
function ChannelFormHeader(
|
|
124
|
+
function ChannelFormHeader({
|
|
125
|
+
channelApplyStatus,
|
|
126
|
+
channelLabel,
|
|
127
|
+
channelName,
|
|
128
|
+
enabled,
|
|
129
|
+
tutorialUrl
|
|
130
|
+
}: {
|
|
125
131
|
channelApplyStatus: ReturnType<typeof buildChannelApplyStatusView>;
|
|
126
132
|
channelLabel?: string;
|
|
127
133
|
channelName: string;
|
|
@@ -134,30 +140,42 @@ function ChannelFormHeader(props: {
|
|
|
134
140
|
<div className="min-w-0">
|
|
135
141
|
<div className="flex items-center gap-3">
|
|
136
142
|
<LogoBadge
|
|
137
|
-
name={
|
|
138
|
-
src={getChannelLogo(
|
|
139
|
-
className={cn('h-9 w-9 rounded-lg border',
|
|
143
|
+
name={channelName}
|
|
144
|
+
src={getChannelLogo(channelName)}
|
|
145
|
+
className={cn('h-9 w-9 rounded-lg border', enabled ? 'border-primary/30 bg-white' : 'border-gray-200/70 bg-white')}
|
|
140
146
|
imgClassName="h-5 w-5 object-contain"
|
|
141
|
-
fallback={<span className="text-sm font-semibold uppercase text-gray-500">{
|
|
147
|
+
fallback={<span className="text-sm font-semibold uppercase text-gray-500">{channelName[0]}</span>}
|
|
142
148
|
/>
|
|
143
|
-
<h3 className="truncate text-lg font-semibold text-gray-900 capitalize">{
|
|
149
|
+
<h3 className="truncate text-lg font-semibold text-gray-900 capitalize">{channelLabel}</h3>
|
|
144
150
|
</div>
|
|
145
151
|
<p className="mt-2 text-sm text-gray-500">{t('channelsFormDescription')}</p>
|
|
146
|
-
{
|
|
147
|
-
{
|
|
148
|
-
<a href={
|
|
152
|
+
{channelApplyStatus ? <p className={cn('mt-2 text-xs font-medium', channelApplyStatus.className)}>{channelApplyStatus.label}</p> : null}
|
|
153
|
+
{tutorialUrl ? (
|
|
154
|
+
<a href={tutorialUrl} className="mt-2 inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover">
|
|
149
155
|
<BookOpen className="h-3.5 w-3.5" />
|
|
150
156
|
{t('channelsGuideTitle')}
|
|
151
157
|
</a>
|
|
152
158
|
) : null}
|
|
153
159
|
</div>
|
|
154
|
-
<StatusDot status={
|
|
160
|
+
<StatusDot status={enabled ? 'active' : 'inactive'} label={enabled ? t('statusActive') : t('statusInactive')} />
|
|
155
161
|
</div>
|
|
156
162
|
</ConfigSplitPaneHeader>
|
|
157
163
|
);
|
|
158
164
|
}
|
|
159
165
|
|
|
160
|
-
function ChannelFormBlocks(
|
|
166
|
+
function ChannelFormBlocks({
|
|
167
|
+
channelConfig,
|
|
168
|
+
channelName,
|
|
169
|
+
disabled,
|
|
170
|
+
enabled,
|
|
171
|
+
fields,
|
|
172
|
+
formData,
|
|
173
|
+
jsonDrafts,
|
|
174
|
+
layoutBlocks,
|
|
175
|
+
setJsonDrafts,
|
|
176
|
+
uiHints,
|
|
177
|
+
updateField
|
|
178
|
+
}: {
|
|
161
179
|
channelConfig: Record<string, unknown>;
|
|
162
180
|
channelName: string;
|
|
163
181
|
enabled: boolean;
|
|
@@ -172,21 +190,21 @@ function ChannelFormBlocks(props: {
|
|
|
172
190
|
}) {
|
|
173
191
|
return (
|
|
174
192
|
<>
|
|
175
|
-
{
|
|
193
|
+
{layoutBlocks.map((block, index) => {
|
|
176
194
|
if (block.type === 'fields') {
|
|
177
|
-
const blockFields = resolveFieldsForSection(
|
|
195
|
+
const blockFields = resolveFieldsForSection(fields, block.section);
|
|
178
196
|
if (blockFields.length === 0) {
|
|
179
197
|
return null;
|
|
180
198
|
}
|
|
181
199
|
const content = (
|
|
182
200
|
<ChannelFormFieldsSection
|
|
183
|
-
channelName={
|
|
201
|
+
channelName={channelName}
|
|
184
202
|
fields={blockFields}
|
|
185
|
-
formData={
|
|
186
|
-
jsonDrafts={
|
|
187
|
-
setJsonDrafts={
|
|
188
|
-
updateField={
|
|
189
|
-
uiHints={
|
|
203
|
+
formData={formData}
|
|
204
|
+
jsonDrafts={jsonDrafts}
|
|
205
|
+
setJsonDrafts={setJsonDrafts}
|
|
206
|
+
updateField={updateField}
|
|
207
|
+
uiHints={uiHints}
|
|
190
208
|
/>
|
|
191
209
|
);
|
|
192
210
|
if (!block.collapsible) {
|
|
@@ -205,14 +223,25 @@ function ChannelFormBlocks(props: {
|
|
|
205
223
|
</details>
|
|
206
224
|
);
|
|
207
225
|
}
|
|
208
|
-
|
|
226
|
+
if (block.sectionId === 'feishu-auth') {
|
|
227
|
+
return (
|
|
228
|
+
<FeishuChannelAuthSection
|
|
229
|
+
key={`${block.type}-${block.sectionId}-${index}`}
|
|
230
|
+
channelConfig={channelConfig}
|
|
231
|
+
formData={formData}
|
|
232
|
+
channelEnabled={enabled}
|
|
233
|
+
disabled={disabled}
|
|
234
|
+
/>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
return block.sectionId === 'weixin-auth' ? (
|
|
209
238
|
<QrChannelAuthSection
|
|
210
239
|
key={`${block.type}-${block.sectionId}-${index}`}
|
|
211
|
-
channelConfig={
|
|
212
|
-
formData={
|
|
213
|
-
channelName=
|
|
214
|
-
channelEnabled={
|
|
215
|
-
disabled={
|
|
240
|
+
channelConfig={channelConfig}
|
|
241
|
+
formData={formData}
|
|
242
|
+
channelName="weixin"
|
|
243
|
+
channelEnabled={enabled}
|
|
244
|
+
disabled={disabled}
|
|
216
245
|
/>
|
|
217
246
|
) : null;
|
|
218
247
|
})}
|
|
@@ -220,14 +249,23 @@ function ChannelFormBlocks(props: {
|
|
|
220
249
|
);
|
|
221
250
|
}
|
|
222
251
|
|
|
223
|
-
function ChannelFormEditor(
|
|
252
|
+
function ChannelFormEditor({
|
|
253
|
+
actions,
|
|
254
|
+
channelConfig,
|
|
255
|
+
channelLabel,
|
|
256
|
+
channelName,
|
|
257
|
+
fields,
|
|
258
|
+
layoutBlocks,
|
|
259
|
+
tutorialUrl,
|
|
260
|
+
uiHints
|
|
261
|
+
}: ChannelFormEditorProps) {
|
|
224
262
|
const updateChannel = useUpdateChannel();
|
|
225
263
|
const executeAction = useExecuteConfigAction();
|
|
226
|
-
const [formData, setFormData] = useState<Record<string, unknown>>(() => ({ ...
|
|
227
|
-
const [jsonDrafts, setJsonDrafts] = useState<Record<string, string>>(() => buildJsonDrafts(
|
|
264
|
+
const [formData, setFormData] = useState<Record<string, unknown>>(() => ({ ...channelConfig }));
|
|
265
|
+
const [jsonDrafts, setJsonDrafts] = useState<Record<string, string>>(() => buildJsonDrafts(channelConfig, fields));
|
|
228
266
|
const [runningActionId, setRunningActionId] = useState<string | null>(null);
|
|
229
|
-
const channelApplyStatus = buildChannelApplyStatusView(useChannelApplyState(
|
|
230
|
-
const enabled = typeof formData.enabled === 'boolean' ? formData.enabled : Boolean(
|
|
267
|
+
const channelApplyStatus = buildChannelApplyStatusView(useChannelApplyState(channelName));
|
|
268
|
+
const enabled = typeof formData.enabled === 'boolean' ? formData.enabled : Boolean(channelConfig.enabled);
|
|
231
269
|
const disabled = updateChannel.isPending || Boolean(runningActionId);
|
|
232
270
|
|
|
233
271
|
const updateField = (name: string, value: unknown) => setFormData((prev) => ({ ...prev, [name]: value }));
|
|
@@ -235,7 +273,7 @@ function ChannelFormEditor(props: ChannelFormEditorProps) {
|
|
|
235
273
|
const handleSubmit = (event: FormEvent) => {
|
|
236
274
|
event.preventDefault();
|
|
237
275
|
const payload: Record<string, unknown> = { ...formData };
|
|
238
|
-
for (const field of
|
|
276
|
+
for (const field of fields) {
|
|
239
277
|
if (field.type === 'password') {
|
|
240
278
|
const value = payload[field.name];
|
|
241
279
|
if (typeof value !== 'string' || value.length === 0) {
|
|
@@ -251,19 +289,19 @@ function ChannelFormEditor(props: ChannelFormEditorProps) {
|
|
|
251
289
|
}
|
|
252
290
|
}
|
|
253
291
|
}
|
|
254
|
-
updateChannel.mutate({ channel:
|
|
292
|
+
updateChannel.mutate({ channel: channelName, data: payload });
|
|
255
293
|
};
|
|
256
294
|
|
|
257
295
|
const applyActionPatchToForm = (patch?: Record<string, unknown>) => {
|
|
258
|
-
if (!isRecord(patch?.channels) || !isRecord(patch.channels[
|
|
296
|
+
if (!isRecord(patch?.channels) || !isRecord(patch.channels[channelName])) {
|
|
259
297
|
return;
|
|
260
298
|
}
|
|
261
|
-
const channelPatch = patch.channels[
|
|
299
|
+
const channelPatch = patch.channels[channelName] as Record<string, unknown>;
|
|
262
300
|
setFormData((prev) => deepMergeRecords(prev, channelPatch));
|
|
263
301
|
setJsonDrafts((prev) => {
|
|
264
302
|
const nextDrafts = { ...prev };
|
|
265
303
|
let changed = false;
|
|
266
|
-
for (const field of
|
|
304
|
+
for (const field of fields) {
|
|
267
305
|
if (field.type !== 'json' || !Object.prototype.hasOwnProperty.call(channelPatch, field.name)) {
|
|
268
306
|
continue;
|
|
269
307
|
}
|
|
@@ -277,12 +315,12 @@ function ChannelFormEditor(props: ChannelFormEditorProps) {
|
|
|
277
315
|
const handleManualAction = async (action: ConfigActionManifest) => {
|
|
278
316
|
setRunningActionId(action.id);
|
|
279
317
|
try {
|
|
280
|
-
const scope = `channels.${
|
|
318
|
+
const scope = `channels.${channelName}`;
|
|
281
319
|
let nextData = { ...formData };
|
|
282
320
|
if (action.saveBeforeRun) {
|
|
283
321
|
nextData = { ...nextData, ...(action.savePatch ?? {}) };
|
|
284
322
|
setFormData(nextData);
|
|
285
|
-
await updateChannel.mutateAsync({ channel:
|
|
323
|
+
await updateChannel.mutateAsync({ channel: channelName, data: nextData });
|
|
286
324
|
}
|
|
287
325
|
const result = await executeAction.mutateAsync({ actionId: action.id, data: { scope, draftConfig: buildScopeDraft(scope, nextData) } });
|
|
288
326
|
applyActionPatchToForm(result.patch);
|
|
@@ -301,25 +339,25 @@ function ChannelFormEditor(props: ChannelFormEditorProps) {
|
|
|
301
339
|
return (
|
|
302
340
|
<ConfigSplitDetailPane>
|
|
303
341
|
<ChannelFormHeader
|
|
304
|
-
channelName={
|
|
305
|
-
channelLabel={
|
|
342
|
+
channelName={channelName}
|
|
343
|
+
channelLabel={channelLabel}
|
|
306
344
|
enabled={enabled}
|
|
307
|
-
tutorialUrl={
|
|
345
|
+
tutorialUrl={tutorialUrl}
|
|
308
346
|
channelApplyStatus={channelApplyStatus}
|
|
309
347
|
/>
|
|
310
348
|
|
|
311
349
|
<form onSubmit={handleSubmit} className="flex min-h-0 flex-1 flex-col">
|
|
312
350
|
<ConfigSplitPaneBody className="space-y-6 px-6 py-5">
|
|
313
351
|
<ChannelFormBlocks
|
|
314
|
-
channelConfig={
|
|
315
|
-
channelName={
|
|
352
|
+
channelConfig={channelConfig}
|
|
353
|
+
channelName={channelName}
|
|
316
354
|
enabled={enabled}
|
|
317
|
-
fields={
|
|
355
|
+
fields={fields}
|
|
318
356
|
formData={formData}
|
|
319
357
|
jsonDrafts={jsonDrafts}
|
|
320
|
-
layoutBlocks={
|
|
358
|
+
layoutBlocks={layoutBlocks}
|
|
321
359
|
setJsonDrafts={setJsonDrafts}
|
|
322
|
-
uiHints={
|
|
360
|
+
uiHints={uiHints}
|
|
323
361
|
updateField={updateField}
|
|
324
362
|
disabled={disabled}
|
|
325
363
|
/>
|
|
@@ -327,7 +365,7 @@ function ChannelFormEditor(props: ChannelFormEditorProps) {
|
|
|
327
365
|
|
|
328
366
|
<ConfigSplitPaneFooter className="flex flex-wrap items-center justify-between gap-3 px-6 py-4">
|
|
329
367
|
<div className="flex flex-wrap items-center gap-2">
|
|
330
|
-
{
|
|
368
|
+
{actions.filter((action) => action.trigger === 'manual').map((action) => (
|
|
331
369
|
<Button key={action.id} type="button" onClick={() => void handleManualAction(action)} disabled={disabled} variant="secondary">
|
|
332
370
|
{runningActionId === action.id ? t('connecting') : action.title}
|
|
333
371
|
</Button>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import type * as ReactQueryModule from '@tanstack/react-query';
|
|
4
|
-
import { QrChannelAuthSection, WeixinChannelAuthSection } from '@/features/channels/components/config/weixin-channel-auth-section';
|
|
4
|
+
import { FeishuChannelAuthSection, QrChannelAuthSection, WeixinChannelAuthSection } from '@/features/channels/components/config/weixin-channel-auth-section';
|
|
5
5
|
|
|
6
6
|
const mocks = vi.hoisted(() => ({
|
|
7
7
|
startChannelAuthMutateAsync: vi.fn(),
|
|
8
8
|
pollChannelAuthMutateAsync: vi.fn(),
|
|
9
|
+
connectChannelAuthMutateAsync: vi.fn(),
|
|
9
10
|
invalidateQueries: vi.fn().mockResolvedValue(undefined)
|
|
10
11
|
}));
|
|
11
12
|
|
|
@@ -34,6 +35,10 @@ vi.mock('@/features/channels/hooks/use-channel-auth', () => ({
|
|
|
34
35
|
usePollChannelAuth: () => ({
|
|
35
36
|
mutateAsync: mocks.pollChannelAuthMutateAsync,
|
|
36
37
|
isPending: false
|
|
38
|
+
}),
|
|
39
|
+
useConnectChannelAuth: () => ({
|
|
40
|
+
mutateAsync: mocks.connectChannelAuthMutateAsync,
|
|
41
|
+
isPending: false
|
|
37
42
|
})
|
|
38
43
|
}));
|
|
39
44
|
|
|
@@ -41,6 +46,7 @@ describe('WeixinChannelAuthSection', () => {
|
|
|
41
46
|
beforeEach(() => {
|
|
42
47
|
mocks.startChannelAuthMutateAsync.mockReset();
|
|
43
48
|
mocks.pollChannelAuthMutateAsync.mockReset();
|
|
49
|
+
mocks.connectChannelAuthMutateAsync.mockReset();
|
|
44
50
|
mocks.invalidateQueries.mockClear();
|
|
45
51
|
});
|
|
46
52
|
|
|
@@ -154,4 +160,42 @@ describe('WeixinChannelAuthSection', () => {
|
|
|
154
160
|
});
|
|
155
161
|
});
|
|
156
162
|
});
|
|
163
|
+
|
|
164
|
+
it('connects an existing feishu agent with app credentials', async () => {
|
|
165
|
+
const user = userEvent.setup();
|
|
166
|
+
mocks.connectChannelAuthMutateAsync.mockResolvedValue({
|
|
167
|
+
channel: 'feishu',
|
|
168
|
+
status: 'authorized',
|
|
169
|
+
message: 'connected',
|
|
170
|
+
accountId: 'cli_existing',
|
|
171
|
+
notes: []
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
render(
|
|
175
|
+
<FeishuChannelAuthSection
|
|
176
|
+
channelConfig={{ enabled: false, domain: 'feishu' }}
|
|
177
|
+
formData={{ domain: 'feishu' }}
|
|
178
|
+
channelEnabled={false}
|
|
179
|
+
/>
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
await user.click(screen.getByRole('button', { name: 'Connect existing agent' }));
|
|
183
|
+
await user.type(screen.getByLabelText('App ID'), 'cli_existing');
|
|
184
|
+
await user.type(screen.getByLabelText('App Secret'), 'secret-existing');
|
|
185
|
+
await user.click(screen.getByRole('button', { name: 'Verify and connect' }));
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(mocks.connectChannelAuthMutateAsync).toHaveBeenCalledWith({
|
|
189
|
+
channel: 'feishu',
|
|
190
|
+
data: {
|
|
191
|
+
domain: 'feishu',
|
|
192
|
+
fields: {
|
|
193
|
+
appId: 'cli_existing',
|
|
194
|
+
appSecret: 'secret-existing'
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
expect(screen.getByRole('link', { name: 'Open Feishu agent list' }).getAttribute('href')).toBe('https://open.feishu.cn/app');
|
|
200
|
+
});
|
|
157
201
|
});
|
|
@@ -4,7 +4,11 @@ import { toDataURL } from 'qrcode';
|
|
|
4
4
|
import { ExternalLink, Loader2, MessageCircleMore, QrCode } from 'lucide-react';
|
|
5
5
|
import { toast } from 'sonner';
|
|
6
6
|
import { Button } from '@/shared/components/ui/button';
|
|
7
|
-
import {
|
|
7
|
+
import { Input } from '@/shared/components/ui/input';
|
|
8
|
+
import { Label } from '@/shared/components/ui/label';
|
|
9
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
|
|
10
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/components/ui/tabs';
|
|
11
|
+
import { useConnectChannelAuth, usePollChannelAuth, useStartChannelAuth } from '@/features/channels/hooks/use-channel-auth';
|
|
8
12
|
import { formatDateTime, t } from '@/shared/lib/i18n';
|
|
9
13
|
import { cn } from '@/shared/lib/utils';
|
|
10
14
|
import type { ChannelAuthPollResult, ChannelAuthStartResult } from '@/shared/lib/api';
|
|
@@ -18,6 +22,8 @@ type QrChannelAuthSectionProps = {
|
|
|
18
22
|
};
|
|
19
23
|
|
|
20
24
|
type WeixinChannelAuthSectionProps = Omit<QrChannelAuthSectionProps, 'channelName'>;
|
|
25
|
+
type FeishuChannelAuthSectionProps = WeixinChannelAuthSectionProps;
|
|
26
|
+
type FeishuPlatform = 'feishu' | 'lark';
|
|
21
27
|
|
|
22
28
|
type QrChannelAuthCopy = {
|
|
23
29
|
title: string;
|
|
@@ -103,6 +109,15 @@ function resolveDomain(formData: Record<string, unknown>, channelConfig: Record<
|
|
|
103
109
|
return configDomain || undefined;
|
|
104
110
|
}
|
|
105
111
|
|
|
112
|
+
function resolveFeishuPlatform(formData: Record<string, unknown>, channelConfig: Record<string, unknown>): FeishuPlatform {
|
|
113
|
+
const domain = resolveDomain(formData, channelConfig);
|
|
114
|
+
return domain === 'lark' ? 'lark' : 'feishu';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function resolveFeishuDeveloperConsoleUrl(platform: FeishuPlatform): string {
|
|
118
|
+
return platform === 'lark' ? 'https://open.larksuite.com/app' : 'https://open.feishu.cn/app';
|
|
119
|
+
}
|
|
120
|
+
|
|
106
121
|
function useQrDataUrl(channelName: string, qrCodeUrl: string | undefined) {
|
|
107
122
|
return useQuery({
|
|
108
123
|
queryKey: ['channel-qr', channelName, qrCodeUrl],
|
|
@@ -337,6 +352,109 @@ export function QrChannelAuthSection({
|
|
|
337
352
|
);
|
|
338
353
|
}
|
|
339
354
|
|
|
355
|
+
function ExistingFeishuAgentConnectPanel({
|
|
356
|
+
channelConfig,
|
|
357
|
+
disabled,
|
|
358
|
+
formData
|
|
359
|
+
}: {
|
|
360
|
+
channelConfig: Record<string, unknown>;
|
|
361
|
+
disabled?: boolean;
|
|
362
|
+
formData: Record<string, unknown>;
|
|
363
|
+
}) {
|
|
364
|
+
const queryClient = useQueryClient();
|
|
365
|
+
const connectChannelAuth = useConnectChannelAuth();
|
|
366
|
+
const [platform, setPlatform] = useState<FeishuPlatform>(() => resolveFeishuPlatform(formData, channelConfig));
|
|
367
|
+
const [appId, setAppId] = useState('');
|
|
368
|
+
const [appSecret, setAppSecret] = useState('');
|
|
369
|
+
const developerConsoleUrl = resolveFeishuDeveloperConsoleUrl(platform);
|
|
370
|
+
const canSubmit = Boolean(appId.trim() && appSecret.trim()) && !disabled && !connectChannelAuth.isPending;
|
|
371
|
+
|
|
372
|
+
const handleConnect = async () => {
|
|
373
|
+
try {
|
|
374
|
+
const result = await connectChannelAuth.mutateAsync({
|
|
375
|
+
channel: 'feishu',
|
|
376
|
+
data: {
|
|
377
|
+
domain: platform,
|
|
378
|
+
fields: {
|
|
379
|
+
appId: appId.trim(),
|
|
380
|
+
appSecret: appSecret.trim()
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
await queryClient.invalidateQueries({ queryKey: ['config'] });
|
|
385
|
+
await queryClient.invalidateQueries({ queryKey: ['config-meta'] });
|
|
386
|
+
toast.success(result.message || t('feishuExistingAgentConnectSuccess'));
|
|
387
|
+
setAppSecret('');
|
|
388
|
+
} catch (error) {
|
|
389
|
+
toast.error(`${t('error')}: ${error instanceof Error ? error.message : String(error)}`);
|
|
390
|
+
}
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
return (
|
|
394
|
+
<section className="rounded-2xl border border-primary/20 bg-white p-5">
|
|
395
|
+
<div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
|
396
|
+
<div className="max-w-xl space-y-3">
|
|
397
|
+
<div className="inline-flex items-center gap-2 rounded-full bg-gray-100 px-3 py-1 text-xs font-medium text-gray-700">
|
|
398
|
+
<MessageCircleMore className="h-3.5 w-3.5" />
|
|
399
|
+
{t('feishuExistingAgentTitle')}
|
|
400
|
+
</div>
|
|
401
|
+
<div>
|
|
402
|
+
<h4 className="text-base font-semibold text-gray-900">{t('feishuExistingAgentDescription')}</h4>
|
|
403
|
+
<p className="mt-1 text-sm text-gray-600">{t('feishuExistingAgentHint')}</p>
|
|
404
|
+
</div>
|
|
405
|
+
<a href={developerConsoleUrl} target="_blank" rel="noreferrer" className="inline-flex items-center gap-1.5 text-sm text-primary transition-colors hover:text-primary-hover">
|
|
406
|
+
<ExternalLink className="h-4 w-4" />
|
|
407
|
+
{platform === 'lark' ? t('feishuExistingAgentOpenLarkList') : t('feishuExistingAgentOpenFeishuList')}
|
|
408
|
+
</a>
|
|
409
|
+
</div>
|
|
410
|
+
<div className="w-full max-w-sm space-y-4">
|
|
411
|
+
<div className="space-y-2">
|
|
412
|
+
<Label htmlFor="feishu-existing-platform">{t('feishuPlatform')}</Label>
|
|
413
|
+
<Select value={platform} onValueChange={(value) => setPlatform(value === 'lark' ? 'lark' : 'feishu')}>
|
|
414
|
+
<SelectTrigger id="feishu-existing-platform" className="rounded-xl">
|
|
415
|
+
<SelectValue />
|
|
416
|
+
</SelectTrigger>
|
|
417
|
+
<SelectContent>
|
|
418
|
+
<SelectItem value="feishu">Feishu</SelectItem>
|
|
419
|
+
<SelectItem value="lark">Lark</SelectItem>
|
|
420
|
+
</SelectContent>
|
|
421
|
+
</Select>
|
|
422
|
+
</div>
|
|
423
|
+
<div className="space-y-2">
|
|
424
|
+
<Label htmlFor="feishu-existing-app-id">App ID</Label>
|
|
425
|
+
<Input id="feishu-existing-app-id" value={appId} onChange={(event) => setAppId(event.target.value)} className="rounded-xl" />
|
|
426
|
+
</div>
|
|
427
|
+
<div className="space-y-2">
|
|
428
|
+
<Label htmlFor="feishu-existing-app-secret">App Secret</Label>
|
|
429
|
+
<Input id="feishu-existing-app-secret" type="password" value={appSecret} onChange={(event) => setAppSecret(event.target.value)} className="rounded-xl" />
|
|
430
|
+
</div>
|
|
431
|
+
<Button type="button" onClick={() => void handleConnect()} disabled={!canSubmit} className="w-full rounded-xl">
|
|
432
|
+
{connectChannelAuth.isPending ? t('feishuExistingAgentConnecting') : t('feishuExistingAgentConnect')}
|
|
433
|
+
</Button>
|
|
434
|
+
</div>
|
|
435
|
+
</div>
|
|
436
|
+
</section>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function FeishuChannelAuthSection(props: FeishuChannelAuthSectionProps) {
|
|
441
|
+
const [mode, setMode] = useState<'create' | 'existing'>('create');
|
|
442
|
+
return (
|
|
443
|
+
<Tabs value={mode} onValueChange={(value) => setMode(value === 'existing' ? 'existing' : 'create')}>
|
|
444
|
+
<TabsList>
|
|
445
|
+
<TabsTrigger value="create">{t('feishuAuthCreateNew')}</TabsTrigger>
|
|
446
|
+
<TabsTrigger value="existing">{t('feishuAuthConnectExisting')}</TabsTrigger>
|
|
447
|
+
</TabsList>
|
|
448
|
+
<TabsContent value="create" className="mt-4">
|
|
449
|
+
<QrChannelAuthSection {...props} channelName="feishu" />
|
|
450
|
+
</TabsContent>
|
|
451
|
+
<TabsContent value="existing" className="mt-4">
|
|
452
|
+
<ExistingFeishuAgentConnectPanel {...props} />
|
|
453
|
+
</TabsContent>
|
|
454
|
+
</Tabs>
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
340
458
|
export function WeixinChannelAuthSection(props: WeixinChannelAuthSectionProps) {
|
|
341
459
|
return <QrChannelAuthSection {...props} channelName="weixin" />;
|
|
342
460
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMutation } from '@tanstack/react-query';
|
|
2
|
-
import { pollChannelAuth, startChannelAuth } from '@/shared/lib/api';
|
|
2
|
+
import { connectChannelAuth, pollChannelAuth, startChannelAuth } from '@/shared/lib/api';
|
|
3
3
|
|
|
4
4
|
export function useStartChannelAuth() {
|
|
5
5
|
return useMutation({
|
|
@@ -14,3 +14,10 @@ export function usePollChannelAuth() {
|
|
|
14
14
|
pollChannelAuth(channel, data as Parameters<typeof pollChannelAuth>[1])
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
export function useConnectChannelAuth() {
|
|
19
|
+
return useMutation({
|
|
20
|
+
mutationFn: ({ channel, data }: { channel: string; data: unknown }) =>
|
|
21
|
+
connectChannelAuth(channel, data as Parameters<typeof connectChannelAuth>[1])
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ChannelForm } from './components/config/channel-form';
|
|
2
2
|
export { ChannelFormFieldsSection } from './components/channel-form-fields-section';
|
|
3
|
-
export { QrChannelAuthSection, WeixinChannelAuthSection } from './components/config/weixin-channel-auth-section';
|
|
3
|
+
export { FeishuChannelAuthSection, QrChannelAuthSection, WeixinChannelAuthSection } from './components/config/weixin-channel-auth-section';
|
|
4
4
|
export { ChannelsList } from './pages/channels-list-page';
|
|
5
5
|
export { buildChannelFields, buildChannelFormDefinitions, type ChannelField, type ChannelFieldType, type ChannelFormBlock, type ChannelFormDefinition, type ChannelFormFieldSection, type ChannelOption } from './utils/channel-form-fields.utils';
|
|
@@ -281,7 +281,7 @@ describe("ChatConversationPanel", () => {
|
|
|
281
281
|
expect(onBackToList).toHaveBeenCalledTimes(1);
|
|
282
282
|
});
|
|
283
283
|
|
|
284
|
-
it("
|
|
284
|
+
it("lets the session list owner route new mobile welcome drafts", async () => {
|
|
285
285
|
const user = userEvent.setup();
|
|
286
286
|
|
|
287
287
|
render(<ChatConversationPanel layoutMode="mobile" />);
|
|
@@ -291,7 +291,7 @@ describe("ChatConversationPanel", () => {
|
|
|
291
291
|
);
|
|
292
292
|
|
|
293
293
|
expect(mocks.createSession).toHaveBeenCalledWith("native");
|
|
294
|
-
expect(mocks.goToChatRoot).
|
|
294
|
+
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
295
295
|
});
|
|
296
296
|
|
|
297
297
|
it("shows the selected session project badge and more actions trigger", () => {
|
|
@@ -141,11 +141,7 @@ function ChatConversationAlerts() {
|
|
|
141
141
|
);
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
function ChatConversationContent({
|
|
145
|
-
layoutMode,
|
|
146
|
-
}: {
|
|
147
|
-
layoutMode: ChatConversationLayoutMode;
|
|
148
|
-
}) {
|
|
144
|
+
function ChatConversationContent() {
|
|
149
145
|
const presenter = usePresenter();
|
|
150
146
|
const defaultSessionType = useChatInputStore(
|
|
151
147
|
(state) => state.snapshot.defaultSessionType,
|
|
@@ -172,7 +168,6 @@ function ChatConversationContent({
|
|
|
172
168
|
defaultSessionType,
|
|
173
169
|
);
|
|
174
170
|
presenter.chatSessionListManager.createSession(sessionType);
|
|
175
|
-
if (layoutMode === "mobile") presenter.chatUiManager.goToChatRoot();
|
|
176
171
|
};
|
|
177
172
|
const selectDraftAgent = (agentId: string) => {
|
|
178
173
|
presenter.chatSessionListManager.setSelectedAgentId(agentId);
|
|
@@ -398,7 +393,7 @@ export function ChatConversationPanel({
|
|
|
398
393
|
onBackToList={onBackToList}
|
|
399
394
|
/>
|
|
400
395
|
<ChatConversationAlerts />
|
|
401
|
-
<ChatConversationContent
|
|
396
|
+
<ChatConversationContent />
|
|
402
397
|
<ChatInputBarContainer />
|
|
403
398
|
</div>
|
|
404
399
|
|
|
@@ -179,6 +179,30 @@ describe('ChatSidebar create and list basics', () => {
|
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
181
|
|
|
182
|
+
it('does not animate the desktop create task button width during runtime option hydration', () => {
|
|
183
|
+
render(
|
|
184
|
+
<MemoryRouter>
|
|
185
|
+
<ChatSidebar />
|
|
186
|
+
</MemoryRouter>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const createButton = screen.getByRole('button', { name: 'New Task' });
|
|
190
|
+
const sessionTypeButton = screen.getByLabelText('Session Type');
|
|
191
|
+
|
|
192
|
+
expect(createButton.className).not.toContain('transition-all');
|
|
193
|
+
expect(sessionTypeButton.className).not.toContain('transition-all');
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('keeps the desktop brand row compact with symmetric vertical padding', () => {
|
|
197
|
+
render(
|
|
198
|
+
<MemoryRouter>
|
|
199
|
+
<ChatSidebar />
|
|
200
|
+
</MemoryRouter>
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
expect(screen.getByTestId('brand-header').parentElement?.className).toContain('py-2.5');
|
|
204
|
+
});
|
|
205
|
+
|
|
182
206
|
it('shows setup required status for runtime session types that are not ready yet', () => {
|
|
183
207
|
useChatInputStore.setState({
|
|
184
208
|
snapshot: {
|
|
@@ -241,7 +265,7 @@ describe('ChatSidebar create and list basics', () => {
|
|
|
241
265
|
|
|
242
266
|
expect(mocks.setQuery).toHaveBeenCalledWith('release notes');
|
|
243
267
|
expect(mocks.createSession).toHaveBeenCalledWith('codex', undefined);
|
|
244
|
-
expect(mocks.goToChatRoot).
|
|
268
|
+
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
245
269
|
});
|
|
246
270
|
|
|
247
271
|
it('keeps low-frequency utility choices behind nested selectors', () => {
|
|
@@ -292,7 +316,7 @@ describe('ChatSidebar create and list basics', () => {
|
|
|
292
316
|
fireEvent.click(screen.getByRole('button', { name: 'New Task' }));
|
|
293
317
|
|
|
294
318
|
expect(mocks.createSession).toHaveBeenCalledWith('native', undefined);
|
|
295
|
-
expect(mocks.goToChatRoot).
|
|
319
|
+
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
296
320
|
});
|
|
297
321
|
|
|
298
322
|
it('shows a session type badge for non-native sessions in the list', () => {
|
|
@@ -601,7 +625,7 @@ describe('ChatSidebar project-first mode', () => {
|
|
|
601
625
|
fireEvent.click(screen.getByText('Codex'));
|
|
602
626
|
|
|
603
627
|
expect(mocks.createSession).toHaveBeenCalledWith('codex', '/tmp/project-mobile');
|
|
604
|
-
expect(mocks.goToChatRoot).
|
|
628
|
+
expect(mocks.goToChatRoot).not.toHaveBeenCalled();
|
|
605
629
|
});
|
|
606
630
|
});
|
|
607
631
|
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
ChatSidebarMobileToolbar,
|
|
31
31
|
} from '@/features/chat/components/layout/chat-sidebar-toolbar';
|
|
32
32
|
import { ChatSidebarUtilityMenu } from '@/features/chat/components/layout/chat-sidebar-utility-menu';
|
|
33
|
-
import {
|
|
33
|
+
import { isWindowsDesktopHost } from '@/platforms/desktop';
|
|
34
34
|
|
|
35
35
|
type DateGroup = {
|
|
36
36
|
label: string;
|
|
@@ -256,7 +256,6 @@ export function ChatSidebar({
|
|
|
256
256
|
);
|
|
257
257
|
const createSessionAndOpenIfNeeded = (sessionType: string, projectRoot?: string | null) => {
|
|
258
258
|
presenter.chatSessionListManager.createSession(sessionType, typeof projectRoot === "string" ? projectRoot : undefined);
|
|
259
|
-
if (isMobileVariant) presenter.chatUiManager.goToChatRoot();
|
|
260
259
|
};
|
|
261
260
|
|
|
262
261
|
return (
|
|
@@ -269,7 +268,7 @@ export function ChatSidebar({
|
|
|
269
268
|
)}
|
|
270
269
|
>
|
|
271
270
|
{!isMobileVariant && !isWindowsDesktopHost() ? (
|
|
272
|
-
<div className=
|
|
271
|
+
<div className="px-5 py-2.5">
|
|
273
272
|
<BrandHeader
|
|
274
273
|
className="flex min-w-0 items-center gap-2"
|
|
275
274
|
suffix={<StatusBadge status={systemStatus.connectionStatus} />}
|