@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.
Files changed (158) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/assets/{api-b1R9VdeH.js → api-B7Qigvtm.js} +2 -2
  3. package/dist/assets/{appearance-settings-page-D2O9pch9.js → appearance-settings-page-CuE0Iiqj.js} +1 -1
  4. package/dist/assets/{book-open-B77lQ2Tg.js → book-open-Ba2f7YiG.js} +1 -1
  5. package/dist/assets/channels-list-page-CTsM6TcP.js +8 -0
  6. package/dist/assets/{chat-page-CZ7WxPir.js → chat-page-C_5dcuE3.js} +1 -1
  7. package/dist/assets/config-split-page-Fbb4PtTf.js +1 -0
  8. package/dist/assets/{confirm-dialog-C-NXSioX.js → confirm-dialog-D2EG3nqE.js} +1 -1
  9. package/dist/assets/{desktop-update-config-usHpXugP.js → desktop-update-config-BGR3HUi1.js} +1 -1
  10. package/dist/assets/{dist-jdGmbb6w.js → dist-7JTjycxE.js} +1 -1
  11. package/dist/assets/{dist-YtoqU-AT.js → dist-wXLhk4hl.js} +1 -1
  12. package/dist/assets/doc-browser-bHyXnxlj.js +1 -0
  13. package/dist/assets/doc-browser-context-CYAGm5d5.js +1 -0
  14. package/dist/assets/doc-browser-gTrQ3jCF.js +1 -0
  15. package/dist/assets/{es2015-bNpL2LKy.js → es2015-j1SRCIX5.js} +1 -1
  16. package/dist/assets/{external-link-BM6fk8q-.js → external-link-DxuPaDoK.js} +1 -1
  17. package/dist/assets/form-actions-BztL7P_T.js +1 -0
  18. package/dist/assets/{i18n-provider-CntWolRA.js → i18n-provider-B4oMxOFx.js} +1 -1
  19. package/dist/assets/index-CvrvblgX.css +1 -0
  20. package/dist/assets/index-Obd4JYU2.js +104 -0
  21. package/dist/assets/loader-circle-CdzgK4eH.js +1 -0
  22. package/dist/assets/{mcp-marketplace-page-CLmUstLN.js → mcp-marketplace-page-Bt7CuOoq.js} +2 -2
  23. package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +1 -0
  24. package/dist/assets/model-config-page-DeKk9gvE.js +1 -0
  25. package/dist/assets/plus-Bx7wQbhG.js +1 -0
  26. package/dist/assets/{provider-scoped-model-input-Cm_a4GHf.js → provider-scoped-model-input-DsH2LF1q.js} +1 -1
  27. package/dist/assets/providers-config-page-BFnGp68F.js +1 -0
  28. package/dist/assets/{react-BN8uzPnp.js → react-UirzUuST.js} +1 -1
  29. package/dist/assets/{refresh-cw-BpVcFPmJ.js → refresh-cw-eT8BF-8B.js} +1 -1
  30. package/dist/assets/remote-CDkHZtA8.js +1 -0
  31. package/dist/assets/rotate-cw-1JqxHTuw.js +1 -0
  32. package/dist/assets/runtime-config-page-BelAqiNG.js +1 -0
  33. package/dist/assets/{save-BpFW5fpk.js → save-Cb2g0BOi.js} +1 -1
  34. package/dist/assets/{search-D0uO7enf.js → search-BcHjwdsb.js} +1 -1
  35. package/dist/assets/search-config-page-zPm6--Ig.js +1 -0
  36. package/dist/assets/secrets-config-page-Cxyp-KuQ.js +3 -0
  37. package/dist/assets/security-config-B_gCq9fn.js +1 -0
  38. package/dist/assets/{select-Bh-CrA4p.js → select-CdSEOwVM.js} +1 -1
  39. package/dist/assets/{setting-row-BJfCHRcb.js → setting-row-CTAtGexr.js} +1 -1
  40. package/dist/assets/{settings-2-Blzi1vr6.js → settings-2-2DWauOjY.js} +1 -1
  41. package/dist/assets/{side-dock-ZdaSrQVB.js → side-dock-DPvakJ0a.js} +2 -2
  42. package/dist/assets/{skeleton-BLlEzebZ.js → skeleton-P5oM14E1.js} +1 -1
  43. package/dist/assets/{switch-CFLJFNSd.js → switch-BU_UEULb.js} +1 -1
  44. package/dist/assets/{tag-chip-CzGihiQx.js → tag-chip-D-WsKoT1.js} +1 -1
  45. package/dist/assets/{tooltip-CdcrBjrC.js → tooltip-CjkOaHW7.js} +1 -1
  46. package/dist/assets/{use-config-CVPgsBmU.js → use-config--hx5oxQn.js} +1 -1
  47. package/dist/assets/{use-confirm-dialog-CSeBk9SF.js → use-confirm-dialog-CD9a_SK1.js} +1 -1
  48. package/dist/assets/x-DT5x45JU.js +1 -0
  49. package/dist/index.html +38 -30
  50. package/index.html +12 -4
  51. package/package.json +9 -9
  52. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +29 -1
  53. package/src/app/components/layout/sidebar-items.tsx +43 -4
  54. package/src/app/components/layout/sidebar.tsx +83 -61
  55. package/src/app/configs/app-navigation.config.ts +49 -0
  56. package/src/app/styles/design-system.css +82 -0
  57. package/src/features/channels/components/config/channel-form.tsx +14 -11
  58. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +40 -0
  59. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +3 -1
  60. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -1
  61. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +48 -0
  62. package/src/features/chat/components/layout/__tests__/chat-sidebar-utility-menu.test.tsx +55 -0
  63. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +0 -3
  64. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +3 -4
  65. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +29 -30
  66. package/src/features/chat/components/layout/chat-sidebar.tsx +21 -7
  67. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +2 -1
  68. package/src/features/chat/features/message/components/__tests__/chat-inline-panel-app-card.test.tsx +22 -1
  69. package/src/features/chat/features/message/components/chat-inline-panel-app-card.tsx +10 -5
  70. package/src/features/chat/features/message/utils/chat-inline-panel-app-card.utils.ts +0 -11
  71. package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +116 -0
  72. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +108 -46
  73. package/src/features/chat/features/session/components/chat-sidebar-session-entry.tsx +6 -0
  74. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +48 -24
  75. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +0 -3
  76. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +0 -4
  77. package/src/features/chat/features/session/utils/chat-sidebar-session-groups.utils.ts +25 -3
  78. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +9 -4
  79. package/src/features/chat/features/welcome/components/chat-welcome.tsx +0 -5
  80. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +159 -56
  81. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +1 -0
  82. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +144 -0
  83. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +99 -0
  84. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +3 -1
  85. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +66 -7
  86. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +3 -0
  87. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +44 -1
  88. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +45 -3
  89. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +33 -2
  90. package/src/features/chat/managers/chat-session-list.manager.ts +32 -0
  91. package/src/features/chat/stores/chat-session-list.store.ts +28 -5
  92. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +5 -12
  93. package/src/features/panel-apps/components/panel-app-list-item.tsx +6 -6
  94. package/src/features/panel-apps/index.ts +1 -0
  95. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +2 -10
  96. package/src/features/panel-apps/utils/panel-app-iframe.utils.ts +6 -0
  97. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +11 -3
  98. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +3 -1
  99. package/src/features/service-apps/components/service-apps-panel.tsx +145 -35
  100. package/src/features/settings/components/config/provider-auth-section.tsx +1 -1
  101. package/src/features/settings/components/config/provider-form.tsx +11 -8
  102. package/src/features/settings/components/config/provider-models-section.tsx +7 -7
  103. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  104. package/src/features/settings/components/config/secrets-config-form.tsx +5 -4
  105. package/src/features/settings/pages/model-config-page.tsx +26 -25
  106. package/src/features/settings/pages/providers-config-page.tsx +10 -13
  107. package/src/features/settings/pages/search-config-page.tsx +8 -5
  108. package/src/features/system-status/components/config/runtime-config-editor.tsx +5 -4
  109. package/src/index.css +18 -0
  110. package/src/shared/components/config-split-page.tsx +6 -6
  111. package/src/shared/components/doc-browser/__tests__/doc-browser-url.utils.test.ts +73 -0
  112. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +48 -0
  113. package/src/shared/components/doc-browser/doc-browser-context.tsx +4 -0
  114. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +5 -1
  115. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +2 -1
  116. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +10 -10
  117. package/src/shared/components/doc-browser/doc-browser.tsx +6 -4
  118. package/src/shared/components/doc-browser/utils/doc-browser-url.utils.ts +62 -4
  119. package/src/shared/components/resizable-right-panel/__tests__/resizable-right-panel.test.tsx +18 -0
  120. package/src/shared/components/resizable-right-panel/resizable-right-panel.tsx +28 -3
  121. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +36 -0
  122. package/src/shared/components/ui/actions/form-actions.tsx +25 -0
  123. package/src/shared/components/ui/actions/icon-action-button.tsx +25 -2
  124. package/src/shared/components/ui/card.tsx +12 -4
  125. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +2 -1
  126. package/src/shared/hooks/use-server-path-browse.ts +26 -4
  127. package/src/shared/hooks/use-server-path-read.ts +11 -1
  128. package/src/shared/lib/api/utils/server-path.utils.ts +1 -0
  129. package/src/shared/lib/i18n/locales/en-US/chat.json +16 -0
  130. package/src/shared/lib/i18n/locales/en-US/core.json +3 -0
  131. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +5 -0
  132. package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -0
  133. package/src/shared/lib/i18n/locales/zh-CN/core.json +3 -0
  134. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +5 -0
  135. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +9 -5
  136. package/src/shared/lib/theme/index.test.ts +21 -0
  137. package/src/shared/lib/theme/index.ts +6 -4
  138. package/src/vite-env.d.ts +2 -0
  139. package/dist/assets/channels-list-page-DcsIQc6A.js +0 -8
  140. package/dist/assets/config-split-page-BWQ6_Raq.js +0 -1
  141. package/dist/assets/doc-browser-B_Tf9n70.js +0 -1
  142. package/dist/assets/doc-browser-BdZPFOVa.js +0 -1
  143. package/dist/assets/doc-browser-context-DwokemUn.js +0 -1
  144. package/dist/assets/index-BIcEw90u.js +0 -104
  145. package/dist/assets/index-BiNSTKTU.css +0 -1
  146. package/dist/assets/loader-circle-MBgNSVIH.js +0 -1
  147. package/dist/assets/mcp-marketplace-page-rZkmWPMy.js +0 -1
  148. package/dist/assets/model-config-page-Di0ySJD2.js +0 -1
  149. package/dist/assets/plus-vDqW5Kxp.js +0 -1
  150. package/dist/assets/providers-config-page-BAf2W0MV.js +0 -1
  151. package/dist/assets/remote-CLQ0IBM8.js +0 -1
  152. package/dist/assets/rotate-cw-CvNBv7_w.js +0 -1
  153. package/dist/assets/runtime-config-page-CflvvoDc.js +0 -1
  154. package/dist/assets/search-config-page-D0klFUXd.js +0 -1
  155. package/dist/assets/secrets-config-page-dVZ631Wp.js +0 -3
  156. package/dist/assets/security-config-C5gnlkuj.js +0 -1
  157. package/dist/assets/x-BF2KQ--V.js +0 -1
  158. /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 natural shell colors by default', () => {
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('#FAF9F7');
20
- expect(document.body.style.backgroundColor).toBe('rgb(250, 249, 247)');
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) ?? 'natural');
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/70 px-4 py-3">
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 text-center text-sm text-muted-foreground">
114
- {t('serviceAppsEmpty')}
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-3">
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-lg border border-border bg-card p-3">
176
- <div className="flex items-start justify-between gap-3">
177
- <div className="min-w-0">
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-1">
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-accent hover:text-accent-foreground disabled:opacity-50"
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
- <div className="mt-3 space-y-1">
234
- {actions.map((action) => (
235
- <ServiceActionRow
236
- key={action.id}
237
- action={action}
238
- grants={grants.filter((grant) => grant.actionId === action.id)}
239
- onRevoke={onRevoke}
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-accent hover:text-accent-foreground disabled:cursor-not-allowed disabled:text-muted-foreground/40 disabled:opacity-60 disabled:hover:bg-transparent"
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
- <dl className="mt-3 grid grid-cols-[4.5rem_minmax(0,1fr)] gap-x-2 gap-y-1 border-t border-border/70 pt-2 text-[11px]">
407
- {rows.map((row) => (
408
- <ServiceAppDiagnosticRow key={row.label} label={row.label} value={row.value} />
409
- ))}
410
- </dl>
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 bg-muted/60 px-2 py-1.5">
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
- <Wrench className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70" />
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-card px-1.5 py-0.5 text-[11px] text-muted-foreground">
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-card px-1.5 py-0.5 text-[11px] text-muted-foreground">{action.risk}</span>
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-5">
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-background hover:text-rose-600"
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-primary/20 bg-primary-50/50 p-3">
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 className='flex items-center justify-between px-6 py-4'>
552
- <Button type='button' variant='outline' size='sm' onClick={onTestConnection} disabled={isTestPending}>
553
- <CircleDotDashed className='mr-1.5 h-4 w-4' />
554
- {isTestPending ? t('providerTestingConnection') : t('providerTestConnection')}
555
- </Button>
556
- <Button type='submit' disabled={isUpdatePending || !hasChanges}>
557
- {isUpdatePending ? t('saving') : hasChanges ? t('save') : t('unchanged')}
558
- </Button>
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/60 px-4 py-6 text-center">
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-card px-3 py-1.5"
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-accent hover:text-accent-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
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 border border-border bg-muted/60 px-3 py-2">
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-primary bg-primary text-white'
163
- : 'border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent/70 hover:text-accent-foreground'
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-accent hover:text-accent-foreground md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
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-primary bg-primary text-primary-foreground shadow-sm' : 'border-border bg-card text-muted-foreground hover:border-primary/40 hover:bg-accent/70 hover:text-accent-foreground'}`}
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
- <div className="flex justify-end">
276
- <Button type="button" onClick={handleSave} disabled={isPending}>
277
- <Save className="mr-2 h-4 w-4" />
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
- </div>
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 { DOCS_DEFAULT_BASE_URL } from '@/shared/components/doc-browser/doc-browser-context';
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-8"
41
+ className="space-y-5"
41
42
  >
42
- <div className="grid grid-cols-1 gap-8 md:grid-cols-2">
43
- <div className="rounded-2xl border border-gray-200 bg-white p-8 shadow-card">
44
- <div className="mb-8 flex items-center gap-4">
45
- <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary text-white">
46
- <Sparkles className="h-5 w-5" />
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-lg font-bold text-gray-900">{t('defaultModel')}</h3>
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-semibold uppercase tracking-wider text-gray-500">
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-gray-400">{modelHelpText}</p>
56
- <a href={`${DOCS_DEFAULT_BASE_URL}/guide/model-selection`} className="inline-flex items-center gap-1.5 text-xs text-primary transition-colors hover:text-primary-hover">
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-gray-200 bg-white p-8 shadow-card">
63
- <div className="mb-8 flex items-center gap-4">
64
- <div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary text-white">
65
- <Folder className="h-5 w-5" />
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-lg font-bold text-gray-900">{t('workspace')}</h3>
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-semibold uppercase tracking-wider text-gray-500">
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
- <div className="flex justify-end pt-4">
78
- <Button type="submit" disabled={isPending} size="lg">
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
- </div>
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-gray-200 p-6">
112
- <div className="mb-6 flex items-center gap-4">
113
- <Skeleton className="h-12 w-12 rounded-xl" />
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-gray-200 p-6">
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>