@nextclaw/ui 0.15.2 → 0.15.3
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-B7Qigvtm.js → api-iNBpppzj.js} +2 -2
- package/dist/assets/{appearance-settings-page-CuE0Iiqj.js → appearance-settings-page-DMffQFR3.js} +1 -1
- package/dist/assets/{book-open-Ba2f7YiG.js → book-open-C3mJGwiy.js} +1 -1
- package/dist/assets/{channels-list-page-CTsM6TcP.js → channels-list-page-CCtp0BRS.js} +1 -1
- package/dist/assets/chat-page-H5a0dWvO.js +1 -0
- package/dist/assets/{config-split-page-Fbb4PtTf.js → config-split-page-CrOJgB-Y.js} +1 -1
- package/dist/assets/{confirm-dialog-D2EG3nqE.js → confirm-dialog-Chv50USy.js} +2 -2
- package/dist/assets/{desktop-update-config-BGR3HUi1.js → desktop-update-config-KzTuXRf_.js} +1 -1
- package/dist/assets/{dist-7JTjycxE.js → dist-BunzmC4B.js} +1 -1
- package/dist/assets/{dist-wXLhk4hl.js → dist-ChmGwvtK.js} +1 -1
- package/dist/assets/doc-browser-DNRL4Zvd.js +1 -0
- package/dist/assets/doc-browser-Dg0pmkOg.js +1 -0
- package/dist/assets/{doc-browser-context-CYAGm5d5.js → doc-browser-context-BHY-0FSD.js} +1 -1
- package/dist/assets/{es2015-j1SRCIX5.js → es2015-DQZ5JWwK.js} +1 -1
- package/dist/assets/{external-link-DxuPaDoK.js → external-link-CU0kzzIP.js} +1 -1
- package/dist/assets/{form-actions-BztL7P_T.js → form-actions-BvCp_0lS.js} +1 -1
- package/dist/assets/{i18n-provider-B4oMxOFx.js → i18n-provider-Cu8XyZb_.js} +1 -1
- package/dist/assets/index-CzfBdvK1.css +1 -0
- package/dist/assets/index-DGqHne1Q.js +104 -0
- package/dist/assets/loader-circle-CDSFuzmk.js +1 -0
- package/dist/assets/mcp-marketplace-page-BeEQK0DL.js +9 -0
- package/dist/assets/mcp-marketplace-page-D6idOTLp.js +1 -0
- package/dist/assets/{model-config-page-DeKk9gvE.js → model-config-page-bNNo-XyG.js} +1 -1
- package/dist/assets/plus-D4DsdLN-.js +1 -0
- package/dist/assets/{provider-scoped-model-input-DsH2LF1q.js → provider-scoped-model-input-IlgQCsoT.js} +1 -1
- package/dist/assets/{providers-config-page-BFnGp68F.js → providers-config-page-wLo6CAJw.js} +1 -1
- package/dist/assets/{react-UirzUuST.js → react-BPIsQIlO.js} +1 -1
- package/dist/assets/{refresh-cw-eT8BF-8B.js → refresh-cw-CILd1vAP.js} +1 -1
- package/dist/assets/remote-uKEYp7AR.js +1 -0
- package/dist/assets/rotate-cw-wPFPg1kH.js +1 -0
- package/dist/assets/{runtime-config-page-BelAqiNG.js → runtime-config-page-DDn1sP4G.js} +1 -1
- package/dist/assets/{save-Cb2g0BOi.js → save-ByJqcPTP.js} +1 -1
- package/dist/assets/{search-BcHjwdsb.js → search-DVIV6pfD.js} +1 -1
- package/dist/assets/{search-config-page-zPm6--Ig.js → search-config-page-B-Cf4iiB.js} +1 -1
- package/dist/assets/{secrets-config-page-Cxyp-KuQ.js → secrets-config-page-DPKsY8Iw.js} +1 -1
- package/dist/assets/security-config-e9SSBlKr.js +1 -0
- package/dist/assets/{select-CdSEOwVM.js → select-BdgczSNw.js} +1 -1
- package/dist/assets/{setting-row-CTAtGexr.js → setting-row-BkTVUTKx.js} +1 -1
- package/dist/assets/{settings-2-2DWauOjY.js → settings-2-CH5shGg_.js} +1 -1
- package/dist/assets/{side-dock-DPvakJ0a.js → side-dock-BsBvcJ9i.js} +2 -2
- package/dist/assets/{skeleton-P5oM14E1.js → skeleton-DeX1FKw7.js} +1 -1
- package/dist/assets/switch-Cv8Ekb3x.js +1 -0
- package/dist/assets/{tag-chip-D-WsKoT1.js → tag-chip-RB9uKcoC.js} +1 -1
- package/dist/assets/{tooltip-CjkOaHW7.js → tooltip-oQlEN-l6.js} +1 -1
- package/dist/assets/{use-config--hx5oxQn.js → use-config-l3W7y8Qz.js} +1 -1
- package/dist/assets/{use-confirm-dialog-CD9a_SK1.js → use-confirm-dialog-BAxaVOJI.js} +1 -1
- package/dist/assets/x-BBMffEN0.js +1 -0
- package/dist/index.html +26 -26
- package/package.json +9 -9
- package/src/app/components/layout/sidebar-items.tsx +3 -3
- package/src/app/components/layout/sidebar-rail.styles.ts +2 -2
- package/src/app/components/layout/sidebar.tsx +1 -1
- package/src/app/styles/design-system.css +17 -4
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -1
- package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +62 -1
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +47 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-process-summary.utils.test.ts +11 -1
- package/src/features/chat/features/message/utils/__tests__/chat-message-texts.utils.test.ts +23 -0
- package/src/features/chat/features/message/utils/chat-message-process-summary.utils.ts +5 -0
- package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +138 -0
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +1 -1
- package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +1 -1
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-file-preview.test.tsx +67 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +1 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +2 -2
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +1 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +174 -22
- package/src/features/chat/features/workspace/utils/chat-workspace-file-tab-persistence.utils.ts +4 -0
- package/src/features/chat/managers/chat-thread.manager.ts +2 -0
- package/src/features/chat/stores/chat-thread.store.ts +3 -0
- package/src/features/cron/components/cron-config.tsx +63 -80
- package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx +44 -6
- package/src/features/marketplace/components/marketplace-detail-doc.tsx +8 -4
- package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
- package/src/features/marketplace/hooks/use-marketplace-detail-doc-entry.ts +84 -0
- package/src/features/panel-apps/components/panel-apps-list.tsx +2 -2
- package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
- package/src/features/side-dock/components/side-dock.tsx +3 -3
- package/src/index.css +239 -13
- package/src/shared/components/actions/action-link.tsx +1 -1
- package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +1 -1
- package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
- package/src/shared/components/ui/button.tsx +1 -1
- package/src/shared/components/ui/dialog.tsx +1 -1
- package/src/shared/components/ui/input.tsx +1 -1
- 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/i18n/locales/en-US/chat.json +83 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +83 -0
- package/dist/assets/chat-page-C_5dcuE3.js +0 -1
- package/dist/assets/doc-browser-bHyXnxlj.js +0 -1
- package/dist/assets/doc-browser-gTrQ3jCF.js +0 -1
- package/dist/assets/index-CvrvblgX.css +0 -1
- package/dist/assets/index-Obd4JYU2.js +0 -104
- package/dist/assets/loader-circle-CdzgK4eH.js +0 -1
- package/dist/assets/mcp-marketplace-page-Bt7CuOoq.js +0 -9
- package/dist/assets/mcp-marketplace-page-Ck3t78_n.js +0 -1
- package/dist/assets/plus-Bx7wQbhG.js +0 -1
- package/dist/assets/remote-CDkHZtA8.js +0 -1
- package/dist/assets/rotate-cw-1JqxHTuw.js +0 -1
- package/dist/assets/security-config-B_gCq9fn.js +0 -1
- package/dist/assets/switch-BU_UEULb.js +0 -1
- package/dist/assets/x-DT5x45JU.js +0 -1
|
@@ -38,7 +38,7 @@ import { cn } from "@/shared/lib/utils";
|
|
|
38
38
|
import { t } from "@/shared/lib/i18n";
|
|
39
39
|
import { PageLayout, PageHeader } from "@/app/components/layout/page-layout";
|
|
40
40
|
import {
|
|
41
|
-
|
|
41
|
+
Search,
|
|
42
42
|
RefreshCw,
|
|
43
43
|
Trash2,
|
|
44
44
|
Play,
|
|
@@ -77,16 +77,16 @@ function StatusBadge({ job }: { job: CronJobView }) {
|
|
|
77
77
|
return (
|
|
78
78
|
<span
|
|
79
79
|
className={cn(
|
|
80
|
-
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-
|
|
80
|
+
"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium",
|
|
81
81
|
job.enabled
|
|
82
82
|
? "bg-emerald-50 text-emerald-700"
|
|
83
|
-
: "bg-
|
|
83
|
+
: "bg-muted text-muted-foreground",
|
|
84
84
|
)}
|
|
85
85
|
>
|
|
86
86
|
<span
|
|
87
87
|
className={cn(
|
|
88
88
|
"h-1.5 w-1.5 rounded-full",
|
|
89
|
-
job.enabled ? "bg-emerald-
|
|
89
|
+
job.enabled ? "bg-emerald-500" : "bg-muted-foreground/50",
|
|
90
90
|
)}
|
|
91
91
|
/>
|
|
92
92
|
{job.enabled ? t("enabled") : t("disabled")}
|
|
@@ -104,10 +104,7 @@ function CronJobCard(props: {
|
|
|
104
104
|
const [expanded, setExpanded] = useState(false);
|
|
105
105
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
106
106
|
|
|
107
|
-
const barColor = job.enabled ? "bg-emerald-400" : "bg-gray-300";
|
|
108
|
-
|
|
109
107
|
const handleCardClick = (e: React.MouseEvent) => {
|
|
110
|
-
// Don't expand when clicking switch or menu
|
|
111
108
|
const target = e.target as HTMLElement;
|
|
112
109
|
if (target.closest("[data-no-expand]")) return;
|
|
113
110
|
setExpanded(!expanded);
|
|
@@ -116,44 +113,33 @@ function CronJobCard(props: {
|
|
|
116
113
|
return (
|
|
117
114
|
<div
|
|
118
115
|
className={cn(
|
|
119
|
-
"group
|
|
120
|
-
expanded
|
|
121
|
-
? "border-gray-300 shadow-sm"
|
|
122
|
-
: "border-gray-200 hover:border-gray-300 hover:shadow-sm",
|
|
116
|
+
"group overflow-hidden rounded-xl border border-border/70 bg-card transition-colors cursor-pointer",
|
|
117
|
+
expanded ? "bg-muted/20" : "hover:bg-muted/30",
|
|
123
118
|
)}
|
|
124
119
|
onClick={handleCardClick}
|
|
125
120
|
>
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
className={cn(
|
|
129
|
-
"absolute left-0 top-0 bottom-0 w-1 rounded-l-xl",
|
|
130
|
-
barColor,
|
|
131
|
-
)}
|
|
132
|
-
/>
|
|
133
|
-
|
|
134
|
-
<div className="pl-4 pr-3 py-3"> {/* Row 1: name + badges + actions */}
|
|
135
|
-
<div className="flex items-center gap-3">
|
|
121
|
+
<div className="px-3 py-2.5">
|
|
122
|
+
<div className="flex items-center gap-2.5">
|
|
136
123
|
<div className="min-w-0 flex-1">
|
|
137
124
|
<div className="flex items-center gap-2">
|
|
138
125
|
{expanded ? (
|
|
139
|
-
<ChevronDown className="h-3.5 w-3.5 text-
|
|
126
|
+
<ChevronDown className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
140
127
|
) : (
|
|
141
|
-
<ChevronRight className="h-3.5 w-3.5 text-
|
|
128
|
+
<ChevronRight className="h-3.5 w-3.5 text-muted-foreground/50 shrink-0 group-hover:text-muted-foreground transition-colors" />
|
|
142
129
|
)}
|
|
143
|
-
<span className="text-sm font-
|
|
130
|
+
<span className="text-sm font-medium text-foreground truncate">
|
|
144
131
|
{job.name || job.id}
|
|
145
132
|
</span>
|
|
146
133
|
<StatusBadge job={job} />
|
|
147
|
-
{job.deleteAfterRun
|
|
148
|
-
<span className="rounded-full bg-
|
|
134
|
+
{job.deleteAfterRun ? (
|
|
135
|
+
<span className="rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
149
136
|
{t("cronOneShot")}
|
|
150
137
|
</span>
|
|
151
|
-
)}
|
|
138
|
+
) : null}
|
|
152
139
|
</div>
|
|
153
140
|
</div>
|
|
154
141
|
|
|
155
|
-
|
|
156
|
-
<div className="flex items-center gap-1.5 shrink-0" data-no-expand>
|
|
142
|
+
<div className="flex items-center gap-1 shrink-0" data-no-expand>
|
|
157
143
|
<Switch
|
|
158
144
|
checked={job.enabled}
|
|
159
145
|
onCheckedChange={(checked) => onToggle(job, checked)}
|
|
@@ -163,7 +149,7 @@ function CronJobCard(props: {
|
|
|
163
149
|
<Button
|
|
164
150
|
variant="ghost"
|
|
165
151
|
size="icon"
|
|
166
|
-
className="h-
|
|
152
|
+
className="h-7 w-7 text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
167
153
|
aria-label={t("cronMoreActions")}
|
|
168
154
|
>
|
|
169
155
|
<MoreVertical className="h-4 w-4" />
|
|
@@ -176,9 +162,9 @@ function CronJobCard(props: {
|
|
|
176
162
|
setMenuOpen(false);
|
|
177
163
|
onRun(job);
|
|
178
164
|
}}
|
|
179
|
-
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-
|
|
165
|
+
className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-foreground transition-colors hover:bg-muted"
|
|
180
166
|
>
|
|
181
|
-
<Play className="h-3.5 w-3.5 text-
|
|
167
|
+
<Play className="h-3.5 w-3.5 text-muted-foreground" />
|
|
182
168
|
{t("cronRunNow")}
|
|
183
169
|
</button>
|
|
184
170
|
<button
|
|
@@ -197,28 +183,27 @@ function CronJobCard(props: {
|
|
|
197
183
|
</div>
|
|
198
184
|
</div>
|
|
199
185
|
|
|
200
|
-
|
|
201
|
-
<div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-gray-500 pl-6">
|
|
186
|
+
<div className="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground pl-6">
|
|
202
187
|
<span className="flex items-center gap-1">
|
|
203
|
-
<CalendarClock className="h-3 w-3 text-
|
|
204
|
-
<span className="font-mono text-
|
|
188
|
+
<CalendarClock className="h-3 w-3 text-muted-foreground/70" />
|
|
189
|
+
<span className="font-mono text-foreground/80">
|
|
205
190
|
{describeCronSchedule(job)}
|
|
206
191
|
</span>
|
|
207
192
|
</span>
|
|
208
|
-
{job.state.nextRunAt
|
|
193
|
+
{job.state.nextRunAt ? (
|
|
209
194
|
<span className="flex items-center gap-1">
|
|
210
|
-
<Clock className="h-3 w-3 text-
|
|
195
|
+
<Clock className="h-3 w-3 text-muted-foreground/70" />
|
|
211
196
|
<span>{formatRelativeTime(job.state.nextRunAt)}</span>
|
|
212
197
|
</span>
|
|
213
|
-
)}
|
|
214
|
-
{job.state.lastRunAt
|
|
198
|
+
) : null}
|
|
199
|
+
{job.state.lastRunAt ? (
|
|
215
200
|
<span className="flex items-center gap-1" data-no-expand>
|
|
216
|
-
<span className="text-
|
|
201
|
+
<span className="text-border">•</span>
|
|
217
202
|
{job.state.lastStatus === "error" && job.state.lastError ? (
|
|
218
203
|
<TooltipProvider delayDuration={200}>
|
|
219
204
|
<Tooltip>
|
|
220
205
|
<TooltipTrigger asChild>
|
|
221
|
-
<span className="inline-flex items-center gap-0.5 text-orange-
|
|
206
|
+
<span className="inline-flex items-center gap-0.5 text-orange-500 cursor-help">
|
|
222
207
|
<AlertCircle className="h-3 w-3" />
|
|
223
208
|
</span>
|
|
224
209
|
</TooltipTrigger>
|
|
@@ -235,8 +220,8 @@ function CronJobCard(props: {
|
|
|
235
220
|
<span
|
|
236
221
|
className={cn(
|
|
237
222
|
job.state.lastStatus === "ok"
|
|
238
|
-
? "text-emerald-
|
|
239
|
-
: "text-
|
|
223
|
+
? "text-emerald-500"
|
|
224
|
+
: "text-muted-foreground/70",
|
|
240
225
|
)}
|
|
241
226
|
>
|
|
242
227
|
{job.state.lastStatus === "ok" ? "✓" : ""}
|
|
@@ -244,14 +229,13 @@ function CronJobCard(props: {
|
|
|
244
229
|
)}
|
|
245
230
|
<span>{formatRelativeTime(job.state.lastRunAt)}</span>
|
|
246
231
|
</span>
|
|
247
|
-
)}
|
|
232
|
+
) : null}
|
|
248
233
|
</div>
|
|
249
234
|
|
|
250
|
-
{/* Row 3: message preview (always visible, 2 lines) */}
|
|
251
235
|
<div className="mt-2 pl-6">
|
|
252
236
|
<div
|
|
253
237
|
className={cn(
|
|
254
|
-
"text-sm text-
|
|
238
|
+
"text-sm text-muted-foreground break-words",
|
|
255
239
|
!expanded && "line-clamp-2",
|
|
256
240
|
)}
|
|
257
241
|
>
|
|
@@ -259,40 +243,38 @@ function CronJobCard(props: {
|
|
|
259
243
|
</div>
|
|
260
244
|
</div>
|
|
261
245
|
|
|
262
|
-
{
|
|
263
|
-
|
|
264
|
-
<div className="mt-3 ml-6 space-y-3 animate-in fade-in-0 slide-in-from-top-1 duration-150">
|
|
265
|
-
{/* Metadata grid */}
|
|
246
|
+
{expanded ? (
|
|
247
|
+
<div className="mt-3 ml-6 space-y-3 border-t border-border/60 pt-3">
|
|
266
248
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-2 text-xs">
|
|
267
249
|
<div>
|
|
268
|
-
<div className="text-[11px] text-
|
|
250
|
+
<div className="text-[11px] text-muted-foreground/70 mb-0.5">
|
|
269
251
|
{t("cronSessionLabel")}
|
|
270
252
|
</div>
|
|
271
253
|
<div
|
|
272
|
-
className="text-
|
|
254
|
+
className="text-foreground/80 font-mono text-[11px] truncate"
|
|
273
255
|
title={describeCronSession(job)}
|
|
274
256
|
>
|
|
275
257
|
{describeCronSession(job)}
|
|
276
258
|
</div>
|
|
277
259
|
</div>
|
|
278
260
|
<div>
|
|
279
|
-
<div className="text-[11px] text-
|
|
261
|
+
<div className="text-[11px] text-muted-foreground/70 mb-0.5">
|
|
280
262
|
{t("cronNextRun")}
|
|
281
263
|
</div>
|
|
282
|
-
<div className="text-
|
|
264
|
+
<div className="text-foreground/80">
|
|
283
265
|
{formatCronDate(job.state.nextRunAt)}
|
|
284
266
|
</div>
|
|
285
267
|
</div>
|
|
286
268
|
<div>
|
|
287
|
-
<div className="text-[11px] text-
|
|
269
|
+
<div className="text-[11px] text-muted-foreground/70 mb-0.5">
|
|
288
270
|
{t("cronLastRun")}
|
|
289
271
|
</div>
|
|
290
|
-
<div className="text-
|
|
272
|
+
<div className="text-foreground/80">
|
|
291
273
|
{formatCronDate(job.state.lastRunAt)}
|
|
292
274
|
</div>
|
|
293
275
|
</div>
|
|
294
276
|
<div>
|
|
295
|
-
<div className="text-[11px] text-
|
|
277
|
+
<div className="text-[11px] text-muted-foreground/70 mb-0.5">
|
|
296
278
|
{t("cronLastStatus")}
|
|
297
279
|
</div>
|
|
298
280
|
<div
|
|
@@ -304,26 +286,26 @@ function CronJobCard(props: {
|
|
|
304
286
|
? "text-red-600"
|
|
305
287
|
: job.state.lastStatus === "skipped"
|
|
306
288
|
? "text-amber-600"
|
|
307
|
-
: "text-
|
|
289
|
+
: "text-muted-foreground",
|
|
308
290
|
)}
|
|
309
291
|
>
|
|
310
292
|
{job.state.lastStatus ?? "-"}
|
|
311
293
|
</div>
|
|
312
294
|
</div>
|
|
313
295
|
<div>
|
|
314
|
-
<div className="text-[11px] text-
|
|
296
|
+
<div className="text-[11px] text-muted-foreground/70 mb-0.5">
|
|
315
297
|
{t("cronCreatedAt")}
|
|
316
298
|
</div>
|
|
317
|
-
<div className="text-
|
|
299
|
+
<div className="text-foreground/80">
|
|
318
300
|
{formatCronDate(job.createdAt)}
|
|
319
301
|
</div>
|
|
320
302
|
</div>
|
|
321
303
|
<div>
|
|
322
|
-
<div className="text-[11px] text-
|
|
304
|
+
<div className="text-[11px] text-muted-foreground/70 mb-0.5">
|
|
323
305
|
{t("cronId")}
|
|
324
306
|
</div>
|
|
325
307
|
<div
|
|
326
|
-
className="text-
|
|
308
|
+
className="text-foreground/80 font-mono text-[11px] truncate"
|
|
327
309
|
title={job.id}
|
|
328
310
|
>
|
|
329
311
|
{job.id}
|
|
@@ -331,14 +313,13 @@ function CronJobCard(props: {
|
|
|
331
313
|
</div>
|
|
332
314
|
</div>
|
|
333
315
|
|
|
334
|
-
{
|
|
335
|
-
|
|
336
|
-
<div className="rounded-lg bg-red-50 border border-red-100 px-3 py-2 text-xs text-red-600 break-words">
|
|
316
|
+
{job.state.lastError ? (
|
|
317
|
+
<div className="rounded-lg bg-red-50/80 border border-red-100 px-3 py-2 text-xs text-red-600 break-words">
|
|
337
318
|
{job.state.lastError}
|
|
338
319
|
</div>
|
|
339
|
-
)}
|
|
320
|
+
) : null}
|
|
340
321
|
</div>
|
|
341
|
-
)}
|
|
322
|
+
) : null}
|
|
342
323
|
</div>
|
|
343
324
|
</div>
|
|
344
325
|
);
|
|
@@ -397,7 +378,7 @@ export function CronConfig() {
|
|
|
397
378
|
<Button
|
|
398
379
|
variant="ghost"
|
|
399
380
|
size="icon"
|
|
400
|
-
className="h-
|
|
381
|
+
className="h-8 w-8 rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted"
|
|
401
382
|
onClick={() => cronQuery.refetch()}
|
|
402
383
|
>
|
|
403
384
|
<RefreshCw
|
|
@@ -407,16 +388,16 @@ export function CronConfig() {
|
|
|
407
388
|
}
|
|
408
389
|
/>
|
|
409
390
|
|
|
410
|
-
<div className="mb-
|
|
411
|
-
<div className="flex flex-wrap gap-
|
|
391
|
+
<div className="mb-4">
|
|
392
|
+
<div className="flex flex-wrap gap-2.5 items-center">
|
|
412
393
|
<div className="relative flex-1 min-w-[240px]">
|
|
394
|
+
<Search className="pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/70" />
|
|
413
395
|
<Input
|
|
414
396
|
value={query}
|
|
415
397
|
onChange={(e) => setQuery(e.target.value)}
|
|
416
398
|
placeholder={t("cronSearchPlaceholder")}
|
|
417
399
|
className="pl-9"
|
|
418
400
|
/>
|
|
419
|
-
<AlarmClock className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
420
401
|
</div>
|
|
421
402
|
<div className="min-w-[140px]">
|
|
422
403
|
<Select
|
|
@@ -437,7 +418,7 @@ export function CronConfig() {
|
|
|
437
418
|
</SelectContent>
|
|
438
419
|
</Select>
|
|
439
420
|
</div>
|
|
440
|
-
<div className="text-xs text-
|
|
421
|
+
<div className="text-xs text-muted-foreground ml-auto tabular-nums">
|
|
441
422
|
{jobs.length} / {cronQuery.data?.total ?? 0}
|
|
442
423
|
</div>
|
|
443
424
|
</div>
|
|
@@ -445,22 +426,24 @@ export function CronConfig() {
|
|
|
445
426
|
|
|
446
427
|
<div className="flex-1 overflow-auto custom-scrollbar">
|
|
447
428
|
{cronQuery.isLoading ? (
|
|
448
|
-
<div className="flex flex-col items-center justify-center py-16 text-
|
|
449
|
-
<RefreshCw className="h-
|
|
429
|
+
<div className="flex flex-col items-center justify-center py-16 text-muted-foreground">
|
|
430
|
+
<RefreshCw className="h-5 w-5 animate-spin mb-3" />
|
|
450
431
|
<span className="text-sm">{t("cronLoading")}</span>
|
|
451
432
|
</div>
|
|
452
433
|
) : jobs.length === 0 ? (
|
|
453
|
-
<div className="flex flex-col items-center justify-center py-16 text-
|
|
454
|
-
<
|
|
455
|
-
|
|
434
|
+
<div className="flex flex-col items-center justify-center py-16 text-muted-foreground">
|
|
435
|
+
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-xl bg-muted">
|
|
436
|
+
<ListTodo className="h-5 w-5 text-muted-foreground/70" />
|
|
437
|
+
</div>
|
|
438
|
+
<span className="text-sm font-medium text-foreground">
|
|
456
439
|
{t("cronEmpty")}
|
|
457
440
|
</span>
|
|
458
|
-
<span className="text-xs text-
|
|
441
|
+
<span className="text-xs text-muted-foreground mt-1">
|
|
459
442
|
{t("cronEmptyGuide")}
|
|
460
443
|
</span>
|
|
461
444
|
</div>
|
|
462
445
|
) : (
|
|
463
|
-
<div className="space-y-2
|
|
446
|
+
<div className="space-y-2">
|
|
464
447
|
{jobs.map((job) => (
|
|
465
448
|
<CronJobCard
|
|
466
449
|
key={job.id}
|
package/src/features/marketplace/components/detail-doc/__tests__/marketplace-detail-doc.test.tsx
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import type * as MarketplaceApi from "@/shared/lib/api";
|
|
2
4
|
import {
|
|
3
5
|
createMarketplaceDetailDocId,
|
|
4
6
|
createMarketplaceDetailDocUrl,
|
|
5
7
|
MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS,
|
|
6
8
|
MARKETPLACE_DETAIL_TAB_KIND,
|
|
7
9
|
} from "@/features/marketplace/components/marketplace-detail-doc";
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
setMarketplaceDetailDocEntry,
|
|
12
|
+
useMarketplaceDetailDocStore,
|
|
13
|
+
} from "@/features/marketplace/stores/marketplace-detail-doc.store";
|
|
14
|
+
|
|
15
|
+
const mocks = vi.hoisted(() => ({
|
|
16
|
+
fetchMarketplaceSkillContent: vi.fn(),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("@/shared/lib/api", async (importOriginal) => {
|
|
20
|
+
const actual = await importOriginal<typeof MarketplaceApi>();
|
|
21
|
+
return {
|
|
22
|
+
...actual,
|
|
23
|
+
fetchMarketplaceSkillContent: mocks.fetchMarketplaceSkillContent,
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
vi.mock("@/shared/lib/i18n", () => ({
|
|
28
|
+
t: (key: string) => key,
|
|
29
|
+
}));
|
|
9
30
|
|
|
10
31
|
function renderDetail(detailId: string) {
|
|
11
32
|
const renderer = MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS[MARKETPLACE_DETAIL_TAB_KIND];
|
|
@@ -31,6 +52,11 @@ function renderDetail(detailId: string) {
|
|
|
31
52
|
}
|
|
32
53
|
|
|
33
54
|
describe("Marketplace detail doc renderer", () => {
|
|
55
|
+
beforeEach(() => {
|
|
56
|
+
mocks.fetchMarketplaceSkillContent.mockReset();
|
|
57
|
+
useMarketplaceDetailDocStore.setState({ entries: {} });
|
|
58
|
+
});
|
|
59
|
+
|
|
34
60
|
it("renders metadata and markdown content as React content", () => {
|
|
35
61
|
const detailId = createMarketplaceDetailDocId("skill", "weather");
|
|
36
62
|
setMarketplaceDetailDocEntry({
|
|
@@ -75,7 +101,7 @@ describe("Marketplace detail doc renderer", () => {
|
|
|
75
101
|
expect(screen.getAllByText(/<script>alert\(1\)<\/script>/)).toHaveLength(1);
|
|
76
102
|
});
|
|
77
103
|
|
|
78
|
-
it("renders loading and missing
|
|
104
|
+
it("renders loading state and rehydrates missing skill details after refresh", async () => {
|
|
79
105
|
const loadingId = createMarketplaceDetailDocId("skill", "loading");
|
|
80
106
|
setMarketplaceDetailDocEntry({
|
|
81
107
|
id: loadingId,
|
|
@@ -88,7 +114,15 @@ describe("Marketplace detail doc renderer", () => {
|
|
|
88
114
|
const { rerender } = renderDetail(loadingId);
|
|
89
115
|
expect(document.querySelector(".animate-pulse")).toBeTruthy();
|
|
90
116
|
|
|
91
|
-
|
|
117
|
+
mocks.fetchMarketplaceSkillContent.mockResolvedValue({
|
|
118
|
+
metadataRaw: "name: weather",
|
|
119
|
+
bodyRaw: "# Weather Skill\n\nLocal forecast",
|
|
120
|
+
raw: "# Weather Skill\n\nLocal forecast",
|
|
121
|
+
source: "catalog",
|
|
122
|
+
sourceUrl: "https://example.com/weather",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const missingId = createMarketplaceDetailDocId("skill-preview", "weather");
|
|
92
126
|
const renderer = MARKETPLACE_DETAIL_DOC_BROWSER_RENDERERS[MARKETPLACE_DETAIL_TAB_KIND];
|
|
93
127
|
rerender(
|
|
94
128
|
<>
|
|
@@ -100,7 +134,7 @@ describe("Marketplace detail doc renderer", () => {
|
|
|
100
134
|
tab: {
|
|
101
135
|
id: "tab-1",
|
|
102
136
|
kind: MARKETPLACE_DETAIL_TAB_KIND,
|
|
103
|
-
title: "
|
|
137
|
+
title: "weather",
|
|
104
138
|
currentUrl: createMarketplaceDetailDocUrl(missingId),
|
|
105
139
|
history: [createMarketplaceDetailDocUrl(missingId)],
|
|
106
140
|
historyIndex: 0,
|
|
@@ -110,6 +144,10 @@ describe("Marketplace detail doc renderer", () => {
|
|
|
110
144
|
</>,
|
|
111
145
|
);
|
|
112
146
|
|
|
113
|
-
|
|
147
|
+
await waitFor(() => {
|
|
148
|
+
expect(screen.getByRole("heading", { name: "weather", level: 1 })).toBeTruthy();
|
|
149
|
+
});
|
|
150
|
+
expect(mocks.fetchMarketplaceSkillContent).toHaveBeenCalledWith("weather");
|
|
151
|
+
expect(screen.getByText("Local forecast")).toBeTruthy();
|
|
114
152
|
});
|
|
115
153
|
});
|
|
@@ -8,9 +8,9 @@ import type {
|
|
|
8
8
|
import { Skeleton } from '@/shared/components/ui/skeleton';
|
|
9
9
|
import { t } from '@/shared/lib/i18n';
|
|
10
10
|
import { cn } from '@/shared/lib/utils';
|
|
11
|
+
import { useMarketplaceDetailDocEntry } from '@/features/marketplace/hooks/use-marketplace-detail-doc-entry';
|
|
11
12
|
import {
|
|
12
13
|
setMarketplaceDetailDocEntry,
|
|
13
|
-
useMarketplaceDetailDocStore,
|
|
14
14
|
type MarketplaceDetailDocEntry,
|
|
15
15
|
} from '@/features/marketplace/stores/marketplace-detail-doc.store';
|
|
16
16
|
|
|
@@ -422,13 +422,17 @@ function MarketplaceDetailDocContent({ entry }: { entry: MarketplaceDetailDocEnt
|
|
|
422
422
|
|
|
423
423
|
function MarketplaceDetailDoc({ currentUrl }: { currentUrl: string }) {
|
|
424
424
|
const detailId = readMarketplaceDetailDocId(currentUrl);
|
|
425
|
-
const entry =
|
|
426
|
-
|
|
425
|
+
const entry = useMarketplaceDetailDocEntry(detailId);
|
|
426
|
+
|
|
427
|
+
if (!detailId) {
|
|
427
428
|
return <MarketplaceDetailUnavailable />;
|
|
428
429
|
}
|
|
429
|
-
if (entry.status === 'loading') {
|
|
430
|
+
if (!entry || entry.status === 'loading') {
|
|
430
431
|
return <MarketplaceDetailSkeleton />;
|
|
431
432
|
}
|
|
433
|
+
if (entry.status === 'error' && !entry.contentRaw) {
|
|
434
|
+
return <MarketplaceDetailUnavailable />;
|
|
435
|
+
}
|
|
432
436
|
return <MarketplaceDetailDocContent entry={entry} />;
|
|
433
437
|
}
|
|
434
438
|
|
|
@@ -37,7 +37,7 @@ export function FilterPanel({
|
|
|
37
37
|
value={searchText}
|
|
38
38
|
onChange={(event) => onSearchTextChange(event.target.value)}
|
|
39
39
|
placeholder={searchPlaceholder}
|
|
40
|
-
className="h-9 w-full rounded-xl border border-
|
|
40
|
+
className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0 focus:border-border"
|
|
41
41
|
/>
|
|
42
42
|
{isRefreshing && (
|
|
43
43
|
<Loader2
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import { fetchMarketplaceSkillContent } from '@/shared/lib/api';
|
|
3
|
+
import { t } from '@/shared/lib/i18n';
|
|
4
|
+
import {
|
|
5
|
+
setMarketplaceDetailDocEntry,
|
|
6
|
+
useMarketplaceDetailDocStore,
|
|
7
|
+
} from '@/features/marketplace/stores/marketplace-detail-doc.store';
|
|
8
|
+
|
|
9
|
+
function parseMarketplaceDetailScopeKey(
|
|
10
|
+
detailId: string,
|
|
11
|
+
): { scope: string; key: string } | null {
|
|
12
|
+
const separator = detailId.indexOf(':');
|
|
13
|
+
if (separator <= 0 || separator >= detailId.length - 1) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
scope: detailId.slice(0, separator),
|
|
18
|
+
key: detailId.slice(separator + 1),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isRehydratableSkillScope(scope: string): boolean {
|
|
23
|
+
return scope === 'skill' || scope === 'skill-preview';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function useMarketplaceDetailDocEntry(detailId: string | null) {
|
|
27
|
+
const entry = useMarketplaceDetailDocStore((state) =>
|
|
28
|
+
detailId ? state.entries[detailId] : undefined,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!detailId || useMarketplaceDetailDocStore.getState().entries[detailId]) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const parsed = parseMarketplaceDetailScopeKey(detailId);
|
|
36
|
+
if (!parsed || !isRehydratableSkillScope(parsed.scope)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let cancelled = false;
|
|
41
|
+
const title = parsed.key;
|
|
42
|
+
const typeLabel = t('marketplaceTypeSkill');
|
|
43
|
+
|
|
44
|
+
void fetchMarketplaceSkillContent(parsed.key)
|
|
45
|
+
.then((content) => {
|
|
46
|
+
if (cancelled) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
setMarketplaceDetailDocEntry({
|
|
50
|
+
id: detailId,
|
|
51
|
+
title,
|
|
52
|
+
typeLabel,
|
|
53
|
+
spec: parsed.key,
|
|
54
|
+
status: 'ready',
|
|
55
|
+
summary: t('marketplaceInstalledLocalSummary'),
|
|
56
|
+
metadataRaw: content.metadataRaw,
|
|
57
|
+
contentRaw: content.bodyRaw || content.raw,
|
|
58
|
+
sourceUrl: content.sourceUrl,
|
|
59
|
+
sourceLabel: content.source ? `Source (${content.source})` : undefined,
|
|
60
|
+
});
|
|
61
|
+
})
|
|
62
|
+
.catch(() => {
|
|
63
|
+
if (cancelled) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setMarketplaceDetailDocEntry({
|
|
67
|
+
id: detailId,
|
|
68
|
+
title,
|
|
69
|
+
typeLabel,
|
|
70
|
+
spec: parsed.key,
|
|
71
|
+
status: 'error',
|
|
72
|
+
summary: t('marketplaceInstalledLocalSummary'),
|
|
73
|
+
metadataRaw: JSON.stringify({ skill: parsed.key }, null, 2),
|
|
74
|
+
contentRaw: t('marketplaceDetailUnavailableDescription'),
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true;
|
|
80
|
+
};
|
|
81
|
+
}, [detailId]);
|
|
82
|
+
|
|
83
|
+
return entry;
|
|
84
|
+
}
|
|
@@ -204,7 +204,7 @@ function PanelAppsEmptyGuide({
|
|
|
204
204
|
<button
|
|
205
205
|
type="button"
|
|
206
206
|
onClick={onRefresh}
|
|
207
|
-
className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-
|
|
207
|
+
className="mt-4 inline-flex items-center justify-center gap-1.5 rounded-md border border-border px-3 py-1.5 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
208
208
|
>
|
|
209
209
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
210
210
|
{t('panelAppsRefresh')}
|
|
@@ -245,7 +245,7 @@ function PanelAppsEmptyGuideStep({
|
|
|
245
245
|
<button
|
|
246
246
|
type="button"
|
|
247
247
|
onClick={onAction}
|
|
248
|
-
className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-
|
|
248
|
+
className="flex w-full gap-2 rounded-md bg-muted/60 px-3 py-2.5 text-left transition-colors hover:bg-accent/70 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
249
249
|
>
|
|
250
250
|
{content}
|
|
251
251
|
</button>
|
|
@@ -80,7 +80,7 @@ export function RightPanelResourceHomePage({ open }: RightPanelResourceHomePageP
|
|
|
80
80
|
key={item.id}
|
|
81
81
|
type="button"
|
|
82
82
|
onClick={() => openTarget(item.target)}
|
|
83
|
-
className="group flex min-h-[68px] flex-col items-center gap-1.5 rounded-md px-1.5 py-1.5 text-center focus-visible:outline-none focus-visible:ring-
|
|
83
|
+
className="group flex min-h-[68px] flex-col items-center gap-1.5 rounded-md px-1.5 py-1.5 text-center focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
84
84
|
>
|
|
85
85
|
<span className={cn('flex h-10 w-10 shrink-0 items-center justify-center rounded-md transition-colors', view.accentClassName, view.hoverAccentClassName)}>
|
|
86
86
|
{view.icon}
|
|
@@ -95,7 +95,7 @@ function SideDockButton({
|
|
|
95
95
|
onClick={() => onOpen(item)}
|
|
96
96
|
className={cn(
|
|
97
97
|
'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
|
|
98
|
-
'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-
|
|
98
|
+
'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
|
|
99
99
|
active
|
|
100
100
|
? 'bg-primary/10 text-primary'
|
|
101
101
|
: 'bg-transparent',
|
|
@@ -113,7 +113,7 @@ function SideDockButton({
|
|
|
113
113
|
event.stopPropagation();
|
|
114
114
|
onUnpin(item);
|
|
115
115
|
}}
|
|
116
|
-
className="absolute -right-1 -top-1 hidden h-4 w-4 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm ring-1 ring-border/70 transition-colors hover:bg-muted hover:text-foreground group-hover:flex focus-visible:flex focus-visible:outline-none focus-visible:ring-
|
|
116
|
+
className="absolute -right-1 -top-1 hidden h-4 w-4 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm ring-1 ring-border/70 transition-colors hover:bg-muted hover:text-foreground group-hover:flex focus-visible:flex focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
|
|
117
117
|
>
|
|
118
118
|
<X className="h-3 w-3" aria-hidden="true" />
|
|
119
119
|
</button>
|
|
@@ -144,7 +144,7 @@ function SideDockActionButton({
|
|
|
144
144
|
onClick={onClick}
|
|
145
145
|
className={cn(
|
|
146
146
|
'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
|
|
147
|
-
'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-
|
|
147
|
+
'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
|
|
148
148
|
)}
|
|
149
149
|
>
|
|
150
150
|
<Icon className="h-5 w-5" aria-hidden="true" />
|