@nextclaw/ui 0.15.1 → 0.15.2
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 +31 -0
- package/dist/assets/{api-b1R9VdeH.js → api-B7Qigvtm.js} +2 -2
- package/dist/assets/{appearance-settings-page-D2O9pch9.js → appearance-settings-page-CuE0Iiqj.js} +1 -1
- package/dist/assets/{book-open-B77lQ2Tg.js → book-open-Ba2f7YiG.js} +1 -1
- package/dist/assets/channels-list-page-CTsM6TcP.js +8 -0
- package/dist/assets/{chat-page-CZ7WxPir.js → chat-page-C_5dcuE3.js} +1 -1
- package/dist/assets/config-split-page-Fbb4PtTf.js +1 -0
- package/dist/assets/{confirm-dialog-C-NXSioX.js → confirm-dialog-D2EG3nqE.js} +1 -1
- package/dist/assets/{desktop-update-config-usHpXugP.js → desktop-update-config-BGR3HUi1.js} +1 -1
- package/dist/assets/{dist-jdGmbb6w.js → dist-7JTjycxE.js} +1 -1
- package/dist/assets/{dist-YtoqU-AT.js → dist-wXLhk4hl.js} +1 -1
- package/dist/assets/doc-browser-bHyXnxlj.js +1 -0
- package/dist/assets/doc-browser-context-CYAGm5d5.js +1 -0
- package/dist/assets/doc-browser-gTrQ3jCF.js +1 -0
- package/dist/assets/{es2015-bNpL2LKy.js → es2015-j1SRCIX5.js} +1 -1
- package/dist/assets/{external-link-BM6fk8q-.js → external-link-DxuPaDoK.js} +1 -1
- package/dist/assets/form-actions-BztL7P_T.js +1 -0
- package/dist/assets/{i18n-provider-CntWolRA.js → i18n-provider-B4oMxOFx.js} +1 -1
- package/dist/assets/index-CvrvblgX.css +1 -0
- package/dist/assets/index-Obd4JYU2.js +104 -0
- package/dist/assets/loader-circle-CdzgK4eH.js +1 -0
- package/dist/assets/{mcp-marketplace-page-CLmUstLN.js → mcp-marketplace-page-Bt7CuOoq.js} +2 -2
- package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +1 -0
- package/dist/assets/model-config-page-DeKk9gvE.js +1 -0
- package/dist/assets/plus-Bx7wQbhG.js +1 -0
- package/dist/assets/{provider-scoped-model-input-Cm_a4GHf.js → provider-scoped-model-input-DsH2LF1q.js} +1 -1
- package/dist/assets/providers-config-page-BFnGp68F.js +1 -0
- package/dist/assets/{react-BN8uzPnp.js → react-UirzUuST.js} +1 -1
- package/dist/assets/{refresh-cw-BpVcFPmJ.js → refresh-cw-eT8BF-8B.js} +1 -1
- package/dist/assets/remote-CDkHZtA8.js +1 -0
- package/dist/assets/rotate-cw-1JqxHTuw.js +1 -0
- package/dist/assets/runtime-config-page-BelAqiNG.js +1 -0
- package/dist/assets/{save-BpFW5fpk.js → save-Cb2g0BOi.js} +1 -1
- package/dist/assets/{search-D0uO7enf.js → search-BcHjwdsb.js} +1 -1
- package/dist/assets/search-config-page-zPm6--Ig.js +1 -0
- package/dist/assets/secrets-config-page-Cxyp-KuQ.js +3 -0
- package/dist/assets/security-config-B_gCq9fn.js +1 -0
- package/dist/assets/{select-Bh-CrA4p.js → select-CdSEOwVM.js} +1 -1
- package/dist/assets/{setting-row-BJfCHRcb.js → setting-row-CTAtGexr.js} +1 -1
- package/dist/assets/{settings-2-Blzi1vr6.js → settings-2-2DWauOjY.js} +1 -1
- package/dist/assets/{side-dock-ZdaSrQVB.js → side-dock-DPvakJ0a.js} +2 -2
- package/dist/assets/{skeleton-BLlEzebZ.js → skeleton-P5oM14E1.js} +1 -1
- package/dist/assets/{switch-CFLJFNSd.js → switch-BU_UEULb.js} +1 -1
- package/dist/assets/{tag-chip-CzGihiQx.js → tag-chip-D-WsKoT1.js} +1 -1
- package/dist/assets/{tooltip-CdcrBjrC.js → tooltip-CjkOaHW7.js} +1 -1
- package/dist/assets/{use-config-CVPgsBmU.js → use-config--hx5oxQn.js} +1 -1
- package/dist/assets/{use-confirm-dialog-CSeBk9SF.js → use-confirm-dialog-CD9a_SK1.js} +1 -1
- package/dist/assets/x-DT5x45JU.js +1 -0
- package/dist/index.html +38 -30
- package/index.html +12 -4
- package/package.json +9 -9
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
- package/src/app/components/layout/sidebar-items.tsx +43 -4
- package/src/app/components/layout/sidebar.tsx +83 -61
- package/src/app/configs/app-navigation.config.ts +49 -0
- package/src/app/styles/design-system.css +82 -0
- package/src/features/channels/components/config/channel-form.tsx +14 -11
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +40 -0
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +3 -1
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
- package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
- package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +29 -30
- package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
- package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
- package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
- package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
- package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
- package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
- package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
- package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
- package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +159 -56
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +3 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +66 -7
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
- package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
- package/src/features/chat/stores/chat-session-list.store.ts +28 -5
- package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
- package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
- package/src/features/panel-apps/index.ts +1 -0
- package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
- package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
- package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
- package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
- package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
- package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
- package/src/features/settings/components/config/provider-form.tsx +11 -8
- package/src/features/settings/components/config/provider-models-section.tsx +7 -7
- package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
- package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
- package/src/features/settings/pages/model-config-page.tsx +26 -25
- package/src/features/settings/pages/providers-config-page.tsx +10 -13
- package/src/features/settings/pages/search-config-page.tsx +8 -5
- package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
- package/src/index.css +18 -0
- package/src/shared/components/config-split-page.tsx +6 -6
- package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
- package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
- package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +5 -1
- package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
- package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
- package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
- package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
- package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
- package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
- package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
- package/src/shared/components/ui/actions/form-actions.tsx +25 -0
- package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
- package/src/shared/components/ui/card.tsx +12 -4
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
- package/src/shared/hooks/use-server-path-browse.ts +26 -4
- package/src/shared/hooks/use-server-path-read.ts +11 -1
- package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +16 -0
- package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
- package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -0
- package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
- package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
- package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
- package/src/shared/lib/theme/index.test.ts +21 -0
- package/src/shared/lib/theme/index.ts +6 -4
- package/src/vite-env.d.ts +2 -0
- package/dist/assets/channels-list-page-DcsIQc6A.js +0 -8
- package/dist/assets/config-split-page-BWQ6_Raq.js +0 -1
- package/dist/assets/doc-browser-B_Tf9n70.js +0 -1
- package/dist/assets/doc-browser-BdZPFOVa.js +0 -1
- package/dist/assets/doc-browser-context-DwokemUn.js +0 -1
- package/dist/assets/index-BIcEw90u.js +0 -104
- package/dist/assets/index-BiNSTKTU.css +0 -1
- package/dist/assets/loader-circle-MBgNSVIH.js +0 -1
- package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +0 -1
- package/dist/assets/model-config-page-Di0ySJD2.js +0 -1
- package/dist/assets/plus-vDqW5Kxp.js +0 -1
- package/dist/assets/providers-config-page-BAf2W0MV.js +0 -1
- package/dist/assets/remote-CLQ0IBM8.js +0 -1
- package/dist/assets/rotate-cw-CvNBv7_w.js +0 -1
- package/dist/assets/runtime-config-page-CflvvoDc.js +0 -1
- package/dist/assets/search-config-page-D0klFUXd.js +0 -1
- package/dist/assets/secrets-config-page-dVZ631Wp.js +0 -3
- package/dist/assets/security-config-C5gnlkuj.js +0 -1
- package/dist/assets/x-BF2KQ--V.js +0 -1
- /package/dist/assets/{config-hints-fGnUjDe9.js → config-hints-C_iP1zl_.js} +0 -0
|
@@ -12,12 +12,12 @@ describe('PwaShellThemeManager', () => {
|
|
|
12
12
|
document.body.style.backgroundColor = '';
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
it('applies
|
|
15
|
+
it('applies default work shell colors when no theme is selected', () => {
|
|
16
16
|
pwaShellThemeManager.syncCurrentTheme();
|
|
17
17
|
|
|
18
18
|
const meta = document.querySelector('meta[name="theme-color"]');
|
|
19
|
-
expect(meta?.getAttribute('content')).toBe('#
|
|
20
|
-
expect(document.body.style.backgroundColor).toBe('rgb(
|
|
19
|
+
expect(meta?.getAttribute('content')).toBe('#FFFFFF');
|
|
20
|
+
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
it('applies natural shell colors explicitly', () => {
|
|
@@ -36,6 +36,14 @@ describe('PwaShellThemeManager', () => {
|
|
|
36
36
|
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('applies work shell colors explicitly', () => {
|
|
40
|
+
pwaShellThemeManager.syncTheme('work');
|
|
41
|
+
|
|
42
|
+
const meta = document.querySelector('meta[name="theme-color"]');
|
|
43
|
+
expect(meta?.getAttribute('content')).toBe('#FFFFFF');
|
|
44
|
+
expect(document.body.style.backgroundColor).toBe('rgb(255, 255, 255)');
|
|
45
|
+
});
|
|
46
|
+
|
|
39
47
|
it('applies warm shell colors', () => {
|
|
40
48
|
pwaShellThemeManager.syncTheme('warm');
|
|
41
49
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DEFAULT_UI_THEME,
|
|
2
3
|
getThemeAppearance,
|
|
3
4
|
normalizeTheme,
|
|
4
5
|
type UiTheme,
|
|
@@ -6,6 +7,7 @@ import {
|
|
|
6
7
|
|
|
7
8
|
const PWA_SHELL_THEME_COLORS: Record<UiTheme, string> = {
|
|
8
9
|
natural: '#FAF9F7',
|
|
10
|
+
work: '#FFFFFF',
|
|
9
11
|
minimal: '#FFFFFF',
|
|
10
12
|
warm: '#FAF8F4',
|
|
11
13
|
cool: '#F8FAFC',
|
|
@@ -33,7 +35,7 @@ export class PwaShellThemeManager {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
36
|
-
this.syncTheme(normalizeTheme(currentTheme) ??
|
|
38
|
+
this.syncTheme(normalizeTheme(currentTheme) ?? DEFAULT_UI_THEME);
|
|
37
39
|
};
|
|
38
40
|
|
|
39
41
|
private updateThemeMeta = (themeColor: string) => {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
import {
|
|
8
8
|
AlertTriangle,
|
|
9
9
|
CheckCircle2,
|
|
10
|
+
ChevronDown,
|
|
10
11
|
CircleDashed,
|
|
11
12
|
LoaderCircle,
|
|
12
13
|
MoreVertical,
|
|
@@ -53,6 +54,7 @@ export function ServiceAppsPanel({
|
|
|
53
54
|
const [discoveredActionsByApp, setDiscoveredActionsByApp] = useState<
|
|
54
55
|
Record<string, ServiceActionView[]>
|
|
55
56
|
>({});
|
|
57
|
+
const [expandedActionsByApp, setExpandedActionsByApp] = useState<Record<string, boolean>>({});
|
|
56
58
|
|
|
57
59
|
const refetch = () => {
|
|
58
60
|
void serviceApps.refetch();
|
|
@@ -86,12 +88,16 @@ export function ServiceAppsPanel({
|
|
|
86
88
|
...current,
|
|
87
89
|
[appId]: result.actions,
|
|
88
90
|
}));
|
|
91
|
+
setExpandedActionsByApp((current) => ({
|
|
92
|
+
...current,
|
|
93
|
+
[appId]: true,
|
|
94
|
+
}));
|
|
89
95
|
});
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
return (
|
|
93
99
|
<div className="flex h-full min-h-0 flex-col bg-card text-card-foreground">
|
|
94
|
-
<div className="flex items-center justify-between gap-2 border-b border-border/
|
|
100
|
+
<div className="flex items-center justify-between gap-2 border-b border-border/60 px-4 py-3">
|
|
95
101
|
<div className="flex min-w-0 items-center gap-2">
|
|
96
102
|
{headerContent ?? (
|
|
97
103
|
<>
|
|
@@ -110,20 +116,31 @@ export function ServiceAppsPanel({
|
|
|
110
116
|
</div>
|
|
111
117
|
|
|
112
118
|
{apps.length === 0 ? (
|
|
113
|
-
<div className="flex flex-1 items-center justify-center px-6
|
|
114
|
-
|
|
119
|
+
<div className="flex flex-1 items-center justify-center px-6 py-8 text-center">
|
|
120
|
+
<div className="w-full max-w-xs">
|
|
121
|
+
<div className="mx-auto flex h-10 w-10 items-center justify-center rounded-xl bg-muted text-muted-foreground">
|
|
122
|
+
<Server className="h-5 w-5" />
|
|
123
|
+
</div>
|
|
124
|
+
<h2 className="mt-3 text-sm font-semibold text-foreground">{t('serviceAppsEmptyTitle')}</h2>
|
|
125
|
+
<p className="mt-1 text-xs leading-5 text-muted-foreground">{t('serviceAppsEmptyDescription')}</p>
|
|
126
|
+
</div>
|
|
115
127
|
</div>
|
|
116
128
|
) : (
|
|
117
|
-
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto p-3">
|
|
118
|
-
<div className="space-y-
|
|
129
|
+
<div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto bg-muted/25 p-3">
|
|
130
|
+
<div className="space-y-2.5">
|
|
119
131
|
{apps.map((app) => (
|
|
120
132
|
<ServiceAppCard
|
|
121
133
|
key={app.id}
|
|
122
134
|
app={app}
|
|
123
135
|
actions={discoveredActionsByApp[app.id] ?? actions.filter((action) => action.appId === app.id)}
|
|
136
|
+
actionsOpen={Boolean(expandedActionsByApp[app.id])}
|
|
124
137
|
grants={grants}
|
|
125
138
|
deletePending={deleteServiceApp.isPending}
|
|
126
139
|
isDiscovering={discoverServiceAppActions.isPending}
|
|
140
|
+
onActionsOpenChange={(open) => setExpandedActionsByApp((current) => ({
|
|
141
|
+
...current,
|
|
142
|
+
[app.id]: open,
|
|
143
|
+
}))}
|
|
127
144
|
onDiscover={discover}
|
|
128
145
|
onDelete={(appId) => void deleteServiceApp.mutate(appId)}
|
|
129
146
|
onRestart={(appId) => void restartServiceApp.mutate(appId)}
|
|
@@ -143,9 +160,11 @@ export function ServiceAppsPanel({
|
|
|
143
160
|
function ServiceAppCard({
|
|
144
161
|
app,
|
|
145
162
|
actions,
|
|
163
|
+
actionsOpen,
|
|
146
164
|
grants,
|
|
147
165
|
deletePending,
|
|
148
166
|
isDiscovering,
|
|
167
|
+
onActionsOpenChange,
|
|
149
168
|
onDiscover,
|
|
150
169
|
onDelete,
|
|
151
170
|
onRestart,
|
|
@@ -153,9 +172,11 @@ function ServiceAppCard({
|
|
|
153
172
|
}: {
|
|
154
173
|
app: ServiceAppRecordView;
|
|
155
174
|
actions: ServiceActionView[];
|
|
175
|
+
actionsOpen: boolean;
|
|
156
176
|
grants: ServiceActionGrantView[];
|
|
157
177
|
deletePending: boolean;
|
|
158
178
|
isDiscovering: boolean;
|
|
179
|
+
onActionsOpenChange: (open: boolean) => void;
|
|
159
180
|
onDiscover: (appId: string) => void;
|
|
160
181
|
onDelete: (appId: string) => void;
|
|
161
182
|
onRestart: (appId: string) => void;
|
|
@@ -172,21 +193,21 @@ function ServiceAppCard({
|
|
|
172
193
|
|
|
173
194
|
return (
|
|
174
195
|
<TooltipProvider delayDuration={250}>
|
|
175
|
-
<section className="rounded-
|
|
176
|
-
<div className="flex items-start
|
|
177
|
-
<div className="
|
|
196
|
+
<section className="overflow-hidden rounded-xl border border-border/70 bg-card shadow-[0_1px_2px_rgba(30,20,10,0.03)]">
|
|
197
|
+
<div className="flex items-start gap-3 px-3 pt-3">
|
|
198
|
+
<div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
199
|
+
<Server className="h-4 w-4" />
|
|
200
|
+
</div>
|
|
201
|
+
<div className="min-w-0 flex-1">
|
|
178
202
|
<div className="flex items-center gap-2">
|
|
179
203
|
<div className="truncate text-sm font-semibold text-foreground">{app.title}</div>
|
|
180
204
|
<ServiceAppStatusBadge status={app.status} />
|
|
181
205
|
</div>
|
|
182
206
|
{app.description ? (
|
|
183
|
-
<div className="mt-1 line-clamp-2 text-xs text-muted-foreground">{app.description}</div>
|
|
184
|
-
) : null}
|
|
185
|
-
{app.lastError ? (
|
|
186
|
-
<div className="mt-2 rounded bg-rose-50 px-2 py-1 text-xs text-rose-700">{app.lastError}</div>
|
|
207
|
+
<div className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">{app.description}</div>
|
|
187
208
|
) : null}
|
|
188
209
|
</div>
|
|
189
|
-
<div className="flex shrink-0 items-center gap-
|
|
210
|
+
<div className="flex shrink-0 items-center gap-0.5">
|
|
190
211
|
<ServiceAppIconButton
|
|
191
212
|
disabled={isDiscovering || !canConnectAndDiscover}
|
|
192
213
|
icon={Radar}
|
|
@@ -207,7 +228,7 @@ function ServiceAppCard({
|
|
|
207
228
|
<PopoverTrigger asChild>
|
|
208
229
|
<button
|
|
209
230
|
type="button"
|
|
210
|
-
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
231
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-50"
|
|
211
232
|
aria-label={t('serviceAppsMoreActions')}
|
|
212
233
|
disabled={deletePending}
|
|
213
234
|
>
|
|
@@ -229,17 +250,85 @@ function ServiceAppCard({
|
|
|
229
250
|
</Popover>
|
|
230
251
|
</div>
|
|
231
252
|
</div>
|
|
253
|
+
|
|
254
|
+
{app.lastError ? (
|
|
255
|
+
<div className="mx-3 mt-3 rounded-lg border border-rose-200/70 bg-rose-50 px-2.5 py-1.5 text-xs text-rose-700">
|
|
256
|
+
{app.lastError}
|
|
257
|
+
</div>
|
|
258
|
+
) : null}
|
|
259
|
+
|
|
232
260
|
<ServiceAppDiagnostics app={app} />
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
261
|
+
|
|
262
|
+
<div className="mt-3 border-t border-border/50 bg-muted/30">
|
|
263
|
+
<button
|
|
264
|
+
type="button"
|
|
265
|
+
onClick={() => onActionsOpenChange(!actionsOpen)}
|
|
266
|
+
className="flex w-full items-start justify-between gap-2 px-3 py-2 text-left transition-colors hover:bg-muted/40"
|
|
267
|
+
aria-expanded={actionsOpen}
|
|
268
|
+
>
|
|
269
|
+
<div className="min-w-0 flex-1">
|
|
270
|
+
<div className="flex items-center gap-2">
|
|
271
|
+
<span className="text-[11px] font-medium uppercase tracking-wide text-muted-foreground/80">
|
|
272
|
+
{t('serviceAppsActionsSection')}
|
|
273
|
+
</span>
|
|
274
|
+
<span className="rounded-full bg-card px-1.5 py-0.5 text-[11px] tabular-nums text-muted-foreground/70">
|
|
275
|
+
{actions.length}
|
|
276
|
+
</span>
|
|
277
|
+
</div>
|
|
278
|
+
{!actionsOpen ? (
|
|
279
|
+
actions.length === 0 ? (
|
|
280
|
+
<div className="mt-1 text-[11px] text-muted-foreground">
|
|
281
|
+
{t('serviceAppsActionsEmpty')}
|
|
282
|
+
</div>
|
|
283
|
+
) : (
|
|
284
|
+
<div className="mt-1.5 flex flex-wrap gap-1">
|
|
285
|
+
{actions.slice(0, 4).map((action) => (
|
|
286
|
+
<span
|
|
287
|
+
key={action.id}
|
|
288
|
+
className="max-w-[9.5rem] truncate rounded-md border border-border/50 bg-card px-1.5 py-0.5 text-[11px] text-muted-foreground"
|
|
289
|
+
title={action.title ?? action.name}
|
|
290
|
+
>
|
|
291
|
+
{action.title ?? action.name}
|
|
292
|
+
</span>
|
|
293
|
+
))}
|
|
294
|
+
{actions.length > 4 ? (
|
|
295
|
+
<span className="rounded-md bg-card px-1.5 py-0.5 text-[11px] text-muted-foreground/70">
|
|
296
|
+
+{actions.length - 4}
|
|
297
|
+
</span>
|
|
298
|
+
) : null}
|
|
299
|
+
</div>
|
|
300
|
+
)
|
|
301
|
+
) : null}
|
|
302
|
+
</div>
|
|
303
|
+
<ChevronDown
|
|
304
|
+
className={cn(
|
|
305
|
+
'mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground/70 transition-transform',
|
|
306
|
+
actionsOpen && 'rotate-180',
|
|
307
|
+
)}
|
|
240
308
|
/>
|
|
241
|
-
|
|
309
|
+
</button>
|
|
310
|
+
{actionsOpen ? (
|
|
311
|
+
<div className="px-3 pb-2.5">
|
|
312
|
+
{actions.length === 0 ? (
|
|
313
|
+
<div className="rounded-lg border border-dashed border-border/70 bg-card/70 px-2.5 py-2 text-[11px] text-muted-foreground">
|
|
314
|
+
{t('serviceAppsActionsEmpty')}
|
|
315
|
+
</div>
|
|
316
|
+
) : (
|
|
317
|
+
<div className="space-y-1.5">
|
|
318
|
+
{actions.map((action) => (
|
|
319
|
+
<ServiceActionRow
|
|
320
|
+
key={action.id}
|
|
321
|
+
action={action}
|
|
322
|
+
grants={grants.filter((grant) => grant.actionId === action.id)}
|
|
323
|
+
onRevoke={onRevoke}
|
|
324
|
+
/>
|
|
325
|
+
))}
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
) : null}
|
|
242
330
|
</div>
|
|
331
|
+
|
|
243
332
|
<ConfirmDialog
|
|
244
333
|
open={isDeleteDialogOpen}
|
|
245
334
|
onOpenChange={setIsDeleteDialogOpen}
|
|
@@ -263,7 +352,7 @@ function ServiceAppStatusBadge({ status }: { status: ServiceAppStatus }) {
|
|
|
263
352
|
<TooltipTrigger asChild>
|
|
264
353
|
<span
|
|
265
354
|
className={cn(
|
|
266
|
-
'inline-flex shrink-0 items-center gap-1 rounded px-1.5 py-0.5 text-[11px] font-medium',
|
|
355
|
+
'inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[11px] font-medium',
|
|
267
356
|
view.className,
|
|
268
357
|
)}
|
|
269
358
|
aria-label={view.label}
|
|
@@ -345,7 +434,7 @@ function ServiceAppIconButton({
|
|
|
345
434
|
type="button"
|
|
346
435
|
onClick={onClick}
|
|
347
436
|
disabled={disabled}
|
|
348
|
-
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-
|
|
437
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:cursor-not-allowed disabled:text-muted-foreground/40 disabled:opacity-60 disabled:hover:bg-transparent"
|
|
349
438
|
aria-label={label}
|
|
350
439
|
>
|
|
351
440
|
<Icon className="h-3.5 w-3.5" />
|
|
@@ -386,6 +475,7 @@ function ServiceAppMenuItem({
|
|
|
386
475
|
}
|
|
387
476
|
|
|
388
477
|
function ServiceAppDiagnostics({ app }: { app: ServiceAppRecordView }) {
|
|
478
|
+
const [open, setOpen] = useState(false);
|
|
389
479
|
const command = [app.command, ...(app.args ?? [])]
|
|
390
480
|
.filter((value): value is string => Boolean(value?.trim()))
|
|
391
481
|
.join(' ');
|
|
@@ -403,11 +493,29 @@ function ServiceAppDiagnostics({ app }: { app: ServiceAppRecordView }) {
|
|
|
403
493
|
}
|
|
404
494
|
|
|
405
495
|
return (
|
|
406
|
-
<
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
496
|
+
<div className="mx-3 mt-3 overflow-hidden rounded-lg border border-border/50 bg-muted/35">
|
|
497
|
+
<button
|
|
498
|
+
type="button"
|
|
499
|
+
onClick={() => setOpen((value) => !value)}
|
|
500
|
+
className="flex w-full items-center justify-between gap-2 px-2.5 py-1.5 text-left transition-colors hover:bg-muted/50"
|
|
501
|
+
aria-expanded={open}
|
|
502
|
+
>
|
|
503
|
+
<span className="text-[11px] font-medium text-muted-foreground">{t('serviceAppsDetails')}</span>
|
|
504
|
+
<ChevronDown
|
|
505
|
+
className={cn(
|
|
506
|
+
'h-3.5 w-3.5 shrink-0 text-muted-foreground/70 transition-transform',
|
|
507
|
+
open && 'rotate-180',
|
|
508
|
+
)}
|
|
509
|
+
/>
|
|
510
|
+
</button>
|
|
511
|
+
{open ? (
|
|
512
|
+
<dl className="grid grid-cols-[4.75rem_minmax(0,1fr)] gap-x-2 gap-y-1 border-t border-border/50 px-2.5 py-2 text-[11px]">
|
|
513
|
+
{rows.map((row) => (
|
|
514
|
+
<ServiceAppDiagnosticRow key={row.label} label={row.label} value={row.value} />
|
|
515
|
+
))}
|
|
516
|
+
</dl>
|
|
517
|
+
) : null}
|
|
518
|
+
</div>
|
|
411
519
|
);
|
|
412
520
|
}
|
|
413
521
|
|
|
@@ -438,10 +546,12 @@ function ServiceActionRow({
|
|
|
438
546
|
onRevoke: (grant: ServiceActionGrantView) => void;
|
|
439
547
|
}) {
|
|
440
548
|
return (
|
|
441
|
-
<div className="rounded
|
|
549
|
+
<div className="rounded-lg border border-border/50 bg-card px-2.5 py-2">
|
|
442
550
|
<div className="flex items-center justify-between gap-2">
|
|
443
551
|
<div className="flex min-w-0 items-center gap-2">
|
|
444
|
-
<
|
|
552
|
+
<span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-muted text-muted-foreground">
|
|
553
|
+
<Wrench className="h-3.5 w-3.5" />
|
|
554
|
+
</span>
|
|
445
555
|
<div className="min-w-0">
|
|
446
556
|
<div className="truncate text-xs font-medium text-foreground">{action.title ?? action.name}</div>
|
|
447
557
|
{action.description ? (
|
|
@@ -451,15 +561,15 @@ function ServiceActionRow({
|
|
|
451
561
|
</div>
|
|
452
562
|
<div className="flex shrink-0 items-center gap-1">
|
|
453
563
|
{action.runtimeState ? (
|
|
454
|
-
<span className="rounded bg-
|
|
564
|
+
<span className="rounded-full bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground">
|
|
455
565
|
{t(`serviceAppsRuntimeState_${action.runtimeState}`)}
|
|
456
566
|
</span>
|
|
457
567
|
) : null}
|
|
458
|
-
<span className="rounded bg-
|
|
568
|
+
<span className="rounded-full bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground">{action.risk}</span>
|
|
459
569
|
</div>
|
|
460
570
|
</div>
|
|
461
571
|
{grants.map((grant) => (
|
|
462
|
-
<div key={`${grant.caller.surface}:${grant.caller.appId}:${grant.actionId}`} className="mt-1 flex items-center justify-between gap-2 pl-
|
|
572
|
+
<div key={`${grant.caller.surface}:${grant.caller.appId}:${grant.actionId}`} className="mt-1.5 flex items-center justify-between gap-2 pl-8">
|
|
463
573
|
<div className="flex min-w-0 items-center gap-1.5 text-[11px] text-muted-foreground">
|
|
464
574
|
<ShieldCheck className="h-3 w-3 shrink-0 text-emerald-500" />
|
|
465
575
|
<span className="truncate">{t('serviceAppsGrantedTo')} {grant.caller.appId}</span>
|
|
@@ -467,7 +577,7 @@ function ServiceActionRow({
|
|
|
467
577
|
<button
|
|
468
578
|
type="button"
|
|
469
579
|
onClick={() => onRevoke(grant)}
|
|
470
|
-
className="rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-
|
|
580
|
+
className="rounded-md p-1 text-muted-foreground/70 transition-colors hover:bg-muted hover:text-rose-600"
|
|
471
581
|
title={t('serviceAppsRevokeGrant')}
|
|
472
582
|
aria-label={t('serviceAppsRevokeGrant')}
|
|
473
583
|
>
|
|
@@ -28,7 +28,7 @@ export function ProviderAuthSection(props: ProviderAuthSectionProps) {
|
|
|
28
28
|
if (providerAuth?.kind !== 'device_code') return null;
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<div className="space-y-2 rounded-xl border border-
|
|
31
|
+
<div className="space-y-2 rounded-xl border border-border/60 bg-muted/35 p-3">
|
|
32
32
|
<Label className="text-sm font-medium text-foreground">
|
|
33
33
|
{providerAuth.displayName || t('providerAuthSectionTitle')}
|
|
34
34
|
</Label>
|
|
@@ -13,6 +13,7 @@ import { MaskedInput } from '@/shared/components/common/masked-input';
|
|
|
13
13
|
import { Button } from '@/shared/components/ui/button';
|
|
14
14
|
import { Input } from '@/shared/components/ui/input';
|
|
15
15
|
import { Label } from '@/shared/components/ui/label';
|
|
16
|
+
import { FormActions } from '@/shared/components/ui/actions/form-actions';
|
|
16
17
|
import { getLanguage, t } from '@/shared/lib/i18n';
|
|
17
18
|
import type { ThinkingLevel } from '@/shared/lib/api';
|
|
18
19
|
import {
|
|
@@ -548,14 +549,16 @@ function ProviderFormDetailPane(props: ProviderFormDetailPaneProps) {
|
|
|
548
549
|
/>
|
|
549
550
|
</ConfigSplitPaneBody>
|
|
550
551
|
|
|
551
|
-
<ConfigSplitPaneFooter
|
|
552
|
-
<
|
|
553
|
-
<
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
552
|
+
<ConfigSplitPaneFooter>
|
|
553
|
+
<FormActions align="between">
|
|
554
|
+
<Button type='button' variant='outline' size='sm' onClick={onTestConnection} disabled={isTestPending}>
|
|
555
|
+
<CircleDotDashed className='mr-1.5 h-3.5 w-3.5' />
|
|
556
|
+
{isTestPending ? t('providerTestingConnection') : t('providerTestConnection')}
|
|
557
|
+
</Button>
|
|
558
|
+
<Button type='submit' size='sm' disabled={isUpdatePending || !hasChanges}>
|
|
559
|
+
{isUpdatePending ? t('saving') : hasChanges ? t('save') : t('unchanged')}
|
|
560
|
+
</Button>
|
|
561
|
+
</FormActions>
|
|
559
562
|
</ConfigSplitPaneFooter>
|
|
560
563
|
</form>
|
|
561
564
|
</ConfigSplitDetailPane>
|
|
@@ -99,7 +99,7 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
|
|
|
99
99
|
) : null}
|
|
100
100
|
|
|
101
101
|
{models.length === 0 ? (
|
|
102
|
-
<div className="rounded-xl border border-dashed border-border bg-muted/
|
|
102
|
+
<div className="rounded-xl border border-dashed border-border/65 bg-muted/30 px-4 py-6 text-center">
|
|
103
103
|
<p className="text-sm text-muted-foreground">{t('providerModelsEmptyShort')}</p>
|
|
104
104
|
{!showModelInput ? (
|
|
105
105
|
<button
|
|
@@ -123,14 +123,14 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
|
|
|
123
123
|
return (
|
|
124
124
|
<div
|
|
125
125
|
key={modelName}
|
|
126
|
-
className="group inline-flex max-w-full items-center gap-1 rounded-full border border-border bg-
|
|
126
|
+
className="group inline-flex max-w-full items-center gap-1 rounded-full border border-border/55 bg-muted/45 px-3 py-1.5"
|
|
127
127
|
>
|
|
128
128
|
<span className="max-w-[140px] truncate text-sm text-foreground sm:max-w-[220px]">{modelName}</span>
|
|
129
129
|
<Popover>
|
|
130
130
|
<PopoverTrigger asChild>
|
|
131
131
|
<button
|
|
132
132
|
type="button"
|
|
133
|
-
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-
|
|
133
|
+
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-muted hover:text-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
|
|
134
134
|
aria-label={t('providerModelThinkingTitle')}
|
|
135
135
|
title={t('providerModelThinkingTitle')}
|
|
136
136
|
>
|
|
@@ -138,7 +138,7 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
|
|
|
138
138
|
</button>
|
|
139
139
|
</PopoverTrigger>
|
|
140
140
|
<PopoverContent className="w-80 space-y-3">
|
|
141
|
-
<div className="flex items-center justify-between gap-3 rounded-lg
|
|
141
|
+
<div className="flex items-center justify-between gap-3 rounded-lg bg-muted/50 px-3 py-2">
|
|
142
142
|
<div className="min-w-0">
|
|
143
143
|
<p className="text-xs font-semibold text-foreground">{t('providerModelVisionTitle')}</p>
|
|
144
144
|
<p className="text-xs text-muted-foreground">{t('providerModelVisionHint')}</p>
|
|
@@ -159,8 +159,8 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
|
|
|
159
159
|
onClick={() => onToggleModelThinkingLevel(modelName, level)}
|
|
160
160
|
className={`rounded-full border px-2.5 py-1 text-xs font-medium transition-colors ${
|
|
161
161
|
selected
|
|
162
|
-
? 'border-
|
|
163
|
-
: 'border-border bg-
|
|
162
|
+
? 'border-foreground/15 bg-foreground text-background'
|
|
163
|
+
: 'border-border/55 bg-muted/50 text-muted-foreground hover:border-border hover:bg-muted hover:text-foreground'
|
|
164
164
|
}`}
|
|
165
165
|
>
|
|
166
166
|
{formatThinkingLevelLabel(level)}
|
|
@@ -198,7 +198,7 @@ export function ProviderModelsSection(props: ProviderModelsSectionProps) {
|
|
|
198
198
|
<button
|
|
199
199
|
type="button"
|
|
200
200
|
onClick={() => onRemoveModel(modelName)}
|
|
201
|
-
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-
|
|
201
|
+
className="inline-flex h-5 w-5 items-center justify-center rounded-full text-muted-foreground/70 opacity-100 transition-opacity hover:bg-muted hover:text-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
|
|
202
202
|
aria-label={t('remove')}
|
|
203
203
|
>
|
|
204
204
|
<X className="h-3 w-3" />
|
|
@@ -16,7 +16,7 @@ export function ProviderPillSelector(props: ProviderPillSelectorProps) {
|
|
|
16
16
|
type="button"
|
|
17
17
|
onClick={() => onChange(option.value)}
|
|
18
18
|
aria-pressed={selected}
|
|
19
|
-
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${selected ? 'border-
|
|
19
|
+
className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${selected ? 'border-foreground/15 bg-foreground text-background' : 'border-border/55 bg-muted/50 text-muted-foreground hover:border-border hover:bg-muted hover:text-foreground'}`}
|
|
20
20
|
>
|
|
21
21
|
{option.label}
|
|
22
22
|
</button>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useMemo, useState } from 'react';
|
|
2
2
|
import { Plus, Save, Trash2 } from 'lucide-react';
|
|
3
3
|
import { Button } from '@/shared/components/ui/button';
|
|
4
|
+
import { FormActions } from '@/shared/components/ui/actions/form-actions';
|
|
4
5
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/components/ui/card';
|
|
5
6
|
import { Input } from '@/shared/components/ui/input';
|
|
6
7
|
import { Label } from '@/shared/components/ui/label';
|
|
@@ -272,12 +273,12 @@ export function SecretsConfigForm(props: {
|
|
|
272
273
|
onRemove={(index) => setState((prev) => ({ ...prev, refs: prev.refs.filter((_, cursor) => cursor !== index) }))}
|
|
273
274
|
onAdd={() => setState((prev) => ({ ...prev, refs: [...prev.refs, createRefRow()] }))}
|
|
274
275
|
/>
|
|
275
|
-
<
|
|
276
|
-
<Button type="button" onClick={handleSave} disabled={isPending}>
|
|
277
|
-
<Save className="mr-
|
|
276
|
+
<FormActions>
|
|
277
|
+
<Button type="button" size="sm" onClick={handleSave} disabled={isPending}>
|
|
278
|
+
<Save className="mr-1.5 h-3.5 w-3.5" />
|
|
278
279
|
{isPending ? t('saving') : t('save')}
|
|
279
280
|
</Button>
|
|
280
|
-
</
|
|
281
|
+
</FormActions>
|
|
281
282
|
</PageLayout>
|
|
282
283
|
);
|
|
283
284
|
}
|
|
@@ -3,13 +3,14 @@ import { Card } from '@/shared/components/ui/card';
|
|
|
3
3
|
import { Input } from '@/shared/components/ui/input';
|
|
4
4
|
import { Label } from '@/shared/components/ui/label';
|
|
5
5
|
import { Skeleton } from '@/shared/components/ui/skeleton';
|
|
6
|
+
import { FormActions } from '@/shared/components/ui/actions/form-actions';
|
|
6
7
|
import { ProviderScopedModelInput } from '@/shared/components/common/provider-scoped-model-input';
|
|
7
8
|
import { useConfig, useConfigSchema, useProviderTemplates, useProviders, useUpdateModel } from '@/shared/hooks/use-config';
|
|
8
9
|
import { hintForPath } from '@/shared/lib/config-hints';
|
|
9
10
|
import { t } from '@/shared/lib/i18n';
|
|
10
11
|
import { buildProviderModelCatalog } from '@/shared/lib/provider-models';
|
|
11
12
|
import { PageLayout, PageHeader } from '@/app/components/layout/page-layout';
|
|
12
|
-
import {
|
|
13
|
+
import { getDocsUrl } from '@/shared/components/doc-browser/doc-browser-context';
|
|
13
14
|
import { BookOpen, Folder, Loader2, Sparkles } from 'lucide-react';
|
|
14
15
|
import { useMemo, useState } from 'react';
|
|
15
16
|
|
|
@@ -37,48 +38,48 @@ function ModelConfigForm(props: {
|
|
|
37
38
|
event.preventDefault();
|
|
38
39
|
onSubmit({ model, workspace });
|
|
39
40
|
}}
|
|
40
|
-
className="space-y-
|
|
41
|
+
className="space-y-5"
|
|
41
42
|
>
|
|
42
|
-
<div className="grid grid-cols-1 gap-
|
|
43
|
-
<div className="rounded-2xl border border-
|
|
44
|
-
<div className="mb-
|
|
45
|
-
<div className="flex h-
|
|
46
|
-
<Sparkles className="h-
|
|
43
|
+
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
|
|
44
|
+
<div className="rounded-2xl border border-border/75 bg-card p-6">
|
|
45
|
+
<div className="mb-6 flex items-center gap-3">
|
|
46
|
+
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
47
|
+
<Sparkles className="h-4 w-4" />
|
|
47
48
|
</div>
|
|
48
|
-
<h3 className="text-
|
|
49
|
+
<h3 className="text-base font-semibold text-foreground">{t('defaultModel')}</h3>
|
|
49
50
|
</div>
|
|
50
51
|
<div className="space-y-2">
|
|
51
|
-
<Label htmlFor="model" className="text-xs font-
|
|
52
|
+
<Label htmlFor="model" className="text-xs font-medium text-muted-foreground">
|
|
52
53
|
{modelLabel}
|
|
53
54
|
</Label>
|
|
54
55
|
<ProviderScopedModelInput id="model" value={model} onChange={setModel} providerCatalog={providerCatalog} modelPlaceholder={modelPlaceholder} />
|
|
55
|
-
<p className="text-xs text-
|
|
56
|
-
<a href={
|
|
56
|
+
<p className="text-xs text-muted-foreground/80">{modelHelpText}</p>
|
|
57
|
+
<a href={getDocsUrl('/guide/model-selection')} className="inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover">
|
|
57
58
|
<BookOpen className="h-3.5 w-3.5" />
|
|
58
59
|
{t('channelsGuideTitle')}
|
|
59
60
|
</a>
|
|
60
61
|
</div>
|
|
61
62
|
</div>
|
|
62
|
-
<div className="rounded-2xl border border-
|
|
63
|
-
<div className="mb-
|
|
64
|
-
<div className="flex h-
|
|
65
|
-
<Folder className="h-
|
|
63
|
+
<div className="rounded-2xl border border-border/75 bg-card p-6">
|
|
64
|
+
<div className="mb-6 flex items-center gap-3">
|
|
65
|
+
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-muted text-muted-foreground">
|
|
66
|
+
<Folder className="h-4 w-4" />
|
|
66
67
|
</div>
|
|
67
|
-
<h3 className="text-
|
|
68
|
+
<h3 className="text-base font-semibold text-foreground">{t('workspace')}</h3>
|
|
68
69
|
</div>
|
|
69
70
|
<div className="space-y-2">
|
|
70
|
-
<Label htmlFor="workspace" className="text-xs font-
|
|
71
|
+
<Label htmlFor="workspace" className="text-xs font-medium text-muted-foreground">
|
|
71
72
|
{workspaceLabel}
|
|
72
73
|
</Label>
|
|
73
74
|
<Input id="workspace" value={workspace} onChange={(event) => setWorkspace(event.target.value)} placeholder={workspacePlaceholder} className="rounded-xl" />
|
|
74
75
|
</div>
|
|
75
76
|
</div>
|
|
76
77
|
</div>
|
|
77
|
-
<
|
|
78
|
-
<Button type="submit" disabled={isPending} size="
|
|
79
|
-
{isPending ? <Loader2 className="h-5 w-5 animate-spin" /> : t('saveChanges')}
|
|
78
|
+
<FormActions>
|
|
79
|
+
<Button type="submit" disabled={isPending} size="sm">
|
|
80
|
+
{isPending ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : t('saveChanges')}
|
|
80
81
|
</Button>
|
|
81
|
-
</
|
|
82
|
+
</FormActions>
|
|
82
83
|
</form>
|
|
83
84
|
);
|
|
84
85
|
}
|
|
@@ -108,9 +109,9 @@ export function ModelConfigPage() {
|
|
|
108
109
|
<Skeleton className="h-8 w-32" />
|
|
109
110
|
<Skeleton className="h-4 w-48" />
|
|
110
111
|
</div>
|
|
111
|
-
<Card className="rounded-2xl border-
|
|
112
|
-
<div className="mb-6 flex items-center gap-
|
|
113
|
-
<Skeleton className="h-
|
|
112
|
+
<Card hover={false} className="rounded-2xl border-border/75 p-6 shadow-none">
|
|
113
|
+
<div className="mb-6 flex items-center gap-3">
|
|
114
|
+
<Skeleton className="h-9 w-9 rounded-lg" />
|
|
114
115
|
<div className="space-y-2">
|
|
115
116
|
<Skeleton className="h-5 w-24" />
|
|
116
117
|
<Skeleton className="h-3 w-32" />
|
|
@@ -119,7 +120,7 @@ export function ModelConfigPage() {
|
|
|
119
120
|
<Skeleton className="mb-2 h-4 w-20" />
|
|
120
121
|
<Skeleton className="h-10 w-full rounded-xl" />
|
|
121
122
|
</Card>
|
|
122
|
-
<Card className="rounded-2xl border-
|
|
123
|
+
<Card hover={false} className="rounded-2xl border-border/75 p-6 shadow-none">
|
|
123
124
|
<Skeleton className="mb-2 h-5 w-24" />
|
|
124
125
|
<Skeleton className="h-10 w-full rounded-xl" />
|
|
125
126
|
</Card>
|