@allbluecn/shared 0.3.13 → 0.4.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/config-CcbSbJoC.js +6344 -0
- package/dist/constants/perf.d.ts +6 -0
- package/dist/constants/perf.js +6 -0
- package/dist/constants-C3hGP_ru.d.ts +33 -0
- package/dist/i18n/client.d.ts +5 -0
- package/dist/i18n/client.js +34 -0
- package/dist/i18n/config.d.ts +6270 -0
- package/dist/i18n/config.js +2 -0
- package/dist/i18n/detect.d.ts +5 -0
- package/dist/i18n/detect.js +35 -0
- package/dist/i18n/server.d.ts +6 -0
- package/dist/i18n/server.js +19 -0
- package/dist/i18n/types.d.ts +3 -0
- package/dist/i18n/types.js +0 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +14 -0
- package/dist/infrastructure/ai/adapter.d.ts +8 -0
- package/dist/infrastructure/ai/adapter.js +32 -0
- package/dist/infrastructure/ai/cli-detector.d.ts +6 -0
- package/dist/infrastructure/ai/cli-detector.js +121 -0
- package/dist/infrastructure/ip-location.d.ts +9 -0
- package/dist/infrastructure/ip-location.js +48 -0
- package/dist/infrastructure/references/engine.d.ts +7 -0
- package/dist/infrastructure/references/engine.js +64 -0
- package/dist/infrastructure/ua-parser.d.ts +20 -0
- package/dist/infrastructure/ua-parser.js +69 -0
- package/dist/interfaces/ai.d.ts +27 -0
- package/dist/interfaces/ai.js +0 -0
- package/dist/interfaces/avatar.d.ts +6 -0
- package/dist/interfaces/avatar.js +0 -0
- package/dist/interfaces/module.d.ts +45 -0
- package/dist/interfaces/module.js +0 -0
- package/dist/interfaces/references.d.ts +27 -0
- package/dist/interfaces/references.js +0 -0
- package/dist/interfaces/transport.d.ts +11 -0
- package/dist/interfaces/transport.js +0 -0
- package/dist/lib/annotation-template.d.ts +9 -0
- package/dist/lib/annotation-template.js +14 -0
- package/dist/lib/comment-utils.d.ts +7 -0
- package/dist/lib/comment-utils.js +27 -0
- package/dist/lib/datetime.d.ts +25 -0
- package/dist/lib/datetime.js +73 -0
- package/dist/lib/desktop-detect.d.ts +13 -0
- package/dist/lib/desktop-detect.js +23 -0
- package/dist/lib/outline-utils.d.ts +6 -0
- package/dist/lib/outline-utils.js +46 -0
- package/dist/lib/pacer.d.ts +45 -0
- package/dist/lib/pacer.js +70 -0
- package/dist/lib/prd/stack-layout.d.ts +18 -0
- package/dist/lib/prd/stack-layout.js +24 -0
- package/dist/lib/prd-tree-utils.d.ts +21 -0
- package/dist/lib/prd-tree-utils.js +62 -0
- package/dist/lib/prototype-registry.d.ts +2 -0
- package/dist/lib/prototype-registry.js +2 -0
- package/dist/lib/query-client-seam.d.ts +6 -0
- package/dist/lib/query-client-seam.js +11 -0
- package/dist/lib/share-events.d.ts +7 -0
- package/dist/lib/share-events.js +47 -0
- package/dist/lib/sidebar-utils.d.ts +4 -0
- package/dist/lib/sidebar-utils.js +7 -0
- package/dist/lib/stores/prd-canvas-store.d.ts +16 -0
- package/dist/lib/stores/prd-canvas-store.js +42 -0
- package/dist/lib/tag-colors.d.ts +12 -0
- package/dist/lib/tag-colors.js +74 -0
- package/dist/lib/time-format.d.ts +4 -0
- package/dist/lib/time-format.js +20 -0
- package/dist/modules/ai-providers/constants.d.ts +2 -0
- package/dist/modules/ai-providers/constants.js +125 -0
- package/dist/modules/ai-providers/index.d.ts +4 -0
- package/dist/modules/ai-providers/index.js +4 -0
- package/dist/modules/ai-providers/seed-data.d.ts +2 -0
- package/dist/modules/ai-providers/seed-data.js +408 -0
- package/dist/modules/ai-providers/types.d.ts +2 -0
- package/dist/modules/ai-providers/types.js +10 -0
- package/dist/modules/billing/feature-registry.d.ts +72 -0
- package/dist/modules/billing/feature-registry.js +67 -0
- package/dist/modules/billing/plan-features-schema.d.ts +6 -0
- package/dist/modules/billing/plan-features-schema.js +6 -0
- package/dist/modules/billing/resource-registry.d.ts +59 -0
- package/dist/modules/billing/resource-registry.js +47 -0
- package/dist/modules/definitions/plugin.module.d.ts +5 -0
- package/dist/modules/definitions/plugin.module.js +24 -0
- package/dist/modules/definitions/prd.module.d.ts +5 -0
- package/dist/modules/definitions/prd.module.js +26 -0
- package/dist/modules/definitions/prototype.module.d.ts +5 -0
- package/dist/modules/definitions/prototype.module.js +20 -0
- package/dist/modules/dependencies/index.d.ts +92 -0
- package/dist/modules/dependencies/index.js +334 -0
- package/dist/modules/register-defaults.d.ts +4 -0
- package/dist/modules/register-defaults.js +12 -0
- package/dist/modules/registry.d.ts +17 -0
- package/dist/modules/registry.js +27 -0
- package/dist/seed-data-BK0cl93n.d.ts +6 -0
- package/dist/server-seam.d.ts +19 -0
- package/dist/server-seam.js +11 -0
- package/dist/types/outline.d.ts +10 -0
- package/dist/types/outline.js +0 -0
- package/dist/types/prd.d.ts +48 -0
- package/dist/types/prd.js +37 -0
- package/dist/types/prototype.d.ts +2 -0
- package/dist/types/prototype.js +2 -0
- package/dist/types-dopOMFRC.d.ts +61 -0
- package/dist/virtual.d.ts +1 -0
- package/package.json +133 -37
- package/src/constants/perf.ts +0 -21
- package/src/i18n/client.ts +0 -32
- package/src/i18n/config.ts +0 -104
- package/src/i18n/detect.ts +0 -43
- package/src/i18n/locales/en/agents.json +0 -134
- package/src/i18n/locales/en/apps.json +0 -6
- package/src/i18n/locales/en/auth.json +0 -88
- package/src/i18n/locales/en/billing.json +0 -78
- package/src/i18n/locales/en/chat.json +0 -194
- package/src/i18n/locales/en/common.json +0 -203
- package/src/i18n/locales/en/dashboard.json +0 -38
- package/src/i18n/locales/en/design.json +0 -70
- package/src/i18n/locales/en/desktop.json +0 -35
- package/src/i18n/locales/en/display.json +0 -37
- package/src/i18n/locales/en/editor.json +0 -304
- package/src/i18n/locales/en/enterprise.json +0 -1
- package/src/i18n/locales/en/invite.json +0 -11
- package/src/i18n/locales/en/knowledge.json +0 -451
- package/src/i18n/locales/en/mindmap.json +0 -28
- package/src/i18n/locales/en/prd.json +0 -43
- package/src/i18n/locales/en/projects.json +0 -133
- package/src/i18n/locales/en/settings.json +0 -375
- package/src/i18n/locales/en/share.json +0 -20
- package/src/i18n/locales/en/story.json +0 -785
- package/src/i18n/locales/en/tags.json +0 -42
- package/src/i18n/locales/en/workspace.json +0 -90
- package/src/i18n/locales/zh/agents.json +0 -134
- package/src/i18n/locales/zh/apps.json +0 -6
- package/src/i18n/locales/zh/auth.json +0 -88
- package/src/i18n/locales/zh/billing.json +0 -78
- package/src/i18n/locales/zh/chat.json +0 -194
- package/src/i18n/locales/zh/common.json +0 -203
- package/src/i18n/locales/zh/dashboard.json +0 -38
- package/src/i18n/locales/zh/design.json +0 -70
- package/src/i18n/locales/zh/desktop.json +0 -35
- package/src/i18n/locales/zh/display.json +0 -37
- package/src/i18n/locales/zh/editor.json +0 -304
- package/src/i18n/locales/zh/enterprise.json +0 -1
- package/src/i18n/locales/zh/invite.json +0 -11
- package/src/i18n/locales/zh/knowledge.json +0 -451
- package/src/i18n/locales/zh/mindmap.json +0 -28
- package/src/i18n/locales/zh/prd.json +0 -43
- package/src/i18n/locales/zh/projects.json +0 -133
- package/src/i18n/locales/zh/settings.json +0 -375
- package/src/i18n/locales/zh/share.json +0 -20
- package/src/i18n/locales/zh/story.json +0 -785
- package/src/i18n/locales/zh/tags.json +0 -42
- package/src/i18n/locales/zh/workspace.json +0 -90
- package/src/i18n/server.ts +0 -18
- package/src/i18n/types.ts +0 -5
- package/src/index.ts +0 -90
- package/src/infrastructure/ai/adapter.ts +0 -59
- package/src/infrastructure/ai/cli-detector.ts +0 -64
- package/src/infrastructure/ip-location.ts +0 -76
- package/src/infrastructure/references/engine.ts +0 -104
- package/src/infrastructure/ua-parser.ts +0 -107
- package/src/interfaces/ai.ts +0 -38
- package/src/interfaces/avatar.ts +0 -22
- package/src/interfaces/module.ts +0 -60
- package/src/interfaces/references.ts +0 -44
- package/src/interfaces/transport.ts +0 -26
- package/src/lib/annotation-template.ts +0 -33
- package/src/lib/comment-utils.ts +0 -47
- package/src/lib/datetime.ts +0 -115
- package/src/lib/desktop-detect.ts +0 -27
- package/src/lib/outline-utils.ts +0 -57
- package/src/lib/pacer.ts +0 -96
- package/src/lib/prd/stack-layout.ts +0 -53
- package/src/lib/prd-tree-utils.ts +0 -94
- package/src/lib/prototype-registry.ts +0 -24
- package/src/lib/query-client-seam.ts +0 -14
- package/src/lib/share-events.ts +0 -78
- package/src/lib/sidebar-utils.ts +0 -21
- package/src/lib/stores/prd-canvas-store.ts +0 -69
- package/src/lib/tag-colors.ts +0 -35
- package/src/lib/time-format.ts +0 -50
- package/src/modules/ai-providers/constants.ts +0 -79
- package/src/modules/ai-providers/index.ts +0 -21
- package/src/modules/ai-providers/seed-data.ts +0 -222
- package/src/modules/ai-providers/types.ts +0 -95
- package/src/modules/billing/feature-registry.ts +0 -63
- package/src/modules/billing/plan-features-schema.ts +0 -11
- package/src/modules/billing/resource-registry.ts +0 -80
- package/src/modules/definitions/plugin.module.ts +0 -38
- package/src/modules/definitions/prd.module.ts +0 -45
- package/src/modules/definitions/prototype.module.ts +0 -38
- package/src/modules/dependencies/index.ts +0 -186
- package/src/modules/register-defaults.ts +0 -27
- package/src/modules/registry.ts +0 -53
- package/src/server-seam.ts +0 -51
- package/src/types/outline.ts +0 -26
- package/src/types/prd.ts +0 -90
- package/src/types/prototype.ts +0 -12
- package/src/virtual.d.ts +0 -4
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DOMParser } from "@tiptap/pm/model";
|
|
2
|
+
//#region src/lib/annotation-template.ts
|
|
3
|
+
/**
|
|
4
|
+
* 将 HTML 字符串转为 ProseMirror content 节点数组(用于 insertContent 的 content 字段)。
|
|
5
|
+
* 客户端调用(onAnnotate 事件触发),document 可用。
|
|
6
|
+
*/
|
|
7
|
+
function htmlToPmContent(editor, html) {
|
|
8
|
+
if (typeof document === "undefined" || !html) return [{ type: "paragraph" }];
|
|
9
|
+
const wrapper = document.createElement("div");
|
|
10
|
+
wrapper.innerHTML = html;
|
|
11
|
+
return DOMParser.fromSchema(editor.schema).parse(wrapper).toJSON().content ?? [{ type: "paragraph" }];
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { htmlToPmContent };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/lib/comment-utils.d.ts
|
|
2
|
+
declare function maskEmail(email?: string | null): string;
|
|
3
|
+
declare function formatTime(date: Date | string): string;
|
|
4
|
+
declare function formatEditedTime(date: Date | string): string;
|
|
5
|
+
declare function getUserInitial(username: string): string;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { formatEditedTime, formatTime, getUserInitial, maskEmail };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/lib/comment-utils.ts
|
|
2
|
+
function maskEmail(email) {
|
|
3
|
+
if (!email) return "未知用户";
|
|
4
|
+
const [local, domain] = email.split("@");
|
|
5
|
+
if (!domain) return email;
|
|
6
|
+
return `${local.length > 3 ? local.slice(0, 3) : local}***@${domain}`;
|
|
7
|
+
}
|
|
8
|
+
function formatTime(date) {
|
|
9
|
+
const d = new Date(date);
|
|
10
|
+
const diff = (/* @__PURE__ */ new Date()).getTime() - d.getTime();
|
|
11
|
+
const minutes = Math.floor(diff / 6e4);
|
|
12
|
+
if (minutes < 1) return "刚刚";
|
|
13
|
+
if (minutes < 60) return `${minutes}分钟前`;
|
|
14
|
+
const hours = Math.floor(minutes / 60);
|
|
15
|
+
if (hours < 24) return `${hours}小时前`;
|
|
16
|
+
const days = Math.floor(hours / 24);
|
|
17
|
+
if (days < 7) return `${days}天前`;
|
|
18
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")} ${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
19
|
+
}
|
|
20
|
+
function formatEditedTime(date) {
|
|
21
|
+
return `修改于${formatTime(date)}`;
|
|
22
|
+
}
|
|
23
|
+
function getUserInitial(username) {
|
|
24
|
+
return (username || "?").charAt(0).toUpperCase();
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { formatEditedTime, formatTime, getUserInitial, maskEmail };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/lib/datetime.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* 解析生效时区:项目时区 > 用户时区 > 浏览器时区 > Asia/Shanghai
|
|
4
|
+
*/
|
|
5
|
+
declare function resolveTimezone(projectTz?: string | null, userTz?: string | null): string;
|
|
6
|
+
/**
|
|
7
|
+
* 时区感知的时间格式化。
|
|
8
|
+
* @param date UTC Date 对象(来自 DB)
|
|
9
|
+
* @param timezone IANA 时区,如 "Asia/Shanghai"
|
|
10
|
+
* @param fmt date-fns 格式,默认 "yyyy-MM-dd HH:mm"
|
|
11
|
+
*/
|
|
12
|
+
declare function formatDate(date: Date, timezone: string, fmt?: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* 相对时间(今天/昨天/具体日期),基于时区计算「今天的 0 点」。
|
|
15
|
+
* @param now 当前时间(测试注入;生产用默认 new Date())
|
|
16
|
+
*/
|
|
17
|
+
declare function formatRelative(date: Date, timezone: string, now?: Date): string;
|
|
18
|
+
/**
|
|
19
|
+
* 对话历史日期分组标签(今天/昨天/本周/具体日期)。
|
|
20
|
+
* @param date UTC Date 对象
|
|
21
|
+
* @param timezone IANA 时区
|
|
22
|
+
*/
|
|
23
|
+
declare function formatChatDate(date: Date, timezone: string): string;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { formatChatDate, formatDate, formatRelative, resolveTimezone };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { formatInTimeZone, fromZonedTime, toZonedTime } from "date-fns-tz";
|
|
2
|
+
import { startOfDay, subDays } from "date-fns";
|
|
3
|
+
//#region src/lib/datetime.ts
|
|
4
|
+
const FALLBACK_TIMEZONE = "Asia/Shanghai";
|
|
5
|
+
/**
|
|
6
|
+
* 解析生效时区:项目时区 > 用户时区 > 浏览器时区 > Asia/Shanghai
|
|
7
|
+
*/
|
|
8
|
+
function resolveTimezone(projectTz, userTz) {
|
|
9
|
+
const browserTz = typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : FALLBACK_TIMEZONE;
|
|
10
|
+
return projectTz || userTz || browserTz || FALLBACK_TIMEZONE;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 时区感知的时间格式化。
|
|
14
|
+
* @param date UTC Date 对象(来自 DB)
|
|
15
|
+
* @param timezone IANA 时区,如 "Asia/Shanghai"
|
|
16
|
+
* @param fmt date-fns 格式,默认 "yyyy-MM-dd HH:mm"
|
|
17
|
+
*/
|
|
18
|
+
function formatDate(date, timezone, fmt = "yyyy-MM-dd HH:mm") {
|
|
19
|
+
try {
|
|
20
|
+
return formatInTimeZone(date, timezone, fmt);
|
|
21
|
+
} catch {
|
|
22
|
+
return formatInTimeZone(date, FALLBACK_TIMEZONE, fmt);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 相对时间(今天/昨天/具体日期),基于时区计算「今天的 0 点」。
|
|
27
|
+
* @param now 当前时间(测试注入;生产用默认 new Date())
|
|
28
|
+
*/
|
|
29
|
+
function formatRelative(date, timezone, now = /* @__PURE__ */ new Date()) {
|
|
30
|
+
try {
|
|
31
|
+
const todayStart = startOfDayInTimeZone(now, timezone);
|
|
32
|
+
const yesterdayStart = new Date(todayStart);
|
|
33
|
+
yesterdayStart.setDate(yesterdayStart.getDate() - 1);
|
|
34
|
+
const dateStart = startOfDayInTimeZone(date, timezone);
|
|
35
|
+
if (dateStart.getTime() === todayStart.getTime()) return "今天";
|
|
36
|
+
if (dateStart.getTime() === yesterdayStart.getTime()) return "昨天";
|
|
37
|
+
return formatInTimeZone(date, timezone, "yyyy-MM-dd");
|
|
38
|
+
} catch {
|
|
39
|
+
return formatInTimeZone(date, FALLBACK_TIMEZONE, "yyyy-MM-dd");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 获取指定时区下某天的 0 点(UTC Date 对象)。
|
|
44
|
+
*/
|
|
45
|
+
function startOfDayInTimeZone(date, timezone) {
|
|
46
|
+
const zoned = toZonedTime(date, timezone);
|
|
47
|
+
const dayStart = startOfDay(zoned);
|
|
48
|
+
return fromZonedTime(dayStart, timezone);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 对话历史日期分组标签(今天/昨天/本周/具体日期)。
|
|
52
|
+
* @param date UTC Date 对象
|
|
53
|
+
* @param timezone IANA 时区
|
|
54
|
+
*/
|
|
55
|
+
function formatChatDate(date, timezone) {
|
|
56
|
+
try {
|
|
57
|
+
const now = /* @__PURE__ */ new Date();
|
|
58
|
+
const todayStart = startOfDayInTimeZone(now, timezone);
|
|
59
|
+
const yesterdayStart = subDays(todayStart, 1);
|
|
60
|
+
const weekStart = subDays(todayStart, 7);
|
|
61
|
+
const dateStart = startOfDayInTimeZone(date, timezone);
|
|
62
|
+
if (dateStart.getTime() === todayStart.getTime()) return "今天";
|
|
63
|
+
if (dateStart.getTime() === yesterdayStart.getTime()) return "昨天";
|
|
64
|
+
if (dateStart.getTime() >= weekStart.getTime()) return "本周";
|
|
65
|
+
const currentYear = Number(formatInTimeZone(now, timezone, "yyyy"));
|
|
66
|
+
if (Number(formatInTimeZone(date, timezone, "yyyy")) === currentYear) return formatInTimeZone(date, timezone, "M月d日");
|
|
67
|
+
return formatInTimeZone(date, timezone, "yyyy年M月d日");
|
|
68
|
+
} catch {
|
|
69
|
+
return formatInTimeZone(date, FALLBACK_TIMEZONE, "yyyy-MM-dd");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { formatChatDate, formatDate, formatRelative, resolveTimezone };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/lib/desktop-detect.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect if code is running inside Tauri desktop environment.
|
|
4
|
+
* Uses `window.__TAURI_INTERNALS__` (Tauri 2) with fallback to `window.__TAURI__` (Tauri 1 compat).
|
|
5
|
+
*/
|
|
6
|
+
declare function isDesktop(): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Dynamically import Tauri core API. Returns null if not in desktop env.
|
|
9
|
+
* Use this to avoid bundling @tauri-apps/api in web production.
|
|
10
|
+
*/
|
|
11
|
+
declare function getTauriInvoke(): Promise<((cmd: string, args?: Record<string, unknown>) => Promise<unknown>) | null>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { getTauriInvoke, isDesktop };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/lib/desktop-detect.ts
|
|
2
|
+
/**
|
|
3
|
+
* Detect if code is running inside Tauri desktop environment.
|
|
4
|
+
* Uses `window.__TAURI_INTERNALS__` (Tauri 2) with fallback to `window.__TAURI__` (Tauri 1 compat).
|
|
5
|
+
*/
|
|
6
|
+
function isDesktop() {
|
|
7
|
+
if (typeof window === "undefined") return false;
|
|
8
|
+
return Boolean(window.__TAURI_INTERNALS__ || window.__TAURI__);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Dynamically import Tauri core API. Returns null if not in desktop env.
|
|
12
|
+
* Use this to avoid bundling @tauri-apps/api in web production.
|
|
13
|
+
*/
|
|
14
|
+
async function getTauriInvoke() {
|
|
15
|
+
if (!isDesktop()) return null;
|
|
16
|
+
try {
|
|
17
|
+
return (await import("@tauri-apps/api/core")).invoke;
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { getTauriInvoke, isDesktop };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/lib/outline-utils.ts
|
|
2
|
+
function buildOutline(elements) {
|
|
3
|
+
const containers = elements.filter((e) => isContainer(e));
|
|
4
|
+
const atoms = elements.filter((e) => !isContainer(e));
|
|
5
|
+
const roots = [];
|
|
6
|
+
const usedAtoms = /* @__PURE__ */ new Set();
|
|
7
|
+
for (const c of containers) {
|
|
8
|
+
const rect = {
|
|
9
|
+
x: c.x,
|
|
10
|
+
y: c.y,
|
|
11
|
+
w: c.width,
|
|
12
|
+
h: c.height
|
|
13
|
+
};
|
|
14
|
+
const children = atoms.filter((a) => !usedAtoms.has(a.id) && isInside(a, rect)).map((a) => {
|
|
15
|
+
usedAtoms.add(a.id);
|
|
16
|
+
return toAtomNode(a);
|
|
17
|
+
});
|
|
18
|
+
roots.push({
|
|
19
|
+
id: `container-${c.id}`,
|
|
20
|
+
label: (c.overrides ?? c.props)?.name || c.type,
|
|
21
|
+
type: "container",
|
|
22
|
+
elementId: c.id,
|
|
23
|
+
children
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
for (const a of atoms) if (!usedAtoms.has(a.id)) roots.push(toAtomNode(a));
|
|
27
|
+
return roots;
|
|
28
|
+
}
|
|
29
|
+
function isContainer(el) {
|
|
30
|
+
return el.width >= 160 && el.height >= 100 || (el.overrides ?? el.props)?.isContainer === true;
|
|
31
|
+
}
|
|
32
|
+
function isInside(atom, rect) {
|
|
33
|
+
return atom.x >= rect.x && atom.y >= rect.y && atom.x + atom.width <= rect.x + rect.w && atom.y + atom.height <= rect.y + rect.h;
|
|
34
|
+
}
|
|
35
|
+
function toAtomNode(el) {
|
|
36
|
+
const p = el.overrides ?? el.props;
|
|
37
|
+
return {
|
|
38
|
+
id: `atom-${el.id}`,
|
|
39
|
+
label: p?.text || p?.label || el.type,
|
|
40
|
+
type: "atom",
|
|
41
|
+
elementId: el.id,
|
|
42
|
+
children: []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { buildOutline };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RateLimiter } from "@tanstack/pacer";
|
|
2
|
+
import { useDebouncedCallback, useThrottledCallback } from "@tanstack/react-pacer";
|
|
3
|
+
//#region src/lib/pacer.d.ts
|
|
4
|
+
declare const EDITOR_AUTO_SAVE_MS = 2000;
|
|
5
|
+
declare const EDITOR_CHANGE_DEBOUNCE_MS = 500;
|
|
6
|
+
declare const SEARCH_DEBOUNCE_MS = 300;
|
|
7
|
+
declare const SCROLL_THROTTLE_MS = 150;
|
|
8
|
+
declare const AI_RATE_LIMIT: {
|
|
9
|
+
readonly chat: {
|
|
10
|
+
readonly max: 10;
|
|
11
|
+
readonly windowMs: 60000;
|
|
12
|
+
};
|
|
13
|
+
readonly execute: {
|
|
14
|
+
readonly max: 5;
|
|
15
|
+
readonly windowMs: 60000;
|
|
16
|
+
};
|
|
17
|
+
readonly generatePrototype: {
|
|
18
|
+
readonly max: 5;
|
|
19
|
+
readonly windowMs: 60000;
|
|
20
|
+
};
|
|
21
|
+
readonly testProvider: {
|
|
22
|
+
readonly max: 10;
|
|
23
|
+
readonly windowMs: 60000;
|
|
24
|
+
};
|
|
25
|
+
readonly detect: {
|
|
26
|
+
readonly max: 10;
|
|
27
|
+
readonly windowMs: 60000;
|
|
28
|
+
};
|
|
29
|
+
readonly webSearch: {
|
|
30
|
+
readonly max: 20;
|
|
31
|
+
readonly windowMs: 60000;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
interface RateLimitConfig {
|
|
35
|
+
max: number;
|
|
36
|
+
windowMs: number;
|
|
37
|
+
}
|
|
38
|
+
interface ServerFnRateLimitResult {
|
|
39
|
+
allowed: boolean;
|
|
40
|
+
retryAfterMs: number;
|
|
41
|
+
}
|
|
42
|
+
declare function getRateLimiter(key: string, config: RateLimitConfig): RateLimiter<(...args: unknown[]) => boolean>;
|
|
43
|
+
declare function checkRateLimit(key: string, config: RateLimitConfig): ServerFnRateLimitResult;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { AI_RATE_LIMIT, EDITOR_AUTO_SAVE_MS, EDITOR_CHANGE_DEBOUNCE_MS, RateLimitConfig, SCROLL_THROTTLE_MS, SEARCH_DEBOUNCE_MS, checkRateLimit, getRateLimiter, useDebouncedCallback, useThrottledCallback };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { RateLimiter } from "@tanstack/pacer";
|
|
2
|
+
import { useDebouncedCallback, useThrottledCallback } from "@tanstack/react-pacer";
|
|
3
|
+
//#region src/lib/pacer.ts
|
|
4
|
+
const EDITOR_AUTO_SAVE_MS = 2e3;
|
|
5
|
+
const EDITOR_CHANGE_DEBOUNCE_MS = 500;
|
|
6
|
+
const SEARCH_DEBOUNCE_MS = 300;
|
|
7
|
+
const SCROLL_THROTTLE_MS = 150;
|
|
8
|
+
const AI_RATE_LIMIT = {
|
|
9
|
+
chat: {
|
|
10
|
+
max: 10,
|
|
11
|
+
windowMs: 6e4
|
|
12
|
+
},
|
|
13
|
+
execute: {
|
|
14
|
+
max: 5,
|
|
15
|
+
windowMs: 6e4
|
|
16
|
+
},
|
|
17
|
+
generatePrototype: {
|
|
18
|
+
max: 5,
|
|
19
|
+
windowMs: 6e4
|
|
20
|
+
},
|
|
21
|
+
testProvider: {
|
|
22
|
+
max: 10,
|
|
23
|
+
windowMs: 6e4
|
|
24
|
+
},
|
|
25
|
+
detect: {
|
|
26
|
+
max: 10,
|
|
27
|
+
windowMs: 6e4
|
|
28
|
+
},
|
|
29
|
+
webSearch: {
|
|
30
|
+
max: 20,
|
|
31
|
+
windowMs: 6e4
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const limiterCache = /* @__PURE__ */ new Map();
|
|
35
|
+
const CACHE_TTL_MS = 3e5;
|
|
36
|
+
function purgeStaleEntries() {
|
|
37
|
+
const now = Date.now();
|
|
38
|
+
for (const [key, entry] of limiterCache) if (now - entry.lastAccess > CACHE_TTL_MS) {
|
|
39
|
+
entry.limiter.reset();
|
|
40
|
+
limiterCache.delete(key);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
setInterval(purgeStaleEntries, 6e4);
|
|
44
|
+
function getRateLimiter(key, config) {
|
|
45
|
+
const existing = limiterCache.get(key);
|
|
46
|
+
if (existing) {
|
|
47
|
+
existing.lastAccess = Date.now();
|
|
48
|
+
return existing.limiter;
|
|
49
|
+
}
|
|
50
|
+
const limiter = new RateLimiter(() => true, {
|
|
51
|
+
limit: config.max,
|
|
52
|
+
window: config.windowMs,
|
|
53
|
+
windowType: "sliding"
|
|
54
|
+
});
|
|
55
|
+
limiterCache.set(key, {
|
|
56
|
+
limiter,
|
|
57
|
+
lastAccess: Date.now()
|
|
58
|
+
});
|
|
59
|
+
return limiter;
|
|
60
|
+
}
|
|
61
|
+
function checkRateLimit(key, config) {
|
|
62
|
+
const limiter = getRateLimiter(key, config);
|
|
63
|
+
const allowed = limiter.maybeExecute();
|
|
64
|
+
return {
|
|
65
|
+
allowed,
|
|
66
|
+
retryAfterMs: allowed ? 0 : limiter.getMsUntilNextWindow()
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { AI_RATE_LIMIT, EDITOR_AUTO_SAVE_MS, EDITOR_CHANGE_DEBOUNCE_MS, SCROLL_THROTTLE_MS, SEARCH_DEBOUNCE_MS, checkRateLimit, getRateLimiter, useDebouncedCallback, useThrottledCallback };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CanvasElement } from "../../types/prototype.js";
|
|
2
|
+
//#region src/lib/prd/stack-layout.d.ts
|
|
3
|
+
interface StackLayoutOptions {
|
|
4
|
+
startX: number;
|
|
5
|
+
startY: number;
|
|
6
|
+
gap: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 计算新组件在画板上的纵向堆叠位置。
|
|
10
|
+
* 起始 y = 现有元素的最大 (y + height) + gap,若无现有元素则用 startY。
|
|
11
|
+
* items 的 height 用于决定每个新组件的纵向偏移,确保大小不一的组件不会重叠。
|
|
12
|
+
*/
|
|
13
|
+
declare function computeStackPositions(existing: Pick<CanvasElement, "x" | "y" | "height">[], items: Array<Pick<CanvasElement, "height">>, options: StackLayoutOptions): Array<{
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
}>;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { StackLayoutOptions, computeStackPositions };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/lib/prd/stack-layout.ts
|
|
2
|
+
/**
|
|
3
|
+
* 计算新组件在画板上的纵向堆叠位置。
|
|
4
|
+
* 起始 y = 现有元素的最大 (y + height) + gap,若无现有元素则用 startY。
|
|
5
|
+
* items 的 height 用于决定每个新组件的纵向偏移,确保大小不一的组件不会重叠。
|
|
6
|
+
*/
|
|
7
|
+
function computeStackPositions(existing, items, options) {
|
|
8
|
+
if (items.length === 0) return [];
|
|
9
|
+
const { startX, startY, gap } = options;
|
|
10
|
+
const maxBottom = existing.reduce((max, el) => Math.max(max, el.y + el.height), 0);
|
|
11
|
+
const baseY = existing.length > 0 ? maxBottom + gap : startY;
|
|
12
|
+
const positions = [];
|
|
13
|
+
let cursorY = baseY;
|
|
14
|
+
for (const item of items) {
|
|
15
|
+
positions.push({
|
|
16
|
+
x: startX,
|
|
17
|
+
y: cursorY
|
|
18
|
+
});
|
|
19
|
+
cursorY += item.height + gap;
|
|
20
|
+
}
|
|
21
|
+
return positions;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { computeStackPositions };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/lib/prd-tree-utils.d.ts
|
|
2
|
+
interface PRDPage {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
type: "folder" | "page";
|
|
6
|
+
parentId: string | null;
|
|
7
|
+
order: number;
|
|
8
|
+
version: number;
|
|
9
|
+
}
|
|
10
|
+
interface TreeNode extends PRDPage {
|
|
11
|
+
children: TreeNode[];
|
|
12
|
+
}
|
|
13
|
+
declare function buildTree(pages: PRDPage[]): TreeNode[];
|
|
14
|
+
declare function flattenTree(nodes: TreeNode[]): PRDPage[];
|
|
15
|
+
declare function findNode(nodes: TreeNode[], id: string): TreeNode | null;
|
|
16
|
+
declare function canMove(node: TreeNode, targetParentId: string | null, allPages: PRDPage[]): boolean;
|
|
17
|
+
declare function sortSnapshotsByTree<T extends {
|
|
18
|
+
pageId: string;
|
|
19
|
+
}>(snapshots: T[], prdPages: PRDPage[]): T[];
|
|
20
|
+
//#endregion
|
|
21
|
+
export { PRDPage, TreeNode, buildTree, canMove, findNode, flattenTree, sortSnapshotsByTree };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/lib/prd-tree-utils.ts
|
|
2
|
+
function buildTree(pages) {
|
|
3
|
+
const map = /* @__PURE__ */ new Map();
|
|
4
|
+
const roots = [];
|
|
5
|
+
for (const p of pages) map.set(p.id, {
|
|
6
|
+
...p,
|
|
7
|
+
children: []
|
|
8
|
+
});
|
|
9
|
+
for (const p of pages) {
|
|
10
|
+
const node = map.get(p.id);
|
|
11
|
+
if (p.parentId && map.has(p.parentId)) map.get(p.parentId).children.push(node);
|
|
12
|
+
else roots.push(node);
|
|
13
|
+
}
|
|
14
|
+
const sortNodes = (nodes) => nodes.sort((a, b) => a.order - b.order);
|
|
15
|
+
sortNodes(roots);
|
|
16
|
+
for (const node of map.values()) sortNodes(node.children);
|
|
17
|
+
return roots;
|
|
18
|
+
}
|
|
19
|
+
function flattenTree(nodes) {
|
|
20
|
+
const result = [];
|
|
21
|
+
for (const n of nodes) {
|
|
22
|
+
result.push({
|
|
23
|
+
id: n.id,
|
|
24
|
+
name: n.name,
|
|
25
|
+
type: n.type,
|
|
26
|
+
parentId: n.parentId,
|
|
27
|
+
order: n.order,
|
|
28
|
+
version: n.version
|
|
29
|
+
});
|
|
30
|
+
result.push(...flattenTree(n.children));
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
function findNode(nodes, id) {
|
|
35
|
+
for (const n of nodes) {
|
|
36
|
+
if (n.id === id) return n;
|
|
37
|
+
const found = findNode(n.children, id);
|
|
38
|
+
if (found) return found;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function canMove(node, targetParentId, allPages) {
|
|
43
|
+
if (node.type === "folder" && targetParentId !== null) return false;
|
|
44
|
+
if (targetParentId === node.id) return false;
|
|
45
|
+
if (allPages.find((p) => p.id === targetParentId)?.type === "folder") return false;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
function sortSnapshotsByTree(snapshots, prdPages) {
|
|
49
|
+
if (snapshots.length <= 1) return snapshots;
|
|
50
|
+
const ordered = flattenTree(buildTree(prdPages));
|
|
51
|
+
const indexMap = new Map(ordered.map((p, i) => [p.id, i]));
|
|
52
|
+
return [...snapshots].sort((a, b) => {
|
|
53
|
+
const ia = indexMap.get(a.pageId);
|
|
54
|
+
const ib = indexMap.get(b.pageId);
|
|
55
|
+
if (ia === void 0 && ib === void 0) return 0;
|
|
56
|
+
if (ia === void 0) return 1;
|
|
57
|
+
if (ib === void 0) return -1;
|
|
58
|
+
return ia - ib;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//#endregion
|
|
62
|
+
export { buildTree, canMove, findNode, flattenTree, sortSnapshotsByTree };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/lib/query-client-seam.ts
|
|
2
|
+
let instance = null;
|
|
3
|
+
function setQueryClient(client) {
|
|
4
|
+
instance = client;
|
|
5
|
+
}
|
|
6
|
+
function getQueryClient() {
|
|
7
|
+
if (!instance) throw new Error("QueryClient 未注入:宿主需在 router 初始化时调用 setQueryClient()");
|
|
8
|
+
return instance;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { getQueryClient, setQueryClient };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/lib/share-events.d.ts
|
|
2
|
+
declare function addClient(prdId: string, controller: ReadableStreamDefaultController): void;
|
|
3
|
+
declare function removeClient(prdId: string, controller: ReadableStreamDefaultController): void;
|
|
4
|
+
declare function notifyClients(prdId: string, event: string, data: unknown): void;
|
|
5
|
+
declare function getClientCount(): number;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { addClient, getClientCount, notifyClients, removeClient };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/lib/share-events.ts
|
|
2
|
+
const clients = /* @__PURE__ */ new Map();
|
|
3
|
+
const MAX_CLIENTS_PER_PRD = 20;
|
|
4
|
+
setInterval(() => {
|
|
5
|
+
for (const [prdId, set] of clients) {
|
|
6
|
+
for (const controller of set) try {
|
|
7
|
+
controller.enqueue(new TextEncoder().encode(":cleanup-ping\n\n"));
|
|
8
|
+
} catch {
|
|
9
|
+
set.delete(controller);
|
|
10
|
+
}
|
|
11
|
+
if (set.size === 0) clients.delete(prdId);
|
|
12
|
+
}
|
|
13
|
+
}, 6e4);
|
|
14
|
+
const controllerMeta = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
function addClient(prdId, controller) {
|
|
16
|
+
const set = clients.get(prdId);
|
|
17
|
+
if (set && set.size >= MAX_CLIENTS_PER_PRD) throw new Error("连接数已达上限");
|
|
18
|
+
if (!set) clients.set(prdId, /* @__PURE__ */ new Set());
|
|
19
|
+
clients.get(prdId).add(controller);
|
|
20
|
+
controllerMeta.set(controller, Date.now());
|
|
21
|
+
}
|
|
22
|
+
function removeClient(prdId, controller) {
|
|
23
|
+
const set = clients.get(prdId);
|
|
24
|
+
if (!set) return;
|
|
25
|
+
set.delete(controller);
|
|
26
|
+
controllerMeta.delete(controller);
|
|
27
|
+
if (set.size === 0) clients.delete(prdId);
|
|
28
|
+
}
|
|
29
|
+
function notifyClients(prdId, event, data) {
|
|
30
|
+
const set = clients.get(prdId);
|
|
31
|
+
if (!set) return;
|
|
32
|
+
const message = new TextEncoder().encode(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
33
|
+
for (const controller of set) try {
|
|
34
|
+
controller.enqueue(message);
|
|
35
|
+
controllerMeta.set(controller, Date.now());
|
|
36
|
+
} catch {
|
|
37
|
+
set.delete(controller);
|
|
38
|
+
controllerMeta.delete(controller);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function getClientCount() {
|
|
42
|
+
let total = 0;
|
|
43
|
+
for (const set of clients.values()) total += set.size;
|
|
44
|
+
return total;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { addClient, getClientCount, notifyClients, removeClient };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CanvasElement } from "../../types/prototype.js";
|
|
2
|
+
import { SetStateAction } from "react";
|
|
3
|
+
//#region src/lib/stores/prd-canvas-store.d.ts
|
|
4
|
+
interface PrdCanvasState {
|
|
5
|
+
elements: CanvasElement[];
|
|
6
|
+
selectedElementId: string | null;
|
|
7
|
+
viewMode: 'edit' | 'preview' | 'prototype';
|
|
8
|
+
}
|
|
9
|
+
declare const prdCanvasStore: import("@tanstack/store").Store<PrdCanvasState, never>;
|
|
10
|
+
declare function setCanvasElements(value: SetStateAction<CanvasElement[]>): void;
|
|
11
|
+
declare function setCanvasSelectedElementId(id: string | null): void;
|
|
12
|
+
declare function setCanvasViewMode(mode: PrdCanvasState['viewMode']): void;
|
|
13
|
+
declare function updateCanvasElement(id: string, patch: Partial<CanvasElement>): void;
|
|
14
|
+
declare function deleteCanvasElement(id: string): void;
|
|
15
|
+
//#endregion
|
|
16
|
+
export { type CanvasElement, type PrdCanvasState, deleteCanvasElement, prdCanvasStore, setCanvasElements, setCanvasSelectedElementId, setCanvasViewMode, updateCanvasElement };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createStore } from "@tanstack/store";
|
|
2
|
+
//#region src/lib/stores/prd-canvas-store.ts
|
|
3
|
+
const prdCanvasStore = createStore({
|
|
4
|
+
elements: [],
|
|
5
|
+
selectedElementId: null,
|
|
6
|
+
viewMode: "edit"
|
|
7
|
+
});
|
|
8
|
+
function setCanvasElements(value) {
|
|
9
|
+
prdCanvasStore.setState((s) => ({
|
|
10
|
+
...s,
|
|
11
|
+
elements: typeof value === "function" ? value(s.elements) : value
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
function setCanvasSelectedElementId(id) {
|
|
15
|
+
prdCanvasStore.setState((s) => ({
|
|
16
|
+
...s,
|
|
17
|
+
selectedElementId: id
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
function setCanvasViewMode(mode) {
|
|
21
|
+
prdCanvasStore.setState((s) => ({
|
|
22
|
+
...s,
|
|
23
|
+
viewMode: mode
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
function updateCanvasElement(id, patch) {
|
|
27
|
+
prdCanvasStore.setState((s) => ({
|
|
28
|
+
...s,
|
|
29
|
+
elements: s.elements.map((e) => e.id === id ? {
|
|
30
|
+
...e,
|
|
31
|
+
...patch
|
|
32
|
+
} : e)
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function deleteCanvasElement(id) {
|
|
36
|
+
prdCanvasStore.setState((s) => ({
|
|
37
|
+
...s,
|
|
38
|
+
elements: s.elements.filter((e) => e.id !== id)
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { deleteCanvasElement, prdCanvasStore, setCanvasElements, setCanvasSelectedElementId, setCanvasViewMode, updateCanvasElement };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/lib/tag-colors.d.ts
|
|
2
|
+
declare const TAG_COLORS: {
|
|
3
|
+
bg: string;
|
|
4
|
+
text: string;
|
|
5
|
+
border: string;
|
|
6
|
+
selected: string;
|
|
7
|
+
check: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}[];
|
|
10
|
+
declare function hashColorIndex(name: string): number;
|
|
11
|
+
//#endregion
|
|
12
|
+
export { TAG_COLORS, hashColorIndex };
|