@teamix-evo/ui 0.6.1 → 0.7.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.
@@ -0,0 +1,502 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import {
3
+ Sidebar,
4
+ SidebarContent,
5
+ SidebarFooter,
6
+ SidebarGroup,
7
+ SidebarGroupContent,
8
+ SidebarGroupLabel,
9
+ SidebarHeader,
10
+ SidebarMenu,
11
+ SidebarMenuButton,
12
+ SidebarMenuItem,
13
+ } from '@/components/sidebar';
14
+ import { PageShell } from '@/components/page-shell';
15
+ import { PageHeader, PageHeaderHeading } from '@/components/page-header';
16
+ import { Card, CardContent } from '@/components/card';
17
+ import { Button } from '@/components/button';
18
+ import { Input } from '@/components/input';
19
+ import { DataTable } from '@/components/data-table';
20
+ import type { DataTableColumn } from '@/components/data-table';
21
+ import { Tag } from '@/components/tag';
22
+ import { TooltipProvider } from '@/components/tooltip';
23
+ import {
24
+ Search,
25
+ Bot,
26
+ BookOpen,
27
+ Sparkles,
28
+ FlaskConical,
29
+ BarChart3,
30
+ MoreVertical,
31
+ PanelLeftClose,
32
+ LayoutGrid,
33
+ Wrench,
34
+ Brain,
35
+ MessageSquare,
36
+ FileText,
37
+ ListChecks,
38
+ Activity,
39
+ ClipboardList,
40
+ ChevronDown,
41
+ Plus,
42
+ } from 'lucide-react';
43
+
44
+ const meta: Meta = {
45
+ title: '页面示例 · Page Examples/评估器 标准列表',
46
+ tags: ['autodocs'],
47
+ parameters: {
48
+ layout: 'fullscreen',
49
+ },
50
+ };
51
+ export default meta;
52
+
53
+ type Story = StoryObj;
54
+
55
+ // ─── Mock Data ─────────────────────────────────────────────────────────────
56
+
57
+ interface Evaluator extends Record<string, unknown> {
58
+ id: string;
59
+ name: string;
60
+ type: string;
61
+ metrics: string;
62
+ status: 'active' | 'inactive' | 'draft';
63
+ creator: string;
64
+ creatorUsername: string;
65
+ createdAt: string;
66
+ }
67
+
68
+ const evaluators: Evaluator[] = [
69
+ {
70
+ id: '1',
71
+ name: '通用问答质量评估',
72
+ type: '模型评估',
73
+ metrics: '准确率、流畅度、相关性',
74
+ status: 'active',
75
+ creator: '张明',
76
+ creatorUsername: 'zhangming',
77
+ createdAt: '2026-05-10 14:30:00',
78
+ },
79
+ {
80
+ id: '2',
81
+ name: 'RAG 检索质量评估',
82
+ type: '检索评估',
83
+ metrics: '召回率、精确率、MRR',
84
+ status: 'active',
85
+ creator: '李华',
86
+ creatorUsername: 'lihua',
87
+ createdAt: '2026-05-08 10:15:00',
88
+ },
89
+ {
90
+ id: '3',
91
+ name: '安全合规检测器',
92
+ type: '安全评估',
93
+ metrics: '违规率、敏感词命中',
94
+ status: 'active',
95
+ creator: '王磊',
96
+ creatorUsername: 'wanglei',
97
+ createdAt: '2026-05-06 16:45:00',
98
+ },
99
+ {
100
+ id: '4',
101
+ name: '代码生成评估',
102
+ type: '模型评估',
103
+ metrics: 'Pass@1、编译通过率',
104
+ status: 'draft',
105
+ creator: '赵婷',
106
+ creatorUsername: 'zhaoting',
107
+ createdAt: '2026-05-04 09:20:00',
108
+ },
109
+ {
110
+ id: '5',
111
+ name: '多轮对话一致性评估',
112
+ type: '对话评估',
113
+ metrics: '上下文一致性、话题连贯性',
114
+ status: 'active',
115
+ creator: '张明',
116
+ creatorUsername: 'zhangming',
117
+ createdAt: '2026-05-02 11:30:00',
118
+ },
119
+ {
120
+ id: '6',
121
+ name: '摘要生成评估',
122
+ type: '模型评估',
123
+ metrics: 'ROUGE-L、信息覆盖率',
124
+ status: 'inactive',
125
+ creator: '李华',
126
+ creatorUsername: 'lihua',
127
+ createdAt: '2026-04-28 15:10:00',
128
+ },
129
+ {
130
+ id: '7',
131
+ name: '意图识别评估',
132
+ type: '分类评估',
133
+ metrics: 'F1-Score、准确率',
134
+ status: 'active',
135
+ creator: '王磊',
136
+ creatorUsername: 'wanglei',
137
+ createdAt: '2026-04-25 08:50:00',
138
+ },
139
+ {
140
+ id: '8',
141
+ name: '知识库问答评估',
142
+ type: '检索评估',
143
+ metrics: 'EM、F1、召回率',
144
+ status: 'active',
145
+ creator: '赵婷',
146
+ creatorUsername: 'zhaoting',
147
+ createdAt: '2026-04-22 13:40:00',
148
+ },
149
+ ];
150
+
151
+ // ─── Table Columns ─────────────────────────────────────────────────────────
152
+
153
+ const columns: DataTableColumn<Evaluator>[] = [
154
+ {
155
+ key: 'name',
156
+ title: '评估器名称',
157
+ dataIndex: 'name',
158
+ width: 200,
159
+ sortable: true,
160
+ render: (value) => (
161
+ <span className="cursor-pointer font-medium text-primary">
162
+ {value as string}
163
+ </span>
164
+ ),
165
+ },
166
+ {
167
+ key: 'type',
168
+ title: '类型',
169
+ dataIndex: 'type',
170
+ width: 120,
171
+ },
172
+ {
173
+ key: 'metrics',
174
+ title: '评测指标',
175
+ dataIndex: 'metrics',
176
+ width: 220,
177
+ },
178
+ {
179
+ key: 'status',
180
+ title: '状态',
181
+ dataIndex: 'status',
182
+ width: 100,
183
+ render: (value) => {
184
+ const statusMap: Record<
185
+ string,
186
+ { status: 'success' | 'default' | 'warning'; text: string }
187
+ > = {
188
+ active: { status: 'success', text: '已启用' },
189
+ inactive: { status: 'default', text: '已停用' },
190
+ draft: { status: 'warning', text: '草稿' },
191
+ };
192
+ const config = statusMap[value as string] ?? statusMap['active'];
193
+ return (
194
+ <Tag variant="status" status={config!.status}>
195
+ {config!.text}
196
+ </Tag>
197
+ );
198
+ },
199
+ },
200
+ {
201
+ key: 'creator',
202
+ title: '创建人',
203
+ dataIndex: 'creator',
204
+ width: 150,
205
+ render: (_value, record) => (
206
+ <span className="text-sm">
207
+ {record.creator}{' '}
208
+ <span className="text-muted-foreground">
209
+ ({record.creatorUsername})
210
+ </span>
211
+ </span>
212
+ ),
213
+ },
214
+ {
215
+ key: 'createdAt',
216
+ title: '创建时间',
217
+ dataIndex: 'createdAt',
218
+ width: 180,
219
+ sortable: true,
220
+ render: (value) => <span className="tabular-nums">{value as string}</span>,
221
+ },
222
+ {
223
+ key: 'actions',
224
+ title: '操作',
225
+ width: 160,
226
+ align: 'right',
227
+ render: () => (
228
+ <div className="flex items-center justify-end gap-3">
229
+ <span className="cursor-pointer text-primary">详情</span>
230
+ <span className="cursor-pointer text-primary">编辑</span>
231
+ <span className="cursor-pointer text-destructive">删除</span>
232
+ </div>
233
+ ),
234
+ },
235
+ ];
236
+
237
+ // ─── OP Sidebar ────────────────────────────────────────────────────────────
238
+
239
+ /**
240
+ * OP Logo — inline SVG(来源:_assets/OP_LOGO.svg)
241
+ */
242
+ function OpLogo({ className }: { className?: string }) {
243
+ return (
244
+ <svg
245
+ xmlns="http://www.w3.org/2000/svg"
246
+ fill="none"
247
+ viewBox="0 0 84 84"
248
+ className={className}
249
+ >
250
+ <defs>
251
+ <clipPath id="opai-logo-clip-evaluators">
252
+ <rect x="0" y="0" width="84" height="84" rx="18" />
253
+ </clipPath>
254
+ </defs>
255
+ <g clipPath="url(#opai-logo-clip-evaluators)">
256
+ <rect x="0" y="0" width="84" height="84" rx="18" fill="currentColor" />
257
+ <path
258
+ d="M56.628 43.209L48.407 45.869C46.698 46.422 45.142 44.688 45.876 43.048L60.648 10.047C61.316 8.554 62.798 7.594 64.434 7.594L70.258 7.594C71.893 7.594 73.376 8.554 74.044 10.047L88.816 43.048C89.55 44.688 87.994 46.422 86.284 45.869L78.063 43.209C77.784 43.119 77.556 42.914 77.436 42.646L69.239 24.334C68.507 22.698 66.185 22.698 65.453 24.334L57.255 42.646C57.136 42.914 56.908 43.119 56.628 43.209ZM54.737 51.511C53.792 51.195 53.792 49.859 54.737 49.543L63.03 46.774C63.3 46.684 63.521 46.487 63.641 46.229L66.407 40.304C66.779 39.506 67.914 39.506 68.286 40.304L71.052 46.229C71.172 46.487 71.393 46.684 71.663 46.774L79.956 49.543C80.901 49.859 80.901 51.195 79.956 51.511L71.663 54.28C71.393 54.37 71.172 54.567 71.052 54.825L68.286 60.75C67.914 61.548 66.779 61.548 66.407 60.75L63.641 54.825C63.521 54.567 63.3 54.37 63.03 54.28L54.737 51.511Z"
259
+ fillRule="evenodd"
260
+ fill="white"
261
+ transform="matrix(0.707 0.707 -0.707 0.707 18.752 -30.083)"
262
+ />
263
+ </g>
264
+ </svg>
265
+ );
266
+ }
267
+
268
+ function OpentrekSidebar() {
269
+ return (
270
+ <Sidebar collapsible="none">
271
+ <SidebarHeader className="px-4 pt-4 pb-0">
272
+ <div className="flex items-center justify-between">
273
+ <div className="flex h-8 items-center gap-2">
274
+ <OpLogo className="size-6 shrink-0 text-foreground" />
275
+ <span className="text-base font-black leading-tight tracking-tight">
276
+ OPENTREK
277
+ {/* eslint-disable-next-line teamix-evo/no-arbitrary-tw-value -- demo badge 10px 无对应 token */}
278
+ <span className="ml-1 text-[10px] font-bold tracking-wider text-muted-foreground">
279
+ DEV
280
+ </span>
281
+ </span>
282
+ </div>
283
+ <button
284
+ type="button"
285
+ className="flex size-7 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
286
+ >
287
+ <PanelLeftClose className="size-4" />
288
+ </button>
289
+ </div>
290
+ </SidebarHeader>
291
+
292
+ <SidebarContent className="pt-4">
293
+ {/* 空间选择器 */}
294
+ <SidebarGroup>
295
+ <SidebarGroupContent>
296
+ <SidebarMenu>
297
+ <SidebarMenuItem>
298
+ <SidebarMenuButton tooltip="产品研发空间">
299
+ <Bot className="size-4" />
300
+ <span>产品研发空间</span>
301
+ <ChevronDown className="ml-auto size-3.5 text-muted-foreground" />
302
+ </SidebarMenuButton>
303
+ </SidebarMenuItem>
304
+ </SidebarMenu>
305
+ </SidebarGroupContent>
306
+ </SidebarGroup>
307
+
308
+ {/* 概览 */}
309
+ <SidebarGroup>
310
+ <SidebarGroupContent>
311
+ <SidebarMenu>
312
+ <SidebarMenuItem>
313
+ <SidebarMenuButton tooltip="概览">
314
+ <LayoutGrid className="size-4" />
315
+ <span>概览</span>
316
+ </SidebarMenuButton>
317
+ </SidebarMenuItem>
318
+ </SidebarMenu>
319
+ </SidebarGroupContent>
320
+ </SidebarGroup>
321
+
322
+ {/* 开发 */}
323
+ <SidebarGroup>
324
+ <SidebarGroupLabel>开发</SidebarGroupLabel>
325
+ <SidebarGroupContent>
326
+ <SidebarMenu>
327
+ <SidebarMenuItem>
328
+ <SidebarMenuButton tooltip="智能体">
329
+ <Bot className="size-4" />
330
+ <span>智能体</span>
331
+ </SidebarMenuButton>
332
+ </SidebarMenuItem>
333
+ <SidebarMenuItem>
334
+ <SidebarMenuButton tooltip="工具箱">
335
+ <Wrench className="size-4" />
336
+ <span>工具箱</span>
337
+ </SidebarMenuButton>
338
+ </SidebarMenuItem>
339
+ </SidebarMenu>
340
+ </SidebarGroupContent>
341
+ </SidebarGroup>
342
+
343
+ {/* Skills Hub / 记忆体 / 知识库 / 提示词 */}
344
+ <SidebarGroup>
345
+ <SidebarGroupContent>
346
+ <SidebarMenu>
347
+ <SidebarMenuItem>
348
+ <SidebarMenuButton tooltip="Skills Hub">
349
+ <Sparkles className="size-4" />
350
+ <span>Skills Hub</span>
351
+ </SidebarMenuButton>
352
+ </SidebarMenuItem>
353
+ <SidebarMenuItem>
354
+ <SidebarMenuButton tooltip="记忆体">
355
+ <Brain className="size-4" />
356
+ <span>记忆体</span>
357
+ </SidebarMenuButton>
358
+ </SidebarMenuItem>
359
+ <SidebarMenuItem>
360
+ <SidebarMenuButton tooltip="知识库">
361
+ <BookOpen className="size-4" />
362
+ <span>知识库</span>
363
+ </SidebarMenuButton>
364
+ </SidebarMenuItem>
365
+ <SidebarMenuItem>
366
+ <SidebarMenuButton tooltip="提示词">
367
+ <MessageSquare className="size-4" />
368
+ <span>提示词</span>
369
+ </SidebarMenuButton>
370
+ </SidebarMenuItem>
371
+ </SidebarMenu>
372
+ </SidebarGroupContent>
373
+ </SidebarGroup>
374
+
375
+ {/* 评测 */}
376
+ <SidebarGroup>
377
+ <SidebarGroupLabel>评测</SidebarGroupLabel>
378
+ <SidebarGroupContent>
379
+ <SidebarMenu>
380
+ <SidebarMenuItem>
381
+ <SidebarMenuButton tooltip="评测集">
382
+ <FileText className="size-4" />
383
+ <span>评测集</span>
384
+ </SidebarMenuButton>
385
+ </SidebarMenuItem>
386
+ <SidebarMenuItem>
387
+ <SidebarMenuButton isActive tooltip="评估器">
388
+ <FlaskConical className="size-4" />
389
+ <span>评估器</span>
390
+ </SidebarMenuButton>
391
+ </SidebarMenuItem>
392
+ <SidebarMenuItem>
393
+ <SidebarMenuButton tooltip="评测任务">
394
+ <ListChecks className="size-4" />
395
+ <span>评测任务</span>
396
+ </SidebarMenuButton>
397
+ </SidebarMenuItem>
398
+ </SidebarMenu>
399
+ </SidebarGroupContent>
400
+ </SidebarGroup>
401
+
402
+ {/* 观测 */}
403
+ <SidebarGroup>
404
+ <SidebarGroupLabel>观测</SidebarGroupLabel>
405
+ <SidebarGroupContent>
406
+ <SidebarMenu>
407
+ <SidebarMenuItem>
408
+ <SidebarMenuButton tooltip="指标监控">
409
+ <BarChart3 className="size-4" />
410
+ <span>指标监控</span>
411
+ </SidebarMenuButton>
412
+ </SidebarMenuItem>
413
+ <SidebarMenuItem>
414
+ <SidebarMenuButton tooltip="Trace追踪">
415
+ <Activity className="size-4" />
416
+ <span>Trace追踪</span>
417
+ </SidebarMenuButton>
418
+ </SidebarMenuItem>
419
+ <SidebarMenuItem>
420
+ <SidebarMenuButton tooltip="操作记录">
421
+ <ClipboardList className="size-4" />
422
+ <span>操作记录</span>
423
+ </SidebarMenuButton>
424
+ </SidebarMenuItem>
425
+ </SidebarMenu>
426
+ </SidebarGroupContent>
427
+ </SidebarGroup>
428
+ </SidebarContent>
429
+
430
+ <SidebarFooter>
431
+ <div className="flex items-center gap-2 px-3 py-3">
432
+ <div className="flex size-7 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold">
433
+
434
+ </div>
435
+ <span className="truncate text-sm font-medium">少食</span>
436
+ <button
437
+ type="button"
438
+ className="ml-auto flex size-7 items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground"
439
+ >
440
+ <MoreVertical className="size-4" />
441
+ </button>
442
+ </div>
443
+ </SidebarFooter>
444
+ </Sidebar>
445
+ );
446
+ }
447
+
448
+ // ─── Page Story ────────────────────────────────────────────────────────────
449
+
450
+ /** 评估器 - OT-STD-2 简单标准列表 */
451
+ export const Default: Story = {
452
+ render: () => (
453
+ <TooltipProvider>
454
+ <PageShell
455
+ sidebar={<OpentrekSidebar />}
456
+ background="muted"
457
+ // eslint-disable-next-line teamix-evo/no-arbitrary-tw-value -- Storybook 固定高度展示
458
+ className="h-[900px]"
459
+ >
460
+ <div className="flex-1 p-5">
461
+ <Card>
462
+ <CardContent className="flex flex-col gap-4">
463
+ {/* PageHeader */}
464
+ <PageHeader>
465
+ <PageHeaderHeading>
466
+ <span className="text-lg font-medium">评估器</span>
467
+ </PageHeaderHeading>
468
+ </PageHeader>
469
+
470
+ {/* ActionToolbar: F 变体 (Primary + Search) */}
471
+ <div className="flex items-center gap-2">
472
+ <Button variant="default" color="primary">
473
+ <Plus className="size-4" />
474
+ 新建评估器
475
+ </Button>
476
+ <div className="relative ml-5">
477
+ <Input
478
+ placeholder="搜索评估器名称..."
479
+ className="w-56 pr-8"
480
+ />
481
+ <Search className="absolute right-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
482
+ </div>
483
+ </div>
484
+
485
+ {/* DataTable */}
486
+ <DataTable<Evaluator>
487
+ columns={columns}
488
+ dataSource={evaluators}
489
+ rowKey="id"
490
+ pagination={{
491
+ current: 1,
492
+ pageSize: 10,
493
+ total: 24,
494
+ }}
495
+ />
496
+ </CardContent>
497
+ </Card>
498
+ </div>
499
+ </PageShell>
500
+ </TooltipProvider>
501
+ ),
502
+ };
@@ -0,0 +1,51 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+
5
+ /**
6
+ * 防止 Radix DismissableLayer 级联关闭 hook。
7
+ *
8
+ * 当弹窗 / 抽屉内嵌 Select / Popover / HoverCard 等 Radix Popper 浮层时,
9
+ * 点击 Popper 外部(但仍在 Dialog / Sheet 内)会被 DismissableLayer 误判为"外部点击",
10
+ * 从而连带关闭宿主弹窗。本 hook 通过 pointerdown 捕获阶段嗅探 `[data-radix-popper-content-wrapper]`
11
+ * 是否存在来决定是否阻止级联关闭。
12
+ *
13
+ * 用法:
14
+ * ```tsx
15
+ * const { onPointerDownOutside } = useRadixPopperGuard();
16
+ * <DialogContent onPointerDownOutside={onPointerDownOutside} />
17
+ * ```
18
+ *
19
+ * 逻辑:
20
+ * - pointerdown 捕获阶段:记录当前 DOM 是否存在 `[data-radix-popper-content-wrapper]`(即有 Popper 处于打开状态)
21
+ * - onPointerDownOutside 回调:若上一次捕获阶段标记为"有 Popper 打开",则 `e.preventDefault()` 阻止 DismissableLayer 触发 dismiss
22
+ * - 若无 Popper(正常遮罩点击),不阻止,DismissableLayer 原生 dismiss 机制正常触发
23
+ */
24
+ export function useRadixPopperGuard() {
25
+ const popperOpenRef = React.useRef(false);
26
+
27
+ React.useEffect(() => {
28
+ function handlePointerDown() {
29
+ popperOpenRef.current = !!document.querySelector(
30
+ '[data-radix-popper-content-wrapper]',
31
+ );
32
+ }
33
+
34
+ // 在捕获阶段侦听,确保在 Radix DismissableLayer 处理之前执行
35
+ document.addEventListener('pointerdown', handlePointerDown, true);
36
+ return () => {
37
+ document.removeEventListener('pointerdown', handlePointerDown, true);
38
+ };
39
+ }, []);
40
+
41
+ const onPointerDownOutside = React.useCallback(
42
+ (e: { preventDefault: () => void }) => {
43
+ if (popperOpenRef.current) {
44
+ e.preventDefault();
45
+ }
46
+ },
47
+ [],
48
+ );
49
+
50
+ return { onPointerDownOutside };
51
+ }