@nextclaw/ui 0.15.21 → 0.15.22
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 +20 -0
- package/dist/assets/{api-CHo2gekJ.js → api-B1yHQYs-.js} +3 -3
- package/dist/assets/app-presenter-provider-uP9waOzv.js +108 -0
- package/dist/assets/appearance-settings-page-C9Zq3hP9.js +1 -0
- package/dist/assets/{book-open-BrZL4o9L.js → book-open-oSmyVXea.js} +1 -1
- package/dist/assets/{card-B5rRLu_6.js → card-CGvJZTZp.js} +1 -1
- package/dist/assets/{channels-list-page-C1dnm68I.js → channels-list-page-5BTxpQHw.js} +2 -2
- package/dist/assets/chat-page-jTvli8Jp.js +1 -0
- package/dist/assets/config-split-page-BgBsUmp1.js +1 -0
- package/dist/assets/confirm-dialog-h849QRWO.js +5 -0
- package/dist/assets/desktop-update-config-BGGHxClR.js +1 -0
- package/dist/assets/{dist-C9EX46wM.js → dist-CxUhurq9.js} +1 -1
- package/dist/assets/doc-browser-0eL8AX42.js +1 -0
- package/dist/assets/{doc-browser-DH-3hDnI.js → doc-browser-DEqE9lsh.js} +1 -1
- package/dist/assets/{doc-browser-context-TU2heon0.js → doc-browser-context-DkzFIUoK.js} +1 -1
- package/dist/assets/{eye-B-3dyZNA.js → eye-gOWQGt8N.js} +1 -1
- package/dist/assets/{form-actions-DvvxCpL4.js → form-actions-CgjYZUzM.js} +1 -1
- package/dist/assets/{index-k_eWScvN.js → index-DSAUWCfl.js} +2 -2
- package/dist/assets/index-G7cBryJ4.css +1 -0
- package/dist/assets/loader-circle-BAuycGf6.js +1 -0
- package/dist/assets/{logo-badge-DB51c0cS.js → logo-badge-BNcsDsUM.js} +1 -1
- package/dist/assets/{mcp-marketplace-page-DWtsIqml.js → mcp-marketplace-page-BX39MtPa.js} +3 -3
- package/dist/assets/mcp-marketplace-page-CkDtOEA4.js +1 -0
- package/dist/assets/{model-config-page-D6cGNnMM.js → model-config-page-7MIXg2fQ.js} +1 -1
- package/dist/assets/{navigation-link-BSJEUiwD.js → navigation-link-B3iFOdxx.js} +1 -1
- package/dist/assets/plus-yvhFgfal.js +1 -0
- package/dist/assets/{popover-Bp9IpE_N.js → popover-Cpep_rJe.js} +1 -1
- package/dist/assets/{provider-models-CXUgDxGR.js → provider-models-CUrptZ-o.js} +1 -1
- package/dist/assets/provider-scoped-model-input-CHmOzz7x.js +1 -0
- package/dist/assets/providers-config-page-A6J7Y0II.js +1 -0
- package/dist/assets/react-BCJGCjrP.js +8 -0
- package/dist/assets/{refresh-cw-C2e94pmp.js → refresh-cw-CkKwPztT.js} +1 -1
- package/dist/assets/remote-FD-ZfSIn.js +1 -0
- package/dist/assets/rotate-cw-DrJMfHhp.js +1 -0
- package/dist/assets/{runtime-config-page-C57OLdPm.js → runtime-config-page-DwjO-Vs6.js} +1 -1
- package/dist/assets/{save-CkwDOgvt.js → save-BINMWate.js} +1 -1
- package/dist/assets/{search-CLXM2reO.js → search-7M38azRp.js} +1 -1
- package/dist/assets/{search-config-page-tnJXaaSr.js → search-config-page-T6tMhXbP.js} +1 -1
- package/dist/assets/{secrets-config-page-DQ_Curaj.js → secrets-config-page-2mVejXf2.js} +2 -2
- package/dist/assets/security-config-DD5RrjDe.js +1 -0
- package/dist/assets/{select-CNFBf00l.js → select-BvidP5Oa.js} +1 -1
- package/dist/assets/{settings-2-B9IT2HGd.js → settings-2-DqZstREV.js} +1 -1
- package/dist/assets/skeleton-BEQW2Dva.js +1 -0
- package/dist/assets/{tabs-BuNr8LSO.js → tabs-BeLxVmkt.js} +1 -1
- package/dist/assets/{tag-chip-CDkB5smU.js → tag-chip-BoDaLnWt.js} +1 -1
- package/dist/assets/{trash-2-GchaJuEM.js → trash-2-CYUskMMN.js} +1 -1
- package/dist/assets/{use-config-C69rR15i.js → use-config-CxKRXXCl.js} +1 -1
- package/dist/assets/use-confirm-dialog-HASTvOP2.js +1 -0
- package/dist/assets/{use-viewport-layout-BACNZyMY.js → use-viewport-layout-CJxkjoRz.js} +1 -1
- package/dist/assets/x-DdUwjre5.js +1 -0
- package/dist/index.html +29 -29
- package/package.json +7 -7
- package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
- package/src/app/components/layout/sidebar.tsx +2 -2
- package/src/app/styles/design-system.css +12 -0
- package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +2 -2
- package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +19 -1
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +1 -1
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +5 -6
- package/src/features/chat/components/conversation/chat-conversation-header.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +52 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +11 -17
- package/src/features/chat/components/layout/chat-page-shell.tsx +2 -11
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +32 -42
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +13 -64
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -4
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +1 -1
- package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +15 -4
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +7 -7
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +4 -2
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +1 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +1 -0
- package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +1 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +3 -0
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +0 -1
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +3 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +29 -0
- package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +4 -1
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +10 -10
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
- package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +26 -0
- package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +4 -2
- package/src/features/chat/features/session/utils/chat-session-display.utils.ts +48 -12
- package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +13 -4
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +6 -0
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +10 -7
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +3 -14
- package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +37 -0
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +33 -31
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +1 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +15 -3
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +3 -10
- package/src/features/chat/managers/chat-session-list.manager.ts +3 -23
- package/src/features/chat/managers/chat-thread.manager.ts +2 -5
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +3 -9
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -1
- package/src/features/chat/types/chat-input-bar.types.ts +1 -0
- package/src/features/cron/components/cron-task-discovery.tsx +1 -1
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +3 -0
- package/src/features/notifications/components/app-notification-toast.tsx +1 -1
- package/src/features/panel-apps/components/panel-app-list-item.tsx +1 -1
- package/src/features/panel-apps/components/panel-app-toolbar.tsx +2 -2
- package/src/features/panel-apps/components/panel-apps-list.tsx +4 -4
- package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
- package/src/features/settings/components/config/provider-auth-section.tsx +90 -59
- package/src/features/settings/components/config/provider-form.tsx +10 -17
- package/src/features/settings/components/config/provider-status-badge.tsx +4 -2
- package/src/features/settings/pages/__tests__/providers-config-page.test.tsx +12 -0
- package/src/features/settings/pages/providers-config-page.tsx +2 -1
- package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +13 -7
- package/src/platforms/mobile/components/__tests__/mobile-bottom-nav.test.tsx +18 -20
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
- package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +1 -1
- package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
- package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +3 -3
- package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +1 -1
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
- package/src/shared/components/ui/button.tsx +4 -4
- package/src/shared/components/ui/dialog.tsx +1 -1
- package/src/shared/components/ui/input.tsx +1 -1
- package/src/shared/components/ui/scroll-area.tsx +2 -0
- package/src/shared/components/ui/select.tsx +1 -1
- package/src/shared/components/ui/switch.tsx +1 -1
- package/src/shared/components/ui/tabs.tsx +1 -1
- package/src/shared/components/ui/textarea.tsx +1 -1
- package/src/shared/lib/api/ncp-session.types.ts +1 -0
- package/src/shared/lib/api/types.ts +2 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +13 -14
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +13 -14
- package/src/shared/lib/provider-models/__tests__/anonymous-provider-configuration.test.ts +40 -0
- package/src/shared/lib/provider-models/index.ts +4 -3
- package/dist/assets/app-presenter-provider-2_DmWYT9.js +0 -108
- package/dist/assets/appearance-settings-page-BEy2UE7R.js +0 -1
- package/dist/assets/chat-page-Dz6tvZ91.js +0 -1
- package/dist/assets/config-split-page-6y6W6dz4.js +0 -1
- package/dist/assets/confirm-dialog-q1CpEmR8.js +0 -5
- package/dist/assets/desktop-update-config-DrdlwP4j.js +0 -1
- package/dist/assets/doc-browser-BfttDSti.js +0 -1
- package/dist/assets/index-CXDPLDFD.css +0 -1
- package/dist/assets/loader-circle-B35NlHTI.js +0 -1
- package/dist/assets/mcp-marketplace-page-CkPtwPBk.js +0 -1
- package/dist/assets/plus-BRVe5jSJ.js +0 -1
- package/dist/assets/provider-scoped-model-input-dk7fz9NL.js +0 -1
- package/dist/assets/providers-config-page-DTP2YfxA.js +0 -1
- package/dist/assets/react-D6ShNq95.js +0 -8
- package/dist/assets/remote-FllhXW3o.js +0 -1
- package/dist/assets/rotate-cw-CTC-Qszi.js +0 -1
- package/dist/assets/security-config-S00DjFfO.js +0 -1
- package/dist/assets/skeleton-Dc06gatK.js +0 -1
- package/dist/assets/use-confirm-dialog-DF_EgPYx.js +0 -1
- package/dist/assets/x-BXFquY9e.js +0 -1
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Button } from '@/shared/components/ui/button';
|
|
2
|
+
import { MaskedInput } from '@/shared/components/common/masked-input';
|
|
2
3
|
import { Label } from '@/shared/components/ui/label';
|
|
3
4
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/components/ui/select';
|
|
4
5
|
import { t } from '@/shared/lib/i18n';
|
|
@@ -6,6 +7,10 @@ import type { ProviderTemplateView } from '@/shared/lib/api';
|
|
|
6
7
|
import { ProviderPillSelector } from './provider-pill-selector';
|
|
7
8
|
|
|
8
9
|
type ProviderAuthSectionProps = {
|
|
10
|
+
apiKey: string;
|
|
11
|
+
apiKeyRequired: boolean;
|
|
12
|
+
apiKeySet: boolean;
|
|
13
|
+
apiKeyPlaceholder: string;
|
|
9
14
|
providerAuth: ProviderTemplateView['auth'];
|
|
10
15
|
providerAuthNote: string;
|
|
11
16
|
providerAuthMethodOptions: Array<{ value: string; label: string }>;
|
|
@@ -20,10 +25,15 @@ type ProviderAuthSectionProps = {
|
|
|
20
25
|
importPending: boolean;
|
|
21
26
|
authSessionId: string | null;
|
|
22
27
|
authStatusMessage: string;
|
|
28
|
+
onApiKeyChange: (value: string) => void;
|
|
23
29
|
};
|
|
24
30
|
|
|
25
31
|
export function ProviderAuthSection(props: ProviderAuthSectionProps) {
|
|
26
32
|
const {
|
|
33
|
+
apiKey,
|
|
34
|
+
apiKeyRequired,
|
|
35
|
+
apiKeySet,
|
|
36
|
+
apiKeyPlaceholder,
|
|
27
37
|
providerAuth,
|
|
28
38
|
providerAuthNote,
|
|
29
39
|
providerAuthMethodOptions,
|
|
@@ -37,75 +47,96 @@ export function ProviderAuthSection(props: ProviderAuthSectionProps) {
|
|
|
37
47
|
startPending,
|
|
38
48
|
importPending,
|
|
39
49
|
authSessionId,
|
|
40
|
-
authStatusMessage
|
|
50
|
+
authStatusMessage,
|
|
51
|
+
onApiKeyChange
|
|
41
52
|
} = props;
|
|
42
53
|
|
|
43
|
-
|
|
54
|
+
const apiKeyField = apiKeyRequired ? (
|
|
55
|
+
<div className='space-y-2'>
|
|
56
|
+
<Label htmlFor='apiKey' className='text-sm font-medium text-foreground'>
|
|
57
|
+
{t('apiKey')}
|
|
58
|
+
</Label>
|
|
59
|
+
<MaskedInput
|
|
60
|
+
id='apiKey'
|
|
61
|
+
value={apiKey}
|
|
62
|
+
isSet={apiKeySet}
|
|
63
|
+
onChange={(event) => onApiKeyChange(event.target.value)}
|
|
64
|
+
placeholder={apiKeyPlaceholder}
|
|
65
|
+
className='rounded-xl'
|
|
66
|
+
/>
|
|
67
|
+
<p className='text-xs text-muted-foreground'>{t('leaveBlankToKeepUnchanged')}</p>
|
|
68
|
+
</div>
|
|
69
|
+
) : null;
|
|
70
|
+
|
|
71
|
+
if (providerAuth?.kind !== 'device_code') return apiKeyField;
|
|
44
72
|
|
|
45
73
|
return (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
variant='outline'
|
|
81
|
-
size='sm'
|
|
82
|
-
onClick={onStartProviderAuth}
|
|
83
|
-
disabled={startPending || Boolean(authSessionId)}
|
|
84
|
-
>
|
|
85
|
-
{startPending
|
|
86
|
-
? t('providerAuthStarting')
|
|
87
|
-
: authSessionId
|
|
88
|
-
? t('providerAuthAuthorizing')
|
|
89
|
-
: t('providerAuthAuthorizeInBrowser')}
|
|
90
|
-
</Button>
|
|
91
|
-
{providerAuth.supportsCliImport ? (
|
|
74
|
+
<>
|
|
75
|
+
{apiKeyField}
|
|
76
|
+
<div className='space-y-2 rounded-xl bg-muted/45 p-3'>
|
|
77
|
+
<Label className='text-sm font-medium text-foreground'>
|
|
78
|
+
{providerAuth.displayName || t('providerAuthSectionTitle')}
|
|
79
|
+
</Label>
|
|
80
|
+
{providerAuthNote ? <p className='text-xs text-muted-foreground'>{providerAuthNote}</p> : null}
|
|
81
|
+
{providerAuthMethodsCount > 1 ? (
|
|
82
|
+
<div className='space-y-2'>
|
|
83
|
+
<Label className='text-xs font-medium text-foreground'>{t('providerAuthMethodLabel')}</Label>
|
|
84
|
+
{shouldUseAuthMethodPills ? (
|
|
85
|
+
<ProviderPillSelector
|
|
86
|
+
value={resolvedAuthMethodId}
|
|
87
|
+
onChange={onAuthMethodChange}
|
|
88
|
+
options={providerAuthMethodOptions}
|
|
89
|
+
/>
|
|
90
|
+
) : (
|
|
91
|
+
<Select value={resolvedAuthMethodId} onValueChange={onAuthMethodChange}>
|
|
92
|
+
<SelectTrigger className='h-8 rounded-lg'>
|
|
93
|
+
<SelectValue placeholder={t('providerAuthMethodPlaceholder')} />
|
|
94
|
+
</SelectTrigger>
|
|
95
|
+
<SelectContent>
|
|
96
|
+
{providerAuthMethodOptions.map((method) => (
|
|
97
|
+
<SelectItem key={method.value} value={method.value}>
|
|
98
|
+
{method.label}
|
|
99
|
+
</SelectItem>
|
|
100
|
+
))}
|
|
101
|
+
</SelectContent>
|
|
102
|
+
</Select>
|
|
103
|
+
)}
|
|
104
|
+
{selectedAuthMethodHint ? <p className='text-xs text-muted-foreground'>{selectedAuthMethodHint}</p> : null}
|
|
105
|
+
</div>
|
|
106
|
+
) : null}
|
|
107
|
+
<div className='flex flex-wrap items-center gap-2'>
|
|
92
108
|
<Button
|
|
93
109
|
type='button'
|
|
94
110
|
variant='outline'
|
|
95
111
|
size='sm'
|
|
96
|
-
onClick={
|
|
97
|
-
disabled={
|
|
112
|
+
onClick={onStartProviderAuth}
|
|
113
|
+
disabled={startPending || Boolean(authSessionId)}
|
|
98
114
|
>
|
|
99
|
-
{
|
|
115
|
+
{startPending
|
|
116
|
+
? t('providerAuthStarting')
|
|
117
|
+
: authSessionId
|
|
118
|
+
? t('providerAuthAuthorizing')
|
|
119
|
+
: t('providerAuthAuthorizeInBrowser')}
|
|
100
120
|
</Button>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
{providerAuth.supportsCliImport ? (
|
|
122
|
+
<Button
|
|
123
|
+
type='button'
|
|
124
|
+
variant='outline'
|
|
125
|
+
size='sm'
|
|
126
|
+
onClick={onImportProviderAuthFromCli}
|
|
127
|
+
disabled={importPending}
|
|
128
|
+
>
|
|
129
|
+
{importPending ? t('providerAuthImporting') : t('providerAuthImportFromCli')}
|
|
130
|
+
</Button>
|
|
131
|
+
) : null}
|
|
132
|
+
{authSessionId ? (
|
|
133
|
+
<span className='text-xs text-muted-foreground'>
|
|
134
|
+
{t('providerAuthSessionLabel')}: {authSessionId.slice(0, 8)}…
|
|
135
|
+
</span>
|
|
136
|
+
) : null}
|
|
137
|
+
</div>
|
|
138
|
+
{authStatusMessage ? <p className='text-xs text-muted-foreground'>{authStatusMessage}</p> : null}
|
|
107
139
|
</div>
|
|
108
|
-
|
|
109
|
-
</div>
|
|
140
|
+
</>
|
|
110
141
|
);
|
|
111
142
|
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
useTestProviderConnection,
|
|
10
10
|
useUpdateProvider
|
|
11
11
|
} from '@/shared/hooks/use-config';
|
|
12
|
-
import { MaskedInput } from '@/shared/components/common/masked-input';
|
|
13
12
|
import { Button } from '@/shared/components/ui/button';
|
|
14
13
|
import { Input } from '@/shared/components/ui/input';
|
|
15
14
|
import { Label } from '@/shared/components/ui/label';
|
|
@@ -445,7 +444,11 @@ function ProviderFormDetailPane(props: ProviderFormDetailPaneProps) {
|
|
|
445
444
|
>
|
|
446
445
|
<Trash2 className='h-4 w-4' />
|
|
447
446
|
</button>
|
|
448
|
-
<ProviderStatusBadge
|
|
447
|
+
<ProviderStatusBadge
|
|
448
|
+
enabled={context.currentEnabled}
|
|
449
|
+
apiKeyRequired={context.resolvedProviderConfig.apiKeyRequired}
|
|
450
|
+
apiKeySet={context.resolvedProviderConfig.apiKeySet}
|
|
451
|
+
/>
|
|
449
452
|
</div>
|
|
450
453
|
</div>
|
|
451
454
|
</ConfigSplitPaneHeader>
|
|
@@ -467,22 +470,11 @@ function ProviderFormDetailPane(props: ProviderFormDetailPaneProps) {
|
|
|
467
470
|
<p className='text-xs text-muted-foreground'>{t('providerDisplayNameHelpShort')}</p>
|
|
468
471
|
</div>
|
|
469
472
|
|
|
470
|
-
<div className='space-y-2'>
|
|
471
|
-
<Label htmlFor='apiKey' className='text-sm font-medium text-foreground'>
|
|
472
|
-
{t('apiKey')}
|
|
473
|
-
</Label>
|
|
474
|
-
<MaskedInput
|
|
475
|
-
id='apiKey'
|
|
476
|
-
value={apiKey}
|
|
477
|
-
isSet={context.resolvedProviderConfig.apiKeySet}
|
|
478
|
-
onChange={(e) => onApiKeyChange(e.target.value)}
|
|
479
|
-
placeholder={context.apiKeyHint?.placeholder ?? t('enterApiKey')}
|
|
480
|
-
className='rounded-xl'
|
|
481
|
-
/>
|
|
482
|
-
<p className='text-xs text-muted-foreground'>{t('leaveBlankToKeepUnchanged')}</p>
|
|
483
|
-
</div>
|
|
484
|
-
|
|
485
473
|
<ProviderAuthSection
|
|
474
|
+
apiKey={apiKey}
|
|
475
|
+
apiKeyRequired={context.resolvedProviderConfig.apiKeyRequired !== false}
|
|
476
|
+
apiKeySet={context.resolvedProviderConfig.apiKeySet}
|
|
477
|
+
apiKeyPlaceholder={context.apiKeyHint?.placeholder ?? t('enterApiKey')}
|
|
486
478
|
providerAuth={context.providerAuth}
|
|
487
479
|
providerAuthNote={context.providerAuth?.note?.[language] || context.providerAuth?.note?.en || context.providerAuth?.displayName || ''}
|
|
488
480
|
providerAuthMethodOptions={context.providerAuthMethodOptions}
|
|
@@ -497,6 +489,7 @@ function ProviderFormDetailPane(props: ProviderFormDetailPaneProps) {
|
|
|
497
489
|
importPending={importPending}
|
|
498
490
|
authSessionId={authSessionId}
|
|
499
491
|
authStatusMessage={authStatusMessage}
|
|
492
|
+
onApiKeyChange={onApiKeyChange}
|
|
500
493
|
/>
|
|
501
494
|
|
|
502
495
|
<div className='space-y-2'>
|
|
@@ -3,13 +3,15 @@ import { t } from '@/shared/lib/i18n';
|
|
|
3
3
|
|
|
4
4
|
type ProviderStatusBadgeProps = {
|
|
5
5
|
enabled: boolean;
|
|
6
|
+
apiKeyRequired?: boolean;
|
|
6
7
|
apiKeySet: boolean;
|
|
7
8
|
className?: string;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
export function ProviderStatusBadge({ apiKeySet, className, enabled }: ProviderStatusBadgeProps) {
|
|
11
|
+
export function ProviderStatusBadge({ apiKeyRequired = true, apiKeySet, className, enabled }: ProviderStatusBadgeProps) {
|
|
11
12
|
if (!enabled) {
|
|
12
13
|
return <StatusDot status="inactive" label={t('disabled')} className={className} />;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
const isReady = !apiKeyRequired || apiKeySet;
|
|
16
|
+
return <StatusDot status={isReady ? 'ready' : 'setup'} label={isReady ? t('statusReady') : t('statusSetup')} className={className} />;
|
|
15
17
|
}
|
|
@@ -11,6 +11,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
11
11
|
providers: {
|
|
12
12
|
openai: { enabled: true, apiKeySet: true },
|
|
13
13
|
anthropic: { enabled: true, apiKeySet: true },
|
|
14
|
+
opencode: { enabled: true, apiKeyRequired: false, apiKeySet: false },
|
|
14
15
|
nextclaw: { enabled: true, apiKeySet: true },
|
|
15
16
|
},
|
|
16
17
|
},
|
|
@@ -22,6 +23,7 @@ const mocks = vi.hoisted(() => ({
|
|
|
22
23
|
{ name: 'nextclaw', displayName: 'NextClaw Builtin', defaultApiBase: 'https://ai-gateway-api.nextclaw.io/v1' },
|
|
23
24
|
{ name: 'openai', displayName: 'OpenAI', defaultApiBase: 'https://api.openai.com/v1' },
|
|
24
25
|
{ name: 'anthropic', displayName: 'Anthropic', defaultApiBase: 'https://api.anthropic.com/v1' },
|
|
26
|
+
{ name: 'opencode', displayName: 'OpenCode Zen Free Trial', defaultApiBase: 'https://opencode.ai/zen/v1' },
|
|
25
27
|
],
|
|
26
28
|
},
|
|
27
29
|
},
|
|
@@ -46,6 +48,7 @@ vi.mock('@/shared/hooks/use-config', () => ({
|
|
|
46
48
|
isBuiltInType: providerId === 'nextclaw',
|
|
47
49
|
isCustom: false,
|
|
48
50
|
enabled: provider.enabled,
|
|
51
|
+
apiKeyRequired: 'apiKeyRequired' in provider ? provider.apiKeyRequired : undefined,
|
|
49
52
|
apiKeySet: provider.apiKeySet,
|
|
50
53
|
},
|
|
51
54
|
]),
|
|
@@ -108,4 +111,13 @@ describe('ProvidersConfigPage', () => {
|
|
|
108
111
|
expect.stringContaining('NextClaw Builtin'),
|
|
109
112
|
]);
|
|
110
113
|
});
|
|
114
|
+
|
|
115
|
+
it('shows an enabled provider without a required API key as ready', () => {
|
|
116
|
+
const { container } = render(<ProvidersConfigPage />);
|
|
117
|
+
const opencodeCard = Array.from(container.querySelectorAll('[role="button"]'))
|
|
118
|
+
.find((element) => element.textContent?.includes('OpenCode Zen Free Trial'));
|
|
119
|
+
|
|
120
|
+
expect(opencodeCard?.textContent).toContain('Ready');
|
|
121
|
+
expect(opencodeCard?.textContent).not.toContain('Setup');
|
|
122
|
+
});
|
|
111
123
|
});
|
|
@@ -17,6 +17,7 @@ import { ConfirmDialog } from '@/shared/components/ui/confirm-dialog';
|
|
|
17
17
|
import { StatusDot } from '@/shared/components/status/status-dot';
|
|
18
18
|
import { t } from '@/shared/lib/i18n';
|
|
19
19
|
import { cn } from '@/shared/lib/utils';
|
|
20
|
+
import { isProviderConfigured } from '@/shared/lib/provider-models';
|
|
20
21
|
import { ProviderForm } from '@/features/settings/components/config/provider-form';
|
|
21
22
|
import { useViewportLayout } from '@/app/hooks/use-viewport-layout';
|
|
22
23
|
import {
|
|
@@ -232,7 +233,7 @@ export function ProvidersConfigPage() {
|
|
|
232
233
|
{filteredProviders.map((provider) => {
|
|
233
234
|
const template = provider.providerType ? templateByType.get(provider.providerType) : null;
|
|
234
235
|
const isEnabled = provider.enabled !== false;
|
|
235
|
-
const isReady =
|
|
236
|
+
const isReady = isProviderConfigured(provider);
|
|
236
237
|
const providerLabel = provider.displayName?.trim() || template?.displayName || provider.providerId;
|
|
237
238
|
const description =
|
|
238
239
|
formatBasePreview(provider.apiBase || template?.defaultApiBase || '') ||
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
3
|
import { MemoryRouter } from "react-router-dom";
|
|
3
4
|
import { describe, expect, it, vi } from "vitest";
|
|
4
5
|
import { I18nProvider } from "@/app/components/i18n-provider";
|
|
@@ -9,14 +10,19 @@ vi.mock("@/shared/components/doc-browser", () => ({
|
|
|
9
10
|
}));
|
|
10
11
|
|
|
11
12
|
function renderShell(pathname: string) {
|
|
13
|
+
const queryClient = new QueryClient({
|
|
14
|
+
defaultOptions: { queries: { retry: false } },
|
|
15
|
+
});
|
|
12
16
|
const view = render(
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
<
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<QueryClientProvider client={queryClient}>
|
|
18
|
+
<I18nProvider>
|
|
19
|
+
<MemoryRouter initialEntries={[pathname]}>
|
|
20
|
+
<MobileAppShell pathname={pathname} isDocBrowserOpen={false}>
|
|
21
|
+
<div>Content</div>
|
|
22
|
+
</MobileAppShell>
|
|
23
|
+
</MemoryRouter>
|
|
24
|
+
</I18nProvider>
|
|
25
|
+
</QueryClientProvider>,
|
|
20
26
|
);
|
|
21
27
|
return {
|
|
22
28
|
...view,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
2
3
|
import { MemoryRouter } from "react-router-dom";
|
|
3
4
|
import { describe, expect, it, vi } from "vitest";
|
|
4
5
|
import { I18nProvider } from "@/app/components/i18n-provider";
|
|
@@ -11,15 +12,24 @@ vi.mock("@/shared/components/doc-browser", () => ({
|
|
|
11
12
|
useDocBrowser: () => ({ open: vi.fn() }),
|
|
12
13
|
}));
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
function renderBottomNav(pathname: string) {
|
|
16
|
+
const queryClient = new QueryClient({
|
|
17
|
+
defaultOptions: { queries: { retry: false } },
|
|
18
|
+
});
|
|
19
|
+
return render(
|
|
20
|
+
<QueryClientProvider client={queryClient}>
|
|
17
21
|
<I18nProvider>
|
|
18
|
-
<MemoryRouter initialEntries={[
|
|
22
|
+
<MemoryRouter initialEntries={[pathname]}>
|
|
19
23
|
<MobileBottomNav />
|
|
20
24
|
</MemoryRouter>
|
|
21
|
-
</I18nProvider
|
|
22
|
-
|
|
25
|
+
</I18nProvider>
|
|
26
|
+
</QueryClientProvider>,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("MobileBottomNav", () => {
|
|
31
|
+
it("highlights the settings tab for nested settings routes", () => {
|
|
32
|
+
renderBottomNav("/providers");
|
|
23
33
|
|
|
24
34
|
expect(
|
|
25
35
|
screen.getByRole("link", { name: /settings/i }).getAttribute("aria-current"),
|
|
@@ -37,13 +47,7 @@ describe("MobileBottomNav", () => {
|
|
|
37
47
|
});
|
|
38
48
|
|
|
39
49
|
it("highlights the chat tab for chat routes", () => {
|
|
40
|
-
|
|
41
|
-
<I18nProvider>
|
|
42
|
-
<MemoryRouter initialEntries={["/chat/demo-session"]}>
|
|
43
|
-
<MobileBottomNav />
|
|
44
|
-
</MemoryRouter>
|
|
45
|
-
</I18nProvider>,
|
|
46
|
-
);
|
|
50
|
+
renderBottomNav("/chat/demo-session");
|
|
47
51
|
|
|
48
52
|
expect(
|
|
49
53
|
screen.getByRole("link", { name: /chat/i }).getAttribute("aria-current"),
|
|
@@ -51,13 +55,7 @@ describe("MobileBottomNav", () => {
|
|
|
51
55
|
});
|
|
52
56
|
|
|
53
57
|
it("opens the apps panel from the mobile navigation", () => {
|
|
54
|
-
|
|
55
|
-
<I18nProvider>
|
|
56
|
-
<MemoryRouter initialEntries={["/chat"]}>
|
|
57
|
-
<MobileBottomNav />
|
|
58
|
-
</MemoryRouter>
|
|
59
|
-
</I18nProvider>,
|
|
60
|
-
);
|
|
58
|
+
renderBottomNav("/chat");
|
|
61
59
|
|
|
62
60
|
fireEvent.click(screen.getByRole("button", { name: "Apps" }));
|
|
63
61
|
|
|
@@ -55,7 +55,7 @@ export function DocBrowserAddressToolbar({
|
|
|
55
55
|
onChange={(e) => onUrlInputChange(e.target.value)}
|
|
56
56
|
aria-label={t('docBrowserAddressLabel')}
|
|
57
57
|
placeholder={placeholder}
|
|
58
|
-
className="h-8 w-full rounded-lg border border-border/75 bg-background pl-8 pr-3 text-xs text-foreground transition-colors placeholder:text-muted-foreground/55 focus:
|
|
58
|
+
className="h-8 w-full rounded-lg border border-border/75 bg-background pl-8 pr-3 text-xs text-foreground transition-colors placeholder:text-muted-foreground/55 focus:outline-none focus:ring-0"
|
|
59
59
|
/>
|
|
60
60
|
</form>
|
|
61
61
|
<IconActionButton
|
|
@@ -63,7 +63,7 @@ export function ServerPathPickerDirectoryList({
|
|
|
63
63
|
'grid w-full grid-cols-[minmax(0,1fr)_8rem] items-center rounded-sm border border-transparent px-2 py-1.5 text-left text-sm text-foreground outline-none transition-colors',
|
|
64
64
|
selected
|
|
65
65
|
? 'border-primary/25 bg-primary/15'
|
|
66
|
-
: 'hover:bg-
|
|
66
|
+
: 'hover:bg-[var(--interaction-hover)] focus-visible:border-primary/35 focus-visible:bg-accent/70',
|
|
67
67
|
)}
|
|
68
68
|
onClick={() => onSelect(entry.path)}
|
|
69
69
|
onDoubleClick={() => onOpen(entry.path)}
|
|
@@ -112,7 +112,7 @@ export function ServerPathPickerLocations({
|
|
|
112
112
|
type="button"
|
|
113
113
|
className={cn(
|
|
114
114
|
'flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
|
|
115
|
-
active ? 'bg-primary/12 text-primary' : 'text-foreground hover:bg-
|
|
115
|
+
active ? 'bg-primary/12 text-primary' : 'text-foreground hover:bg-[var(--interaction-hover)]',
|
|
116
116
|
)}
|
|
117
117
|
aria-current={active ? 'location' : undefined}
|
|
118
118
|
onClick={() => onNavigate(item.path)}
|
|
@@ -99,7 +99,7 @@ export function ServerPathPickerNavigationBar({
|
|
|
99
99
|
disabled={!canGoUp || disabled}
|
|
100
100
|
/>
|
|
101
101
|
|
|
102
|
-
<div className="ml-1 flex h-9 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-border bg-background shadow-sm
|
|
102
|
+
<div className="ml-1 flex h-9 min-w-0 flex-1 items-center overflow-hidden rounded-md border border-border bg-background shadow-sm">
|
|
103
103
|
{editingAddress ? (
|
|
104
104
|
<Input
|
|
105
105
|
value={addressDraft}
|
|
@@ -136,7 +136,7 @@ export function ServerPathPickerNavigationBar({
|
|
|
136
136
|
<>
|
|
137
137
|
<button
|
|
138
138
|
type="button"
|
|
139
|
-
className="rounded px-1.5 py-1 text-sm text-muted-foreground hover:bg-
|
|
139
|
+
className="rounded px-1.5 py-1 text-sm text-muted-foreground hover:bg-[var(--interaction-hover)]"
|
|
140
140
|
aria-label={t('pathPickerShowFullAddress')}
|
|
141
141
|
onClick={beginAddressEdit}
|
|
142
142
|
>
|
|
@@ -149,7 +149,7 @@ export function ServerPathPickerNavigationBar({
|
|
|
149
149
|
) : null}
|
|
150
150
|
<button
|
|
151
151
|
type="button"
|
|
152
|
-
className="max-w-40 truncate rounded px-1.5 py-1 text-sm text-foreground hover:bg-
|
|
152
|
+
className="max-w-40 truncate rounded px-1.5 py-1 text-sm text-foreground hover:bg-[var(--interaction-hover)] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
153
153
|
onClick={() => onNavigate(breadcrumb.path)}
|
|
154
154
|
disabled={disabled}
|
|
155
155
|
>
|
|
@@ -31,7 +31,7 @@ describe('IconActionButton', () => {
|
|
|
31
31
|
const button = screen.getByRole('button', { name: 'More' });
|
|
32
32
|
expect(button.className).toContain('h-7');
|
|
33
33
|
expect(button.className).toContain('w-7');
|
|
34
|
-
expect(button.className).toContain('hover:bg-
|
|
34
|
+
expect(button.className).toContain('hover:bg-[var(--interaction-hover)]');
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
it('ignores restored pointer focus while preserving keyboard tooltip focus', () => {
|
|
@@ -31,7 +31,7 @@ const SIZE_CLASS: Record<IconActionButtonSize, string> = {
|
|
|
31
31
|
|
|
32
32
|
const TONE_CLASS: Record<IconActionButtonTone, string> = {
|
|
33
33
|
default:
|
|
34
|
-
'text-muted-foreground hover:bg-
|
|
34
|
+
'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
|
|
35
35
|
strong:
|
|
36
36
|
'text-muted-foreground hover:bg-black/10 hover:text-foreground disabled:text-muted-foreground/45 disabled:hover:bg-transparent disabled:hover:text-muted-foreground/45',
|
|
37
37
|
};
|
|
@@ -9,12 +9,12 @@ const buttonVariants = cva(
|
|
|
9
9
|
variant: {
|
|
10
10
|
default: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
|
|
11
11
|
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
12
|
-
outline: 'border border-border bg-card text-muted-foreground hover:bg-
|
|
13
|
-
secondary: 'bg-muted text-foreground hover:bg-
|
|
14
|
-
ghost: 'text-muted-foreground hover:bg-
|
|
12
|
+
outline: 'border border-border bg-card text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
13
|
+
secondary: 'bg-muted text-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
14
|
+
ghost: 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
15
15
|
link: 'text-primary underline-offset-4 hover:underline',
|
|
16
16
|
primary: 'bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm',
|
|
17
|
-
subtle: 'bg-muted text-muted-foreground hover:bg-
|
|
17
|
+
subtle: 'bg-muted text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
18
18
|
'primary-outline': 'border border-primary/30 text-primary hover:bg-primary hover:text-primary-foreground'
|
|
19
19
|
},
|
|
20
20
|
size: {
|
|
@@ -42,7 +42,7 @@ const DialogContent = React.forwardRef<
|
|
|
42
42
|
{...props}
|
|
43
43
|
>
|
|
44
44
|
{children}
|
|
45
|
-
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 transition-all duration-fast hover:bg-
|
|
45
|
+
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg p-1 opacity-70 transition-all duration-fast hover:bg-[var(--interaction-hover)] hover:text-accent-foreground hover:opacity-100 focus:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:pointer-events-none">
|
|
46
46
|
<X className="h-4 w-4 text-muted-foreground" />
|
|
47
47
|
<span className="sr-only">Close</span>
|
|
48
48
|
</DialogPrimitive.Close>
|
|
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
9
9
|
<input
|
|
10
10
|
type={type}
|
|
11
11
|
className={cn(
|
|
12
|
-
'flex h-9 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2 text-sm text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0
|
|
12
|
+
'flex h-9 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2 text-sm text-foreground file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
13
|
className
|
|
14
14
|
)}
|
|
15
15
|
ref={ref}
|
|
@@ -5,6 +5,8 @@ interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
5
5
|
children: React.ReactNode;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
export const SCROLL_BOTTOM_EDGE_FADE_CLASS = '[mask-image:linear-gradient(to_bottom,black_calc(100%_-_28px),transparent)] [-webkit-mask-image:linear-gradient(to_bottom,black_calc(100%_-_28px),transparent)]';
|
|
9
|
+
|
|
8
10
|
const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
|
|
9
11
|
({ className, children, ...props }, ref) => (
|
|
10
12
|
<div
|
|
@@ -119,7 +119,7 @@ const SelectItem = React.forwardRef<
|
|
|
119
119
|
<SelectPrimitive.Item
|
|
120
120
|
ref={ref}
|
|
121
121
|
className={cn(
|
|
122
|
-
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-
|
|
122
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 hover:bg-[var(--interaction-hover)]",
|
|
123
123
|
className
|
|
124
124
|
)}
|
|
125
125
|
{...props}
|
|
@@ -18,7 +18,7 @@ const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
|
|
|
18
18
|
ref={ref}
|
|
19
19
|
className={cn(
|
|
20
20
|
'switch-track peer inline-flex h-[22px] w-10 shrink-0 cursor-pointer items-center rounded-full border-2 transition-colors duration-fast focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:cursor-not-allowed disabled:opacity-50',
|
|
21
|
-
checked ? 'border-primary/70 bg-primary' : 'border-border/60 bg-muted hover:bg-
|
|
21
|
+
checked ? 'border-primary/70 bg-primary' : 'border-border/60 bg-muted hover:bg-[var(--interaction-hover)]',
|
|
22
22
|
className
|
|
23
23
|
)}
|
|
24
24
|
onClick={() => onCheckedChange?.(!checked)}
|
|
@@ -67,7 +67,7 @@ export function TabsTrigger({ value, children, className }: TabsTriggerProps) {
|
|
|
67
67
|
'inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-[13px] font-medium transition-all duration-fast focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:pointer-events-none disabled:opacity-50',
|
|
68
68
|
isActive
|
|
69
69
|
? 'bg-card text-foreground shadow-sm'
|
|
70
|
-
: 'text-muted-foreground hover:bg-
|
|
70
|
+
: 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-accent-foreground',
|
|
71
71
|
className
|
|
72
72
|
)}
|
|
73
73
|
>
|
|
@@ -7,7 +7,7 @@ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
7
7
|
({ className, ...props }, ref) => (
|
|
8
8
|
<textarea
|
|
9
9
|
className={cn(
|
|
10
|
-
"flex min-h-28 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0
|
|
10
|
+
"flex min-h-28 w-full rounded-xl border border-border/75 bg-card px-3.5 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/55 placeholder:font-normal transition-colors focus:outline-none focus:ring-0 disabled:cursor-not-allowed disabled:opacity-50",
|
|
11
11
|
className,
|
|
12
12
|
)}
|
|
13
13
|
ref={ref}
|
|
@@ -39,6 +39,7 @@ export type SessionContextWindowView = {
|
|
|
39
39
|
export type SessionActivityPreviewView = {
|
|
40
40
|
state: "running" | "completed" | "failed" | "cancelled" | "idle";
|
|
41
41
|
timestamp: string;
|
|
42
|
+
statusKind?: "thinking" | "tool-running" | "tool-completed" | "run-failed" | "run-interrupted";
|
|
42
43
|
statusText?: string;
|
|
43
44
|
replyText?: string;
|
|
44
45
|
};
|
|
@@ -68,6 +68,7 @@ export type ProviderInstanceView = {
|
|
|
68
68
|
isCustom: boolean;
|
|
69
69
|
enabled: boolean;
|
|
70
70
|
displayName?: string;
|
|
71
|
+
apiKeyRequired?: boolean;
|
|
71
72
|
apiKeySet: boolean;
|
|
72
73
|
apiKeyMasked?: string;
|
|
73
74
|
apiBase?: string | null;
|
|
@@ -621,6 +622,7 @@ export type ProviderTemplateView = {
|
|
|
621
622
|
envKey: string;
|
|
622
623
|
isGateway?: boolean;
|
|
623
624
|
isLocal?: boolean;
|
|
625
|
+
apiKeyRequired?: boolean;
|
|
624
626
|
defaultApiBase?: string;
|
|
625
627
|
logo?: string;
|
|
626
628
|
apiBaseHelp?: {
|