@pactor-app/blocks 0.0.0-stage → 1.46.0

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/dist/index.js ADDED
@@ -0,0 +1,4581 @@
1
+ // src/index.ts
2
+ import { Registry } from "@pactor-app/core";
3
+
4
+ // src/components/Login/index.tsx
5
+ import { useI18n } from "@pactor-app/runtime";
6
+
7
+ // src/lib/utils.ts
8
+ import { clsx } from "clsx";
9
+ import { twMerge } from "tailwind-merge";
10
+ function cn(...inputs) {
11
+ return twMerge(clsx(inputs));
12
+ }
13
+
14
+ // src/components/Login/index.tsx
15
+ import { Button } from "@pactor-app/ui/components";
16
+ import { Card } from "@pactor-app/ui/components";
17
+ import { Field } from "@pactor-app/ui/components";
18
+ import { Form } from "@pactor-app/ui/components";
19
+ import { Input } from "@pactor-app/ui/components";
20
+ import { Link } from "@pactor-app/ui/components";
21
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
22
+ function DefaultIllustration({ className }) {
23
+ return /* @__PURE__ */ jsxs(
24
+ "svg",
25
+ {
26
+ "data-slot": "login-default-illustration",
27
+ viewBox: "0 0 320 180",
28
+ role: "img",
29
+ "aria-hidden": "true",
30
+ className: cn("text-primary", className),
31
+ children: [
32
+ /* @__PURE__ */ jsx(
33
+ "rect",
34
+ {
35
+ x: "24",
36
+ y: "96",
37
+ width: "88",
38
+ height: "60",
39
+ rx: "10",
40
+ fill: "currentColor",
41
+ opacity: "0.16"
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsx(
45
+ "rect",
46
+ {
47
+ x: "120",
48
+ y: "60",
49
+ width: "88",
50
+ height: "96",
51
+ rx: "10",
52
+ fill: "currentColor",
53
+ opacity: "0.28"
54
+ }
55
+ ),
56
+ /* @__PURE__ */ jsx(
57
+ "rect",
58
+ {
59
+ x: "216",
60
+ y: "24",
61
+ width: "80",
62
+ height: "132",
63
+ rx: "10",
64
+ fill: "currentColor",
65
+ opacity: "0.4"
66
+ }
67
+ ),
68
+ /* @__PURE__ */ jsx("circle", { cx: "68", cy: "52", r: "26", fill: "currentColor", opacity: "0.2" }),
69
+ /* @__PURE__ */ jsx("circle", { cx: "248", cy: "140", r: "14", fill: "currentColor", opacity: "0.55" }),
70
+ /* @__PURE__ */ jsx(
71
+ "path",
72
+ {
73
+ d: "M40 156 C 90 116, 130 150, 180 118 S 260 96, 300 128",
74
+ fill: "none",
75
+ stroke: "currentColor",
76
+ strokeWidth: "3",
77
+ strokeLinecap: "round",
78
+ opacity: "0.5"
79
+ }
80
+ )
81
+ ]
82
+ }
83
+ );
84
+ }
85
+ var BRAND_PANEL_BG = "bg-muted bg-linear-to-br from-primary/15 via-muted to-primary/5 dark:from-primary/25 dark:to-primary/10";
86
+ function BgDecor({ className }) {
87
+ return /* @__PURE__ */ jsxs(
88
+ "div",
89
+ {
90
+ "data-slot": "login-bg-decor",
91
+ "aria-hidden": "true",
92
+ className: cn(
93
+ "pointer-events-none absolute inset-0 overflow-hidden",
94
+ className
95
+ ),
96
+ children: [
97
+ /* @__PURE__ */ jsx("div", { className: "absolute -right-24 -top-24 size-96 rounded-full bg-primary/8" }),
98
+ /* @__PURE__ */ jsx("div", { className: "absolute -left-32 bottom-0 size-80 rounded-full bg-primary/5" }),
99
+ /* @__PURE__ */ jsx("div", { className: "absolute right-10 top-1/3 size-40 rounded-full border-[12px] border-primary/10" }),
100
+ /* @__PURE__ */ jsx("div", { className: "absolute left-1/4 top-16 h-40 w-64 opacity-35 [background-image:radial-gradient(var(--primary)_1.2px,transparent_1.2px)] [background-size:18px_18px]" }),
101
+ /* @__PURE__ */ jsxs(
102
+ "svg",
103
+ {
104
+ className: "absolute bottom-0 left-0 w-full text-primary",
105
+ viewBox: "0 0 1440 320",
106
+ preserveAspectRatio: "none",
107
+ children: [
108
+ /* @__PURE__ */ jsx(
109
+ "path",
110
+ {
111
+ d: "M0,224 C240,160 480,288 720,240 C960,192 1200,96 1440,160",
112
+ fill: "none",
113
+ stroke: "currentColor",
114
+ strokeOpacity: "0.12",
115
+ strokeWidth: "2"
116
+ }
117
+ ),
118
+ /* @__PURE__ */ jsx(
119
+ "path",
120
+ {
121
+ d: "M0,256 C240,192 480,320 720,272 C960,224 1200,128 1440,192 L1440,320 L0,320 Z",
122
+ fill: "currentColor",
123
+ fillOpacity: "0.05"
124
+ }
125
+ )
126
+ ]
127
+ }
128
+ )
129
+ ]
130
+ }
131
+ );
132
+ }
133
+ function Login({
134
+ layout = "split",
135
+ brandSide = "left",
136
+ brandRatio = 0.5,
137
+ authVariant = "card",
138
+ brandPanelBackground = true,
139
+ decor = true,
140
+ brand,
141
+ title,
142
+ subtitle,
143
+ usernameLabel,
144
+ usernamePlaceholder,
145
+ passwordLabel,
146
+ passwordPlaceholder,
147
+ submitText,
148
+ registerLink,
149
+ forgotLink,
150
+ footer,
151
+ children,
152
+ className,
153
+ style,
154
+ onSubmit,
155
+ onRegisterClick,
156
+ onForgotClick
157
+ }) {
158
+ const { t } = useI18n();
159
+ const brandIdentity = brand && (brand.logo || brand.name) ? /* @__PURE__ */ jsxs("div", { "data-slot": "login-brand-identity", className: "flex items-center gap-2", children: [
160
+ brand.logo ? /* @__PURE__ */ jsx(
161
+ "img",
162
+ {
163
+ src: brand.logo,
164
+ alt: brand.name ?? "",
165
+ className: "size-7 rounded-md"
166
+ }
167
+ ) : null,
168
+ brand.name ? /* @__PURE__ */ jsx("span", { className: "text-base font-semibold", children: brand.name }) : null
169
+ ] }) : null;
170
+ const brandVisual = (alignRight = false) => /* @__PURE__ */ jsxs(
171
+ "div",
172
+ {
173
+ className: cn("flex w-full flex-col gap-4", alignRight && "text-right"),
174
+ children: [
175
+ brand?.title ? /* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold leading-tight tracking-tight", children: brand.title }) : null,
176
+ brand?.description ? /* @__PURE__ */ jsx(
177
+ "p",
178
+ {
179
+ className: cn(
180
+ "max-w-md text-sm text-muted-foreground",
181
+ alignRight && "ml-auto"
182
+ ),
183
+ children: brand.description
184
+ }
185
+ ) : null,
186
+ brand?.illustration ? /* @__PURE__ */ jsx(
187
+ "img",
188
+ {
189
+ src: brand.illustration,
190
+ alt: "",
191
+ className: cn(
192
+ "mt-4 w-full max-w-lg rounded-lg object-cover",
193
+ alignRight && "self-end"
194
+ )
195
+ }
196
+ ) : brand?.illustration === "" ? null : /* @__PURE__ */ jsx(
197
+ DefaultIllustration,
198
+ {
199
+ className: cn("mt-4 w-full max-w-md", alignRight && "self-end")
200
+ }
201
+ )
202
+ ]
203
+ }
204
+ );
205
+ const renderAuxLink = (link, onClick) => {
206
+ if (!link) return null;
207
+ if (link.href) {
208
+ return /* @__PURE__ */ jsx(Link, { href: link.href, text: link.text, className: "text-sm" });
209
+ }
210
+ return /* @__PURE__ */ jsx(
211
+ "button",
212
+ {
213
+ type: "button",
214
+ className: "text-sm text-primary underline-offset-4 hover:underline",
215
+ onClick,
216
+ children: link.text
217
+ }
218
+ );
219
+ };
220
+ const authContent = /* @__PURE__ */ jsxs(Fragment, { children: [
221
+ /* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-col gap-1.5", children: [
222
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: title ?? t("pactor.login.title") }),
223
+ subtitle ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: subtitle }) : null
224
+ ] }),
225
+ children ?? /* @__PURE__ */ jsxs(Form, { name: "login", className: "flex flex-col gap-2", onSubmit, children: [
226
+ /* @__PURE__ */ jsx(Field, { label: usernameLabel ?? t("pactor.login.username"), required: true, children: /* @__PURE__ */ jsx(
227
+ Input,
228
+ {
229
+ name: "username",
230
+ size: "large",
231
+ placeholder: usernamePlaceholder ?? t("pactor.login.usernamePlaceholder"),
232
+ rules: [
233
+ { required: true, message: t("pactor.login.usernameRequired") }
234
+ ]
235
+ }
236
+ ) }),
237
+ /* @__PURE__ */ jsx(Field, { label: passwordLabel ?? t("pactor.login.password"), required: true, children: /* @__PURE__ */ jsx(
238
+ Input,
239
+ {
240
+ name: "password",
241
+ type: "password",
242
+ size: "large",
243
+ placeholder: passwordPlaceholder ?? t("pactor.login.passwordPlaceholder"),
244
+ rules: [
245
+ { required: true, message: t("pactor.login.passwordRequired") }
246
+ ]
247
+ }
248
+ ) }),
249
+ /* @__PURE__ */ jsx(Field, { children: /* @__PURE__ */ jsx(
250
+ Button,
251
+ {
252
+ submit: true,
253
+ variant: "primary",
254
+ size: "large",
255
+ text: submitText ?? t("pactor.login.submit"),
256
+ className: "mt-2 w-full"
257
+ }
258
+ ) })
259
+ ] }),
260
+ registerLink || forgotLink ? /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between gap-4", children: [
261
+ renderAuxLink(registerLink, onRegisterClick),
262
+ renderAuxLink(forgotLink, onForgotClick)
263
+ ] }) : null
264
+ ] });
265
+ const authBox = (variant) => {
266
+ if (variant === "plain") {
267
+ return /* @__PURE__ */ jsx("div", { className: "w-full max-w-[360px]", children: authContent });
268
+ }
269
+ if (variant === "float") {
270
+ return /* @__PURE__ */ jsx(Card, { className: "w-full max-w-[440px] p-4 shadow-xl md:p-6", children: authContent });
271
+ }
272
+ return /* @__PURE__ */ jsx(Card, { className: "w-full max-w-[400px] p-2 md:p-4", children: authContent });
273
+ };
274
+ const footerBar = footer ? /* @__PURE__ */ jsx("footer", { className: "p-4 text-center text-xs text-muted-foreground", children: footer }) : null;
275
+ const brandPanel = (extraClass, withIdentity = true) => /* @__PURE__ */ jsxs(
276
+ "div",
277
+ {
278
+ className: cn(
279
+ "relative hidden flex-col overflow-hidden p-10 md:flex",
280
+ brandPanelBackground && BRAND_PANEL_BG,
281
+ extraClass
282
+ ),
283
+ children: [
284
+ decor ? /* @__PURE__ */ jsx(BgDecor, {}) : null,
285
+ /* @__PURE__ */ jsxs("div", { className: "relative flex h-full flex-col justify-between", children: [
286
+ withIdentity ? brandIdentity : null,
287
+ brandVisual(),
288
+ /* @__PURE__ */ jsx("div", {})
289
+ ] })
290
+ ]
291
+ }
292
+ );
293
+ const authColumn = (variant, identityAlways = false) => /* @__PURE__ */ jsxs("div", { className: "flex min-h-[var(--login-min-h,100svh)] flex-col bg-linear-to-b from-primary/5 via-background to-background dark:from-primary/10", children: [
294
+ brandIdentity ? /* @__PURE__ */ jsx("div", { className: cn("p-6", !identityAlways && "md:hidden"), children: brandIdentity }) : null,
295
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center p-6", children: authBox(variant) }),
296
+ footerBar
297
+ ] });
298
+ const brandCard = (widthClass, alignRight = false) => /* @__PURE__ */ jsx(Card, { className: cn("w-full", widthClass, BRAND_PANEL_BG), children: /* @__PURE__ */ jsxs(
299
+ "div",
300
+ {
301
+ "data-slot": "login-brand-card-content",
302
+ className: cn("flex flex-col gap-6", alignRight && "text-right"),
303
+ children: [
304
+ brandIdentity ? /* @__PURE__ */ jsx("div", { className: cn(alignRight && "flex justify-end"), children: brandIdentity }) : null,
305
+ brandVisual(alignRight)
306
+ ]
307
+ }
308
+ ) });
309
+ const overlapAuthCard = /* @__PURE__ */ jsx(
310
+ "div",
311
+ {
312
+ className: cn(
313
+ "z-10 w-full max-w-[400px]",
314
+ brandSide === "left" ? "md:-ml-24" : "md:-mr-24"
315
+ ),
316
+ children: /* @__PURE__ */ jsx(Card, { className: "p-2 shadow-xl md:p-4", children: authContent })
317
+ }
318
+ );
319
+ const backdropBrandColumn = /* @__PURE__ */ jsxs("div", { className: "relative hidden flex-col justify-between p-10 md:flex", children: [
320
+ brandIdentity,
321
+ brandVisual(),
322
+ /* @__PURE__ */ jsx("div", {})
323
+ ] });
324
+ const backdropAuthColumn = /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-[var(--login-min-h,100svh)] flex-col", children: [
325
+ brandIdentity ? /* @__PURE__ */ jsx("div", { className: "p-6 md:hidden", children: brandIdentity }) : null,
326
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center p-6", children: /* @__PURE__ */ jsx(Card, { className: "w-full max-w-[400px] p-2 shadow-xl md:p-4", children: authContent }) }),
327
+ footerBar
328
+ ] });
329
+ if (layout === "centered") {
330
+ return /* @__PURE__ */ jsxs(
331
+ "div",
332
+ {
333
+ "data-slot": "login",
334
+ "data-layout": layout,
335
+ className: cn(
336
+ "relative flex min-h-[var(--login-min-h,100svh)] w-full flex-col overflow-hidden bg-background bg-linear-to-b from-primary/10 via-background to-background dark:from-primary/15",
337
+ className
338
+ ),
339
+ style,
340
+ children: [
341
+ decor ? /* @__PURE__ */ jsx(BgDecor, {}) : null,
342
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 flex-col items-center justify-center gap-6 p-6", children: [
343
+ brandIdentity,
344
+ authBox(authVariant)
345
+ ] }),
346
+ footerBar
347
+ ]
348
+ }
349
+ );
350
+ }
351
+ if (layout === "brand-card") {
352
+ return /* @__PURE__ */ jsxs(
353
+ "div",
354
+ {
355
+ "data-slot": "login",
356
+ "data-layout": layout,
357
+ "data-brand-side": brandSide,
358
+ className: cn(
359
+ "relative flex min-h-[var(--login-min-h,100svh)] w-full flex-col overflow-hidden bg-background p-6 md:p-10",
360
+ className
361
+ ),
362
+ style,
363
+ children: [
364
+ decor ? /* @__PURE__ */ jsx(BgDecor, {}) : null,
365
+ /* @__PURE__ */ jsx("div", { className: "relative flex flex-1 flex-col items-center gap-6 md:flex-row md:gap-10", children: brandSide === "left" ? /* @__PURE__ */ jsxs(Fragment, { children: [
366
+ brandCard("md:w-[45%]"),
367
+ /* @__PURE__ */ jsx("div", { className: "flex w-full flex-1 justify-center", children: authBox("plain") })
368
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
369
+ /* @__PURE__ */ jsx("div", { className: "flex w-full flex-1 justify-center", children: authBox("plain") }),
370
+ brandCard("md:w-[45%]")
371
+ ] }) }),
372
+ footerBar
373
+ ]
374
+ }
375
+ );
376
+ }
377
+ if (layout === "overlap") {
378
+ return /* @__PURE__ */ jsxs(
379
+ "div",
380
+ {
381
+ "data-slot": "login",
382
+ "data-layout": layout,
383
+ "data-brand-side": brandSide,
384
+ className: cn(
385
+ "relative flex min-h-[var(--login-min-h,100svh)] w-full flex-col overflow-hidden bg-background p-6 md:p-10",
386
+ className
387
+ ),
388
+ style,
389
+ children: [
390
+ decor ? /* @__PURE__ */ jsx(BgDecor, {}) : null,
391
+ /* @__PURE__ */ jsx("div", { className: "relative flex flex-1 flex-col items-center justify-center gap-6 md:flex-row md:items-center md:gap-0", children: brandSide === "left" ? /* @__PURE__ */ jsxs(Fragment, { children: [
392
+ brandCard("md:w-[52%]"),
393
+ overlapAuthCard
394
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
395
+ overlapAuthCard,
396
+ brandCard("md:w-[52%]", true)
397
+ ] }) }),
398
+ footerBar
399
+ ]
400
+ }
401
+ );
402
+ }
403
+ if (layout === "backdrop") {
404
+ return /* @__PURE__ */ jsxs(
405
+ "div",
406
+ {
407
+ "data-slot": "login",
408
+ "data-layout": layout,
409
+ "data-brand-side": brandSide,
410
+ className: cn(
411
+ "relative grid min-h-[var(--login-min-h,100svh)] w-full overflow-hidden bg-linear-to-br from-primary/20 via-accent to-primary/10 md:grid-cols-2 dark:from-primary/25 dark:to-primary/15",
412
+ className
413
+ ),
414
+ style,
415
+ children: [
416
+ decor ? /* @__PURE__ */ jsx(BgDecor, {}) : null,
417
+ brandSide === "left" ? /* @__PURE__ */ jsxs(Fragment, { children: [
418
+ backdropBrandColumn,
419
+ backdropAuthColumn
420
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
421
+ backdropAuthColumn,
422
+ backdropBrandColumn
423
+ ] })
424
+ ]
425
+ }
426
+ );
427
+ }
428
+ const ratio = Math.min(Math.max(brandRatio, 0.2), 0.8);
429
+ const rootStyle = {
430
+ "--login-grid": `minmax(0, ${ratio}fr) minmax(0, ${1 - ratio}fr)`,
431
+ ...style
432
+ };
433
+ return /* @__PURE__ */ jsx(
434
+ "div",
435
+ {
436
+ "data-slot": "login",
437
+ "data-layout": layout,
438
+ "data-brand-side": brandSide,
439
+ className: cn(
440
+ "grid min-h-[var(--login-min-h,100svh)] w-full bg-background md:[grid-template-columns:var(--login-grid)]",
441
+ className
442
+ ),
443
+ style: rootStyle,
444
+ children: brandSide === "left" ? /* @__PURE__ */ jsxs(Fragment, { children: [
445
+ brandPanel(),
446
+ authColumn(authVariant)
447
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
448
+ authColumn(authVariant, true),
449
+ brandPanel("order-last", false)
450
+ ] })
451
+ }
452
+ );
453
+ }
454
+
455
+ // src/components/EntityCard/index.tsx
456
+ import { Button as Button2 } from "@pactor-app/ui/components";
457
+ import { Card as Card2 } from "@pactor-app/ui/components";
458
+ import { Icon } from "@pactor-app/ui/components";
459
+ import { Tag } from "@pactor-app/ui/components";
460
+ import { Text } from "@pactor-app/ui/components";
461
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
462
+ function EntityCard({
463
+ icon,
464
+ logo,
465
+ title,
466
+ subtitle,
467
+ tags,
468
+ description,
469
+ meta,
470
+ actions,
471
+ footerBackground = true,
472
+ hoverable,
473
+ className,
474
+ contentClassName,
475
+ style,
476
+ onClick,
477
+ onAction
478
+ }) {
479
+ return /* @__PURE__ */ jsx2(
480
+ Card2,
481
+ {
482
+ hoverable,
483
+ className,
484
+ contentClassName,
485
+ style,
486
+ onClick,
487
+ children: /* @__PURE__ */ jsxs2("div", { "data-slot": "entity-card", className: "flex flex-col gap-2", children: [
488
+ /* @__PURE__ */ jsxs2("div", { "data-slot": "entity-card-header", className: "flex items-center justify-between gap-2", children: [
489
+ /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 items-center gap-2", children: [
490
+ logo ? /* @__PURE__ */ jsx2("img", { src: logo, alt: title ?? "", className: "size-9 shrink-0 rounded-md" }) : icon ? /* @__PURE__ */ jsx2("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-md bg-muted", children: /* @__PURE__ */ jsx2(Icon, { name: icon }) }) : null,
491
+ title || subtitle ? /* @__PURE__ */ jsxs2("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
492
+ title ? /* @__PURE__ */ jsx2(Text, { text: title, strong: true, className: "truncate" }) : null,
493
+ subtitle ? /* @__PURE__ */ jsx2(Text, { text: subtitle, type: "secondary", size: "small" }) : null
494
+ ] }) : null
495
+ ] }),
496
+ tags?.length ? /* @__PURE__ */ jsx2("span", { className: "flex shrink-0 items-center gap-1", children: tags.map((tag, i) => /* @__PURE__ */ jsx2(Tag, { text: tag.text, color: tag.color }, `${tag.text}-${i}`)) }) : null
497
+ ] }),
498
+ description || meta ? /* @__PURE__ */ jsxs2("div", { "data-slot": "entity-card-body", className: "flex items-center justify-between gap-4", children: [
499
+ description ? /* @__PURE__ */ jsx2(
500
+ Text,
501
+ {
502
+ text: description,
503
+ type: "secondary",
504
+ size: "small",
505
+ className: "line-clamp-2",
506
+ style: { minHeight: 32 }
507
+ }
508
+ ) : null,
509
+ meta ? /* @__PURE__ */ jsx2(Text, { text: meta, type: "secondary", size: "small", className: "shrink-0" }) : null
510
+ ] }) : null,
511
+ actions?.length ? /* @__PURE__ */ jsx2(
512
+ "div",
513
+ {
514
+ "data-slot": "entity-card-footer",
515
+ className: cn(
516
+ "mt-1 flex items-center justify-end gap-1",
517
+ footerBackground && "-mx-4 -mb-4 rounded-b-lg bg-muted px-4 py-2"
518
+ ),
519
+ onClick: (event) => event.stopPropagation(),
520
+ children: actions.map((action, index) => /* @__PURE__ */ jsx2(
521
+ Button2,
522
+ {
523
+ text: action.text,
524
+ variant: action.variant,
525
+ danger: action.danger,
526
+ onClick: () => onAction?.({ key: action.key ?? action.text, text: action.text, index })
527
+ },
528
+ `${action.key ?? action.text}-${index}`
529
+ ))
530
+ }
531
+ ) : null
532
+ ] })
533
+ }
534
+ );
535
+ }
536
+
537
+ // src/components/CardList/index.tsx
538
+ import { useI18n as useI18n2 } from "@pactor-app/runtime";
539
+ import { Card as Card3 } from "@pactor-app/ui/components";
540
+ import { Empty } from "@pactor-app/ui/components";
541
+ import { Icon as Icon2 } from "@pactor-app/ui/components";
542
+ import { Text as Text2 } from "@pactor-app/ui/components";
543
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
544
+ function CardList({
545
+ items,
546
+ columns = 3,
547
+ gap = 16,
548
+ createText,
549
+ createIcon = "plus",
550
+ hoverable,
551
+ showCount,
552
+ emptyText,
553
+ className,
554
+ style,
555
+ onItemClick,
556
+ onItemAction,
557
+ onCreateClick
558
+ }) {
559
+ const { t } = useI18n2();
560
+ const list = items ?? [];
561
+ return /* @__PURE__ */ jsxs3("div", { "data-slot": "card-list", className: cn("flex flex-col gap-3", className), style, children: [
562
+ list.length === 0 && !createText ? /* @__PURE__ */ jsx3(Empty, { title: emptyText ?? t("pactor.table.empty") }) : /* @__PURE__ */ jsxs3(
563
+ "div",
564
+ {
565
+ "data-slot": "card-list-grid",
566
+ className: "grid",
567
+ style: { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gap },
568
+ children: [
569
+ createText ? /* @__PURE__ */ jsxs3(
570
+ Card3,
571
+ {
572
+ hoverable: true,
573
+ className: "h-full border-dashed",
574
+ contentClassName: "flex flex-1 flex-col items-center justify-center gap-2",
575
+ onClick: onCreateClick,
576
+ children: [
577
+ /* @__PURE__ */ jsx3(Icon2, { name: createIcon, size: "lg", className: "text-muted-foreground" }),
578
+ /* @__PURE__ */ jsx3(Text2, { text: createText, type: "secondary" })
579
+ ]
580
+ }
581
+ ) : null,
582
+ list.map((item, index) => /* @__PURE__ */ jsx3(
583
+ EntityCard,
584
+ {
585
+ icon: item.icon,
586
+ logo: item.logo,
587
+ title: item.title,
588
+ subtitle: item.subtitle,
589
+ tags: item.tags,
590
+ description: item.description,
591
+ meta: item.meta,
592
+ actions: item.actions,
593
+ hoverable,
594
+ onClick: onItemClick ? () => onItemClick({ item, index }) : void 0,
595
+ onAction: onItemAction ? ({ key, text }) => onItemAction({ key, text, item, index }) : void 0
596
+ },
597
+ index
598
+ ))
599
+ ]
600
+ }
601
+ ),
602
+ showCount ? /* @__PURE__ */ jsx3(
603
+ Text2,
604
+ {
605
+ text: t("pactor.cardList.total", { count: list.length }),
606
+ type: "secondary",
607
+ size: "small"
608
+ }
609
+ ) : null
610
+ ] });
611
+ }
612
+
613
+ // src/components/RecordingPage/index.tsx
614
+ import { useState as useState6 } from "react";
615
+
616
+ // src/components/RecordingPage/detail.tsx
617
+ import { useRenderer } from "@pactor-app/runtime";
618
+ import { isValidElement, useEffect, useRef, useState as useState4 } from "react";
619
+ import { Avatar as Avatar4 } from "@pactor-app/ui/components";
620
+ import { Badge } from "@pactor-app/ui/components";
621
+ import { Button as Button11 } from "@pactor-app/ui/components";
622
+ import { Icon as Icon7 } from "@pactor-app/ui/components";
623
+ import { Progress } from "@pactor-app/ui/components";
624
+ import { Text as Text7 } from "@pactor-app/ui/components";
625
+
626
+ // src/components/RecordingPage/insight.tsx
627
+ import { useState } from "react";
628
+ import { Avatar } from "@pactor-app/ui/components";
629
+ import { Button as Button4 } from "@pactor-app/ui/components";
630
+ import { Icon as Icon3 } from "@pactor-app/ui/components";
631
+ import { Text as Text3 } from "@pactor-app/ui/components";
632
+
633
+ // src/components/RecordingPage/perspective-dialog.tsx
634
+ import {
635
+ Dialog,
636
+ DialogContent,
637
+ DialogDescription,
638
+ DialogTitle
639
+ } from "@pactor-app/ui/ui";
640
+ import { Button as Button3 } from "@pactor-app/ui/components";
641
+ import { Textarea } from "@pactor-app/ui/components";
642
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
643
+ function RecordingPerspectiveDialog({
644
+ open,
645
+ title,
646
+ hint,
647
+ value,
648
+ placeholder,
649
+ cancelLabel,
650
+ confirmLabel,
651
+ onChange,
652
+ onCancel,
653
+ onConfirm
654
+ }) {
655
+ return /* @__PURE__ */ jsx4(
656
+ Dialog,
657
+ {
658
+ open,
659
+ onOpenChange: (next) => {
660
+ if (!next) onCancel();
661
+ },
662
+ children: /* @__PURE__ */ jsxs4(
663
+ DialogContent,
664
+ {
665
+ "data-slot": "recording-perspective-dialog",
666
+ className: "gap-4 sm:max-w-xl",
667
+ children: [
668
+ /* @__PURE__ */ jsx4(DialogTitle, { children: title }),
669
+ hint !== void 0 ? /* @__PURE__ */ jsx4(DialogDescription, { children: hint }) : null,
670
+ /* @__PURE__ */ jsx4(
671
+ Textarea,
672
+ {
673
+ value,
674
+ placeholder,
675
+ rows: 4,
676
+ onChange
677
+ }
678
+ ),
679
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-end gap-2", children: [
680
+ /* @__PURE__ */ jsx4(Button3, { text: cancelLabel, onClick: onCancel }),
681
+ /* @__PURE__ */ jsx4(Button3, { text: confirmLabel, variant: "primary", onClick: onConfirm })
682
+ ] })
683
+ ]
684
+ }
685
+ )
686
+ }
687
+ );
688
+ }
689
+
690
+ // src/components/RecordingPage/insight.tsx
691
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
692
+ function RecordingInsightView({
693
+ insight,
694
+ onPerspectiveEdit,
695
+ onPerspectiveApply,
696
+ onInsightAction
697
+ }) {
698
+ const [dialogOpen, setDialogOpen] = useState(false);
699
+ const inputControlled = insight.perspectiveInput !== void 0;
700
+ const [innerInput, setInnerInput] = useState(insight.defaultPerspectiveInput);
701
+ const committedInput = inputControlled ? insight.perspectiveInput : innerInput;
702
+ const [draftInput, setDraftInput] = useState(committedInput);
703
+ const openDialog = () => {
704
+ setDraftInput(committedInput);
705
+ setDialogOpen(true);
706
+ };
707
+ const confirmDialog = () => {
708
+ if (!inputControlled) setInnerInput(draftInput);
709
+ onPerspectiveApply?.(draftInput ?? "");
710
+ setDialogOpen(false);
711
+ };
712
+ return /* @__PURE__ */ jsxs5("div", { "data-slot": "recording-insight", className: "flex flex-col gap-6", children: [
713
+ insight.perspectiveTitle !== void 0 ? /* @__PURE__ */ jsxs5(
714
+ "section",
715
+ {
716
+ "data-slot": "recording-insight-perspective",
717
+ className: "flex flex-col gap-2 rounded-lg bg-success/10 p-4",
718
+ children: [
719
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
720
+ /* @__PURE__ */ jsxs5("div", { className: "flex min-w-0 items-center gap-2", children: [
721
+ /* @__PURE__ */ jsx5(Icon3, { name: "user", size: 16, className: "shrink-0 text-muted-foreground" }),
722
+ /* @__PURE__ */ jsx5(Text3, { text: insight.perspectiveTitle, strong: true, className: "truncate" })
723
+ ] }),
724
+ insight.perspectiveActionLabel !== void 0 ? /* @__PURE__ */ jsx5(
725
+ Button4,
726
+ {
727
+ text: insight.perspectiveActionLabel,
728
+ variant: "link",
729
+ size: "small",
730
+ className: "text-success",
731
+ onClick: () => {
732
+ onPerspectiveEdit?.();
733
+ openDialog();
734
+ }
735
+ }
736
+ ) : null
737
+ ] }),
738
+ insight.perspectiveHint !== void 0 ? /* @__PURE__ */ jsx5(Text3, { text: insight.perspectiveHint, size: "small", type: "secondary" }) : null
739
+ ]
740
+ }
741
+ ) : null,
742
+ insight.basics !== void 0 ? /* @__PURE__ */ jsxs5("section", { className: "flex flex-col gap-3", children: [
743
+ /* @__PURE__ */ jsx5(Text3, { text: insight.basicsTitle ?? "\u57FA\u7840\u4FE1\u606F", strong: true, size: "large" }),
744
+ /* @__PURE__ */ jsx5(Text3, { text: insight.basics })
745
+ ] }) : null,
746
+ insight.chapters !== void 0 && insight.chapters.length > 0 ? /* @__PURE__ */ jsxs5("section", { className: "flex flex-col gap-3", children: [
747
+ /* @__PURE__ */ jsx5(Text3, { text: insight.chaptersTitle ?? "\u7AE0\u8282\u901F\u89C8\u4E0E\u89E3\u8BFB", strong: true, size: "large" }),
748
+ insight.chapters.map((chapter, index) => /* @__PURE__ */ jsxs5(
749
+ "div",
750
+ {
751
+ "data-slot": "recording-insight-chapter",
752
+ className: "flex flex-col gap-2 rounded-lg border border-border p-4",
753
+ children: [
754
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-baseline gap-2", children: [
755
+ chapter.time !== void 0 ? /* @__PURE__ */ jsx5(Text3, { text: chapter.time, size: "small", type: "secondary" }) : null,
756
+ /* @__PURE__ */ jsx5(Text3, { text: chapter.title, strong: true, size: "small" })
757
+ ] }),
758
+ /* @__PURE__ */ jsx5(Text3, { text: chapter.content, size: "small" })
759
+ ]
760
+ },
761
+ chapter.time ?? index
762
+ ))
763
+ ] }) : null,
764
+ insight.viewpoints !== void 0 && insight.viewpoints.length > 0 ? /* @__PURE__ */ jsxs5("section", { className: "flex flex-col gap-3", children: [
765
+ /* @__PURE__ */ jsx5(Text3, { text: insight.viewpointsTitle ?? "\u89C2\u70B9\u5206\u6790", strong: true, size: "large" }),
766
+ insight.viewpoints.map((viewpoint, index) => /* @__PURE__ */ jsxs5(
767
+ "div",
768
+ {
769
+ "data-slot": "recording-insight-viewpoint",
770
+ className: "flex flex-col gap-2 rounded-lg border border-border p-4",
771
+ children: [
772
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
773
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
774
+ /* @__PURE__ */ jsx5(Avatar, { fallback: viewpoint.avatarText ?? viewpoint.speaker.charAt(0) }),
775
+ /* @__PURE__ */ jsx5(Text3, { text: viewpoint.speaker, strong: true, size: "small" })
776
+ ] }),
777
+ viewpoint.tag !== "" ? /* @__PURE__ */ jsx5(Text3, { text: viewpoint.tag ?? "\u81EA\u52A8\u8BC6\u522B", size: "small", type: "secondary" }) : null
778
+ ] }),
779
+ /* @__PURE__ */ jsxs5("p", { className: "text-sm", children: [
780
+ viewpoint.label !== void 0 ? /* @__PURE__ */ jsxs5("strong", { children: [
781
+ viewpoint.label,
782
+ "\uFF1A"
783
+ ] }) : null,
784
+ viewpoint.content
785
+ ] })
786
+ ]
787
+ },
788
+ viewpoint.speaker ?? index
789
+ ))
790
+ ] }) : null,
791
+ insight.actions !== void 0 && insight.actions.length > 0 ? /* @__PURE__ */ jsx5("div", { "data-slot": "recording-insight-actions", className: "flex items-center gap-1", children: insight.actions.map((action) => /* @__PURE__ */ jsx5(
792
+ Button4,
793
+ {
794
+ icon: action.icon,
795
+ variant: "text",
796
+ size: "small",
797
+ onClick: () => onInsightAction?.(action.key)
798
+ },
799
+ action.key
800
+ )) }) : null,
801
+ /* @__PURE__ */ jsx5(
802
+ RecordingPerspectiveDialog,
803
+ {
804
+ open: dialogOpen,
805
+ title: insight.perspectiveDialogTitle ?? "\u4FEE\u6539\u89E3\u8BFB\u8EAB\u4EFD",
806
+ hint: insight.perspectiveDialogHint ?? "\u7528\u81EA\u7136\u8BED\u8A00\u8BF4\u660E\u4F60\u5728\u672C\u6B21\u4F1A\u8BAE\u4E2D\u7684\u8EAB\u4EFD\u4E0E\u5173\u6CE8\u70B9\u3002",
807
+ value: draftInput,
808
+ placeholder: insight.perspectivePlaceholder,
809
+ cancelLabel: insight.perspectiveCancelLabel ?? "\u53D6\u6D88",
810
+ confirmLabel: insight.perspectiveConfirmLabel ?? "\u5E94\u7528\u8EAB\u4EFD",
811
+ onChange: setDraftInput,
812
+ onCancel: () => setDialogOpen(false),
813
+ onConfirm: confirmDialog
814
+ }
815
+ )
816
+ ] });
817
+ }
818
+
819
+ // src/components/RecordingPage/memory.tsx
820
+ import { Button as Button5 } from "@pactor-app/ui/components";
821
+ import { Text as Text4 } from "@pactor-app/ui/components";
822
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
823
+ function RecordingMemoryView({ memory, onMemoryAction }) {
824
+ return /* @__PURE__ */ jsxs6("div", { "data-slot": "recording-memory", className: "flex flex-col gap-6", children: [
825
+ (memory.groups ?? []).map((group, groupIndex) => /* @__PURE__ */ jsxs6("section", { className: "flex flex-col gap-3", children: [
826
+ /* @__PURE__ */ jsx6(Text4, { text: `${group.label} \xB7 ${group.items.length}`, strong: true, size: "large" }),
827
+ group.items.map((item, index) => /* @__PURE__ */ jsxs6(
828
+ "div",
829
+ {
830
+ "data-slot": "recording-memory-item",
831
+ className: "flex flex-col gap-2 rounded-lg border border-border p-4",
832
+ children: [
833
+ /* @__PURE__ */ jsx6(Text4, { text: item.title, strong: true, size: "small" }),
834
+ /* @__PURE__ */ jsx6(Text4, { text: item.content, size: "small" })
835
+ ]
836
+ },
837
+ index
838
+ ))
839
+ ] }, group.key ?? group.label ?? groupIndex)),
840
+ memory.actions !== void 0 && memory.actions.length > 0 ? /* @__PURE__ */ jsx6("div", { "data-slot": "recording-memory-actions", className: "flex items-center gap-1", children: memory.actions.map((action) => /* @__PURE__ */ jsx6(
841
+ Button5,
842
+ {
843
+ icon: action.icon,
844
+ variant: "text",
845
+ size: "small",
846
+ onClick: () => onMemoryAction?.(action.key)
847
+ },
848
+ action.key
849
+ )) }) : null
850
+ ] });
851
+ }
852
+
853
+ // src/components/RecordingPage/share.tsx
854
+ import { useState as useState2 } from "react";
855
+ import {
856
+ Dialog as Dialog2,
857
+ DialogContent as DialogContent2,
858
+ DialogTitle as DialogTitle2
859
+ } from "@pactor-app/ui/ui";
860
+ import {
861
+ Popover,
862
+ PopoverContent,
863
+ PopoverTrigger
864
+ } from "@pactor-app/ui/ui";
865
+ import { Button as Button6 } from "@pactor-app/ui/components";
866
+ import { Icon as Icon4 } from "@pactor-app/ui/components";
867
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
868
+ function copyText(text) {
869
+ try {
870
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
871
+ void navigator.clipboard.writeText(text);
872
+ return;
873
+ }
874
+ const textarea = document.createElement("textarea");
875
+ textarea.value = text;
876
+ document.body.appendChild(textarea);
877
+ textarea.select();
878
+ document.execCommand?.("copy");
879
+ textarea.remove();
880
+ } catch {
881
+ }
882
+ }
883
+ function RecordingShare({
884
+ shareLabel,
885
+ options,
886
+ selectedKeys,
887
+ popoverTitle,
888
+ cancelLabel,
889
+ confirmLabel,
890
+ dialogTitle,
891
+ link,
892
+ copyLinkLabel,
893
+ copiedLabel,
894
+ mailLabel,
895
+ systemLabel,
896
+ onShare,
897
+ onSelectionCommit,
898
+ onShareConfirm,
899
+ onShareChannel
900
+ }) {
901
+ const [popoverOpen, setPopoverOpen] = useState2(false);
902
+ const [dialogOpen, setDialogOpen] = useState2(false);
903
+ const [draftKeys, setDraftKeys] = useState2(selectedKeys);
904
+ const [copied, setCopied] = useState2(false);
905
+ const copyLink = () => {
906
+ if (link !== void 0) {
907
+ copyText(link);
908
+ setCopied(true);
909
+ setTimeout(() => setCopied(false), 1500);
910
+ }
911
+ onShareChannel?.("copyLink");
912
+ };
913
+ const openPopover = (next) => {
914
+ if (next) {
915
+ setDraftKeys(selectedKeys);
916
+ onShare?.();
917
+ }
918
+ setPopoverOpen(next);
919
+ };
920
+ const toggleOption = (option) => {
921
+ if (option.disabled === true) return;
922
+ setDraftKeys(
923
+ (prev) => prev.includes(option.key) ? prev.filter((key) => key !== option.key) : [...prev, option.key]
924
+ );
925
+ };
926
+ const confirm = () => {
927
+ const next = options.filter((option) => draftKeys.includes(option.key)).map((option) => option.key);
928
+ onSelectionCommit(next);
929
+ onShareConfirm?.(next);
930
+ setPopoverOpen(false);
931
+ setDialogOpen(true);
932
+ };
933
+ const trigger = /* @__PURE__ */ jsx7(Button6, { text: shareLabel, size: "small" });
934
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
935
+ /* @__PURE__ */ jsxs7(Popover, { open: popoverOpen, onOpenChange: openPopover, children: [
936
+ /* @__PURE__ */ jsx7(
937
+ PopoverTrigger,
938
+ {
939
+ nativeButton: false,
940
+ render: /* @__PURE__ */ jsx7("span", { "data-slot": "recording-share-trigger", className: "inline-flex" }),
941
+ children: trigger
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsxs7(
945
+ PopoverContent,
946
+ {
947
+ align: "end",
948
+ sideOffset: 8,
949
+ className: "w-64 p-0",
950
+ "data-slot": "recording-share-popover",
951
+ children: [
952
+ /* @__PURE__ */ jsx7("div", { className: "px-4 pt-4 pb-2 text-sm font-semibold", children: popoverTitle }),
953
+ /* @__PURE__ */ jsx7("div", { className: "flex flex-col px-2 pb-2", children: options.map((option) => {
954
+ const checked = draftKeys.includes(option.key);
955
+ const disabled = option.disabled === true;
956
+ return /* @__PURE__ */ jsxs7(
957
+ "button",
958
+ {
959
+ type: "button",
960
+ "data-slot": "recording-share-option",
961
+ "aria-pressed": checked,
962
+ disabled,
963
+ className: cn(
964
+ "flex w-full items-center gap-2.5 rounded-md px-2 py-2 text-left text-sm",
965
+ disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer hover:bg-muted"
966
+ ),
967
+ onClick: () => toggleOption(option),
968
+ children: [
969
+ /* @__PURE__ */ jsx7(
970
+ "span",
971
+ {
972
+ className: cn(
973
+ "flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
974
+ checked ? "bg-primary text-primary-foreground" : "border border-input"
975
+ ),
976
+ children: checked ? /* @__PURE__ */ jsx7(Icon4, { name: "check", size: 12 }) : null
977
+ }
978
+ ),
979
+ option.label
980
+ ]
981
+ },
982
+ option.key
983
+ );
984
+ }) }),
985
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-end gap-2 border-t border-border px-4 py-3", children: [
986
+ /* @__PURE__ */ jsx7(Button6, { text: cancelLabel, variant: "text", onClick: () => setPopoverOpen(false) }),
987
+ /* @__PURE__ */ jsx7(Button6, { text: confirmLabel, variant: "primary", onClick: confirm })
988
+ ] })
989
+ ]
990
+ }
991
+ )
992
+ ] }),
993
+ /* @__PURE__ */ jsx7(Dialog2, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxs7(DialogContent2, { "data-slot": "recording-share-dialog", className: "gap-4 sm:max-w-xl", children: [
994
+ /* @__PURE__ */ jsx7(DialogTitle2, { children: dialogTitle }),
995
+ link !== void 0 ? /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-3 rounded-lg border border-border px-4 py-3", children: [
996
+ /* @__PURE__ */ jsx7("span", { className: "min-w-0 flex-1 truncate text-sm", children: link }),
997
+ /* @__PURE__ */ jsx7(
998
+ Button6,
999
+ {
1000
+ text: copied ? copiedLabel : copyLinkLabel,
1001
+ variant: "link",
1002
+ size: "small",
1003
+ onClick: copyLink
1004
+ }
1005
+ )
1006
+ ] }) : null,
1007
+ /* @__PURE__ */ jsxs7("div", { className: "grid grid-cols-2 gap-3", children: [
1008
+ /* @__PURE__ */ jsx7(
1009
+ Button6,
1010
+ {
1011
+ text: mailLabel,
1012
+ icon: "mail",
1013
+ className: "h-12",
1014
+ onClick: () => onShareChannel?.("mail")
1015
+ }
1016
+ ),
1017
+ /* @__PURE__ */ jsx7(
1018
+ Button6,
1019
+ {
1020
+ text: systemLabel,
1021
+ icon: "share",
1022
+ className: "h-12",
1023
+ onClick: () => onShareChannel?.("system")
1024
+ }
1025
+ )
1026
+ ] })
1027
+ ] }) })
1028
+ ] });
1029
+ }
1030
+
1031
+ // src/components/RecordingPage/speaker-dialog.tsx
1032
+ import {
1033
+ Dialog as Dialog3,
1034
+ DialogContent as DialogContent3,
1035
+ DialogDescription as DialogDescription2,
1036
+ DialogTitle as DialogTitle3
1037
+ } from "@pactor-app/ui/ui";
1038
+ import { Avatar as Avatar2 } from "@pactor-app/ui/components";
1039
+ import { Button as Button7 } from "@pactor-app/ui/components";
1040
+ import { Input as Input2 } from "@pactor-app/ui/components";
1041
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1042
+ function RecordingSpeakerDialog({
1043
+ open,
1044
+ title,
1045
+ hint,
1046
+ speakers,
1047
+ selectedIndex,
1048
+ customName,
1049
+ inputPlaceholder,
1050
+ applyLabel,
1051
+ onSelect,
1052
+ onInputName,
1053
+ onCancel,
1054
+ onConfirm
1055
+ }) {
1056
+ const canApply = customName.trim() !== "" || selectedIndex >= 0;
1057
+ return /* @__PURE__ */ jsx8(
1058
+ Dialog3,
1059
+ {
1060
+ open,
1061
+ onOpenChange: (next) => {
1062
+ if (!next) onCancel();
1063
+ },
1064
+ children: /* @__PURE__ */ jsxs8(
1065
+ DialogContent3,
1066
+ {
1067
+ "data-slot": "recording-speaker-dialog",
1068
+ className: "gap-4 sm:max-w-xl",
1069
+ children: [
1070
+ /* @__PURE__ */ jsx8(DialogTitle3, { children: title }),
1071
+ hint !== void 0 ? /* @__PURE__ */ jsx8(DialogDescription2, { children: hint }) : null,
1072
+ speakers.length > 0 ? /* @__PURE__ */ jsx8("div", { className: "flex flex-col divide-y divide-border", children: speakers.map((speaker, index) => {
1073
+ const selected = index === selectedIndex;
1074
+ return /* @__PURE__ */ jsxs8(
1075
+ "button",
1076
+ {
1077
+ type: "button",
1078
+ "data-slot": "recording-speaker-option",
1079
+ "aria-pressed": selected,
1080
+ className: cn(
1081
+ "flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-left",
1082
+ selected ? "bg-primary/10" : "hover:bg-muted"
1083
+ ),
1084
+ onClick: () => onSelect(index),
1085
+ children: [
1086
+ /* @__PURE__ */ jsx8(Avatar2, { fallback: speaker.avatarText ?? speaker.name.charAt(0) }),
1087
+ /* @__PURE__ */ jsxs8("span", { className: "flex min-w-0 flex-1 items-baseline gap-2", children: [
1088
+ /* @__PURE__ */ jsx8("span", { className: "text-sm font-medium", children: speaker.name }),
1089
+ speaker.role !== void 0 ? /* @__PURE__ */ jsx8("span", { className: "truncate text-sm text-muted-foreground", children: speaker.role }) : null
1090
+ ] }),
1091
+ speaker.tag !== void 0 ? /* @__PURE__ */ jsx8("span", { className: "shrink-0 text-sm text-muted-foreground", children: speaker.tag }) : null
1092
+ ]
1093
+ },
1094
+ `${speaker.name}-${index}`
1095
+ );
1096
+ }) }) : null,
1097
+ /* @__PURE__ */ jsx8(
1098
+ Input2,
1099
+ {
1100
+ value: customName,
1101
+ placeholder: inputPlaceholder,
1102
+ onChange: onInputName
1103
+ }
1104
+ ),
1105
+ /* @__PURE__ */ jsx8("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsx8(
1106
+ Button7,
1107
+ {
1108
+ text: applyLabel,
1109
+ variant: "primary",
1110
+ disabled: !canApply,
1111
+ onClick: onConfirm
1112
+ }
1113
+ ) })
1114
+ ]
1115
+ }
1116
+ )
1117
+ }
1118
+ );
1119
+ }
1120
+
1121
+ // src/components/RecordingPage/summary.tsx
1122
+ import { Button as Button8 } from "@pactor-app/ui/components";
1123
+ import { Icon as Icon5 } from "@pactor-app/ui/components";
1124
+ import { Table } from "@pactor-app/ui/components";
1125
+ import { Text as Text5 } from "@pactor-app/ui/components";
1126
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1127
+ function MindmapPill({ label, center }) {
1128
+ return /* @__PURE__ */ jsx9(
1129
+ "span",
1130
+ {
1131
+ className: cn(
1132
+ "rounded-lg px-4 py-1.5 text-sm",
1133
+ center ? "bg-primary px-5 py-2.5 font-medium text-primary-foreground" : "border border-border bg-background"
1134
+ ),
1135
+ children: label
1136
+ }
1137
+ );
1138
+ }
1139
+ var mindmapLine = /* @__PURE__ */ jsx9("div", { className: "h-px w-10 shrink-0", style: { background: "var(--border)" } });
1140
+ function RecordingSummaryView({
1141
+ summary,
1142
+ onMindmapAction,
1143
+ onMaterialClick,
1144
+ selectedTodoIds,
1145
+ onTodoToggle,
1146
+ onSummaryAction
1147
+ }) {
1148
+ return /* @__PURE__ */ jsxs9("div", { "data-slot": "recording-summary", className: "flex flex-col gap-6", children: [
1149
+ summary.mindmap ? /* @__PURE__ */ jsxs9("section", { className: "rounded-lg border border-border", children: [
1150
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-between px-4 py-3", children: [
1151
+ /* @__PURE__ */ jsx9(Text5, { text: summary.mindmapTitle ?? "\u601D\u7EF4\u5BFC\u56FE", strong: true }),
1152
+ summary.mindmapActionLabel !== void 0 ? /* @__PURE__ */ jsx9(
1153
+ Button8,
1154
+ {
1155
+ text: summary.mindmapActionLabel,
1156
+ variant: "link",
1157
+ size: "small",
1158
+ className: "text-success",
1159
+ onClick: () => onMindmapAction?.()
1160
+ }
1161
+ ) : null
1162
+ ] }),
1163
+ /* @__PURE__ */ jsxs9("div", { className: "mx-4 mb-4 flex items-center justify-center rounded-lg bg-muted p-8", children: [
1164
+ /* @__PURE__ */ jsx9("div", { className: "flex flex-col items-start gap-2", children: (summary.mindmap.left ?? []).map((node) => /* @__PURE__ */ jsx9(MindmapPill, { label: node }, node)) }),
1165
+ mindmapLine,
1166
+ /* @__PURE__ */ jsx9(MindmapPill, { label: summary.mindmap.center, center: true }),
1167
+ mindmapLine,
1168
+ /* @__PURE__ */ jsx9("div", { className: "flex flex-col items-start gap-2", children: (summary.mindmap.right ?? []).map((node) => /* @__PURE__ */ jsx9(MindmapPill, { label: node }, node)) })
1169
+ ] })
1170
+ ] }) : null,
1171
+ summary.overview !== void 0 ? /* @__PURE__ */ jsxs9("section", { className: "flex flex-col gap-3", children: [
1172
+ /* @__PURE__ */ jsx9(
1173
+ Text5,
1174
+ {
1175
+ text: summary.overviewTitle ?? "\u4F1A\u8BAE\u6982\u51B5",
1176
+ strong: true,
1177
+ size: "large"
1178
+ }
1179
+ ),
1180
+ /* @__PURE__ */ jsx9("div", { className: "rounded-lg bg-muted p-4", children: /* @__PURE__ */ jsx9(Text5, { text: summary.overview }) })
1181
+ ] }) : null,
1182
+ summary.topics !== void 0 && summary.topics.length > 0 ? /* @__PURE__ */ jsxs9("section", { className: "flex flex-col gap-3", children: [
1183
+ /* @__PURE__ */ jsx9(Text5, { text: summary.topicsTitle ?? "\u4E3B\u8981\u8BAE\u9898", strong: true, size: "large" }),
1184
+ /* @__PURE__ */ jsx9(
1185
+ "div",
1186
+ {
1187
+ className: "grid gap-3",
1188
+ style: { gridTemplateColumns: "1fr 1fr" },
1189
+ children: summary.topics.map((topic, index) => /* @__PURE__ */ jsxs9(
1190
+ "div",
1191
+ {
1192
+ className: "flex flex-col gap-2 rounded-lg border border-border p-4",
1193
+ children: [
1194
+ /* @__PURE__ */ jsx9(Text5, { text: topic.title, strong: true, size: "small" }),
1195
+ /* @__PURE__ */ jsx9(Text5, { text: topic.content, size: "small" })
1196
+ ]
1197
+ },
1198
+ index
1199
+ ))
1200
+ }
1201
+ )
1202
+ ] }) : null,
1203
+ summary.conclusions !== void 0 && summary.conclusions.length > 0 ? /* @__PURE__ */ jsxs9("section", { className: "rounded-lg border border-border p-4", children: [
1204
+ /* @__PURE__ */ jsx9(
1205
+ Text5,
1206
+ {
1207
+ text: summary.conclusionsTitle ?? "\u8BC4\u5BA1\u7ED3\u8BBA\u4E0E\u540E\u7EED\u5B89\u6392",
1208
+ strong: true,
1209
+ size: "small"
1210
+ }
1211
+ ),
1212
+ /* @__PURE__ */ jsx9(
1213
+ Table,
1214
+ {
1215
+ className: "mt-3",
1216
+ columns: [
1217
+ { title: "\u4E8B\u9879", dataIndex: "item" },
1218
+ { title: "\u4F1A\u8BAE\u7ED3\u8BBA", dataIndex: "conclusion" },
1219
+ { title: "\u8D1F\u8D23\u4EBA", dataIndex: "owner" }
1220
+ ],
1221
+ dataSource: summary.conclusions.map((row, index) => ({
1222
+ id: index,
1223
+ ...row
1224
+ }))
1225
+ }
1226
+ )
1227
+ ] }) : null,
1228
+ summary.followUp !== void 0 ? /* @__PURE__ */ jsxs9("section", { className: "flex flex-col gap-2 rounded-lg border border-border p-4", children: [
1229
+ /* @__PURE__ */ jsx9(Text5, { text: summary.followUpTitle, strong: true, size: "small" }),
1230
+ /* @__PURE__ */ jsx9(Text5, { text: summary.followUp, size: "small" })
1231
+ ] }) : null,
1232
+ summary.materials !== void 0 && summary.materials.length > 0 ? /* @__PURE__ */ jsxs9("section", { className: "flex flex-col gap-3", children: [
1233
+ /* @__PURE__ */ jsx9(
1234
+ Text5,
1235
+ {
1236
+ text: summary.materialsTitle ?? "\u76F8\u5173\u6750\u6599",
1237
+ strong: true,
1238
+ size: "large"
1239
+ }
1240
+ ),
1241
+ /* @__PURE__ */ jsx9("div", { className: "flex flex-col divide-y divide-border rounded-lg border border-border", children: summary.materials.map((material, index) => /* @__PURE__ */ jsxs9(
1242
+ "button",
1243
+ {
1244
+ type: "button",
1245
+ "data-slot": "recording-material",
1246
+ className: "flex cursor-pointer items-center gap-2 px-4 py-3 text-left",
1247
+ onClick: () => onMaterialClick?.(material.id ?? index),
1248
+ children: [
1249
+ /* @__PURE__ */ jsx9(
1250
+ Icon5,
1251
+ {
1252
+ name: material.icon ?? "file-text",
1253
+ size: 16,
1254
+ className: "text-muted-foreground"
1255
+ }
1256
+ ),
1257
+ /* @__PURE__ */ jsx9(
1258
+ Text5,
1259
+ {
1260
+ text: material.title,
1261
+ size: "small",
1262
+ className: "min-w-0 flex-1 truncate"
1263
+ }
1264
+ ),
1265
+ /* @__PURE__ */ jsx9(
1266
+ Text5,
1267
+ {
1268
+ text: material.meta,
1269
+ size: "small",
1270
+ type: "secondary",
1271
+ className: "shrink-0"
1272
+ }
1273
+ ),
1274
+ /* @__PURE__ */ jsx9(
1275
+ Icon5,
1276
+ {
1277
+ name: "chevron-right",
1278
+ size: 14,
1279
+ className: "text-muted-foreground"
1280
+ }
1281
+ )
1282
+ ]
1283
+ },
1284
+ material.id ?? index
1285
+ )) })
1286
+ ] }) : null,
1287
+ summary.todos !== void 0 && summary.todos.length > 0 ? /* @__PURE__ */ jsxs9("section", { className: "flex flex-col gap-3", children: [
1288
+ /* @__PURE__ */ jsx9(Text5, { text: summary.todosTitle ?? "\u5F85\u529E\u4E8B\u9879", strong: true, size: "large" }),
1289
+ /* @__PURE__ */ jsx9("div", { className: "flex flex-col divide-y divide-border rounded-lg border border-border", children: summary.todos.map((todo, index) => {
1290
+ const todoId = todo.id ?? index;
1291
+ const checked = selectedTodoIds !== void 0 ? selectedTodoIds.includes(todoId) : todo.done === true;
1292
+ return /* @__PURE__ */ jsxs9(
1293
+ "button",
1294
+ {
1295
+ type: "button",
1296
+ "data-slot": "recording-todo",
1297
+ "aria-pressed": checked,
1298
+ className: "flex cursor-pointer items-center gap-2 px-4 py-3 text-left",
1299
+ onClick: () => onTodoToggle?.(todoId),
1300
+ children: [
1301
+ checked ? /* @__PURE__ */ jsx9(
1302
+ Icon5,
1303
+ {
1304
+ name: "check-circle",
1305
+ size: 16,
1306
+ className: "shrink-0 text-success"
1307
+ }
1308
+ ) : /* @__PURE__ */ jsx9(
1309
+ "span",
1310
+ {
1311
+ className: "shrink-0 rounded-full border border-border",
1312
+ style: { width: 16, height: 16 }
1313
+ }
1314
+ ),
1315
+ /* @__PURE__ */ jsx9(
1316
+ Text5,
1317
+ {
1318
+ text: todo.title,
1319
+ size: "small",
1320
+ className: "min-w-0 flex-1 truncate"
1321
+ }
1322
+ ),
1323
+ /* @__PURE__ */ jsx9(
1324
+ Text5,
1325
+ {
1326
+ text: todo.owner,
1327
+ size: "small",
1328
+ type: "secondary",
1329
+ className: "shrink-0"
1330
+ }
1331
+ )
1332
+ ]
1333
+ },
1334
+ todoId
1335
+ );
1336
+ }) })
1337
+ ] }) : null,
1338
+ summary.actions !== void 0 && summary.actions.length > 0 ? /* @__PURE__ */ jsx9(
1339
+ "div",
1340
+ {
1341
+ "data-slot": "recording-summary-actions",
1342
+ className: "flex items-center gap-1",
1343
+ children: summary.actions.map((action) => /* @__PURE__ */ jsx9(
1344
+ Button8,
1345
+ {
1346
+ icon: action.icon,
1347
+ variant: "text",
1348
+ size: "small",
1349
+ onClick: () => onSummaryAction?.(action.key)
1350
+ },
1351
+ action.key
1352
+ ))
1353
+ }
1354
+ ) : null
1355
+ ] });
1356
+ }
1357
+
1358
+ // src/components/RecordingPage/translation.tsx
1359
+ import { useState as useState3 } from "react";
1360
+ import { Avatar as Avatar3 } from "@pactor-app/ui/components";
1361
+ import { Button as Button10 } from "@pactor-app/ui/components";
1362
+ import { Icon as Icon6 } from "@pactor-app/ui/components";
1363
+ import { Text as Text6 } from "@pactor-app/ui/components";
1364
+
1365
+ // src/components/RecordingPage/language-dialog.tsx
1366
+ import {
1367
+ Dialog as Dialog4,
1368
+ DialogContent as DialogContent4,
1369
+ DialogTitle as DialogTitle4
1370
+ } from "@pactor-app/ui/ui";
1371
+ import { Button as Button9 } from "@pactor-app/ui/components";
1372
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1373
+ function LanguageOptionItem({
1374
+ option,
1375
+ selected,
1376
+ onSelect
1377
+ }) {
1378
+ return /* @__PURE__ */ jsxs10(
1379
+ "button",
1380
+ {
1381
+ type: "button",
1382
+ "data-slot": "recording-language-option",
1383
+ "aria-pressed": selected,
1384
+ className: cn(
1385
+ "flex w-full cursor-pointer items-center gap-2.5 px-3 py-2.5 text-left text-sm",
1386
+ selected ? "bg-primary/10" : "hover:bg-muted"
1387
+ ),
1388
+ onClick: () => onSelect(option.key),
1389
+ children: [
1390
+ /* @__PURE__ */ jsx10(
1391
+ "span",
1392
+ {
1393
+ className: cn(
1394
+ "flex h-4 w-4 shrink-0 items-center justify-center rounded-full border",
1395
+ selected ? "border-primary" : "border-input"
1396
+ ),
1397
+ children: selected ? /* @__PURE__ */ jsx10("span", { className: "h-2 w-2 rounded-full bg-primary" }) : null
1398
+ }
1399
+ ),
1400
+ /* @__PURE__ */ jsx10("span", { className: cn(selected && "font-medium"), children: option.label })
1401
+ ]
1402
+ }
1403
+ );
1404
+ }
1405
+ function RecordingLanguageDialog({
1406
+ open,
1407
+ title,
1408
+ sourceTitle,
1409
+ targetTitle,
1410
+ sourceLanguages,
1411
+ targetLanguages,
1412
+ source,
1413
+ target,
1414
+ cancelLabel,
1415
+ confirmLabel,
1416
+ onSelectSource,
1417
+ onSelectTarget,
1418
+ onCancel,
1419
+ onConfirm
1420
+ }) {
1421
+ const renderColumn = (columnTitle, options, value, onSelect) => /* @__PURE__ */ jsxs10("div", { className: "flex min-w-0 flex-col gap-2", children: [
1422
+ /* @__PURE__ */ jsx10("span", { className: "text-sm font-medium", children: columnTitle }),
1423
+ /* @__PURE__ */ jsx10("div", { className: "flex flex-col divide-y divide-border", children: options.map((option) => /* @__PURE__ */ jsx10(
1424
+ LanguageOptionItem,
1425
+ {
1426
+ option,
1427
+ selected: option.key === value,
1428
+ onSelect
1429
+ },
1430
+ option.key
1431
+ )) })
1432
+ ] });
1433
+ return /* @__PURE__ */ jsx10(
1434
+ Dialog4,
1435
+ {
1436
+ open,
1437
+ onOpenChange: (next) => {
1438
+ if (!next) onCancel();
1439
+ },
1440
+ children: /* @__PURE__ */ jsxs10(
1441
+ DialogContent4,
1442
+ {
1443
+ "data-slot": "recording-language-dialog",
1444
+ className: "gap-6 sm:max-w-xl",
1445
+ children: [
1446
+ /* @__PURE__ */ jsx10(DialogTitle4, { children: title }),
1447
+ /* @__PURE__ */ jsxs10("div", { className: "grid grid-cols-2 gap-6", children: [
1448
+ renderColumn(sourceTitle, sourceLanguages, source, onSelectSource),
1449
+ renderColumn(targetTitle, targetLanguages, target, onSelectTarget)
1450
+ ] }),
1451
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-end gap-2", children: [
1452
+ /* @__PURE__ */ jsx10(Button9, { text: cancelLabel, onClick: onCancel }),
1453
+ /* @__PURE__ */ jsx10(Button9, { text: confirmLabel, variant: "primary", onClick: onConfirm })
1454
+ ] })
1455
+ ]
1456
+ }
1457
+ )
1458
+ }
1459
+ );
1460
+ }
1461
+
1462
+ // src/components/RecordingPage/translation.tsx
1463
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1464
+ function RecordingTranslationView({
1465
+ translation,
1466
+ onLanguageClick,
1467
+ onLanguageChange,
1468
+ onTranslationAction
1469
+ }) {
1470
+ const sourceLanguages = translation.sourceLanguages ?? [];
1471
+ const targetLanguages = translation.targetLanguages ?? [];
1472
+ const dialogEnabled = sourceLanguages.length > 0 || targetLanguages.length > 0;
1473
+ const [dialogOpen, setDialogOpen] = useState3(false);
1474
+ const sourceControlled = translation.sourceLanguage !== void 0;
1475
+ const targetControlled = translation.targetLanguage !== void 0;
1476
+ const [innerSource, setInnerSource] = useState3(
1477
+ translation.defaultSourceLanguage ?? sourceLanguages[0]?.key
1478
+ );
1479
+ const [innerTarget, setInnerTarget] = useState3(
1480
+ translation.defaultTargetLanguage ?? targetLanguages[0]?.key
1481
+ );
1482
+ const committedSource = sourceControlled ? translation.sourceLanguage : innerSource;
1483
+ const committedTarget = targetControlled ? translation.targetLanguage : innerTarget;
1484
+ const [draftSource, setDraftSource] = useState3(committedSource);
1485
+ const [draftTarget, setDraftTarget] = useState3(committedTarget);
1486
+ const openDialog = () => {
1487
+ setDraftSource(committedSource);
1488
+ setDraftTarget(committedTarget);
1489
+ setDialogOpen(true);
1490
+ };
1491
+ const confirmDialog = () => {
1492
+ if (!sourceControlled) setInnerSource(draftSource);
1493
+ if (!targetControlled) setInnerTarget(draftTarget);
1494
+ onLanguageChange?.({
1495
+ source: draftSource,
1496
+ sourceLabel: sourceLanguages.find((item) => item.key === draftSource)?.label,
1497
+ target: draftTarget,
1498
+ targetLabel: targetLanguages.find((item) => item.key === draftTarget)?.label
1499
+ });
1500
+ setDialogOpen(false);
1501
+ };
1502
+ return /* @__PURE__ */ jsxs11("div", { "data-slot": "recording-translation", className: "flex flex-col gap-4", children: [
1503
+ translation.languageLabel !== void 0 ? /* @__PURE__ */ jsx11("div", { children: /* @__PURE__ */ jsxs11(
1504
+ "button",
1505
+ {
1506
+ type: "button",
1507
+ "data-slot": "recording-translation-language",
1508
+ className: "inline-flex cursor-pointer items-center gap-1.5 rounded-full bg-primary/10 px-3 py-1.5 text-sm",
1509
+ onClick: () => {
1510
+ onLanguageClick?.();
1511
+ if (dialogEnabled) openDialog();
1512
+ },
1513
+ children: [
1514
+ /* @__PURE__ */ jsx11(Icon6, { name: "globe", size: 14 }),
1515
+ translation.languageLabel,
1516
+ /* @__PURE__ */ jsx11(Icon6, { name: "chevron-down", size: 14, className: "text-muted-foreground" })
1517
+ ]
1518
+ }
1519
+ ) }) : null,
1520
+ /* @__PURE__ */ jsx11("div", { className: "flex flex-col divide-y divide-border", children: (translation.segments ?? []).map((segment, index) => /* @__PURE__ */ jsxs11(
1521
+ "div",
1522
+ {
1523
+ "data-slot": "recording-translation-segment",
1524
+ className: "grid gap-4 py-4",
1525
+ style: { gridTemplateColumns: "1fr 1fr" },
1526
+ children: [
1527
+ /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 flex-col gap-3", children: [
1528
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
1529
+ /* @__PURE__ */ jsx11(Avatar3, { fallback: segment.avatarText ?? segment.speaker?.charAt(0) ?? "?" }),
1530
+ /* @__PURE__ */ jsx11(Text6, { text: segment.speaker, strong: true, size: "small" })
1531
+ ] }),
1532
+ /* @__PURE__ */ jsx11(Text6, { text: segment.source })
1533
+ ] }),
1534
+ /* @__PURE__ */ jsxs11("div", { className: "flex min-w-0 flex-col gap-3", children: [
1535
+ /* @__PURE__ */ jsx11(Text6, { text: translation.targetLabel ?? "\u8BD1\u6587", strong: true, size: "small" }),
1536
+ /* @__PURE__ */ jsx11(Text6, { text: segment.target })
1537
+ ] })
1538
+ ]
1539
+ },
1540
+ index
1541
+ )) }),
1542
+ translation.actions !== void 0 && translation.actions.length > 0 ? /* @__PURE__ */ jsx11("div", { "data-slot": "recording-translation-actions", className: "flex items-center gap-1", children: translation.actions.map((action) => /* @__PURE__ */ jsx11(
1543
+ Button10,
1544
+ {
1545
+ icon: action.icon,
1546
+ variant: "text",
1547
+ size: "small",
1548
+ onClick: () => onTranslationAction?.(action.key)
1549
+ },
1550
+ action.key
1551
+ )) }) : null,
1552
+ dialogEnabled ? /* @__PURE__ */ jsx11(
1553
+ RecordingLanguageDialog,
1554
+ {
1555
+ open: dialogOpen,
1556
+ title: translation.languageDialogTitle ?? "\u9009\u62E9\u7FFB\u8BD1\u8BED\u8A00",
1557
+ sourceTitle: translation.sourceLanguagesTitle ?? "\u539F\u6587\u8BED\u8A00\u8BC6\u522B",
1558
+ targetTitle: translation.targetLanguagesTitle ?? "\u8BD1\u6587\u8BED\u8A00\u9009\u62E9",
1559
+ sourceLanguages,
1560
+ targetLanguages,
1561
+ source: draftSource,
1562
+ target: draftTarget,
1563
+ cancelLabel: translation.cancelLabel ?? "\u53D6\u6D88",
1564
+ confirmLabel: translation.confirmLabel ?? "\u786E\u5B9A",
1565
+ onSelectSource: setDraftSource,
1566
+ onSelectTarget: setDraftTarget,
1567
+ onCancel: () => setDialogOpen(false),
1568
+ onConfirm: confirmDialog
1569
+ }
1570
+ ) : null
1571
+ ] });
1572
+ }
1573
+
1574
+ // src/components/RecordingPage/detail.tsx
1575
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1576
+ function TranscriptSegment({
1577
+ segment,
1578
+ speaker,
1579
+ onRename
1580
+ }) {
1581
+ return /* @__PURE__ */ jsxs12("div", { "data-slot": "recording-segment", className: "flex flex-col gap-2", children: [
1582
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between", children: [
1583
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
1584
+ /* @__PURE__ */ jsx12(Avatar4, { fallback: segment.avatarText ?? speaker?.charAt(0) ?? "?" }),
1585
+ /* @__PURE__ */ jsx12(Text7, { text: speaker, strong: true, size: "small" }),
1586
+ /* @__PURE__ */ jsx12(
1587
+ "button",
1588
+ {
1589
+ type: "button",
1590
+ "data-slot": "recording-speaker-edit",
1591
+ "aria-label": "\u4FEE\u6539\u53D1\u8A00\u4EBA",
1592
+ className: "cursor-pointer text-muted-foreground",
1593
+ onClick: () => onRename?.(),
1594
+ children: /* @__PURE__ */ jsx12(Icon7, { name: "pencil", size: 14 })
1595
+ }
1596
+ )
1597
+ ] }),
1598
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
1599
+ segment.marked ? /* @__PURE__ */ jsx12(Badge, { text: "\u2691 \u5DF2\u6807\u8BB0", variant: "outline" }) : null,
1600
+ /* @__PURE__ */ jsx12(Text7, { text: segment.time, size: "small", type: "secondary" })
1601
+ ] })
1602
+ ] }),
1603
+ /* @__PURE__ */ jsx12(Text7, { text: segment.content })
1604
+ ] });
1605
+ }
1606
+ function RecordingDetail({
1607
+ title,
1608
+ meta,
1609
+ editLabel,
1610
+ shareLabel,
1611
+ shareOptions = [],
1612
+ shareSelectedKeys = [],
1613
+ sharePopoverTitle,
1614
+ shareCancelLabel,
1615
+ shareConfirmLabel,
1616
+ shareDialogTitle,
1617
+ shareLink,
1618
+ shareCopyLinkLabel,
1619
+ shareCopiedLabel,
1620
+ shareMailLabel,
1621
+ shareSystemLabel,
1622
+ onShareSelectionCommit,
1623
+ onShareConfirm,
1624
+ onShareChannel,
1625
+ onEdit,
1626
+ onShare,
1627
+ tabs,
1628
+ activeTab,
1629
+ onTabChange,
1630
+ transcriptTabKey,
1631
+ segments,
1632
+ speakers = [],
1633
+ speakerDialogTitle,
1634
+ speakerDialogHint,
1635
+ speakerInputPlaceholder,
1636
+ speakerApplyLabel,
1637
+ onSpeakerChange,
1638
+ summary,
1639
+ summaryTabKey,
1640
+ onMindmapAction,
1641
+ onMaterialClick,
1642
+ selectedTodoIds,
1643
+ onTodoToggle,
1644
+ onSummaryAction,
1645
+ insight,
1646
+ insightTabKey,
1647
+ onPerspectiveEdit,
1648
+ onPerspectiveApply,
1649
+ onInsightAction,
1650
+ memory,
1651
+ memoryTabKey,
1652
+ onMemoryAction,
1653
+ translation,
1654
+ translationTabKey,
1655
+ onLanguageClick,
1656
+ onLanguageChange,
1657
+ onTranslationAction,
1658
+ player,
1659
+ onPlay,
1660
+ onSkipBack,
1661
+ onSkipForward
1662
+ }) {
1663
+ const { renderChildren } = useRenderer();
1664
+ const renderSlot = (slot) => isValidElement(slot) || typeof slot !== "object" || slot === null ? slot : renderChildren([slot]);
1665
+ const activeTabItem = tabs.find((tab) => tab.key === activeTab);
1666
+ const [renameIndex, setRenameIndex] = useState4(null);
1667
+ const [draftSpeakerIndex, setDraftSpeakerIndex] = useState4(-1);
1668
+ const [draftSpeakerName, setDraftSpeakerName] = useState4("");
1669
+ const [speakerOverrides, setSpeakerOverrides] = useState4({});
1670
+ const speakerSignature = useRef("");
1671
+ useEffect(() => {
1672
+ const signature = JSON.stringify(segments.map((segment) => segment.speaker ?? ""));
1673
+ if (speakerSignature.current !== signature) {
1674
+ speakerSignature.current = signature;
1675
+ setSpeakerOverrides((prev) => Object.keys(prev).length > 0 ? {} : prev);
1676
+ }
1677
+ }, [segments]);
1678
+ const displaySpeaker = (index) => speakerOverrides[index] ?? segments[index]?.speaker;
1679
+ const openRename = (index) => {
1680
+ const current = displaySpeaker(index);
1681
+ setDraftSpeakerIndex(speakers.findIndex((item) => item.name === current));
1682
+ setDraftSpeakerName("");
1683
+ setRenameIndex(index);
1684
+ };
1685
+ const confirmRename = () => {
1686
+ if (renameIndex === null) return;
1687
+ const name = draftSpeakerName.trim() !== "" ? draftSpeakerName.trim() : speakers[draftSpeakerIndex]?.name;
1688
+ if (name === void 0 || name === "") return;
1689
+ const previous = displaySpeaker(renameIndex);
1690
+ setSpeakerOverrides((prev) => ({ ...prev, [renameIndex]: name }));
1691
+ onSpeakerChange?.({ index: renameIndex, previous, name });
1692
+ setRenameIndex(null);
1693
+ };
1694
+ return /* @__PURE__ */ jsxs12("div", { "data-slot": "recording-page-detail", className: "flex h-full min-w-0 flex-1 flex-col", children: [
1695
+ /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center justify-between px-6 py-4", children: [
1696
+ /* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-col gap-1", children: [
1697
+ /* @__PURE__ */ jsx12(Text7, { text: title, strong: true, size: "large", className: "truncate" }),
1698
+ meta !== void 0 ? /* @__PURE__ */ jsx12(Text7, { text: meta, size: "small", type: "secondary" }) : null
1699
+ ] }),
1700
+ /* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 items-center gap-3", children: [
1701
+ editLabel !== void 0 ? /* @__PURE__ */ jsx12(Button11, { text: editLabel, variant: "link", size: "small", onClick: () => onEdit?.() }) : null,
1702
+ shareLabel !== void 0 ? shareOptions.length > 0 ? /* @__PURE__ */ jsx12(
1703
+ RecordingShare,
1704
+ {
1705
+ shareLabel,
1706
+ options: shareOptions,
1707
+ selectedKeys: shareSelectedKeys,
1708
+ popoverTitle: sharePopoverTitle ?? "\u9009\u62E9\u5206\u4EAB\u5185\u5BB9",
1709
+ cancelLabel: shareCancelLabel ?? "\u53D6\u6D88",
1710
+ confirmLabel: shareConfirmLabel ?? "\u786E\u5B9A",
1711
+ dialogTitle: shareDialogTitle ?? "\u5206\u4EAB\u4F1A\u8BAE\u5185\u5BB9",
1712
+ link: shareLink,
1713
+ copyLinkLabel: shareCopyLinkLabel ?? "\u590D\u5236\u94FE\u63A5",
1714
+ copiedLabel: shareCopiedLabel ?? "\u5DF2\u590D\u5236",
1715
+ mailLabel: shareMailLabel ?? "\u90AE\u4EF6",
1716
+ systemLabel: shareSystemLabel ?? "\u7CFB\u7EDF\u5206\u4EAB",
1717
+ onShare,
1718
+ onSelectionCommit: (keys) => onShareSelectionCommit?.(keys),
1719
+ onShareConfirm,
1720
+ onShareChannel
1721
+ }
1722
+ ) : /* @__PURE__ */ jsx12(Button11, { text: shareLabel, size: "small", onClick: () => onShare?.() }) : null
1723
+ ] })
1724
+ ] }),
1725
+ tabs.length > 0 ? /* @__PURE__ */ jsxs12(Fragment3, { children: [
1726
+ /* @__PURE__ */ jsx12("div", { "data-slot": "recording-page-tabs", className: "flex shrink-0 px-6 pt-3", children: /* @__PURE__ */ jsx12("div", { className: "flex w-full items-center gap-1 rounded-lg bg-muted p-1", children: tabs.map((tab) => {
1727
+ const active = tab.key === activeTab;
1728
+ return /* @__PURE__ */ jsx12(
1729
+ "button",
1730
+ {
1731
+ type: "button",
1732
+ role: "tab",
1733
+ "aria-selected": active,
1734
+ className: cn(
1735
+ "flex-1 cursor-pointer rounded-md px-3 py-1 text-sm",
1736
+ active ? "bg-background shadow-sm" : "text-muted-foreground"
1737
+ ),
1738
+ onClick: () => onTabChange(tab.key),
1739
+ children: tab.label
1740
+ },
1741
+ tab.key
1742
+ );
1743
+ }) }) }),
1744
+ /* @__PURE__ */ jsx12(
1745
+ "div",
1746
+ {
1747
+ "data-slot": "recording-page-tab-body",
1748
+ className: "scrollbar-subtle min-h-0 flex-1 overflow-y-auto px-6 py-4",
1749
+ children: activeTabItem?.content !== void 0 ? renderSlot(activeTabItem.content) : activeTabItem?.key === transcriptTabKey ? /* @__PURE__ */ jsx12("div", { className: "flex flex-col gap-6", children: segments.map((segment, index) => /* @__PURE__ */ jsx12(
1750
+ TranscriptSegment,
1751
+ {
1752
+ segment,
1753
+ speaker: displaySpeaker(index),
1754
+ onRename: () => openRename(index)
1755
+ },
1756
+ segment.time ?? index
1757
+ )) }) : activeTabItem?.key === summaryTabKey && summary !== void 0 ? /* @__PURE__ */ jsx12(
1758
+ RecordingSummaryView,
1759
+ {
1760
+ summary,
1761
+ onMindmapAction,
1762
+ onMaterialClick,
1763
+ selectedTodoIds,
1764
+ onTodoToggle,
1765
+ onSummaryAction
1766
+ }
1767
+ ) : activeTabItem?.key === insightTabKey && insight !== void 0 ? /* @__PURE__ */ jsx12(
1768
+ RecordingInsightView,
1769
+ {
1770
+ insight,
1771
+ onPerspectiveEdit,
1772
+ onPerspectiveApply,
1773
+ onInsightAction
1774
+ }
1775
+ ) : activeTabItem?.key === memoryTabKey && memory !== void 0 ? /* @__PURE__ */ jsx12(RecordingMemoryView, { memory, onMemoryAction }) : activeTabItem?.key === translationTabKey && translation !== void 0 ? /* @__PURE__ */ jsx12(
1776
+ RecordingTranslationView,
1777
+ {
1778
+ translation,
1779
+ onLanguageClick,
1780
+ onLanguageChange,
1781
+ onTranslationAction
1782
+ }
1783
+ ) : null
1784
+ }
1785
+ )
1786
+ ] }) : null,
1787
+ player !== void 0 ? /* @__PURE__ */ jsxs12(
1788
+ "div",
1789
+ {
1790
+ "data-slot": "recording-page-player",
1791
+ className: "flex shrink-0 items-center gap-3 border-t border-border px-6 py-3",
1792
+ children: [
1793
+ /* @__PURE__ */ jsx12(Button11, { icon: "rotate-ccw", variant: "text", onClick: () => onSkipBack?.() }),
1794
+ /* @__PURE__ */ jsx12(
1795
+ Button11,
1796
+ {
1797
+ icon: "play",
1798
+ style: { borderRadius: 999 },
1799
+ onClick: () => onPlay?.()
1800
+ }
1801
+ ),
1802
+ /* @__PURE__ */ jsx12(Button11, { icon: "rotate-cw", variant: "text", onClick: () => onSkipForward?.() }),
1803
+ /* @__PURE__ */ jsx12(Text7, { text: player.currentTime, size: "small", type: "secondary" }),
1804
+ /* @__PURE__ */ jsx12(Progress, { value: player.progress, style: { flex: 1 } }),
1805
+ /* @__PURE__ */ jsx12(Text7, { text: player.totalTime, size: "small", type: "secondary" })
1806
+ ]
1807
+ }
1808
+ ) : null,
1809
+ /* @__PURE__ */ jsx12(
1810
+ RecordingSpeakerDialog,
1811
+ {
1812
+ open: renameIndex !== null,
1813
+ title: speakerDialogTitle ?? "\u4FEE\u6539\u53D1\u8A00\u4EBA",
1814
+ hint: speakerDialogHint ?? "\u9009\u62E9\u53C2\u4F1A\u4FE1\u606F\u3001\u5DF2\u8BC6\u522B\u4EBA\u5458\u6216\u4F60\u66FE\u624B\u52A8\u521B\u5EFA\u7684\u8054\u7CFB\u4EBA\u3002",
1815
+ speakers,
1816
+ selectedIndex: draftSpeakerIndex,
1817
+ customName: draftSpeakerName,
1818
+ inputPlaceholder: speakerInputPlaceholder ?? "\u8F93\u5165\u59D3\u540D",
1819
+ applyLabel: speakerApplyLabel ?? "\u5E94\u7528\u59D3\u540D",
1820
+ onSelect: setDraftSpeakerIndex,
1821
+ onInputName: setDraftSpeakerName,
1822
+ onCancel: () => setRenameIndex(null),
1823
+ onConfirm: confirmRename
1824
+ }
1825
+ )
1826
+ ] });
1827
+ }
1828
+
1829
+ // src/components/RecordingPage/list.tsx
1830
+ import { useState as useState5 } from "react";
1831
+ import {
1832
+ AlertDialog,
1833
+ AlertDialogAction,
1834
+ AlertDialogCancel,
1835
+ AlertDialogContent,
1836
+ AlertDialogDescription,
1837
+ AlertDialogFooter,
1838
+ AlertDialogHeader,
1839
+ AlertDialogTitle
1840
+ } from "@pactor-app/ui/ui";
1841
+ import { Button as Button12 } from "@pactor-app/ui/components";
1842
+ import { Empty as Empty2 } from "@pactor-app/ui/components";
1843
+ import { Icon as Icon8 } from "@pactor-app/ui/components";
1844
+ import { Text as Text8 } from "@pactor-app/ui/components";
1845
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1846
+ function RecordingList({
1847
+ listTitle,
1848
+ selectLabel,
1849
+ selectDoneLabel,
1850
+ selectAllLabel,
1851
+ selectedCountText,
1852
+ deleteLabel,
1853
+ deleteConfirmTitle,
1854
+ deleteConfirmText,
1855
+ deleteCancelLabel,
1856
+ deleteConfirmLabel,
1857
+ selecting = false,
1858
+ selectedRecordingIds = [],
1859
+ onEnterSelectMode,
1860
+ onExitSelectMode,
1861
+ onToggleRecordingSelect,
1862
+ onToggleSelectAll,
1863
+ onDeleteSelected,
1864
+ filters,
1865
+ activeFilter,
1866
+ onFilterChange,
1867
+ processingText,
1868
+ processingHint,
1869
+ onProcessingClick,
1870
+ newRecordingLabel,
1871
+ onNewRecording,
1872
+ groups,
1873
+ activeRecordingId,
1874
+ onSelectRecording,
1875
+ listWidth,
1876
+ emptyText
1877
+ }) {
1878
+ const [deleteConfirmOpen, setDeleteConfirmOpen] = useState5(false);
1879
+ const renderRecordingItem = (item) => {
1880
+ const active = !selecting && item.id === activeRecordingId;
1881
+ const checked = selecting && selectedRecordingIds.includes(item.id);
1882
+ return /* @__PURE__ */ jsxs13(
1883
+ "button",
1884
+ {
1885
+ type: "button",
1886
+ "data-slot": "recording-item",
1887
+ "data-active": active || void 0,
1888
+ "aria-pressed": selecting ? checked : void 0,
1889
+ className: cn(
1890
+ "flex w-full cursor-pointer items-start gap-2 rounded-lg p-2 text-left",
1891
+ active ? "bg-background shadow-sm" : void 0
1892
+ ),
1893
+ onClick: () => selecting ? onToggleRecordingSelect?.(item.id) : onSelectRecording(item.id),
1894
+ children: [
1895
+ selecting ? /* @__PURE__ */ jsx13(
1896
+ "span",
1897
+ {
1898
+ className: cn(
1899
+ "mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full",
1900
+ checked ? "bg-success text-background" : "border border-border bg-background"
1901
+ ),
1902
+ children: checked ? /* @__PURE__ */ jsx13(Icon8, { name: "check", size: 12 }) : null
1903
+ }
1904
+ ) : /* @__PURE__ */ jsx13(Icon8, { name: item.icon ?? "audio-lines", size: 18, className: "mt-0.5 shrink-0" }),
1905
+ /* @__PURE__ */ jsxs13("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
1906
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between gap-2", children: [
1907
+ /* @__PURE__ */ jsx13(
1908
+ Text8,
1909
+ {
1910
+ text: item.title,
1911
+ size: "small",
1912
+ strong: active || checked,
1913
+ className: "min-w-0 truncate"
1914
+ }
1915
+ ),
1916
+ item.status !== void 0 ? /* @__PURE__ */ jsx13(
1917
+ Text8,
1918
+ {
1919
+ text: item.status,
1920
+ size: "small",
1921
+ type: item.statusType ?? "secondary",
1922
+ className: "shrink-0"
1923
+ }
1924
+ ) : null
1925
+ ] }),
1926
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between gap-2", children: [
1927
+ /* @__PURE__ */ jsx13(Text8, { text: item.time, size: "small", type: "secondary" }),
1928
+ /* @__PURE__ */ jsx13(Text8, { text: item.duration, size: "small", type: "secondary" })
1929
+ ] })
1930
+ ] })
1931
+ ]
1932
+ },
1933
+ item.id
1934
+ );
1935
+ };
1936
+ return /* @__PURE__ */ jsxs13(
1937
+ "div",
1938
+ {
1939
+ "data-slot": "recording-page-list",
1940
+ className: "m-3 flex min-h-0 shrink-0 flex-col gap-3 rounded-xl bg-muted p-4",
1941
+ style: { width: listWidth },
1942
+ children: [
1943
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between", children: [
1944
+ /* @__PURE__ */ jsx13(Text8, { text: listTitle, strong: true, size: "large" }),
1945
+ selecting ? /* @__PURE__ */ jsx13(
1946
+ Button12,
1947
+ {
1948
+ text: selectDoneLabel ?? "\u5B8C\u6210",
1949
+ variant: "link",
1950
+ size: "small",
1951
+ onClick: () => onExitSelectMode?.()
1952
+ }
1953
+ ) : selectLabel !== void 0 ? /* @__PURE__ */ jsx13(Button12, { text: selectLabel, variant: "link", size: "small", onClick: () => onEnterSelectMode?.() }) : null
1954
+ ] }),
1955
+ selecting ? /* @__PURE__ */ jsxs13(
1956
+ "div",
1957
+ {
1958
+ "data-slot": "recording-page-selection-bar",
1959
+ className: "flex items-center justify-between",
1960
+ children: [
1961
+ /* @__PURE__ */ jsx13(
1962
+ Text8,
1963
+ {
1964
+ text: (selectedCountText ?? "\u5DF2\u9009 {count} \u6761").replace(
1965
+ "{count}",
1966
+ String(selectedRecordingIds.length)
1967
+ ),
1968
+ size: "small",
1969
+ type: "secondary"
1970
+ }
1971
+ ),
1972
+ /* @__PURE__ */ jsx13(
1973
+ Button12,
1974
+ {
1975
+ text: selectAllLabel ?? "\u5168\u9009",
1976
+ variant: "link",
1977
+ size: "small",
1978
+ onClick: () => onToggleSelectAll?.()
1979
+ }
1980
+ )
1981
+ ]
1982
+ }
1983
+ ) : null,
1984
+ !selecting && filters.length > 0 ? /* @__PURE__ */ jsx13("div", { "data-slot": "recording-page-filters", className: "flex gap-2", children: filters.map((filter) => {
1985
+ const active = filter.key === activeFilter;
1986
+ return /* @__PURE__ */ jsx13(
1987
+ "button",
1988
+ {
1989
+ type: "button",
1990
+ "data-active": active || void 0,
1991
+ className: cn(
1992
+ "cursor-pointer rounded-full px-3 py-1 text-xs",
1993
+ active ? "bg-primary text-primary-foreground" : "bg-background"
1994
+ ),
1995
+ onClick: () => onFilterChange(filter.key),
1996
+ children: filter.label
1997
+ },
1998
+ filter.key
1999
+ );
2000
+ }) }) : null,
2001
+ !selecting && processingText !== void 0 ? /* @__PURE__ */ jsxs13(
2002
+ "button",
2003
+ {
2004
+ type: "button",
2005
+ "data-slot": "recording-page-processing",
2006
+ className: "flex cursor-pointer items-center gap-2 rounded-lg bg-background p-3 text-left",
2007
+ onClick: () => onProcessingClick?.(),
2008
+ children: [
2009
+ /* @__PURE__ */ jsx13(Icon8, { name: "audio-lines", size: 18 }),
2010
+ /* @__PURE__ */ jsxs13("div", { className: "flex flex-1 flex-col", children: [
2011
+ /* @__PURE__ */ jsx13(Text8, { text: processingText, size: "small", strong: true }),
2012
+ processingHint !== void 0 ? /* @__PURE__ */ jsx13(Text8, { text: processingHint, size: "small", type: "secondary" }) : null
2013
+ ] }),
2014
+ /* @__PURE__ */ jsx13(Icon8, { name: "chevron-right", size: 16, className: "text-muted-foreground" })
2015
+ ]
2016
+ }
2017
+ ) : null,
2018
+ !selecting && newRecordingLabel !== null && newRecordingLabel !== void 0 ? /* @__PURE__ */ jsx13(
2019
+ Button12,
2020
+ {
2021
+ text: newRecordingLabel,
2022
+ icon: "mic",
2023
+ variant: "primary",
2024
+ className: "w-full",
2025
+ onClick: () => onNewRecording?.()
2026
+ }
2027
+ ) : null,
2028
+ /* @__PURE__ */ jsx13(
2029
+ "div",
2030
+ {
2031
+ "data-slot": "recording-page-groups",
2032
+ className: "scrollbar-subtle flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto",
2033
+ children: groups.length === 0 ? /* @__PURE__ */ jsx13(Empty2, { title: emptyText }) : groups.map((group, groupIndex) => /* @__PURE__ */ jsxs13("div", { className: "flex flex-col gap-1", children: [
2034
+ /* @__PURE__ */ jsx13(Text8, { text: group.label, size: "small", type: "secondary", className: "px-1" }),
2035
+ group.items.map(renderRecordingItem)
2036
+ ] }, group.key ?? group.label ?? groupIndex))
2037
+ }
2038
+ ),
2039
+ selecting ? /* @__PURE__ */ jsxs13(Fragment4, { children: [
2040
+ /* @__PURE__ */ jsx13(
2041
+ Button12,
2042
+ {
2043
+ text: deleteLabel ?? "\u5220\u9664",
2044
+ variant: "primary",
2045
+ danger: true,
2046
+ className: "w-full shrink-0",
2047
+ onClick: () => setDeleteConfirmOpen(true)
2048
+ }
2049
+ ),
2050
+ /* @__PURE__ */ jsx13(AlertDialog, { open: deleteConfirmOpen, onOpenChange: setDeleteConfirmOpen, children: /* @__PURE__ */ jsxs13(AlertDialogContent, { "data-slot": "recording-delete-confirm", children: [
2051
+ /* @__PURE__ */ jsxs13(AlertDialogHeader, { children: [
2052
+ /* @__PURE__ */ jsx13(AlertDialogTitle, { children: deleteConfirmTitle ?? "\u5220\u9664\u5F55\u97F3" }),
2053
+ /* @__PURE__ */ jsx13(AlertDialogDescription, { children: (deleteConfirmText ?? "\u786E\u5B9A\u5220\u9664\u9009\u4E2D\u7684 {count} \u6761\u5F55\u97F3\u5417\uFF1F\u5220\u9664\u540E\u4E0D\u53EF\u6062\u590D\u3002").replace("{count}", String(selectedRecordingIds.length)) })
2054
+ ] }),
2055
+ /* @__PURE__ */ jsxs13(AlertDialogFooter, { children: [
2056
+ /* @__PURE__ */ jsx13(AlertDialogCancel, { children: deleteCancelLabel ?? "\u53D6\u6D88" }),
2057
+ /* @__PURE__ */ jsx13(
2058
+ AlertDialogAction,
2059
+ {
2060
+ variant: "destructive",
2061
+ onClick: () => onDeleteSelected?.(),
2062
+ children: deleteConfirmLabel ?? "\u5220\u9664"
2063
+ }
2064
+ )
2065
+ ] })
2066
+ ] }) })
2067
+ ] }) : null
2068
+ ]
2069
+ }
2070
+ );
2071
+ }
2072
+
2073
+ // src/components/RecordingPage/index.tsx
2074
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2075
+ function RecordingPage({
2076
+ listTitle = "\u5F55\u97F3\u8BB0\u5F55\u5217\u8868",
2077
+ selectLabel,
2078
+ selectDoneLabel,
2079
+ selectAllLabel,
2080
+ selectedCountText,
2081
+ deleteLabel,
2082
+ deleteConfirmTitle,
2083
+ deleteConfirmText,
2084
+ deleteCancelLabel,
2085
+ deleteConfirmLabel,
2086
+ selectedRecordingIds,
2087
+ defaultSelectedRecordingIds,
2088
+ onRecordingSelectionChange,
2089
+ onDeleteRecordings,
2090
+ onSelectClick,
2091
+ filters = [],
2092
+ activeFilterKey,
2093
+ defaultActiveFilterKey,
2094
+ onFilterChange,
2095
+ processingText,
2096
+ processingHint,
2097
+ onProcessingClick,
2098
+ newRecordingLabel = "\u5F00\u59CB\u65B0\u5F55\u97F3",
2099
+ onNewRecording,
2100
+ groups = [],
2101
+ activeRecordingId,
2102
+ defaultActiveRecordingId,
2103
+ onSelectRecording,
2104
+ listWidth = 300,
2105
+ emptyText = "\u6682\u65E0\u5F55\u97F3",
2106
+ title,
2107
+ meta,
2108
+ editLabel,
2109
+ shareLabel,
2110
+ shareOptions,
2111
+ shareSelectedKeys,
2112
+ defaultShareSelectedKeys,
2113
+ sharePopoverTitle,
2114
+ shareCancelLabel,
2115
+ shareConfirmLabel,
2116
+ shareDialogTitle,
2117
+ shareLink,
2118
+ shareCopyLinkLabel,
2119
+ shareCopiedLabel,
2120
+ shareMailLabel,
2121
+ shareSystemLabel,
2122
+ onShareConfirm,
2123
+ onShareChannel,
2124
+ onEdit,
2125
+ onShare,
2126
+ tabs = [],
2127
+ activeTabKey,
2128
+ defaultActiveTabKey,
2129
+ onTabChange,
2130
+ transcriptTabKey = "transcript",
2131
+ segments = [],
2132
+ speakers,
2133
+ speakerDialogTitle,
2134
+ speakerDialogHint,
2135
+ speakerInputPlaceholder,
2136
+ speakerApplyLabel,
2137
+ onSpeakerChange,
2138
+ summary,
2139
+ summaryTabKey = "summary",
2140
+ onMindmapAction,
2141
+ onMaterialClick,
2142
+ onTodoClick,
2143
+ selectedTodoIds,
2144
+ defaultSelectedTodoIds,
2145
+ onTodoSelectionChange,
2146
+ onSummaryAction,
2147
+ insight,
2148
+ insightTabKey = "ai",
2149
+ onPerspectiveEdit,
2150
+ onPerspectiveApply,
2151
+ onInsightAction,
2152
+ memory,
2153
+ memoryTabKey = "knowledge",
2154
+ onMemoryAction,
2155
+ translation,
2156
+ translationTabKey = "translation",
2157
+ onLanguageClick,
2158
+ onLanguageChange,
2159
+ onTranslationAction,
2160
+ player,
2161
+ onPlay,
2162
+ onSkipBack,
2163
+ onSkipForward,
2164
+ className,
2165
+ style
2166
+ }) {
2167
+ const filterControlled = activeFilterKey !== void 0;
2168
+ const [innerFilter, setInnerFilter] = useState6(
2169
+ defaultActiveFilterKey ?? filters[0]?.key
2170
+ );
2171
+ const activeFilter = filterControlled ? activeFilterKey : innerFilter;
2172
+ const setActiveFilter = (next) => {
2173
+ if (!filterControlled) setInnerFilter(next);
2174
+ onFilterChange?.(next);
2175
+ };
2176
+ const recControlled = activeRecordingId !== void 0;
2177
+ const [innerRec, setInnerRec] = useState6(defaultActiveRecordingId);
2178
+ const activeRec = recControlled ? activeRecordingId : innerRec;
2179
+ const setActiveRec = (next) => {
2180
+ if (!recControlled) setInnerRec(next);
2181
+ onSelectRecording?.(next);
2182
+ };
2183
+ const tabControlled = activeTabKey !== void 0;
2184
+ const [innerTab, setInnerTab] = useState6(defaultActiveTabKey ?? tabs[0]?.key);
2185
+ const activeTab = tabControlled ? activeTabKey : innerTab;
2186
+ const setActiveTab = (next) => {
2187
+ if (!tabControlled) setInnerTab(next);
2188
+ onTabChange?.(next);
2189
+ };
2190
+ const todoControlled = selectedTodoIds !== void 0;
2191
+ const [innerTodos, setInnerTodos] = useState6(
2192
+ () => defaultSelectedTodoIds ?? (summary?.todos ?? []).flatMap(
2193
+ (todo, index) => todo.done === true ? [todo.id ?? index] : []
2194
+ )
2195
+ );
2196
+ const selectedTodos = todoControlled ? selectedTodoIds : innerTodos;
2197
+ const toggleTodo = (id) => {
2198
+ const next = selectedTodos.includes(id) ? selectedTodos.filter((item) => item !== id) : [...selectedTodos, id];
2199
+ if (!todoControlled) setInnerTodos(next);
2200
+ onTodoClick?.(id);
2201
+ onTodoSelectionChange?.(next);
2202
+ };
2203
+ const shareControlled = shareSelectedKeys !== void 0;
2204
+ const [innerShareKeys, setInnerShareKeys] = useState6(
2205
+ () => defaultShareSelectedKeys ?? (shareOptions ?? []).map((option) => option.key)
2206
+ );
2207
+ const shareKeys = shareControlled ? shareSelectedKeys : innerShareKeys;
2208
+ const commitShareKeys = (next) => {
2209
+ if (!shareControlled) setInnerShareKeys(next);
2210
+ };
2211
+ const [selecting, setSelecting] = useState6(false);
2212
+ const recSelControlled = selectedRecordingIds !== void 0;
2213
+ const [innerSelectedRecs, setInnerSelectedRecs] = useState6(
2214
+ () => defaultSelectedRecordingIds ?? []
2215
+ );
2216
+ const selectedRecs = recSelControlled ? selectedRecordingIds : innerSelectedRecs;
2217
+ const commitSelectedRecs = (next) => {
2218
+ if (!recSelControlled) setInnerSelectedRecs(next);
2219
+ onRecordingSelectionChange?.(next);
2220
+ };
2221
+ const allRecordingIds = groups.flatMap((group) => group.items.map((item) => item.id));
2222
+ const toggleRecordingSelect = (id) => {
2223
+ commitSelectedRecs(
2224
+ selectedRecs.includes(id) ? selectedRecs.filter((item) => item !== id) : [...selectedRecs, id]
2225
+ );
2226
+ };
2227
+ const toggleSelectAll = () => {
2228
+ commitSelectedRecs(
2229
+ selectedRecs.length === allRecordingIds.length ? [] : allRecordingIds
2230
+ );
2231
+ };
2232
+ return /* @__PURE__ */ jsxs14(
2233
+ "div",
2234
+ {
2235
+ "data-slot": "recording-page",
2236
+ className: cn("flex h-full w-full min-h-0 items-stretch", className),
2237
+ style,
2238
+ children: [
2239
+ /* @__PURE__ */ jsx14(
2240
+ RecordingList,
2241
+ {
2242
+ listTitle,
2243
+ selectLabel,
2244
+ selectDoneLabel,
2245
+ selectAllLabel,
2246
+ selectedCountText,
2247
+ deleteLabel,
2248
+ deleteConfirmTitle,
2249
+ deleteConfirmText,
2250
+ deleteCancelLabel,
2251
+ deleteConfirmLabel,
2252
+ selecting,
2253
+ selectedRecordingIds: selectedRecs,
2254
+ onEnterSelectMode: () => {
2255
+ setSelecting(true);
2256
+ onSelectClick?.();
2257
+ },
2258
+ onExitSelectMode: () => setSelecting(false),
2259
+ onToggleRecordingSelect: toggleRecordingSelect,
2260
+ onToggleSelectAll: toggleSelectAll,
2261
+ onDeleteSelected: () => onDeleteRecordings?.(selectedRecs),
2262
+ filters,
2263
+ activeFilter,
2264
+ onFilterChange: setActiveFilter,
2265
+ processingText,
2266
+ processingHint,
2267
+ onProcessingClick,
2268
+ newRecordingLabel,
2269
+ onNewRecording,
2270
+ groups,
2271
+ activeRecordingId: activeRec,
2272
+ onSelectRecording: setActiveRec,
2273
+ listWidth,
2274
+ emptyText
2275
+ }
2276
+ ),
2277
+ /* @__PURE__ */ jsx14(
2278
+ RecordingDetail,
2279
+ {
2280
+ title,
2281
+ meta,
2282
+ editLabel,
2283
+ shareLabel,
2284
+ shareOptions,
2285
+ shareSelectedKeys: shareKeys,
2286
+ sharePopoverTitle,
2287
+ shareCancelLabel,
2288
+ shareConfirmLabel,
2289
+ shareDialogTitle,
2290
+ shareLink,
2291
+ shareCopyLinkLabel,
2292
+ shareCopiedLabel,
2293
+ shareMailLabel,
2294
+ shareSystemLabel,
2295
+ onShareSelectionCommit: commitShareKeys,
2296
+ onShareConfirm,
2297
+ onShareChannel,
2298
+ onEdit,
2299
+ onShare,
2300
+ tabs,
2301
+ activeTab,
2302
+ onTabChange: setActiveTab,
2303
+ transcriptTabKey,
2304
+ segments,
2305
+ speakers,
2306
+ speakerDialogTitle,
2307
+ speakerDialogHint,
2308
+ speakerInputPlaceholder,
2309
+ speakerApplyLabel,
2310
+ onSpeakerChange,
2311
+ summary,
2312
+ summaryTabKey,
2313
+ onMindmapAction,
2314
+ onMaterialClick,
2315
+ selectedTodoIds: selectedTodos,
2316
+ onTodoToggle: toggleTodo,
2317
+ onSummaryAction,
2318
+ insight,
2319
+ insightTabKey,
2320
+ onPerspectiveEdit,
2321
+ onPerspectiveApply,
2322
+ onInsightAction,
2323
+ memory,
2324
+ memoryTabKey,
2325
+ onMemoryAction,
2326
+ translation,
2327
+ translationTabKey,
2328
+ onLanguageClick,
2329
+ onLanguageChange,
2330
+ onTranslationAction,
2331
+ player,
2332
+ onPlay,
2333
+ onSkipBack,
2334
+ onSkipForward
2335
+ }
2336
+ )
2337
+ ]
2338
+ }
2339
+ );
2340
+ }
2341
+
2342
+ // src/components/SiteFooter/index.tsx
2343
+ import { Link as Link2 } from "@pactor-app/ui/components";
2344
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2345
+ function SiteFooter({
2346
+ brand,
2347
+ groups,
2348
+ copyright,
2349
+ bottomLinks,
2350
+ onLinkClick,
2351
+ className,
2352
+ style
2353
+ }) {
2354
+ const renderLink = (link) => {
2355
+ if (link.href) {
2356
+ return /* @__PURE__ */ jsx15(
2357
+ Link2,
2358
+ {
2359
+ href: link.href,
2360
+ text: link.text,
2361
+ className: "text-sm text-muted-foreground transition-colors hover:text-foreground"
2362
+ }
2363
+ );
2364
+ }
2365
+ return /* @__PURE__ */ jsx15(
2366
+ "button",
2367
+ {
2368
+ type: "button",
2369
+ "data-slot": "site-footer-link",
2370
+ className: "text-left text-sm text-muted-foreground transition-colors hover:text-foreground",
2371
+ onClick: () => onLinkClick?.({ key: link.text, text: link.text }),
2372
+ children: link.text
2373
+ }
2374
+ );
2375
+ };
2376
+ const hasBrand = Boolean(brand && (brand.logo || brand.name || brand.description));
2377
+ const hasGroups = Boolean(groups && groups.length > 0);
2378
+ const hasBottom = Boolean(copyright || bottomLinks && bottomLinks.length > 0);
2379
+ if (!hasBrand && !hasGroups && !hasBottom) {
2380
+ return null;
2381
+ }
2382
+ return /* @__PURE__ */ jsxs15(
2383
+ "footer",
2384
+ {
2385
+ "data-slot": "site-footer",
2386
+ className: cn("border-t bg-muted/40", className),
2387
+ style,
2388
+ children: [
2389
+ hasBrand || hasGroups ? /* @__PURE__ */ jsxs15(
2390
+ "div",
2391
+ {
2392
+ "data-slot": "site-footer-main",
2393
+ className: "mx-auto flex w-full max-w-6xl flex-col gap-8 px-6 py-10 md:flex-row md:justify-between",
2394
+ children: [
2395
+ hasBrand ? /* @__PURE__ */ jsxs15("div", { "data-slot": "site-footer-brand", className: "flex max-w-xs flex-col gap-3", children: [
2396
+ brand.logo || brand.name ? /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", children: [
2397
+ brand.logo ? /* @__PURE__ */ jsx15(
2398
+ "img",
2399
+ {
2400
+ src: brand.logo,
2401
+ alt: brand.name ?? "",
2402
+ className: "size-8 rounded-md object-contain"
2403
+ }
2404
+ ) : null,
2405
+ brand.name ? /* @__PURE__ */ jsx15("span", { className: "text-base font-semibold", children: brand.name }) : null
2406
+ ] }) : null,
2407
+ brand.description ? /* @__PURE__ */ jsx15("p", { className: "text-sm leading-relaxed text-muted-foreground", children: brand.description }) : null
2408
+ ] }) : null,
2409
+ hasGroups ? /* @__PURE__ */ jsx15(
2410
+ "div",
2411
+ {
2412
+ "data-slot": "site-footer-groups",
2413
+ className: "grid flex-1 grid-cols-2 gap-8 sm:grid-cols-3 md:justify-items-end",
2414
+ children: groups.map((group) => /* @__PURE__ */ jsxs15(
2415
+ "div",
2416
+ {
2417
+ "data-slot": "site-footer-group",
2418
+ className: "flex min-w-24 flex-col gap-3",
2419
+ children: [
2420
+ /* @__PURE__ */ jsx15("span", { className: "text-sm font-medium", children: group.title }),
2421
+ /* @__PURE__ */ jsx15("div", { className: "flex flex-col gap-2", children: group.links.map((link) => /* @__PURE__ */ jsx15("div", { children: renderLink(link) }, link.text)) })
2422
+ ]
2423
+ },
2424
+ group.title
2425
+ ))
2426
+ }
2427
+ ) : null
2428
+ ]
2429
+ }
2430
+ ) : null,
2431
+ hasBottom ? /* @__PURE__ */ jsx15("div", { "data-slot": "site-footer-bottom", className: "border-t", children: /* @__PURE__ */ jsxs15("div", { className: "mx-auto flex w-full max-w-6xl flex-col items-center gap-2 px-6 py-4 text-xs text-muted-foreground sm:flex-row sm:justify-between", children: [
2432
+ copyright ? /* @__PURE__ */ jsx15("span", { children: copyright }) : /* @__PURE__ */ jsx15("span", {}),
2433
+ bottomLinks && bottomLinks.length > 0 ? /* @__PURE__ */ jsx15("div", { "data-slot": "site-footer-bottom-links", className: "flex items-center gap-4", children: bottomLinks.map((link) => /* @__PURE__ */ jsx15("div", { children: renderLink(link) }, link.text)) }) : null
2434
+ ] }) }) : null
2435
+ ]
2436
+ }
2437
+ );
2438
+ }
2439
+
2440
+ // src/components/FieldCondition/index.tsx
2441
+ import { Plus, X as X2 } from "lucide-react";
2442
+ import { useCallback, useMemo as useMemo2 } from "react";
2443
+ import { Button as Button13 } from "@pactor-app/ui/ui";
2444
+ import { Input as Input3 } from "@pactor-app/ui/ui";
2445
+ import {
2446
+ Select,
2447
+ SelectContent,
2448
+ SelectItem,
2449
+ SelectTrigger,
2450
+ SelectValue
2451
+ } from "@pactor-app/ui/ui";
2452
+ import { DatePicker } from "@pactor-app/ui/components";
2453
+ import {
2454
+ CONTROL_SIZE_BOX,
2455
+ CONTROL_SIZE_SELECT_ATTR,
2456
+ CONTROL_SIZE_TRIGGER_TEXT
2457
+ } from "@pactor-app/ui/components";
2458
+
2459
+ // src/components/internal/SearchableSelect/index.tsx
2460
+ import { Check, ChevronsUpDown, X } from "lucide-react";
2461
+ import * as React from "react";
2462
+ import {
2463
+ Command,
2464
+ CommandEmpty,
2465
+ CommandGroup,
2466
+ CommandInput,
2467
+ CommandItem,
2468
+ CommandList
2469
+ } from "@pactor-app/ui/ui";
2470
+ import {
2471
+ Popover as Popover2,
2472
+ PopoverContent as PopoverContent2,
2473
+ PopoverTrigger as PopoverTrigger2
2474
+ } from "@pactor-app/ui/ui";
2475
+ import { ClearHoverButton } from "@pactor-app/ui/components";
2476
+ import { CONTROL_SIZE_BOX_GROW } from "@pactor-app/ui/components";
2477
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2478
+ var DEFAULT_PLACEHOLDER = "\u8BF7\u9009\u62E9";
2479
+ var DEFAULT_SEARCH_PLACEHOLDER = "\u641C\u7D22\u2026";
2480
+ var DEFAULT_EMPTY_TEXT = "\u65E0\u5339\u914D\u7ED3\u679C";
2481
+ var NO_OPTIONS_TEXT = "\u65E0\u9009\u9879";
2482
+ function SearchableSelect({
2483
+ value,
2484
+ onChange,
2485
+ options,
2486
+ placeholder,
2487
+ searchPlaceholder,
2488
+ emptyText,
2489
+ disabled = false,
2490
+ allowClear = true,
2491
+ size = "middle",
2492
+ className,
2493
+ id,
2494
+ showSearch,
2495
+ multiple = false,
2496
+ fullOptionText = false,
2497
+ listClassName
2498
+ }) {
2499
+ const resolvedPlaceholder = placeholder ?? DEFAULT_PLACEHOLDER;
2500
+ const resolvedSearchPlaceholder = searchPlaceholder ?? DEFAULT_SEARCH_PLACEHOLDER;
2501
+ const resolvedEmptyText = emptyText ?? DEFAULT_EMPTY_TEXT;
2502
+ const [open, setOpen] = React.useState(false);
2503
+ const shouldShowSearch = showSearch ?? options.length > 8;
2504
+ const selectedValues = multiple ? Array.isArray(value) ? value : value ? [value] : [] : [];
2505
+ const selectedLabel = React.useMemo(
2506
+ () => !multiple ? options.find((opt) => opt.value === value)?.label : void 0,
2507
+ [options, value, multiple]
2508
+ );
2509
+ const handleSelect = (optValue) => {
2510
+ if (!multiple) {
2511
+ onChange?.(optValue);
2512
+ setOpen(false);
2513
+ return;
2514
+ }
2515
+ const next = selectedValues.includes(optValue) ? selectedValues.filter((v) => v !== optValue) : [...selectedValues, optValue];
2516
+ onChange?.(next);
2517
+ };
2518
+ const handleRemove = (e, optValue) => {
2519
+ e.stopPropagation();
2520
+ onChange?.(selectedValues.filter((v) => v !== optValue));
2521
+ };
2522
+ return /* @__PURE__ */ jsxs16(Popover2, { open, onOpenChange: setOpen, children: [
2523
+ /* @__PURE__ */ jsxs16(
2524
+ PopoverTrigger2,
2525
+ {
2526
+ id,
2527
+ type: "button",
2528
+ role: "combobox",
2529
+ "aria-expanded": open,
2530
+ disabled,
2531
+ className: cn(
2532
+ "group border-input bg-background ring-offset-background flex min-h-9 w-full items-center justify-between rounded-md border px-3 py-2 text-sm",
2533
+ "placeholder:text-muted-foreground min-w-32",
2534
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
2535
+ "disabled:cursor-not-allowed disabled:opacity-50",
2536
+ CONTROL_SIZE_BOX_GROW[size],
2537
+ multiple && "flex-wrap gap-1",
2538
+ className
2539
+ ),
2540
+ children: [
2541
+ multiple ? selectedValues.length > 0 ? /* @__PURE__ */ jsx16("div", { className: "flex flex-wrap gap-1 flex-1", children: selectedValues.map((v) => {
2542
+ const label = options.find((o) => o.value === v)?.label ?? v;
2543
+ return /* @__PURE__ */ jsxs16(
2544
+ "span",
2545
+ {
2546
+ className: "inline-flex items-center gap-1 rounded bg-muted px-2 py-0.5 text-xs font-medium",
2547
+ children: [
2548
+ label,
2549
+ /* @__PURE__ */ jsx16(
2550
+ X,
2551
+ {
2552
+ className: "h-3 w-3 cursor-pointer opacity-60 hover:opacity-100",
2553
+ onClick: (e) => handleRemove(e, v)
2554
+ }
2555
+ )
2556
+ ]
2557
+ },
2558
+ v
2559
+ );
2560
+ }) }) : /* @__PURE__ */ jsx16("span", { className: "text-muted-foreground flex-1 text-left", children: resolvedPlaceholder }) : /* @__PURE__ */ jsx16("span", { className: cn("truncate", !selectedLabel && "text-muted-foreground"), children: selectedLabel || resolvedPlaceholder }),
2561
+ allowClear && !multiple && !disabled && value !== void 0 && value !== "" ? /* @__PURE__ */ jsx16(
2562
+ ClearHoverButton,
2563
+ {
2564
+ label: "clear-selection",
2565
+ className: "ml-2 rounded-sm opacity-50 hover:opacity-100",
2566
+ onClear: () => onChange?.("")
2567
+ }
2568
+ ) : null,
2569
+ /* @__PURE__ */ jsx16(
2570
+ ChevronsUpDown,
2571
+ {
2572
+ className: cn(
2573
+ "ml-2 h-4 w-4 shrink-0 opacity-50",
2574
+ allowClear && !multiple && !disabled && value !== void 0 && value !== "" && "group-hover:hidden"
2575
+ )
2576
+ }
2577
+ )
2578
+ ]
2579
+ }
2580
+ ),
2581
+ /* @__PURE__ */ jsx16(
2582
+ PopoverContent2,
2583
+ {
2584
+ className: cn(
2585
+ "p-0",
2586
+ // Base UI Positioner 在定位元素上暴露 --anchor-width(原 Radix 的
2587
+ // --radix-popover-trigger-width 在 Base UI 下不存在,会退化为 auto 宽度)
2588
+ fullOptionText ? "w-auto min-w-(--anchor-width)" : "w-(--anchor-width)"
2589
+ ),
2590
+ align: "start",
2591
+ onWheel: (e) => e.stopPropagation(),
2592
+ onTouchMove: (e) => e.stopPropagation(),
2593
+ children: /* @__PURE__ */ jsxs16(Command, { value: !multiple ? selectedLabel : void 0, children: [
2594
+ shouldShowSearch && /* @__PURE__ */ jsx16(CommandInput, { placeholder: resolvedSearchPlaceholder }),
2595
+ /* @__PURE__ */ jsxs16(CommandList, { className: listClassName, children: [
2596
+ /* @__PURE__ */ jsx16(CommandEmpty, { children: options.length === 0 ? NO_OPTIONS_TEXT : resolvedEmptyText }),
2597
+ /* @__PURE__ */ jsx16(CommandGroup, { children: options.map((opt) => {
2598
+ const isSelected = multiple ? selectedValues.includes(opt.value) : value === opt.value;
2599
+ return /* @__PURE__ */ jsxs16(
2600
+ CommandItem,
2601
+ {
2602
+ value: opt.label,
2603
+ onSelect: () => handleSelect(opt.value),
2604
+ className: "flex items-center justify-between",
2605
+ children: [
2606
+ /* @__PURE__ */ jsx16("span", { className: cn(fullOptionText ? "whitespace-normal" : "truncate"), children: opt.label }),
2607
+ /* @__PURE__ */ jsx16(
2608
+ Check,
2609
+ {
2610
+ className: cn(
2611
+ "ml-2 h-4 w-4 shrink-0",
2612
+ isSelected ? "opacity-100" : "opacity-0"
2613
+ )
2614
+ }
2615
+ )
2616
+ ]
2617
+ },
2618
+ opt.value
2619
+ );
2620
+ }) })
2621
+ ] })
2622
+ ] })
2623
+ }
2624
+ )
2625
+ ] });
2626
+ }
2627
+
2628
+ // src/components/FieldCondition/index.tsx
2629
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2630
+ var OPERATORS = [
2631
+ { value: "EQUAL", valueTypes: ["NUMBER", "STRING", "DATE"] },
2632
+ { value: "GREATER", valueTypes: ["NUMBER", "DATE"] },
2633
+ { value: "GREATER_EQUAL", valueTypes: ["NUMBER", "DATE"] },
2634
+ { value: "LESS", valueTypes: ["NUMBER", "DATE"] },
2635
+ { value: "LESS_EQUAL", valueTypes: ["NUMBER", "DATE"] },
2636
+ { value: "UNEQUAL", valueTypes: ["NUMBER", "STRING", "DATE"] },
2637
+ { value: "BETWEEN", valueTypes: ["NUMBER", "DATE"] },
2638
+ { value: "HASVAL", valueTypes: "ALL" },
2639
+ { value: "NOHASVAL", valueTypes: "ALL" },
2640
+ { value: "TRUE", valueTypes: ["BOOLEAN"] },
2641
+ { value: "FALSE", valueTypes: ["BOOLEAN"] },
2642
+ { value: "IN", valueTypes: ["NUMBER", "STRING"] },
2643
+ { value: "NOT_IN", valueTypes: ["NUMBER", "STRING"] }
2644
+ ];
2645
+ var NO_VALUE_OPERATORS = ["HASVAL", "NOHASVAL", "TRUE", "FALSE"];
2646
+ var OPERATOR_LABELS = {
2647
+ EQUAL: "\u7B49\u4E8E",
2648
+ GREATER: "\u5927\u4E8E",
2649
+ GREATER_EQUAL: "\u5927\u4E8E\u7B49\u4E8E",
2650
+ LESS: "\u5C0F\u4E8E",
2651
+ LESS_EQUAL: "\u5C0F\u4E8E\u7B49\u4E8E",
2652
+ UNEQUAL: "\u4E0D\u7B49\u4E8E",
2653
+ BETWEEN: "\u8303\u56F4",
2654
+ HASVAL: "\u6709\u503C",
2655
+ NOHASVAL: "\u65E0\u503C",
2656
+ TRUE: "\u4E3A\u771F",
2657
+ FALSE: "\u4E3A\u5047",
2658
+ IN: "\u5305\u542B",
2659
+ NOT_IN: "\u4E0D\u5305\u542B"
2660
+ };
2661
+ function operatorLabel(operator) {
2662
+ return OPERATOR_LABELS[operator] ?? operator;
2663
+ }
2664
+ function getOperatorsByFieldType(fieldType) {
2665
+ return OPERATORS.filter(
2666
+ (op) => !fieldType || op.valueTypes === "ALL" || op.valueTypes.includes(fieldType)
2667
+ );
2668
+ }
2669
+ function isNoValueOperator(operator) {
2670
+ return !!operator && NO_VALUE_OPERATORS.includes(operator);
2671
+ }
2672
+ var OPERATOR_STATEMENTS = {
2673
+ EQUAL: "${field} == ${value}",
2674
+ GREATER: "${field} > ${value}",
2675
+ GREATER_EQUAL: "${field} >= ${value}",
2676
+ LESS: "${field} < ${value}",
2677
+ LESS_EQUAL: "${field} <= ${value}",
2678
+ UNEQUAL: "${field} != ${value}",
2679
+ BETWEEN: "${field} BETWEEN ${v1} AND ${v2}",
2680
+ HASVAL: "${field} HASVAL",
2681
+ NOHASVAL: "${field} NOHASVAL",
2682
+ TRUE: "${field} == TRUE",
2683
+ FALSE: "${field} == FALSE",
2684
+ IN: "${field} IN (${value})",
2685
+ NOT_IN: "${field} NOT IN (${value})"
2686
+ };
2687
+ function formatConditionValue(item) {
2688
+ const isString = item.field.dataType === "STRING";
2689
+ const quote = (v) => isString ? `'${v}'` : String(v);
2690
+ if (item.term.value === "IN" || item.term.value === "NOT_IN") {
2691
+ return String(item.value ?? "").split(",").map((s) => s.trim()).filter(Boolean).map(quote).join(", ");
2692
+ }
2693
+ return quote(item.value ?? "");
2694
+ }
2695
+ function itemToStatement(item) {
2696
+ const fieldCode = item.field?.value;
2697
+ const termValue = item.term?.value;
2698
+ const statement = termValue ? OPERATOR_STATEMENTS[termValue] : void 0;
2699
+ if (!fieldCode || !statement) return null;
2700
+ if (termValue === "BETWEEN") {
2701
+ const arr = Array.isArray(item.value) ? item.value : [];
2702
+ const isString = item.field.dataType === "STRING";
2703
+ const quote = (v) => isString ? `'${v ?? ""}'` : String(v ?? "");
2704
+ return statement.replace("${field}", fieldCode).replace("${v1}", quote(arr[0])).replace("${v2}", quote(arr[1]));
2705
+ }
2706
+ let result = statement.replace("${field}", fieldCode);
2707
+ if (!isNoValueOperator(termValue)) {
2708
+ result = result.replace("${value}", formatConditionValue(item));
2709
+ }
2710
+ return result;
2711
+ }
2712
+ function conditionToStatement(value) {
2713
+ if (!value?.children?.length) return "";
2714
+ const groupParts = value.children.map((group) => {
2715
+ const joiner = group.relation === "AND" ? " && " : " || ";
2716
+ const parts = group.children.map(itemToStatement).filter((s) => !!s);
2717
+ if (!parts.length) return "";
2718
+ const joined = parts.join(joiner);
2719
+ return parts.length > 1 ? `(${joined})` : joined;
2720
+ }).filter(Boolean);
2721
+ return groupParts.join(value.relation === "AND" ? " && " : " || ");
2722
+ }
2723
+ function splitByJoiner(expr, joiner) {
2724
+ const parts = [];
2725
+ const token = ` ${joiner} `;
2726
+ let depth = 0;
2727
+ let quote = null;
2728
+ let current = "";
2729
+ for (let i = 0; i < expr.length; i++) {
2730
+ const ch = expr[i];
2731
+ if (quote) {
2732
+ current += ch;
2733
+ if (ch === quote) quote = null;
2734
+ continue;
2735
+ }
2736
+ if (ch === "'" || ch === '"') {
2737
+ quote = ch;
2738
+ current += ch;
2739
+ continue;
2740
+ }
2741
+ if (ch === "(") depth++;
2742
+ if (ch === ")") depth--;
2743
+ if (depth === 0 && expr.startsWith(token, i)) {
2744
+ parts.push(current);
2745
+ current = "";
2746
+ i += token.length - 1;
2747
+ continue;
2748
+ }
2749
+ current += ch;
2750
+ }
2751
+ parts.push(current);
2752
+ return parts.map((s) => s.trim()).filter(Boolean);
2753
+ }
2754
+ function isWrappedByOuterParens(s) {
2755
+ if (!s.startsWith("(") || !s.endsWith(")")) return false;
2756
+ let depth = 0;
2757
+ let quote = null;
2758
+ for (let i = 0; i < s.length; i++) {
2759
+ const ch = s[i];
2760
+ if (quote) {
2761
+ if (ch === quote) quote = null;
2762
+ continue;
2763
+ }
2764
+ if (ch === "'" || ch === '"') {
2765
+ quote = ch;
2766
+ continue;
2767
+ }
2768
+ if (ch === "(") depth++;
2769
+ if (ch === ")") {
2770
+ depth--;
2771
+ if (depth === 0 && i < s.length - 1) return false;
2772
+ }
2773
+ }
2774
+ return depth === 0;
2775
+ }
2776
+ function unquote(v) {
2777
+ const t = v.trim();
2778
+ if (t.length >= 2 && (t.startsWith("'") && t.endsWith("'") || t.startsWith('"') && t.endsWith('"'))) {
2779
+ return t.slice(1, -1);
2780
+ }
2781
+ return t;
2782
+ }
2783
+ var COMPARE_OPERATOR_MAP = {
2784
+ "==": "EQUAL",
2785
+ "!=": "UNEQUAL",
2786
+ ">": "GREATER",
2787
+ ">=": "GREATER_EQUAL",
2788
+ "<": "LESS",
2789
+ "<=": "LESS_EQUAL"
2790
+ };
2791
+ function parseAtom(atom, fields) {
2792
+ const s = atom.trim();
2793
+ if (!s) return null;
2794
+ const fieldInfo = (code) => {
2795
+ const f = fields?.find((x) => x.featureCode === code);
2796
+ return { value: code, label: f?.featureName, dataType: f?.fieldType };
2797
+ };
2798
+ let m = s.match(/^([\w.]+)\s+BETWEEN\s+(.+?)\s+AND\s+(.+)$/i);
2799
+ if (m) {
2800
+ return {
2801
+ field: fieldInfo(m[1]),
2802
+ term: { value: "BETWEEN" },
2803
+ value: [unquote(m[2]), unquote(m[3])]
2804
+ };
2805
+ }
2806
+ m = s.match(/^([\w.]+)\s+(NOT\s+IN|IN)\s*\((.*)\)\s*$/i);
2807
+ if (m) {
2808
+ const vals = m[3].split(",").map((v) => unquote(v)).filter((v) => v !== "");
2809
+ return {
2810
+ field: fieldInfo(m[1]),
2811
+ term: { value: m[2].toUpperCase().replace(/\s+/g, "_") },
2812
+ value: vals.join(", ")
2813
+ };
2814
+ }
2815
+ m = s.match(/^([\w.]+)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
2816
+ if (m) {
2817
+ const raw = m[3].trim();
2818
+ const val = unquote(raw);
2819
+ if (m[2] === "==" && raw === val && (val === "TRUE" || val === "FALSE")) {
2820
+ return { field: fieldInfo(m[1]), term: { value: val }, value: void 0 };
2821
+ }
2822
+ return {
2823
+ field: fieldInfo(m[1]),
2824
+ term: { value: COMPARE_OPERATOR_MAP[m[2]] },
2825
+ value: val
2826
+ };
2827
+ }
2828
+ m = s.match(/^([\w.]+)\s+(HASVAL|NOHASVAL)\s*$/i);
2829
+ if (m) {
2830
+ return {
2831
+ field: fieldInfo(m[1]),
2832
+ term: { value: m[2].toUpperCase() },
2833
+ value: void 0
2834
+ };
2835
+ }
2836
+ return null;
2837
+ }
2838
+ function statementToCondition(statement, fields) {
2839
+ const trimmed = statement?.trim();
2840
+ if (!trimmed) return null;
2841
+ const stripOuterParens = (s) => {
2842
+ const t = s.trim();
2843
+ return isWrappedByOuterParens(t) ? t.slice(1, -1).trim() : t;
2844
+ };
2845
+ const toGroup = (relation, parts) => {
2846
+ if (parts.some(
2847
+ (s) => splitByJoiner(s, "&&").length > 1 || splitByJoiner(s, "||").length > 1
2848
+ )) {
2849
+ return null;
2850
+ }
2851
+ const items = parts.map((s) => parseAtom(s, fields));
2852
+ if (items.length === 0 || items.some((it) => !it)) return null;
2853
+ return {
2854
+ relation,
2855
+ children: [{ relation, children: items }]
2856
+ };
2857
+ };
2858
+ const orParts = splitByJoiner(trimmed, "||");
2859
+ if (orParts.length > 1) {
2860
+ return toGroup("OR", orParts.map(stripOuterParens));
2861
+ }
2862
+ const inner = stripOuterParens(trimmed);
2863
+ const innerOr = splitByJoiner(inner, "||");
2864
+ if (innerOr.length > 1) return toGroup("OR", innerOr);
2865
+ return toGroup("AND", splitByJoiner(inner, "&&"));
2866
+ }
2867
+ var defaultItem = () => ({ field: {}, term: {} });
2868
+ var defaultGroup = () => ({
2869
+ relation: "AND",
2870
+ children: [defaultItem()]
2871
+ });
2872
+ function defaultFieldConditionValue() {
2873
+ return { relation: "OR", children: [defaultGroup()] };
2874
+ }
2875
+ var toggleRelation = (r) => r === "AND" ? "OR" : "AND";
2876
+ function FieldCondition({
2877
+ value,
2878
+ onChange,
2879
+ fields = [],
2880
+ disabled,
2881
+ size = "middle"
2882
+ }) {
2883
+ const data = useMemo2(
2884
+ () => value?.children?.length ? value : defaultFieldConditionValue(),
2885
+ [value]
2886
+ );
2887
+ const group = data.children[0];
2888
+ const updateGroup = useCallback(
2889
+ (nextGroup) => onChange?.({ ...data, children: [nextGroup] }),
2890
+ [data, onChange]
2891
+ );
2892
+ const updateItem = (itemIndex, patch) => {
2893
+ updateGroup({
2894
+ ...group,
2895
+ children: group.children.map(
2896
+ (item, ii) => ii === itemIndex ? { ...item, ...patch } : item
2897
+ )
2898
+ });
2899
+ };
2900
+ const fieldOptions = fields.map((f) => ({
2901
+ value: f.featureCode,
2902
+ label: f.featureName
2903
+ }));
2904
+ return /* @__PURE__ */ jsxs17("div", { className: "w-full", children: [
2905
+ /* @__PURE__ */ jsxs17("div", { className: "flex gap-2", children: [
2906
+ /* @__PURE__ */ jsx17(
2907
+ RelationButton,
2908
+ {
2909
+ relation: group.relation,
2910
+ visible: group.children.length > 1,
2911
+ disabled,
2912
+ onToggle: () => updateGroup({ ...group, relation: toggleRelation(group.relation) })
2913
+ }
2914
+ ),
2915
+ /* @__PURE__ */ jsx17("div", { className: "flex-1 space-y-2", children: group.children.map((item, itemIndex) => /* @__PURE__ */ jsx17(
2916
+ ConditionRow,
2917
+ {
2918
+ item,
2919
+ fields: fieldOptions,
2920
+ rawFields: fields,
2921
+ disabled,
2922
+ size,
2923
+ fieldPlaceholder: "\u9009\u62E9\u5B57\u6BB5",
2924
+ operatorLabel,
2925
+ inputPlaceholder: "\u8BF7\u8F93\u5165\u503C",
2926
+ showDelete: group.children.length > 1,
2927
+ onChange: (patch) => updateItem(itemIndex, patch),
2928
+ onDelete: () => updateGroup({
2929
+ ...group,
2930
+ children: group.children.filter((_, ii) => ii !== itemIndex)
2931
+ })
2932
+ },
2933
+ itemIndex
2934
+ )) })
2935
+ ] }),
2936
+ !disabled && /* @__PURE__ */ jsxs17(
2937
+ Button13,
2938
+ {
2939
+ type: "button",
2940
+ variant: "link",
2941
+ size: "sm",
2942
+ className: "h-auto p-0 ml-10 mt-2 text-sm",
2943
+ onClick: () => updateGroup({ ...group, children: [...group.children, defaultItem()] }),
2944
+ children: [
2945
+ /* @__PURE__ */ jsx17(Plus, { className: "h-3.5 w-3.5" }),
2946
+ "\u6DFB\u52A0\u6761\u4EF6"
2947
+ ]
2948
+ }
2949
+ )
2950
+ ] });
2951
+ }
2952
+ function RelationButton({
2953
+ relation,
2954
+ visible = true,
2955
+ disabled,
2956
+ onToggle
2957
+ }) {
2958
+ if (!visible) return /* @__PURE__ */ jsx17("div", { className: "w-8 shrink-0" });
2959
+ return /* @__PURE__ */ jsx17(
2960
+ "button",
2961
+ {
2962
+ type: "button",
2963
+ disabled,
2964
+ onClick: onToggle,
2965
+ className: cn(
2966
+ "w-8 shrink-0 self-center rounded px-1 py-0.5 text-xs font-medium",
2967
+ disabled ? "cursor-not-allowed" : "cursor-pointer",
2968
+ // 语义色走皮肤令牌(AND=信息蓝 / OR=警示橙),随宿主主题明暗自适应
2969
+ relation === "AND" ? "bg-info/10 text-info" : "bg-warning/10 text-warning"
2970
+ ),
2971
+ children: relation === "AND" ? "\u4E14" : "\u6216"
2972
+ }
2973
+ );
2974
+ }
2975
+ function ConditionRow({
2976
+ item,
2977
+ fields,
2978
+ rawFields,
2979
+ disabled,
2980
+ size,
2981
+ fieldPlaceholder,
2982
+ operatorLabel: operatorLabel3,
2983
+ inputPlaceholder,
2984
+ showDelete,
2985
+ onChange,
2986
+ onDelete
2987
+ }) {
2988
+ const dataType = item.field.dataType;
2989
+ const termValue = item.term.value;
2990
+ const operators = getOperatorsByFieldType(dataType);
2991
+ return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
2992
+ /* @__PURE__ */ jsx17(
2993
+ SearchableSelect,
2994
+ {
2995
+ value: item.field.value || void 0,
2996
+ onChange: (v) => {
2997
+ const raw = rawFields.find((f) => f.featureCode === v);
2998
+ onChange({
2999
+ field: raw ? { value: raw.featureCode, label: raw.featureName, dataType: raw.fieldType } : { value: v },
3000
+ term: {},
3001
+ value: void 0
3002
+ });
3003
+ },
3004
+ options: fields,
3005
+ placeholder: fieldPlaceholder,
3006
+ disabled,
3007
+ size,
3008
+ fullOptionText: true,
3009
+ className: "w-36"
3010
+ }
3011
+ ),
3012
+ /* @__PURE__ */ jsxs17(
3013
+ Select,
3014
+ {
3015
+ items: operators.map((op) => ({
3016
+ value: op.value,
3017
+ label: operatorLabel3(op.value)
3018
+ })),
3019
+ value: termValue ?? null,
3020
+ disabled: disabled || !dataType,
3021
+ onValueChange: (v) => {
3022
+ if (v == null) return;
3023
+ const resetValue = v === "BETWEEN" || isNoValueOperator(v) || isNoValueOperator(termValue);
3024
+ onChange({
3025
+ term: { value: v, label: operatorLabel3(v) },
3026
+ value: resetValue ? void 0 : item.value
3027
+ });
3028
+ },
3029
+ children: [
3030
+ /* @__PURE__ */ jsx17(
3031
+ SelectTrigger,
3032
+ {
3033
+ size: CONTROL_SIZE_SELECT_ATTR[size],
3034
+ className: cn("w-28 shrink-0", CONTROL_SIZE_TRIGGER_TEXT[size]),
3035
+ children: /* @__PURE__ */ jsx17(SelectValue, { placeholder: "\u8BF7\u9009\u62E9" })
3036
+ }
3037
+ ),
3038
+ /* @__PURE__ */ jsx17(SelectContent, { children: operators.map((op) => /* @__PURE__ */ jsx17(SelectItem, { value: op.value, children: operatorLabel3(op.value) }, op.value)) })
3039
+ ]
3040
+ }
3041
+ ),
3042
+ dataType && termValue && !isNoValueOperator(termValue) && /* @__PURE__ */ jsx17(
3043
+ ConditionValueInput,
3044
+ {
3045
+ dataType,
3046
+ termValue,
3047
+ value: item.value,
3048
+ disabled,
3049
+ size,
3050
+ placeholder: inputPlaceholder,
3051
+ onChange: (v) => onChange({ value: v })
3052
+ }
3053
+ ),
3054
+ showDelete && !disabled && /* @__PURE__ */ jsx17(
3055
+ "button",
3056
+ {
3057
+ type: "button",
3058
+ "aria-label": "delete-condition",
3059
+ className: "text-muted-foreground hover:text-destructive",
3060
+ onClick: onDelete,
3061
+ children: /* @__PURE__ */ jsx17(X2, { className: "h-4 w-4" })
3062
+ }
3063
+ )
3064
+ ] });
3065
+ }
3066
+ function ConditionValueInput({
3067
+ dataType,
3068
+ termValue,
3069
+ value,
3070
+ disabled,
3071
+ size,
3072
+ placeholder,
3073
+ onChange
3074
+ }) {
3075
+ const inputType = dataType === "NUMBER" ? "number" : "text";
3076
+ if (termValue === "BETWEEN") {
3077
+ const arr = Array.isArray(value) ? value : [];
3078
+ if (dataType === "DATE") {
3079
+ return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1", children: [
3080
+ /* @__PURE__ */ jsx17(
3081
+ DatePicker,
3082
+ {
3083
+ value: typeof arr[0] === "string" && arr[0] ? arr[0] : void 0,
3084
+ disabled,
3085
+ size,
3086
+ placeholder,
3087
+ className: "w-32",
3088
+ onChange: (v) => onChange([v, arr[1] ?? ""])
3089
+ }
3090
+ ),
3091
+ /* @__PURE__ */ jsx17("span", { className: "text-xs text-muted-foreground", children: "~" }),
3092
+ /* @__PURE__ */ jsx17(
3093
+ DatePicker,
3094
+ {
3095
+ value: typeof arr[1] === "string" && arr[1] ? arr[1] : void 0,
3096
+ disabled,
3097
+ size,
3098
+ placeholder,
3099
+ className: "w-32",
3100
+ onChange: (v) => onChange([arr[0] ?? "", v])
3101
+ }
3102
+ )
3103
+ ] });
3104
+ }
3105
+ return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1", children: [
3106
+ /* @__PURE__ */ jsx17(
3107
+ Input3,
3108
+ {
3109
+ type: inputType,
3110
+ className: cn("w-28", CONTROL_SIZE_BOX[size]),
3111
+ value: arr[0] ?? "",
3112
+ disabled,
3113
+ placeholder,
3114
+ "aria-label": "between-min",
3115
+ onChange: (e) => onChange([e.target.value, arr[1] ?? ""])
3116
+ }
3117
+ ),
3118
+ /* @__PURE__ */ jsx17("span", { className: "text-xs text-muted-foreground", children: "~" }),
3119
+ /* @__PURE__ */ jsx17(
3120
+ Input3,
3121
+ {
3122
+ type: inputType,
3123
+ className: cn("w-28", CONTROL_SIZE_BOX[size]),
3124
+ value: arr[1] ?? "",
3125
+ disabled,
3126
+ placeholder,
3127
+ "aria-label": "between-max",
3128
+ onChange: (e) => onChange([arr[0] ?? "", e.target.value])
3129
+ }
3130
+ )
3131
+ ] });
3132
+ }
3133
+ if (dataType === "DATE") {
3134
+ return /* @__PURE__ */ jsx17(
3135
+ DatePicker,
3136
+ {
3137
+ value: typeof value === "string" && value ? value : void 0,
3138
+ disabled,
3139
+ size,
3140
+ placeholder,
3141
+ className: "w-36",
3142
+ onChange: (v) => onChange(v ?? "")
3143
+ }
3144
+ );
3145
+ }
3146
+ return /* @__PURE__ */ jsx17(
3147
+ Input3,
3148
+ {
3149
+ type: inputType,
3150
+ className: cn("w-36", CONTROL_SIZE_BOX[size]),
3151
+ value: value ?? "",
3152
+ disabled,
3153
+ placeholder,
3154
+ "aria-label": "condition-value",
3155
+ onChange: (e) => onChange(e.target.value)
3156
+ }
3157
+ );
3158
+ }
3159
+
3160
+ // src/components/ExpressionEditor/index.tsx
3161
+ import { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState8 } from "react";
3162
+ import { Button as Button14 } from "@pactor-app/ui/ui";
3163
+ import { Input as Input4 } from "@pactor-app/ui/ui";
3164
+ import { Label } from "@pactor-app/ui/ui";
3165
+ import { Textarea as Textarea2 } from "@pactor-app/ui/ui";
3166
+ import {
3167
+ CONTROL_SIZE_BOX as CONTROL_SIZE_BOX2,
3168
+ CONTROL_SIZE_BOX_GROW as CONTROL_SIZE_BOX_GROW2
3169
+ } from "@pactor-app/ui/components";
3170
+ import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
3171
+ var TEXT = {
3172
+ /** 头部进入编辑态的按钮文案 */
3173
+ edit: "\u7F16\u8F91",
3174
+ cancel: "\u53D6\u6D88",
3175
+ confirm: "\u786E\u5B9A",
3176
+ /** validation 未配置 message 时的兜底提示 */
3177
+ invalidFormat: "\u8F93\u5165\u683C\u5F0F\u4E0D\u6B63\u786E"
3178
+ };
3179
+ function resolveI18nText(value, locale) {
3180
+ if (value === null || value === void 0) return "";
3181
+ if (typeof value === "string") return value;
3182
+ if (typeof value === "number" || typeof value === "boolean") {
3183
+ return String(value);
3184
+ }
3185
+ if (typeof value === "object") {
3186
+ const lang = (locale || "zh").toLowerCase();
3187
+ const candidates = lang.startsWith("zh") ? ["zh-CN", "zh_CN", "zh", "en-US", "en_US", "en"] : ["en-US", "en_US", "en", "zh-CN", "zh_CN", "zh"];
3188
+ for (const key of candidates) {
3189
+ if (value[key]) return String(value[key]);
3190
+ }
3191
+ const first = Object.values(value).find((v) => v);
3192
+ return first ? String(first) : "";
3193
+ }
3194
+ return String(value);
3195
+ }
3196
+ var PLACEHOLDER_RE = /\$\{(\w+)\}|\{\{(\w+)\}\}/g;
3197
+ function parseTemplateSegments(template, mapping) {
3198
+ const segments = [];
3199
+ const re = new RegExp(PLACEHOLDER_RE.source, "g");
3200
+ let lastIndex = 0;
3201
+ let match;
3202
+ while ((match = re.exec(template)) !== null) {
3203
+ const key = match[1] ?? match[2];
3204
+ const config = mapping?.[key];
3205
+ if (!config) continue;
3206
+ if (match.index > lastIndex) {
3207
+ segments.push({ type: "text", text: template.slice(lastIndex, match.index) });
3208
+ }
3209
+ segments.push({ type: "field", key, config });
3210
+ lastIndex = match.index + match[0].length;
3211
+ }
3212
+ if (lastIndex < template.length) {
3213
+ segments.push({ type: "text", text: template.slice(lastIndex) });
3214
+ }
3215
+ return segments;
3216
+ }
3217
+ function coerceMappingValue(raw, type) {
3218
+ if (type !== "number") return raw ?? "";
3219
+ if (raw === "" || raw === void 0 || raw === null) return "";
3220
+ const num = Number(raw);
3221
+ return Number.isFinite(num) ? num : raw;
3222
+ }
3223
+ function composeExpression(template, values, mapping) {
3224
+ return template.replace(
3225
+ new RegExp(PLACEHOLDER_RE.source, "g"),
3226
+ (raw, dollarKey, braceKey) => {
3227
+ const key = dollarKey ?? braceKey;
3228
+ return mapping?.[key] ? String(values[key] ?? "") : raw;
3229
+ }
3230
+ );
3231
+ }
3232
+ function buildDefaultValues(mapping) {
3233
+ const values = {};
3234
+ for (const [key, config] of Object.entries(mapping ?? {})) {
3235
+ values[key] = coerceMappingValue(config.default ?? "", config.type);
3236
+ }
3237
+ return values;
3238
+ }
3239
+ function validateMappingValue(value, config, fallbackMessage) {
3240
+ const isEmpty = value === "" || value === void 0 || value === null;
3241
+ if (config.required && isEmpty) {
3242
+ return config.validation?.message ?? fallbackMessage;
3243
+ }
3244
+ const pattern = config.validation?.pattern;
3245
+ if (!pattern) return null;
3246
+ if (isEmpty) return null;
3247
+ if (typeof value === "number" && Number.isNaN(value)) return null;
3248
+ return new RegExp(pattern).test(String(value)) ? null : config.validation?.message ?? fallbackMessage;
3249
+ }
3250
+ function escapeRegExp(text) {
3251
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3252
+ }
3253
+ function parseExpressionValues(template, value, mapping) {
3254
+ if (!value) return null;
3255
+ const segments = parseTemplateSegments(template, mapping);
3256
+ const keys = [];
3257
+ const pattern = segments.map((seg) => {
3258
+ if (seg.type === "text") return escapeRegExp(seg.text);
3259
+ keys.push(seg.key);
3260
+ return "(.*?)";
3261
+ }).join("");
3262
+ const match = value.match(new RegExp(`^${pattern}$`));
3263
+ if (!match) return null;
3264
+ const values = {};
3265
+ keys.forEach((key, i) => {
3266
+ values[key] = coerceMappingValue(match[i + 1], mapping?.[key]?.type);
3267
+ });
3268
+ return values;
3269
+ }
3270
+ function resolveMappingOptions(config, optionSources, locale) {
3271
+ const raw = config.options?.length ? config.options : config.optionSource ? optionSources?.[config.optionSource]?.options : null;
3272
+ if (!raw?.length) return null;
3273
+ return raw.map((opt) => ({
3274
+ ...opt,
3275
+ label: resolveI18nText(opt?.label, locale),
3276
+ value: opt?.value
3277
+ }));
3278
+ }
3279
+ function MappingSelect({
3280
+ config,
3281
+ optionSources,
3282
+ value,
3283
+ onChange,
3284
+ disabled,
3285
+ size,
3286
+ fallbackLabel,
3287
+ className,
3288
+ viewOnly
3289
+ }) {
3290
+ const options = resolveMappingOptions(config, optionSources) ?? [];
3291
+ const displayOptions = useMemo3(() => {
3292
+ if (!viewOnly || value === void 0 || value === null || value === "") {
3293
+ return options;
3294
+ }
3295
+ return options.some((opt) => String(opt.value) === String(value)) ? options : [...options, { label: String(value), value: String(value) }];
3296
+ }, [options, value, viewOnly]);
3297
+ return /* @__PURE__ */ jsx18(
3298
+ SearchableSelect,
3299
+ {
3300
+ value: value === "" ? void 0 : value,
3301
+ onChange: (v) => onChange(v),
3302
+ options: displayOptions,
3303
+ placeholder: fallbackLabel,
3304
+ disabled,
3305
+ size,
3306
+ showSearch: options.length > 10,
3307
+ fullOptionText: true,
3308
+ listClassName: "max-h-60 overflow-y-scroll",
3309
+ className
3310
+ }
3311
+ );
3312
+ }
3313
+ function useMappingFieldOptions(config, optionSources) {
3314
+ const entry = config.optionSource ? optionSources?.[config.optionSource] : null;
3315
+ return useMemo3(() => {
3316
+ const opts = config.options?.length ? config.options : entry?.options ?? [];
3317
+ return {
3318
+ loading: false,
3319
+ fields: opts.map((o) => ({
3320
+ featureCode: o.value,
3321
+ featureName: o.label,
3322
+ fieldType: o.fieldType
3323
+ }))
3324
+ };
3325
+ }, [entry, config.options]);
3326
+ }
3327
+ function MappingFieldCondition({
3328
+ config,
3329
+ optionSources,
3330
+ value,
3331
+ onChange,
3332
+ disabled,
3333
+ size
3334
+ }) {
3335
+ const { fields } = useMappingFieldOptions(config, optionSources);
3336
+ const [condition, setCondition] = useState8(
3337
+ () => (value ? statementToCondition(value, fields) : null) ?? defaultFieldConditionValue()
3338
+ );
3339
+ const touchedRef = useRef2(false);
3340
+ useEffect2(() => {
3341
+ if (touchedRef.current || !value) return;
3342
+ const parsed = statementToCondition(value, fields);
3343
+ if (parsed) setCondition(parsed);
3344
+ }, [fields, value]);
3345
+ return /* @__PURE__ */ jsx18(
3346
+ FieldCondition,
3347
+ {
3348
+ value: condition,
3349
+ fields,
3350
+ disabled,
3351
+ size,
3352
+ onChange: (next) => {
3353
+ touchedRef.current = true;
3354
+ setCondition(next);
3355
+ onChange(conditionToStatement(next));
3356
+ }
3357
+ }
3358
+ );
3359
+ }
3360
+ function ExpressionEditor({
3361
+ id,
3362
+ value,
3363
+ onChange,
3364
+ valueTemplate,
3365
+ valueMapping,
3366
+ optionSources,
3367
+ label,
3368
+ required,
3369
+ readonly,
3370
+ viewOnly,
3371
+ size = "middle",
3372
+ className,
3373
+ onValidityChange
3374
+ }) {
3375
+ const labelOf = (seg) => resolveI18nText(seg.config.label) || seg.key;
3376
+ const hintOf = (seg) => resolveI18nText(seg.config.placeholder) || labelOf(seg);
3377
+ const segments = useMemo3(
3378
+ () => parseTemplateSegments(valueTemplate, valueMapping),
3379
+ [valueTemplate, valueMapping]
3380
+ );
3381
+ const fieldSegments = useMemo3(() => {
3382
+ const seen = /* @__PURE__ */ new Set();
3383
+ return segments.filter((seg) => {
3384
+ if (seg.type !== "field" || seen.has(seg.key)) return false;
3385
+ seen.add(seg.key);
3386
+ return true;
3387
+ });
3388
+ }, [segments]);
3389
+ const [values, setValues] = useState8(
3390
+ () => (
3391
+ // 外部已有值(编辑回显/AI 填充)时优先反向解析回控件
3392
+ (value ? parseExpressionValues(valueTemplate, value, valueMapping) : null) ?? buildDefaultValues(valueMapping)
3393
+ )
3394
+ );
3395
+ const composed = composeExpression(valueTemplate, values, valueMapping);
3396
+ const [editing, setEditing] = useState8(false);
3397
+ const [draft, setDraft] = useState8(values);
3398
+ const [draftErrors, setDraftErrors] = useState8({});
3399
+ const [text, setText] = useState8(composed);
3400
+ const formValues = viewOnly ? values : draft;
3401
+ const lastEmittedRef = useRef2(null);
3402
+ const onChangeRef = useRef2(onChange);
3403
+ onChangeRef.current = onChange;
3404
+ const valuesRef = useRef2(values);
3405
+ valuesRef.current = values;
3406
+ const onValidityChangeRef = useRef2(onValidityChange);
3407
+ onValidityChangeRef.current = onValidityChange;
3408
+ useEffect2(() => {
3409
+ onValidityChangeRef.current?.(editing && Object.keys(draftErrors).length > 0);
3410
+ }, [editing, draftErrors]);
3411
+ useEffect2(() => {
3412
+ if (viewOnly) return;
3413
+ const initial = composeExpression(valueTemplate, values, valueMapping);
3414
+ if (initial !== (value ?? "")) {
3415
+ lastEmittedRef.current = initial;
3416
+ onChangeRef.current?.(initial);
3417
+ }
3418
+ }, []);
3419
+ useEffect2(() => {
3420
+ if (!value) return;
3421
+ const composed2 = composeExpression(valueTemplate, valuesRef.current, valueMapping);
3422
+ if (value === composed2) return;
3423
+ const parsed = parseExpressionValues(valueTemplate, value, valueMapping);
3424
+ if (parsed) setValues(parsed);
3425
+ }, [value, valueTemplate, valueMapping]);
3426
+ useEffect2(() => {
3427
+ if (value !== void 0 && value !== "") return;
3428
+ const defaults = buildDefaultValues(valueMapping);
3429
+ setValues((prev) => {
3430
+ const same = Object.keys(defaults).every((k) => prev[k] === defaults[k]);
3431
+ return same ? prev : defaults;
3432
+ });
3433
+ }, [value, valueMapping]);
3434
+ useEffect2(() => {
3435
+ if (value === void 0 || value === lastEmittedRef.current) return;
3436
+ setText(value === "" ? composed : value);
3437
+ }, [value, composed]);
3438
+ const enterEdit = () => {
3439
+ setDraft(valuesRef.current);
3440
+ setDraftErrors({});
3441
+ setEditing(true);
3442
+ };
3443
+ const handleConfirm = () => {
3444
+ const nextErrors = {};
3445
+ for (const seg of fieldSegments) {
3446
+ const message = validateMappingValue(draft[seg.key], seg.config, TEXT.invalidFormat);
3447
+ if (message) nextErrors[seg.key] = message;
3448
+ }
3449
+ setDraftErrors(nextErrors);
3450
+ if (Object.keys(nextErrors).length > 0) return;
3451
+ const next = composeExpression(valueTemplate, draft, valueMapping);
3452
+ setValues(draft);
3453
+ lastEmittedRef.current = next;
3454
+ setText(next);
3455
+ onChangeRef.current?.(next);
3456
+ setEditing(false);
3457
+ };
3458
+ const handleCancel = () => {
3459
+ setDraftErrors({});
3460
+ setEditing(false);
3461
+ };
3462
+ const handleTextChange = (raw) => {
3463
+ setText(raw);
3464
+ lastEmittedRef.current = raw;
3465
+ const parsed = parseExpressionValues(valueTemplate, raw, valueMapping);
3466
+ if (parsed) setValues(parsed);
3467
+ onChangeRef.current?.(raw);
3468
+ };
3469
+ const handleDraftChange = (key, raw) => {
3470
+ const config = valueMapping?.[key];
3471
+ const next = coerceMappingValue(raw, config?.type);
3472
+ setDraft((prev) => ({
3473
+ ...prev,
3474
+ [key]: next
3475
+ }));
3476
+ if (config?.validation?.pattern) {
3477
+ const message = validateMappingValue(next, config, TEXT.invalidFormat);
3478
+ setDraftErrors((prev) => {
3479
+ if (!message && !prev[key]) return prev;
3480
+ const updated = { ...prev };
3481
+ if (message) updated[key] = message;
3482
+ else delete updated[key];
3483
+ return updated;
3484
+ });
3485
+ }
3486
+ };
3487
+ if (!valueTemplate) return null;
3488
+ return /* @__PURE__ */ jsxs18("div", { id, className: cn("w-full space-y-2", className), children: [
3489
+ !viewOnly && /* @__PURE__ */ jsxs18("div", { className: "flex items-center justify-between", children: [
3490
+ label ? /* @__PURE__ */ jsxs18(Label, { children: [
3491
+ label,
3492
+ required && /* @__PURE__ */ jsx18("span", { className: "text-destructive ml-1", children: "*" })
3493
+ ] }) : /* @__PURE__ */ jsx18("span", {}),
3494
+ !readonly && !editing && /* @__PURE__ */ jsx18(
3495
+ Button14,
3496
+ {
3497
+ type: "button",
3498
+ variant: "ghost",
3499
+ size: "sm",
3500
+ className: "h-6 px-2 text-xs",
3501
+ onClick: enterEdit,
3502
+ children: TEXT.edit
3503
+ }
3504
+ )
3505
+ ] }),
3506
+ editing || viewOnly ? /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-3 rounded-md border bg-background px-3 py-3", children: [
3507
+ fieldSegments.map((seg) => seg.config.type === "fieldCondition" ? (
3508
+ // fieldCondition:label 在上,组件在下(块级布局)
3509
+ /* @__PURE__ */ jsxs18("div", { className: "flex flex-col gap-1.5", children: [
3510
+ /* @__PURE__ */ jsx18("span", { className: "text-sm text-muted-foreground", children: labelOf(seg) }),
3511
+ /* @__PURE__ */ jsx18(
3512
+ MappingFieldCondition,
3513
+ {
3514
+ config: seg.config,
3515
+ optionSources,
3516
+ disabled: viewOnly,
3517
+ size,
3518
+ value: formValues[seg.key] === "" || formValues[seg.key] === void 0 ? void 0 : String(formValues[seg.key]),
3519
+ onChange: (v) => handleDraftChange(seg.key, v)
3520
+ }
3521
+ ),
3522
+ draftErrors[seg.key] && /* @__PURE__ */ jsx18("p", { className: "text-xs text-destructive", children: draftErrors[seg.key] })
3523
+ ] }, seg.key)
3524
+ ) : /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-3", children: [
3525
+ /* @__PURE__ */ jsx18("span", { className: "w-24 shrink-0 text-sm text-muted-foreground truncate", children: labelOf(seg) }),
3526
+ /* @__PURE__ */ jsxs18("div", { className: "flex-1", children: [
3527
+ seg.config.type === "select" ? /* @__PURE__ */ jsx18(
3528
+ MappingSelect,
3529
+ {
3530
+ config: seg.config,
3531
+ optionSources,
3532
+ disabled: viewOnly,
3533
+ viewOnly,
3534
+ size,
3535
+ value: formValues[seg.key] === "" ? void 0 : String(formValues[seg.key]),
3536
+ onChange: (v) => handleDraftChange(seg.key, v),
3537
+ fallbackLabel: hintOf(seg)
3538
+ }
3539
+ ) : /* @__PURE__ */ jsx18(
3540
+ Input4,
3541
+ {
3542
+ type: seg.config.type === "number" ? "number" : "text",
3543
+ value: formValues[seg.key] ?? "",
3544
+ onChange: (e) => handleDraftChange(seg.key, e.target.value),
3545
+ disabled: viewOnly,
3546
+ placeholder: hintOf(seg),
3547
+ "aria-label": labelOf(seg),
3548
+ className: cn(CONTROL_SIZE_BOX2[size], "w-full font-mono")
3549
+ }
3550
+ ),
3551
+ draftErrors[seg.key] && /* @__PURE__ */ jsx18("p", { className: "mt-1 text-xs text-destructive", children: draftErrors[seg.key] })
3552
+ ] })
3553
+ ] }, seg.key)),
3554
+ !viewOnly && /* @__PURE__ */ jsxs18("div", { className: "flex justify-end gap-2", children: [
3555
+ /* @__PURE__ */ jsx18(
3556
+ Button14,
3557
+ {
3558
+ type: "button",
3559
+ variant: "outline",
3560
+ size: "sm",
3561
+ onClick: handleCancel,
3562
+ children: TEXT.cancel
3563
+ }
3564
+ ),
3565
+ /* @__PURE__ */ jsx18(Button14, { type: "button", size: "sm", onClick: handleConfirm, children: TEXT.confirm })
3566
+ ] })
3567
+ ] }) : (
3568
+ // display:可直接编辑的表达式文本框;内容实时 emit,
3569
+ // 能按模板解析时会映射回「编辑」表单的控件
3570
+ /* @__PURE__ */ jsx18(
3571
+ Textarea2,
3572
+ {
3573
+ value: text,
3574
+ onChange: (e) => handleTextChange(e.target.value),
3575
+ readOnly: readonly,
3576
+ rows: 1,
3577
+ className: cn(
3578
+ CONTROL_SIZE_BOX_GROW2[size],
3579
+ "w-full resize-none break-all bg-muted/30 font-mono [field-sizing:content]"
3580
+ )
3581
+ }
3582
+ )
3583
+ )
3584
+ ] });
3585
+ }
3586
+
3587
+ // src/components/RuleConfig/index.tsx
3588
+ import { Plus as Plus2, Trash2 } from "lucide-react";
3589
+ import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState9 } from "react";
3590
+ import { Badge as Badge2 } from "@pactor-app/ui/ui";
3591
+ import { Button as Button15 } from "@pactor-app/ui/ui";
3592
+ import { Input as Input5 } from "@pactor-app/ui/ui";
3593
+ import {
3594
+ Select as Select2,
3595
+ SelectContent as SelectContent2,
3596
+ SelectItem as SelectItem2,
3597
+ SelectTrigger as SelectTrigger2,
3598
+ SelectValue as SelectValue2
3599
+ } from "@pactor-app/ui/ui";
3600
+ import {
3601
+ CONTROL_SIZE_BOX as CONTROL_SIZE_BOX3,
3602
+ CONTROL_SIZE_BOX_GROW as CONTROL_SIZE_BOX_GROW3,
3603
+ CONTROL_SIZE_SELECT_ATTR as CONTROL_SIZE_SELECT_ATTR2,
3604
+ CONTROL_SIZE_TRIGGER_TEXT as CONTROL_SIZE_TRIGGER_TEXT2
3605
+ } from "@pactor-app/ui/components";
3606
+ import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
3607
+ var isBlank = (v) => v == null || String(v).trim() === "";
3608
+ function isValueMissing(condition) {
3609
+ const { opt, value } = condition;
3610
+ if (NO_VALUE_OPT.includes(opt)) return false;
3611
+ if (opt === "BETWEEN") {
3612
+ const [a, b] = normalizeBetweenValue(value);
3613
+ return isBlank(a) || isBlank(b);
3614
+ }
3615
+ if (Array.isArray(value)) return value.length === 0 || value.every(isBlank);
3616
+ return isBlank(value);
3617
+ }
3618
+ function getConditionIssue(condition) {
3619
+ const missingValue = isValueMissing(condition);
3620
+ const missingScore = condition.score == null || Number.isNaN(condition.score);
3621
+ if (!missingValue && !missingScore) return null;
3622
+ return { missingValue, missingScore };
3623
+ }
3624
+ var OPERATOR_VALUES = [
3625
+ "GREATER",
3626
+ "GREATER_EQUAL",
3627
+ "LESS_EQUAL",
3628
+ "LESS",
3629
+ "EQUAL",
3630
+ "IN",
3631
+ "NOT_IN",
3632
+ "UNEQUAL",
3633
+ "LIKE",
3634
+ "NULL",
3635
+ "HASVAL",
3636
+ "NOTNULL",
3637
+ "BETWEEN"
3638
+ ];
3639
+ var NUMERIC_OPT = [
3640
+ "GREATER",
3641
+ "LESS",
3642
+ "LESS_EQUAL",
3643
+ "GREATER_EQUAL",
3644
+ "UNEQUAL"
3645
+ ];
3646
+ var NO_VALUE_OPT = ["NULL", "HASVAL", "NOTNULL"];
3647
+ var MULTI_VALUE_OPT = ["IN", "NOT_IN"];
3648
+ var OPERATOR_LABELS2 = {
3649
+ GREATER: "\u5927\u4E8E",
3650
+ GREATER_EQUAL: "\u5927\u4E8E\u7B49\u4E8E",
3651
+ LESS_EQUAL: "\u5C0F\u4E8E\u7B49\u4E8E",
3652
+ LESS: "\u5C0F\u4E8E",
3653
+ EQUAL: "\u7B49\u4E8E",
3654
+ IN: "\u5305\u542B",
3655
+ NOT_IN: "\u4E0D\u5305\u542B",
3656
+ UNEQUAL: "\u4E0D\u7B49\u4E8E",
3657
+ LIKE: "\u7C7B\u4F3C",
3658
+ NULL: "\u4E3A\u7A7A",
3659
+ HASVAL: "\u6709\u503C",
3660
+ NOTNULL: "\u4E0D\u4E3A\u7A7A",
3661
+ BETWEEN: "\u8303\u56F4"
3662
+ };
3663
+ function operatorLabel2(operator) {
3664
+ return OPERATOR_LABELS2[operator] ?? operator;
3665
+ }
3666
+ var _key = 0;
3667
+ var nextKey = () => ++_key;
3668
+ var defaultCondition = () => ({ key: nextKey(), opt: "EQUAL" });
3669
+ function normalizeBetweenValue(value) {
3670
+ if (Array.isArray(value)) return [value[0], value[1]];
3671
+ if (typeof value === "string" && value.includes(",")) {
3672
+ const [a, b] = value.split(",");
3673
+ return [a?.trim(), b?.trim()];
3674
+ }
3675
+ if (value == null || value === "") return [void 0, void 0];
3676
+ return [value, void 0];
3677
+ }
3678
+ function formatConditionValue2(condition) {
3679
+ const { opt, value } = condition;
3680
+ if (value == null) return "-";
3681
+ if (opt === "BETWEEN") {
3682
+ const [a, b] = normalizeBetweenValue(value);
3683
+ return `${a ?? "-"} ~ ${b ?? "-"}`;
3684
+ }
3685
+ if (Array.isArray(value)) return value.join(", ");
3686
+ return String(value);
3687
+ }
3688
+ function RuleConfig({ value, onChange, disabled, size = "middle" }) {
3689
+ const rawConditions = value?.ruleConditions;
3690
+ const conditions = useMemo4(() => {
3691
+ if (!rawConditions?.length) return [defaultCondition()];
3692
+ const seen = /* @__PURE__ */ new Set();
3693
+ return rawConditions.map((c) => {
3694
+ if (c.key != null && !seen.has(c.key)) {
3695
+ seen.add(c.key);
3696
+ return c;
3697
+ }
3698
+ const key = nextKey();
3699
+ seen.add(key);
3700
+ return { ...c, key };
3701
+ });
3702
+ }, [rawConditions]);
3703
+ const initScore = value?.score ?? 1;
3704
+ const [scoreText, setScoreText] = useState9(String(initScore));
3705
+ const lastScoreRef = useRef3(value?.score);
3706
+ useEffect3(() => {
3707
+ if (value?.score === lastScoreRef.current) return;
3708
+ lastScoreRef.current = value?.score;
3709
+ setScoreText(value?.score == null ? "" : String(value.score));
3710
+ }, [value?.score]);
3711
+ const commitScore = useCallback2(
3712
+ (score) => {
3713
+ lastScoreRef.current = score;
3714
+ onChange?.({ score, ruleConditions: conditions });
3715
+ },
3716
+ [onChange, conditions]
3717
+ );
3718
+ const normalizeScore = (n) => Math.min(1e3, Math.max(1, Math.round(n)));
3719
+ const handleScoreTextChange = (text) => {
3720
+ setScoreText(text);
3721
+ if (text === "") return;
3722
+ const n = Number(text);
3723
+ if (Number.isFinite(n) && n >= 1 && n <= 1e3 && Number.isInteger(n)) {
3724
+ commitScore(n);
3725
+ }
3726
+ };
3727
+ const handleScoreBlur = () => {
3728
+ const n = scoreText.trim() === "" ? NaN : Number(scoreText);
3729
+ const next = Number.isFinite(n) ? normalizeScore(n) : 1;
3730
+ setScoreText(String(next));
3731
+ if (next !== lastScoreRef.current) commitScore(next);
3732
+ };
3733
+ const update = useCallback2(
3734
+ (next, nextScore) => onChange?.({
3735
+ score: nextScore ?? initScore,
3736
+ ruleConditions: next
3737
+ }),
3738
+ [onChange, initScore]
3739
+ );
3740
+ const handleOptChange = (key, opt) => {
3741
+ update(
3742
+ conditions.map(
3743
+ (c) => c.key === key ? { ...c, opt, value: opt === "BETWEEN" ? [] : void 0 } : c
3744
+ )
3745
+ );
3746
+ };
3747
+ const handleValueChange = (key, val) => {
3748
+ update(conditions.map((c) => c.key === key ? { ...c, value: val } : c));
3749
+ };
3750
+ const handleScoreChange = (key, score) => {
3751
+ update(
3752
+ conditions.map((c) => c.key === key ? { ...c, score } : c)
3753
+ );
3754
+ };
3755
+ const handleAdd = () => update([...conditions, defaultCondition()]);
3756
+ const handleDelete = (key) => update(conditions.filter((c) => c.key !== key));
3757
+ if (disabled) {
3758
+ return /* @__PURE__ */ jsxs19("div", { className: "space-y-2 text-sm", children: [
3759
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2 pb-2 border-b", children: [
3760
+ /* @__PURE__ */ jsx19("span", { className: "text-muted-foreground", children: "\u6743\u91CD" }),
3761
+ /* @__PURE__ */ jsx19("span", { className: "font-medium", children: initScore })
3762
+ ] }),
3763
+ conditions.map((condition) => /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
3764
+ /* @__PURE__ */ jsx19("span", { children: operatorLabel2(condition.opt) }),
3765
+ !NO_VALUE_OPT.includes(condition.opt) && /* @__PURE__ */ jsx19("span", { className: "font-medium", children: formatConditionValue2(condition) }),
3766
+ /* @__PURE__ */ jsxs19("span", { className: "text-muted-foreground", children: [
3767
+ "\u8BC4\u5206: ",
3768
+ condition.score ?? "-"
3769
+ ] })
3770
+ ] }, condition.key))
3771
+ ] });
3772
+ }
3773
+ return /* @__PURE__ */ jsxs19("div", { className: "space-y-2", children: [
3774
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1 pb-2 border-b gap-2", children: [
3775
+ /* @__PURE__ */ jsx19("span", { className: "text-sm shrink-0", children: "\u6743\u91CD" }),
3776
+ /* @__PURE__ */ jsx19(
3777
+ Input5,
3778
+ {
3779
+ type: "number",
3780
+ className: cn("w-48", CONTROL_SIZE_BOX3[size]),
3781
+ min: 1,
3782
+ max: 1e3,
3783
+ step: 1,
3784
+ value: scoreText,
3785
+ disabled,
3786
+ placeholder: "\u8BF7\u8F93\u5165\u6743\u91CD",
3787
+ onChange: (e) => handleScoreTextChange(e.target.value),
3788
+ onBlur: handleScoreBlur
3789
+ }
3790
+ )
3791
+ ] }),
3792
+ conditions.map((condition) => /* @__PURE__ */ jsx19(
3793
+ ConditionRow2,
3794
+ {
3795
+ condition,
3796
+ issue: getConditionIssue(condition),
3797
+ showDelete: conditions.length > 1,
3798
+ disabled,
3799
+ size,
3800
+ onOptChange: (opt) => handleOptChange(condition.key, opt),
3801
+ onValueChange: (val) => handleValueChange(condition.key, val),
3802
+ onScoreChange: (score) => handleScoreChange(condition.key, score),
3803
+ onDelete: () => handleDelete(condition.key)
3804
+ },
3805
+ condition.key
3806
+ )),
3807
+ !disabled && /* @__PURE__ */ jsxs19(
3808
+ Button15,
3809
+ {
3810
+ type: "button",
3811
+ variant: "link",
3812
+ size: "sm",
3813
+ className: "h-auto p-0 text-sm mt-1",
3814
+ onClick: handleAdd,
3815
+ children: [
3816
+ /* @__PURE__ */ jsx19(Plus2, { className: "h-3.5 w-3.5" }),
3817
+ "\u6DFB\u52A0\u6761\u4EF6"
3818
+ ]
3819
+ }
3820
+ )
3821
+ ] });
3822
+ }
3823
+ function ConditionRow2({
3824
+ condition,
3825
+ issue,
3826
+ showDelete,
3827
+ disabled,
3828
+ size,
3829
+ onOptChange,
3830
+ onValueChange,
3831
+ onScoreChange,
3832
+ onDelete
3833
+ }) {
3834
+ const { opt, value, score } = condition;
3835
+ const missingValue = !disabled && !!issue?.missingValue;
3836
+ const missingScore = !disabled && !!issue?.missingScore;
3837
+ return /* @__PURE__ */ jsxs19("div", { className: "space-y-1", children: [
3838
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
3839
+ /* @__PURE__ */ jsxs19(
3840
+ Select2,
3841
+ {
3842
+ items: OPERATOR_VALUES.map((op) => ({
3843
+ value: op,
3844
+ label: operatorLabel2(op)
3845
+ })),
3846
+ value: opt,
3847
+ onValueChange: (v) => onOptChange(v),
3848
+ disabled,
3849
+ children: [
3850
+ /* @__PURE__ */ jsx19(
3851
+ SelectTrigger2,
3852
+ {
3853
+ size: CONTROL_SIZE_SELECT_ATTR2[size],
3854
+ className: cn("w-36 shrink-0", CONTROL_SIZE_TRIGGER_TEXT2[size]),
3855
+ children: /* @__PURE__ */ jsx19(SelectValue2, {})
3856
+ }
3857
+ ),
3858
+ /* @__PURE__ */ jsx19(SelectContent2, { children: OPERATOR_VALUES.map((op) => /* @__PURE__ */ jsx19(SelectItem2, { value: op, children: operatorLabel2(op) }, op)) })
3859
+ ]
3860
+ }
3861
+ ),
3862
+ !NO_VALUE_OPT.includes(opt) && /* @__PURE__ */ jsx19(
3863
+ "div",
3864
+ {
3865
+ className: "w-[30%]" + (missingValue ? " [&_input]:border-destructive" : ""),
3866
+ children: /* @__PURE__ */ jsx19(
3867
+ ValueInput,
3868
+ {
3869
+ opt,
3870
+ value,
3871
+ disabled,
3872
+ size,
3873
+ onChange: onValueChange
3874
+ }
3875
+ )
3876
+ }
3877
+ ),
3878
+ /* @__PURE__ */ jsx19(
3879
+ Input5,
3880
+ {
3881
+ type: "number",
3882
+ className: cn(
3883
+ "w-24 shrink-0",
3884
+ CONTROL_SIZE_BOX3[size],
3885
+ missingScore && "border-destructive"
3886
+ ),
3887
+ placeholder: "\u8BC4\u5206",
3888
+ value: score ?? "",
3889
+ disabled,
3890
+ "aria-invalid": missingScore || void 0,
3891
+ onChange: (e) => onScoreChange(
3892
+ e.target.value === "" ? void 0 : e.target.valueAsNumber
3893
+ )
3894
+ }
3895
+ ),
3896
+ showDelete && !disabled && /* @__PURE__ */ jsx19(
3897
+ Button15,
3898
+ {
3899
+ type: "button",
3900
+ variant: "ghost",
3901
+ size: "icon",
3902
+ className: "h-8 w-8 shrink-0 text-muted-foreground hover:text-destructive",
3903
+ onClick: onDelete,
3904
+ children: /* @__PURE__ */ jsx19(Trash2, { className: "h-4 w-4" })
3905
+ }
3906
+ )
3907
+ ] }),
3908
+ !disabled && issue && /* @__PURE__ */ jsx19("p", { className: "text-xs text-destructive", children: [
3909
+ missingValue ? "\u8BF7\u586B\u5199\u53D6\u503C" : null,
3910
+ missingScore ? "\u8BF7\u586B\u5199\u8BC4\u5206" : null
3911
+ ].filter(Boolean).join("\uFF1B") })
3912
+ ] });
3913
+ }
3914
+ function ValueInput({ opt, value, disabled, size, onChange }) {
3915
+ if (NO_VALUE_OPT.includes(opt)) {
3916
+ return null;
3917
+ }
3918
+ if (opt === "BETWEEN") {
3919
+ const [v1, v2] = normalizeBetweenValue(value);
3920
+ return /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", children: [
3921
+ /* @__PURE__ */ jsx19(
3922
+ Input5,
3923
+ {
3924
+ type: "number",
3925
+ className: cn("flex-1", CONTROL_SIZE_BOX3[size]),
3926
+ value: v1 ?? "",
3927
+ disabled,
3928
+ onChange: (e) => onChange([e.target.valueAsNumber, v2])
3929
+ }
3930
+ ),
3931
+ /* @__PURE__ */ jsx19("span", { className: "text-muted-foreground text-sm shrink-0", children: "~" }),
3932
+ /* @__PURE__ */ jsx19(
3933
+ Input5,
3934
+ {
3935
+ type: "number",
3936
+ className: cn("flex-1", CONTROL_SIZE_BOX3[size]),
3937
+ value: v2 ?? "",
3938
+ disabled,
3939
+ onChange: (e) => onChange([v1, e.target.valueAsNumber])
3940
+ }
3941
+ )
3942
+ ] });
3943
+ }
3944
+ if (MULTI_VALUE_OPT.includes(opt)) {
3945
+ return /* @__PURE__ */ jsx19(
3946
+ TagInput,
3947
+ {
3948
+ value: value ?? [],
3949
+ disabled,
3950
+ size,
3951
+ onChange: (tags) => onChange(tags)
3952
+ }
3953
+ );
3954
+ }
3955
+ if (NUMERIC_OPT.includes(opt)) {
3956
+ return /* @__PURE__ */ jsx19(
3957
+ Input5,
3958
+ {
3959
+ type: "number",
3960
+ className: CONTROL_SIZE_BOX3[size],
3961
+ value: value ?? "",
3962
+ disabled,
3963
+ onChange: (e) => onChange(e.target.valueAsNumber)
3964
+ }
3965
+ );
3966
+ }
3967
+ return /* @__PURE__ */ jsx19(
3968
+ Input5,
3969
+ {
3970
+ type: "text",
3971
+ className: CONTROL_SIZE_BOX3[size],
3972
+ value: value ?? "",
3973
+ disabled,
3974
+ onChange: (e) => onChange(e.target.value)
3975
+ }
3976
+ );
3977
+ }
3978
+ function TagInput({
3979
+ value,
3980
+ disabled,
3981
+ size,
3982
+ onChange
3983
+ }) {
3984
+ const [input, setInput] = useState9("");
3985
+ const addTag = () => {
3986
+ const tag = input.trim();
3987
+ if (tag && !value.includes(tag)) {
3988
+ onChange([...value, tag]);
3989
+ }
3990
+ setInput("");
3991
+ };
3992
+ const removeTag = (tag) => onChange(value.filter((t) => t !== tag));
3993
+ return /* @__PURE__ */ jsxs19(
3994
+ "div",
3995
+ {
3996
+ className: cn(
3997
+ "flex flex-wrap items-center gap-1 min-h-9 rounded-md border bg-transparent px-3 py-1 text-sm",
3998
+ CONTROL_SIZE_BOX_GROW3[size]
3999
+ ),
4000
+ children: [
4001
+ " ",
4002
+ value.map((tag) => /* @__PURE__ */ jsxs19(Badge2, { variant: "secondary", className: "gap-1 pr-1", children: [
4003
+ tag,
4004
+ !disabled && /* @__PURE__ */ jsx19(
4005
+ "button",
4006
+ {
4007
+ type: "button",
4008
+ className: "text-muted-foreground hover:text-foreground",
4009
+ onClick: () => removeTag(tag),
4010
+ children: "\xD7"
4011
+ }
4012
+ )
4013
+ ] }, tag)),
4014
+ !disabled && /* @__PURE__ */ jsx19(
4015
+ "input",
4016
+ {
4017
+ className: "flex-1 min-w-16 bg-transparent outline-none placeholder:text-muted-foreground",
4018
+ value: input,
4019
+ placeholder: "\u8F93\u5165\u540E\u6309 Enter",
4020
+ onChange: (e) => setInput(e.target.value),
4021
+ onKeyDown: (e) => {
4022
+ if (e.key === "Enter") {
4023
+ e.preventDefault();
4024
+ addTag();
4025
+ }
4026
+ }
4027
+ }
4028
+ )
4029
+ ]
4030
+ }
4031
+ );
4032
+ }
4033
+
4034
+ // src/components/RuleExpression/index.tsx
4035
+ import { useLayoutEffect, useRef as useRef4, useState as useState10 } from "react";
4036
+ import { CONTROL_SIZE_BOX_GROW as CONTROL_SIZE_BOX_GROW4 } from "@pactor-app/ui/components";
4037
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
4038
+ function trailingWord(text) {
4039
+ const m = text.match(/[\w一-龥]+$/u);
4040
+ return m ? m[0] : "";
4041
+ }
4042
+ function labelSuffixLength(input, label) {
4043
+ for (let len = Math.min(input.length, label.length); len > 0; len--) {
4044
+ const suffix = input.slice(input.length - len);
4045
+ if (label.includes(suffix)) return len;
4046
+ }
4047
+ return 0;
4048
+ }
4049
+ function serialize(tokens, fieldKey = "value") {
4050
+ let out = "";
4051
+ tokens.forEach((t, i) => {
4052
+ if (t.type === "field") {
4053
+ if (out && !out.endsWith(" ")) out += " ";
4054
+ out += t[fieldKey];
4055
+ } else {
4056
+ if (i === tokens.length - 1) {
4057
+ out += t.text;
4058
+ return;
4059
+ }
4060
+ const text = t.text.trim();
4061
+ if (!text) return;
4062
+ if (out && !out.endsWith(" ")) out += " ";
4063
+ out += `${text} `;
4064
+ }
4065
+ });
4066
+ return out;
4067
+ }
4068
+ function seedTokens(value, options) {
4069
+ if (!value) return [];
4070
+ if (typeof value === "string") {
4071
+ return value ? [{ type: "text", text: value }] : [];
4072
+ }
4073
+ const fields = (value.sourceField || []).map((v) => ({
4074
+ type: "field",
4075
+ value: String(v),
4076
+ label: options.find((o) => o.value === String(v))?.label ?? String(v)
4077
+ }));
4078
+ const tokens = [];
4079
+ let rest = value.expression ?? "";
4080
+ for (const field of fields) {
4081
+ const idx = rest.indexOf(field.value);
4082
+ if (idx === -1) {
4083
+ tokens.push(field);
4084
+ continue;
4085
+ }
4086
+ const before = rest.slice(0, idx);
4087
+ if (before) tokens.push({ type: "text", text: before });
4088
+ tokens.push(field);
4089
+ rest = rest.slice(idx + field.value.length);
4090
+ }
4091
+ if (rest) tokens.push({ type: "text", text: rest });
4092
+ return tokens;
4093
+ }
4094
+ var X_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
4095
+ function buildChip(field, disabled) {
4096
+ const chip = document.createElement("span");
4097
+ chip.contentEditable = "false";
4098
+ chip.dataset.value = field.value;
4099
+ chip.dataset.label = field.label;
4100
+ chip.className = "mx-0.5 inline-flex items-center gap-1 rounded bg-muted px-2 py-0.5 align-middle text-xs font-medium";
4101
+ const label = document.createElement("span");
4102
+ label.textContent = field.label;
4103
+ chip.appendChild(label);
4104
+ if (!disabled) {
4105
+ const btn = document.createElement("button");
4106
+ btn.type = "button";
4107
+ btn.dataset.remove = "true";
4108
+ btn.setAttribute("aria-label", `remove-${field.value}`);
4109
+ btn.className = "cursor-pointer opacity-60 hover:opacity-100";
4110
+ btn.innerHTML = X_SVG;
4111
+ chip.appendChild(btn);
4112
+ }
4113
+ return chip;
4114
+ }
4115
+ function parseEditor(root) {
4116
+ const tokens = [];
4117
+ const pushText = (text) => {
4118
+ if (!text) return;
4119
+ const last = tokens[tokens.length - 1];
4120
+ if (last?.type === "text") last.text += text;
4121
+ else tokens.push({ type: "text", text });
4122
+ };
4123
+ const walk = (node) => {
4124
+ if (node.nodeType === Node.TEXT_NODE) {
4125
+ pushText(node.textContent ?? "");
4126
+ return;
4127
+ }
4128
+ if (!(node instanceof HTMLElement)) return;
4129
+ if (node.dataset.value != null) {
4130
+ tokens.push({
4131
+ type: "field",
4132
+ value: node.dataset.value,
4133
+ label: node.dataset.label ?? node.dataset.value
4134
+ });
4135
+ return;
4136
+ }
4137
+ if (node.tagName === "BR") {
4138
+ pushText("\n");
4139
+ return;
4140
+ }
4141
+ node.childNodes.forEach(walk);
4142
+ if (node.tagName === "DIV" || node.tagName === "P") pushText("\n");
4143
+ };
4144
+ root.childNodes.forEach(walk);
4145
+ return tokens;
4146
+ }
4147
+ function setCaret(node, offset) {
4148
+ const sel = window.getSelection();
4149
+ if (!sel) return;
4150
+ const range = document.createRange();
4151
+ range.setStart(node, offset);
4152
+ range.collapse(true);
4153
+ sel.removeAllRanges();
4154
+ sel.addRange(range);
4155
+ }
4156
+ function setCaretToEnd(el) {
4157
+ const sel = window.getSelection();
4158
+ if (!sel) return;
4159
+ const range = document.createRange();
4160
+ range.selectNodeContents(el);
4161
+ range.collapse(false);
4162
+ sel.removeAllRanges();
4163
+ sel.addRange(range);
4164
+ }
4165
+ function RuleExpression({
4166
+ options = [],
4167
+ value,
4168
+ onChange,
4169
+ placeholder,
4170
+ disabled,
4171
+ size = "middle",
4172
+ id,
4173
+ className
4174
+ }) {
4175
+ const editorRef = useRef4(null);
4176
+ const [focused, setFocused] = useState10(false);
4177
+ const [activeIndex, setActiveIndex] = useState10(0);
4178
+ const [matchText, setMatchText] = useState10("");
4179
+ const lastEmittedRef = useRef4(null);
4180
+ const matched = matchText ? options.filter((o) => o.label.toLowerCase().includes(matchText.toLowerCase())) : [];
4181
+ const showSuggestions = focused && !disabled && matched.length > 0;
4182
+ const emitTokens = (tokens) => {
4183
+ const out = {
4184
+ expression: serialize(tokens),
4185
+ expressionLabel: serialize(tokens, "label"),
4186
+ sourceField: tokens.filter((t) => t.type === "field").map((t) => t.value)
4187
+ };
4188
+ lastEmittedRef.current = JSON.stringify(out);
4189
+ onChange?.(out);
4190
+ };
4191
+ const caretBeforeText = () => {
4192
+ const ed = editorRef.current;
4193
+ const sel = window.getSelection();
4194
+ if (!ed || !sel || !sel.rangeCount) return "";
4195
+ const node = sel.anchorNode;
4196
+ if (!node || !ed.contains(node)) return "";
4197
+ if (node === ed) {
4198
+ const prev = ed.childNodes[sel.anchorOffset - 1];
4199
+ return prev?.nodeType === Node.TEXT_NODE ? prev.textContent ?? "" : "";
4200
+ }
4201
+ if (node.nodeType === Node.TEXT_NODE) {
4202
+ return (node.textContent ?? "").slice(0, sel.anchorOffset);
4203
+ }
4204
+ return "";
4205
+ };
4206
+ const refreshSuggest = () => {
4207
+ setMatchText(trailingWord(caretBeforeText()));
4208
+ };
4209
+ const syncFromDOM = () => {
4210
+ const ed = editorRef.current;
4211
+ if (!ed) return;
4212
+ emitTokens(parseEditor(ed));
4213
+ refreshSuggest();
4214
+ };
4215
+ const rebuild = (tokens) => {
4216
+ const ed = editorRef.current;
4217
+ if (!ed) return;
4218
+ ed.textContent = "";
4219
+ for (const t of tokens) {
4220
+ if (t.type === "field") ed.appendChild(buildChip(t, disabled));
4221
+ else ed.appendChild(document.createTextNode(t.text));
4222
+ }
4223
+ };
4224
+ useLayoutEffect(() => {
4225
+ const sig = JSON.stringify(value ?? null);
4226
+ if (sig !== lastEmittedRef.current) {
4227
+ rebuild(seedTokens(value, options));
4228
+ lastEmittedRef.current = sig;
4229
+ setMatchText("");
4230
+ }
4231
+ }, [value, options, disabled]);
4232
+ const selectOption = (opt) => {
4233
+ const ed = editorRef.current;
4234
+ const sel = window.getSelection();
4235
+ if (!ed || !sel || !sel.rangeCount) return;
4236
+ let node = sel.anchorNode;
4237
+ let offset = sel.anchorOffset;
4238
+ if (!node || !ed.contains(node)) return;
4239
+ if (node === ed) {
4240
+ const tn = document.createTextNode("");
4241
+ ed.insertBefore(tn, ed.childNodes[offset] ?? null);
4242
+ node = tn;
4243
+ offset = 0;
4244
+ }
4245
+ if (node.nodeType !== Node.TEXT_NODE) return;
4246
+ const textNode = node;
4247
+ const text = textNode.textContent ?? "";
4248
+ const before = text.slice(0, offset);
4249
+ const after = text.slice(offset);
4250
+ const replaceLen = Math.max(
4251
+ labelSuffixLength(before, opt.label),
4252
+ trailingWord(before).length
4253
+ );
4254
+ textNode.textContent = before.slice(0, before.length - replaceLen);
4255
+ const afterNode = document.createTextNode(after);
4256
+ const chip = buildChip({ type: "field", label: opt.label, value: opt.value }, disabled);
4257
+ ed.insertBefore(chip, textNode.nextSibling);
4258
+ ed.insertBefore(afterNode, chip.nextSibling);
4259
+ if (!textNode.textContent) ed.removeChild(textNode);
4260
+ ed.focus();
4261
+ setCaret(afterNode, 0);
4262
+ setActiveIndex(0);
4263
+ syncFromDOM();
4264
+ };
4265
+ const removeChip = (chip) => {
4266
+ const ed = editorRef.current;
4267
+ if (!ed) return;
4268
+ const prev = chip.previousSibling;
4269
+ const caretNode = prev && prev.nodeType === Node.TEXT_NODE ? prev : document.createTextNode("");
4270
+ if (!caretNode.isConnected) ed.insertBefore(caretNode, chip);
4271
+ const caretPos = caretNode.textContent?.length ?? 0;
4272
+ ed.removeChild(chip);
4273
+ ed.normalize();
4274
+ ed.focus();
4275
+ if (caretNode.isConnected) setCaret(caretNode, caretPos);
4276
+ else setCaretToEnd(ed);
4277
+ syncFromDOM();
4278
+ };
4279
+ const tryRemoveAdjacentChip = (dir) => {
4280
+ const ed = editorRef.current;
4281
+ const sel = window.getSelection();
4282
+ if (!ed || !sel || !sel.rangeCount || !sel.isCollapsed) return false;
4283
+ const node = sel.anchorNode;
4284
+ if (!node || !ed.contains(node)) return false;
4285
+ let target = null;
4286
+ if (node === ed) {
4287
+ target = dir === "before" ? ed.childNodes[sel.anchorOffset - 1] : ed.childNodes[sel.anchorOffset];
4288
+ } else if (node.nodeType === Node.TEXT_NODE) {
4289
+ const len = node.textContent?.length ?? 0;
4290
+ const atEdge = dir === "before" ? sel.anchorOffset === 0 : sel.anchorOffset === len;
4291
+ if (atEdge) {
4292
+ target = dir === "before" ? node.previousSibling : node.nextSibling;
4293
+ }
4294
+ }
4295
+ if (target instanceof HTMLElement && target.dataset.value != null) {
4296
+ removeChip(target);
4297
+ return true;
4298
+ }
4299
+ return false;
4300
+ };
4301
+ const handleKeyDown = (e) => {
4302
+ if (e.key === "ArrowDown" && showSuggestions) {
4303
+ e.preventDefault();
4304
+ setActiveIndex((x) => (x + 1) % matched.length);
4305
+ return;
4306
+ }
4307
+ if (e.key === "ArrowUp" && showSuggestions) {
4308
+ e.preventDefault();
4309
+ setActiveIndex((x) => (x - 1 + matched.length) % matched.length);
4310
+ return;
4311
+ }
4312
+ if (e.key === "Enter") {
4313
+ if (showSuggestions) {
4314
+ e.preventDefault();
4315
+ selectOption(matched[Math.min(activeIndex, matched.length - 1)]);
4316
+ }
4317
+ return;
4318
+ }
4319
+ if (e.key === "Backspace" && tryRemoveAdjacentChip("before")) {
4320
+ e.preventDefault();
4321
+ return;
4322
+ }
4323
+ if (e.key === "Delete" && tryRemoveAdjacentChip("after")) {
4324
+ e.preventDefault();
4325
+ }
4326
+ };
4327
+ return /* @__PURE__ */ jsxs20(
4328
+ "div",
4329
+ {
4330
+ className: cn("relative", className),
4331
+ onBlur: (e) => {
4332
+ if (!e.currentTarget.contains(e.relatedTarget)) {
4333
+ setFocused(false);
4334
+ }
4335
+ },
4336
+ children: [
4337
+ /* @__PURE__ */ jsx20(
4338
+ "div",
4339
+ {
4340
+ ref: editorRef,
4341
+ id,
4342
+ contentEditable: !disabled,
4343
+ suppressContentEditableWarning: true,
4344
+ role: "textbox",
4345
+ "aria-multiline": "true",
4346
+ "data-placeholder": placeholder,
4347
+ className: cn(
4348
+ "min-h-9 w-full whitespace-pre-wrap break-words rounded-md border border-input bg-background px-3 py-2 text-sm",
4349
+ CONTROL_SIZE_BOX_GROW4[size],
4350
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
4351
+ "empty:before:pointer-events-none empty:before:text-muted-foreground empty:before:content-[attr(data-placeholder)]",
4352
+ disabled && "cursor-not-allowed opacity-50"
4353
+ ),
4354
+ onFocus: () => {
4355
+ setFocused(true);
4356
+ refreshSuggest();
4357
+ },
4358
+ onInput: syncFromDOM,
4359
+ onKeyDown: handleKeyDown,
4360
+ onKeyUp: refreshSuggest,
4361
+ onClick: (e) => {
4362
+ const btn = e.target.closest?.("[data-remove]");
4363
+ if (btn) {
4364
+ e.preventDefault();
4365
+ const chip = btn.closest("[data-value]");
4366
+ if (chip instanceof HTMLElement) removeChip(chip);
4367
+ return;
4368
+ }
4369
+ refreshSuggest();
4370
+ }
4371
+ }
4372
+ ),
4373
+ showSuggestions && /* @__PURE__ */ jsx20(
4374
+ "ul",
4375
+ {
4376
+ role: "list",
4377
+ className: "absolute left-0 top-full z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 shadow-md",
4378
+ onMouseDown: (e) => e.preventDefault(),
4379
+ children: matched.map((opt, index) => /* @__PURE__ */ jsx20("li", { children: /* @__PURE__ */ jsx20(
4380
+ "button",
4381
+ {
4382
+ type: "button",
4383
+ className: cn(
4384
+ "w-full cursor-pointer rounded-sm px-2 py-1.5 text-left text-sm",
4385
+ index === activeIndex && "bg-accent"
4386
+ ),
4387
+ onMouseEnter: () => setActiveIndex(index),
4388
+ onClick: () => selectOption(opt),
4389
+ children: opt.label
4390
+ }
4391
+ ) }, opt.value))
4392
+ }
4393
+ )
4394
+ ]
4395
+ }
4396
+ );
4397
+ }
4398
+
4399
+ // src/components/ScriptEditor/index.tsx
4400
+ import { useEffect as useEffect4, useRef as useRef5, useState as useState11 } from "react";
4401
+ import { jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
4402
+ function escapeHtml(text) {
4403
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
4404
+ }
4405
+ function buildFunctionSnippet(fn) {
4406
+ const params = fn.parameters ?? [];
4407
+ if (params.length === 0) return `${fn.name}()`;
4408
+ const inner = params.map((p, i) => `\${${i + 1}:${p.name}}`).join(", ");
4409
+ return `${fn.name}(${inner})`;
4410
+ }
4411
+ function buildFunctionDoc(fn) {
4412
+ const parts = [];
4413
+ if (fn.description) parts.push(`<p>${escapeHtml(fn.description)}</p>`);
4414
+ const params = fn.parameters ?? [];
4415
+ if (params.length > 0) {
4416
+ const rows = params.map(
4417
+ (p) => `<li><b>${escapeHtml(p.name)}</b>` + (p.type ? ` <i>${escapeHtml(p.type)}</i>` : "") + (p.description ? ` \u2014 ${escapeHtml(p.description)}` : "") + `</li>`
4418
+ ).join("");
4419
+ parts.push(`<ul>${rows}</ul>`);
4420
+ }
4421
+ return parts.join("");
4422
+ }
4423
+ function buildVariableDoc(variable) {
4424
+ const parts = [];
4425
+ if (variable.type) parts.push(`<p><i>${escapeHtml(variable.type)}</i></p>`);
4426
+ if (variable.description) parts.push(`<p>${escapeHtml(variable.description)}</p>`);
4427
+ return parts.join("");
4428
+ }
4429
+ function buildScriptCompletions(functions, variables) {
4430
+ return {
4431
+ getCompletions(_editor, _session, _position, _prefix, callback) {
4432
+ const completions = [
4433
+ ...(functions ?? []).map((fn) => ({
4434
+ caption: fn.name,
4435
+ snippet: buildFunctionSnippet(fn),
4436
+ meta: "function",
4437
+ docHTML: buildFunctionDoc(fn)
4438
+ })),
4439
+ ...(variables ?? []).map((v) => ({
4440
+ caption: v.name,
4441
+ value: v.name,
4442
+ meta: "variable",
4443
+ docHTML: buildVariableDoc(v)
4444
+ }))
4445
+ ];
4446
+ callback(null, completions);
4447
+ }
4448
+ };
4449
+ }
4450
+ var MODE_LOADERS = {
4451
+ python: () => Promise.all([
4452
+ import("ace-builds/src-noconflict/mode-python"),
4453
+ import("ace-builds/src-noconflict/snippets/python")
4454
+ ])
4455
+ };
4456
+ function ScriptEditor({
4457
+ id,
4458
+ value,
4459
+ onChange,
4460
+ placeholder,
4461
+ language = "python",
4462
+ functions,
4463
+ variables,
4464
+ readonly,
4465
+ height = 160,
4466
+ className
4467
+ }) {
4468
+ const containerRef = useRef5(null);
4469
+ const editorRef = useRef5(null);
4470
+ const onChangeRef = useRef5(onChange);
4471
+ onChangeRef.current = onChange;
4472
+ const [isEmpty, setIsEmpty] = useState11(!value);
4473
+ useEffect4(() => {
4474
+ let disposed = false;
4475
+ (async () => {
4476
+ const ace = await import("ace-builds");
4477
+ await Promise.all([
4478
+ import("ace-builds/src-noconflict/theme-textmate"),
4479
+ import("ace-builds/src-noconflict/ext-language_tools"),
4480
+ MODE_LOADERS[language]?.() ?? Promise.resolve()
4481
+ ]);
4482
+ if (disposed || !containerRef.current) return;
4483
+ const editor = ace.edit(containerRef.current, {
4484
+ value: value ?? "",
4485
+ theme: "ace/theme/textmate",
4486
+ mode: MODE_LOADERS[language] ? `ace/mode/${language}` : "ace/mode/text",
4487
+ showPrintMargin: false,
4488
+ highlightActiveLine: true,
4489
+ tabSize: 4,
4490
+ useSoftTabs: true,
4491
+ readOnly: readonly ?? false,
4492
+ enableBasicAutocompletion: [buildScriptCompletions(functions, variables)],
4493
+ enableLiveAutocompletion: true,
4494
+ enableSnippets: true
4495
+ });
4496
+ editor.session.setUseWorker(false);
4497
+ editor.on("change", () => {
4498
+ const current = editor.getValue();
4499
+ setIsEmpty(current === "");
4500
+ onChangeRef.current?.(current);
4501
+ });
4502
+ editorRef.current = editor;
4503
+ })().catch(() => {
4504
+ });
4505
+ return () => {
4506
+ disposed = true;
4507
+ editorRef.current?.destroy();
4508
+ editorRef.current = null;
4509
+ };
4510
+ }, []);
4511
+ useEffect4(() => {
4512
+ const editor = editorRef.current;
4513
+ if (!editor) return;
4514
+ const next = value ?? "";
4515
+ if (editor.getValue() !== next) {
4516
+ editor.setValue(next, -1);
4517
+ setIsEmpty(next === "");
4518
+ }
4519
+ }, [value]);
4520
+ useEffect4(() => {
4521
+ editorRef.current?.setOptions({
4522
+ enableBasicAutocompletion: [buildScriptCompletions(functions, variables)]
4523
+ });
4524
+ }, [functions, variables]);
4525
+ useEffect4(() => {
4526
+ editorRef.current?.setReadOnly(readonly ?? false);
4527
+ }, [readonly]);
4528
+ return /* @__PURE__ */ jsxs21(
4529
+ "div",
4530
+ {
4531
+ className: cn(
4532
+ "relative w-full rounded-md border bg-background overflow-hidden",
4533
+ readonly && "bg-muted",
4534
+ className
4535
+ ),
4536
+ children: [
4537
+ /* @__PURE__ */ jsx21("div", { ref: containerRef, id, style: { height }, className: "w-full" }),
4538
+ isEmpty && placeholder && /* @__PURE__ */ jsx21("div", { className: "absolute top-0 left-0 px-3 py-2 text-sm text-muted-foreground pointer-events-none z-10", children: placeholder })
4539
+ ]
4540
+ }
4541
+ );
4542
+ }
4543
+
4544
+ // src/index.ts
4545
+ var blockComponents = {
4546
+ Login,
4547
+ EntityCard,
4548
+ CardList,
4549
+ RecordingPage,
4550
+ SiteFooter,
4551
+ FieldCondition,
4552
+ ExpressionEditor,
4553
+ RuleConfig,
4554
+ RuleExpression,
4555
+ ScriptEditor
4556
+ };
4557
+ function registerBlockComponents(registry) {
4558
+ for (const [type, component] of Object.entries(blockComponents)) {
4559
+ registry.register(type, component);
4560
+ }
4561
+ }
4562
+ function createBlockComponentRegistry() {
4563
+ const registry = new Registry("ComponentRegistry");
4564
+ registerBlockComponents(registry);
4565
+ return registry;
4566
+ }
4567
+ export {
4568
+ CardList,
4569
+ EntityCard,
4570
+ ExpressionEditor,
4571
+ FieldCondition,
4572
+ Login,
4573
+ RecordingPage,
4574
+ RuleConfig,
4575
+ RuleExpression,
4576
+ ScriptEditor,
4577
+ SiteFooter,
4578
+ blockComponents,
4579
+ createBlockComponentRegistry,
4580
+ registerBlockComponents
4581
+ };