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