@allbluecn/web-app 0.10.2 → 0.11.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/package.json +26 -11
- package/public/favicon.ico +0 -0
- package/public/logo/allblue-logo-app-icon.svg +7 -0
- package/public/logo/allblue-logo-dark-square.svg +4 -0
- package/public/logo/allblue-logo-dark.svg +4 -0
- package/public/logo/allblue-logo-light-square.svg +4 -0
- package/public/logo/allblue-logo-light.svg +4 -0
- package/src/__tests__/bench/baselines/format-timestamp.json +44 -0
- package/src/__tests__/bench/core-hot-path.bench.ts +71 -0
- package/src/__tests__/integration/db-crud.integration.test.ts +79 -0
- package/src/__tests__/rejects-tothrow-regression.test.ts +31 -0
- package/src/__tests__/security/ai/prompt-injection.test.ts +105 -0
- package/src/__tests__/security/ssrf-url-whitelist.test.ts +54 -0
- package/src/__tests__/security/xss-sanitize.test.ts +99 -0
- package/src/__tests__/setup.ts +9 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-dark-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/theme-surface.vrt.test.tsx/theme-surface-light-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/__screenshots__/user-avatar.vrt.test.tsx/user-avatar-initial-fallback-baseline-1-chromium-darwin.png +0 -0
- package/src/__tests__/visual/theme-surface.vrt.test.tsx +54 -0
- package/src/__tests__/visual/user-avatar.vrt.test.tsx +32 -0
- package/src/components/error/global-error-boundary.tsx +2 -2
- package/src/components/knowledge/general-groups.ts +15 -0
- package/src/components/knowledge/kb-filter-popover.tsx +143 -0
- package/src/components/knowledge-public/legal-compliance-editor.tsx +4 -2
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +9 -6
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +19 -17
- package/src/components/layout/sidebar-03/nav-secondary.tsx +57 -47
- package/src/components/layout/sidebar-03/nav-workspace.tsx +20 -22
- package/src/components/mdx.tsx +9 -0
- package/src/components/shared-kit/avatars/notion-avatar.tsx +2 -1
- package/src/components/shared-kit/icons/__tests__/lucide-curated-icons.test.ts +2 -2
- package/src/components/shared-kit/icons/__tests__/lucide-icon-picker.test.tsx +3 -2
- package/src/components/shared-kit/icons/lucide-icon-picker/curated-icons.ts +5 -10
- package/src/components/shared-kit/icons/lucide-icon-picker/lucide-icon-by-name.tsx +3 -3
- package/src/components/shared-kit/theme/theme-surface.tsx +52 -0
- package/src/components/story/relations/general-groups.ts +1 -15
- package/src/components/story/relations/knowledge-select-dialog.tsx +13 -87
- package/src/components/story/story-list-view.tsx +7 -1
- package/src/components/tiptap/node/table-node/ui/table-move-row-column-button/use-table-move-row-column.ts +0 -2
- package/src/components/workspace/__tests__/activity/workspace-activity-card.test.tsx +36 -0
- package/src/components/workspace/__tests__/contribution/workspace-contribution-card.test.tsx +17 -0
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +63 -0
- package/src/components/workspace/__tests__/dialogs/delete-workspace-options.test.tsx +89 -0
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +68 -0
- package/src/components/workspace/__tests__/header/workspace-header.test.tsx +40 -0
- package/src/components/workspace/__tests__/kb/workspace-kb-card.test.tsx +154 -0
- package/src/components/workspace/__tests__/members/workspace-member-item.test.tsx +24 -0
- package/src/components/workspace/__tests__/members/workspace-members-manager.test.tsx +51 -0
- package/src/components/workspace/__tests__/projects/workspace-project-list.test.tsx +46 -0
- package/src/components/workspace/{create-workspace-dialog.tsx → dialogs/create-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/{edit-workspace-dialog.tsx → dialogs/edit-workspace-dialog.tsx} +1 -1
- package/src/components/workspace/kb/workspace-kb-card.tsx +594 -0
- package/src/components/workspace/kb/workspace-kb-manage-dialog.tsx +174 -0
- package/src/components/workspace/kb/workspace-quick-doc-dialog.tsx +431 -0
- package/src/components/workspace/{workspace-members-manager.tsx → members/workspace-members-manager.tsx} +78 -40
- package/src/lib/__tests__/msw-sample.test.ts +68 -0
- package/src/lib/__tests__/pgvector.test.ts +2 -2
- package/src/lib/changelog/sdk-versions.ts +21 -21
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +3 -1
- package/src/lib/collections/reference/workspaces-collection.ts +3 -1
- package/src/lib/fetch-json.ts +23 -0
- package/src/lib/fumadocs-i18n.ts +7 -0
- package/src/{components/workspace/workspace-kb-card.tsx → lib/i18n/resolver.ts} +6 -12
- package/src/lib/layout.shared.tsx +40 -0
- package/src/lib/sanitize.ts +14 -1
- package/src/lib/session.ts +9 -1
- package/src/lib/source.ts +15 -0
- package/src/lib/vector-store/__tests__/factory.test.ts +1 -1
- package/src/lib/vector-store/__tests__/sqlite-vec-store.test.ts +2 -2
- package/src/plugins/core/tags/__tests__/tags.test.ts +10 -10
- package/src/routeTree.gen.ts +65 -0
- package/src/routes/$lang/docs/$/route.lazy.tsx +62 -0
- package/src/routes/$lang/docs/$/route.tsx +52 -0
- package/src/routes/-tests/docs-redirect.test.ts +45 -0
- package/src/routes/__root.tsx +13 -7
- package/src/routes/__tests__/-edition-routing.test.tsx +4 -4
- package/src/routes/__tests__/route-tree-structure.test.ts +65 -0
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +5 -2
- package/src/routes/_nav/knowledge/index.lazy.tsx +1 -1
- package/src/routes/_nav/knowledge/laws/index.lazy.tsx +5 -2
- package/src/routes/_nav/route.tsx +21 -0
- package/src/routes/_nav/workspaces/$workspaceId/route.lazy.tsx +13 -11
- package/src/routes/_nav/workspaces/$workspaceId/route.tsx +4 -2
- package/src/routes/_nav/workspaces/$workspaceId/settings/route.lazy.tsx +1 -1
- package/src/routes/_nav/workspaces/index.lazy.tsx +3 -3
- package/src/routes/api/search.ts +13 -0
- package/src/routes/docs.tsx +9 -0
- package/src/server/__tests__/agent-crud.test.ts +4 -4
- package/src/server/__tests__/chat-conversations.test.ts +1 -3
- package/src/server/__tests__/fetch-guard.test.ts +99 -0
- package/src/server/__tests__/permissions.test.ts +1 -3
- package/src/server/__tests__/resources.team-seats.test.ts +2 -0
- package/src/server/__tests__/skill-crud.test.ts +3 -3
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +95 -0
- package/src/server/ai-gateway/__tests__/kb-context-guard.test.ts +136 -0
- package/src/server/ai-gateway/__tests__/knowledge-search-fence.test.ts +240 -0
- package/src/server/ai-gateway/__tests__/llm-fence.test.ts +125 -0
- package/src/server/ai-gateway/__tests__/run-store.test.ts +52 -0
- package/src/server/ai-gateway/ai-output-guard.ts +77 -0
- package/src/server/ai-gateway/index.ts +5 -0
- package/src/server/ai-gateway/kb-context-guard.ts +46 -0
- package/src/server/ai-gateway/llm-fence.ts +82 -0
- package/src/server/ai-gateway/persistence/run-store.ts +19 -0
- package/src/server/ai-gateway/story-prompt.ts +4 -2
- package/src/server/ai-gateway/tools/definitions.ts +12 -6
- package/src/server/ai-gateway/tools/server.ts +97 -54
- package/src/server/builtin-project.ts +21 -0
- package/src/server/email/sender.ts +8 -2
- package/src/server/fetch-guard.ts +170 -0
- package/src/server/middlewares/security-headers.ts +113 -0
- package/src/server/serverFns/__tests__/billing.test.ts +263 -0
- package/src/server/serverFns/__tests__/link-preview.test.ts +239 -0
- package/src/server/serverFns/__tests__/project-kb.test.ts +3 -3
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +1 -1
- package/src/server/serverFns/__tests__/story-comments.test.ts +265 -0
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +266 -0
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +227 -0
- package/src/server/serverFns/__tests__/story-crud.test.ts +526 -0
- package/src/server/serverFns/__tests__/story-links.test.ts +262 -0
- package/src/server/serverFns/__tests__/story-list-query.test.ts +259 -0
- package/src/server/serverFns/__tests__/story-reactions.test.ts +230 -0
- package/src/server/serverFns/__tests__/story-tree.test.ts +317 -0
- package/src/server/serverFns/__tests__/story-types.test.ts +294 -0
- package/src/server/serverFns/__tests__/team-accept.test.ts +6 -6
- package/src/server/serverFns/__tests__/team-invitation.test.ts +3 -3
- package/src/server/serverFns/__tests__/team-join-request.test.ts +8 -8
- package/src/server/serverFns/__tests__/update-seats.test.ts +4 -4
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +160 -0
- package/src/server/serverFns/__tests__/workspace-kb-utils.test.ts +54 -0
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +348 -0
- package/src/server/serverFns/__tests__/workspace.test.ts +28 -8
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +3 -5
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +1 -1
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +2 -2
- package/src/server/serverFns/kb-doc-search.ts +111 -0
- package/src/server/serverFns/knowledge-public/compliance/create.ts +2 -1
- package/src/server/serverFns/knowledge-public/legal/create.ts +2 -1
- package/src/server/serverFns/story/__tests__/bulk.test.ts +1 -1
- package/src/server/serverFns/story/__tests__/label-suggest-fn.test.ts +2 -6
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +2 -4
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +2 -2
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +2 -2
- package/src/server/serverFns/story/kb-general-consts.ts +39 -0
- package/src/server/serverFns/story/kb-general.ts +30 -17
- package/src/server/serverFns/story/story-assoc.ts +13 -103
- package/src/server/serverFns/story/story-crud.ts +2 -5
- package/src/server/serverFns/story/story-groups.ts +4 -13
- package/src/server/serverFns/team.ts +1 -1
- package/src/server/serverFns/workspace-kb-utils.ts +138 -0
- package/src/server/serverFns/workspace-kb.ts +403 -0
- package/src/server/serverFns/workspace.ts +4 -1
- package/src/shared/ai-cli/__tests__/adapter.test.ts +67 -0
- package/src/shared/ai-cli/__tests__/cli-detector.test.ts +99 -0
- package/src/shared/ai-cli/adapter.ts +2 -2
- package/src/shared/ai-cli/cli-detector.ts +18 -9
- package/src/start.ts +2 -1
- package/src/styles/globals.css +116 -0
- package/src/types/jest-dom-vitest.d.ts +8 -7
- package/vite.shared.ts +18 -1
- package/public/file.svg +0 -1
- package/public/globe.svg +0 -1
- package/public/window.svg +0 -1
- package/src/lib/__tests__/cli-detector.test.ts +0 -80
- /package/src/components/workspace/{workspace-activity-card.tsx → activity/workspace-activity-card.tsx} +0 -0
- /package/src/components/workspace/{workspace-contribution-card.tsx → contribution/workspace-contribution-card.tsx} +0 -0
- /package/src/components/workspace/{delete-workspace-options.tsx → dialogs/delete-workspace-options.tsx} +0 -0
- /package/src/components/workspace/{workspace-header.tsx → header/workspace-header.tsx} +0 -0
- /package/src/components/workspace/{workspace-member-item.tsx → members/workspace-member-item.tsx} +0 -0
- /package/src/components/workspace/{workspace-project-list.tsx → projects/workspace-project-list.tsx} +0 -0
|
@@ -84,7 +84,7 @@ export function WorkspaceMembersManager({ workspaceId, workspaceOwnerId, members
|
|
|
84
84
|
const [teamUsers, setTeamUsers] = useState<TeamUser[]>([]);
|
|
85
85
|
const [teamUsersLoading, setTeamUsersLoading] = useState(false);
|
|
86
86
|
const [searchQuery, setSearchQuery] = useState("");
|
|
87
|
-
const [
|
|
87
|
+
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([]);
|
|
88
88
|
const [memberRole, setMemberRole] = useState<"admin" | "member" | "viewer">("member");
|
|
89
89
|
const [addingMember, setAddingMember] = useState(false);
|
|
90
90
|
const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
|
|
@@ -107,29 +107,54 @@ export function WorkspaceMembersManager({ workspaceId, workspaceOwnerId, members
|
|
|
107
107
|
u.email?.toLowerCase().includes(searchQuery.toLowerCase())
|
|
108
108
|
);
|
|
109
109
|
|
|
110
|
+
const toggleUser = (userId: string) => {
|
|
111
|
+
setSelectedUserIds((prev) =>
|
|
112
|
+
prev.includes(userId) ? prev.filter((id) => id !== userId) : [...prev, userId]
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
110
116
|
const openAddMember = () => {
|
|
111
|
-
|
|
117
|
+
setSelectedUserIds([]);
|
|
112
118
|
setSearchQuery("");
|
|
113
119
|
setMemberRole("member");
|
|
114
120
|
setMemberDialogOpen(true);
|
|
115
121
|
};
|
|
116
122
|
|
|
117
123
|
const handleAddMember = async () => {
|
|
118
|
-
if (
|
|
124
|
+
if (selectedUserIds.length === 0) {
|
|
119
125
|
toast.error(m.project_invite_selectMember());
|
|
120
126
|
return;
|
|
121
127
|
}
|
|
122
128
|
setAddingMember(true);
|
|
129
|
+
const failed: string[] = [];
|
|
130
|
+
let lastError: string | null = null;
|
|
123
131
|
try {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
for (const userId of selectedUserIds) {
|
|
133
|
+
try {
|
|
134
|
+
await addWorkspaceMemberFn({
|
|
135
|
+
data: { workspaceId, userId, role: memberRole },
|
|
136
|
+
});
|
|
137
|
+
} catch (err) {
|
|
138
|
+
failed.push(userId);
|
|
139
|
+
lastError = err instanceof Error ? err.message : null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (failed.length === 0) {
|
|
143
|
+
toast.success(m.workspace_home_memberAdded());
|
|
144
|
+
setMemberDialogOpen(false);
|
|
145
|
+
setSelectedUserIds([]);
|
|
146
|
+
onMembersUpdated();
|
|
147
|
+
} else {
|
|
148
|
+
const failedNames = teamUsers
|
|
149
|
+
.filter((u) => failed.includes(u.id))
|
|
150
|
+
.map((u) => u.username)
|
|
151
|
+
.join(", ");
|
|
152
|
+
toast.error(
|
|
153
|
+
`${m.workspace_home_addMemberFailed()}: ${failedNames}${lastError ? ` (${lastError})` : ""}`
|
|
154
|
+
);
|
|
155
|
+
setSelectedUserIds(failed);
|
|
156
|
+
if (failed.length < selectedUserIds.length) onMembersUpdated();
|
|
157
|
+
}
|
|
133
158
|
} finally {
|
|
134
159
|
setAddingMember(false);
|
|
135
160
|
}
|
|
@@ -222,32 +247,45 @@ export function WorkspaceMembersManager({ workspaceId, workspaceOwnerId, members
|
|
|
222
247
|
{m.workspace_home_noMembers()}
|
|
223
248
|
</div>
|
|
224
249
|
) : (
|
|
225
|
-
filteredTeamUsers.map((user) =>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
filteredTeamUsers.map((user) => {
|
|
251
|
+
const isOwner = user.seatType === "owner";
|
|
252
|
+
const isSelected = selectedUserIds.includes(user.id);
|
|
253
|
+
return (
|
|
254
|
+
<label
|
|
255
|
+
key={user.id}
|
|
256
|
+
className={`flex items-center gap-3 px-3 py-2.5 transition-colors ${
|
|
257
|
+
isOwner
|
|
258
|
+
? "cursor-not-allowed opacity-60"
|
|
259
|
+
: isSelected
|
|
260
|
+
? "cursor-pointer bg-primary/10"
|
|
261
|
+
: "cursor-pointer hover:bg-accent"
|
|
262
|
+
}`}
|
|
263
|
+
>
|
|
264
|
+
<input
|
|
265
|
+
type="checkbox"
|
|
266
|
+
value={user.id}
|
|
267
|
+
checked={isSelected}
|
|
268
|
+
disabled={isOwner}
|
|
269
|
+
onChange={() => toggleUser(user.id)}
|
|
270
|
+
className="accent-primary"
|
|
271
|
+
/>
|
|
272
|
+
<UserAvatar
|
|
273
|
+
name={user.username}
|
|
274
|
+
avatarConfig={user.avatarConfig as AvatarConfig | undefined}
|
|
275
|
+
bgShape={user.bgShape as ShapeType | undefined}
|
|
276
|
+
bgColor={user.bgColor ?? undefined}
|
|
277
|
+
size="sm"
|
|
278
|
+
/>
|
|
279
|
+
<span className="text-sm font-medium">{user.username}</span>
|
|
280
|
+
{isOwner && (
|
|
281
|
+
<span className="text-xs text-muted-foreground">
|
|
282
|
+
({m.workspace_home_role_owner()})
|
|
283
|
+
</span>
|
|
284
|
+
)}
|
|
285
|
+
<span className="text-xs text-muted-foreground ml-auto">{user.email}</span>
|
|
286
|
+
</label>
|
|
287
|
+
);
|
|
288
|
+
})
|
|
251
289
|
)}
|
|
252
290
|
</div>
|
|
253
291
|
<div className="grid gap-2">
|
|
@@ -268,9 +306,9 @@ export function WorkspaceMembersManager({ workspaceId, workspaceOwnerId, members
|
|
|
268
306
|
<Button variant="outline" onClick={() => setMemberDialogOpen(false)}>
|
|
269
307
|
{m.common_button_cancel()}
|
|
270
308
|
</Button>
|
|
271
|
-
<Button onClick={handleAddMember} disabled={addingMember ||
|
|
309
|
+
<Button onClick={handleAddMember} disabled={addingMember || selectedUserIds.length === 0}>
|
|
272
310
|
{addingMember ? <Loader2 className="size-4 animate-spin" /> : <Check className="size-4 mr-1.5" />}
|
|
273
|
-
{m.
|
|
311
|
+
{m.workspace_home_addMember()}
|
|
274
312
|
</Button>
|
|
275
313
|
</DialogFooter>
|
|
276
314
|
</DialogContent>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it, afterEach } from "vitest"
|
|
2
|
+
import { setupServer } from "msw/node"
|
|
3
|
+
import { http, HttpResponse } from "msw"
|
|
4
|
+
import { fetchJson } from "../fetch-json"
|
|
5
|
+
|
|
6
|
+
// msw 样板:拦截 fetch 层,让接口测试与真实网络解耦。
|
|
7
|
+
// 后续 serverFn 客户端 / SDK fetch 测试统一复用此模式。
|
|
8
|
+
const server = setupServer(
|
|
9
|
+
http.get("https://api.example.com/models", () =>
|
|
10
|
+
HttpResponse.json([{ id: "gpt-x", displayName: "GPT X" }])
|
|
11
|
+
),
|
|
12
|
+
http.post("https://api.example.com/echo", async ({ request }) =>
|
|
13
|
+
HttpResponse.json({ received: await request.json() })
|
|
14
|
+
),
|
|
15
|
+
http.get("https://api.example.com/fail", () =>
|
|
16
|
+
HttpResponse.json({ error: "boom" }, { status: 500 })
|
|
17
|
+
)
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
describe("msw 接口 mock 样板", () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
server.resetHandlers()
|
|
23
|
+
server.close()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it("GET 拦截返回桩数据", async () => {
|
|
27
|
+
server.listen({ onUnhandledRequest: "error" })
|
|
28
|
+
const data = await fetchJson<{ id: string }[]>("https://api.example.com/models")
|
|
29
|
+
expect(data[0]!.id).toBe("gpt-x")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("POST 请求体透传", async () => {
|
|
33
|
+
server.listen({ onUnhandledRequest: "error" })
|
|
34
|
+
const data = await fetchJson<{ received: { a: number } }>("https://api.example.com/echo", {
|
|
35
|
+
method: "POST",
|
|
36
|
+
body: JSON.stringify({ a: 1 }),
|
|
37
|
+
})
|
|
38
|
+
expect(data.received.a).toBe(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it("未拦截的请求直接报错(防测试悄悄打真网)", async () => {
|
|
42
|
+
server.listen({ onUnhandledRequest: "error" })
|
|
43
|
+
await expect(fetchJson("https://unhandled.example.com/x")).rejects.toThrow()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("非 2xx 响应抛错,消息含状态码", async () => {
|
|
47
|
+
server.listen({ onUnhandledRequest: "error" })
|
|
48
|
+
await expect(fetchJson("https://api.example.com/fail")).rejects.toThrow(/HTTP 500/)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("调用方传入的 AbortSignal 不被默认超时覆盖", async () => {
|
|
52
|
+
server.listen({ onUnhandledRequest: "error" })
|
|
53
|
+
const controller = new AbortController()
|
|
54
|
+
const data = await fetchJson<{ id: string }[]>("https://api.example.com/models", {
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
})
|
|
57
|
+
expect(data[0]!.id).toBe("gpt-x")
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it("传入已 abort 的 signal 立即抛 AbortError", async () => {
|
|
61
|
+
server.listen({ onUnhandledRequest: "error" })
|
|
62
|
+
const controller = new AbortController()
|
|
63
|
+
controller.abort()
|
|
64
|
+
await expect(
|
|
65
|
+
fetchJson("https://api.example.com/models", { signal: controller.signal })
|
|
66
|
+
).rejects.toThrow()
|
|
67
|
+
})
|
|
68
|
+
})
|
|
@@ -52,7 +52,7 @@ describe('searchByVector', () => {
|
|
|
52
52
|
|
|
53
53
|
it('throws on illegal table name', async () => {
|
|
54
54
|
await expect(searchByVector([0.1], 'users; DROP TABLE users; --'))
|
|
55
|
-
.rejects.
|
|
55
|
+
.rejects.toThrow(/非法表名/)
|
|
56
56
|
})
|
|
57
57
|
|
|
58
58
|
it('accepts allowed table names', async () => {
|
|
@@ -81,7 +81,7 @@ describe('insertEmbedding', () => {
|
|
|
81
81
|
|
|
82
82
|
it('throws on illegal table name', async () => {
|
|
83
83
|
await expect(insertEmbedding('users', 'id', 'c', [0.1]))
|
|
84
|
-
.rejects.
|
|
84
|
+
.rejects.toThrow(/非法表名/)
|
|
85
85
|
})
|
|
86
86
|
|
|
87
87
|
it('extraFields values are not concatenated into SQL string', async () => {
|
|
@@ -10,7 +10,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
10
10
|
"name": "@tanstack/react-start",
|
|
11
11
|
"displayName": "TanStack Start",
|
|
12
12
|
"version": "v1.168.49",
|
|
13
|
-
"lastUpdate": "Sep
|
|
13
|
+
"lastUpdate": "Sep 11, 2026",
|
|
14
14
|
"source": "apps/web/package.json#dependencies",
|
|
15
15
|
"docsUrl": "https://tanstack.com/start/latest"
|
|
16
16
|
},
|
|
@@ -18,7 +18,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
18
18
|
"name": "@tanstack/react-router",
|
|
19
19
|
"displayName": "TanStack Router",
|
|
20
20
|
"version": "v1.170.32",
|
|
21
|
-
"lastUpdate": "Sep
|
|
21
|
+
"lastUpdate": "Sep 11, 2026",
|
|
22
22
|
"source": "apps/web/package.json#dependencies",
|
|
23
23
|
"docsUrl": "https://tanstack.com/router/latest"
|
|
24
24
|
},
|
|
@@ -26,7 +26,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
26
26
|
"name": "@tanstack/react-query",
|
|
27
27
|
"displayName": "TanStack Query",
|
|
28
28
|
"version": "v5.102.8",
|
|
29
|
-
"lastUpdate": "Sep
|
|
29
|
+
"lastUpdate": "Sep 11, 2026",
|
|
30
30
|
"source": "apps/web/package.json#dependencies",
|
|
31
31
|
"docsUrl": "https://tanstack.com/query/latest"
|
|
32
32
|
},
|
|
@@ -34,7 +34,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
34
34
|
"name": "@tanstack/ai",
|
|
35
35
|
"displayName": "TanStack AI",
|
|
36
36
|
"version": "v0.52.0",
|
|
37
|
-
"lastUpdate": "Sep
|
|
37
|
+
"lastUpdate": "Sep 11, 2026",
|
|
38
38
|
"source": "apps/web/package.json#dependencies",
|
|
39
39
|
"docsUrl": "https://tanstack.com/ai/latest"
|
|
40
40
|
},
|
|
@@ -42,7 +42,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
42
42
|
"name": "@tanstack/react-table",
|
|
43
43
|
"displayName": "TanStack Table",
|
|
44
44
|
"version": "v9.2.3",
|
|
45
|
-
"lastUpdate": "Sep
|
|
45
|
+
"lastUpdate": "Sep 11, 2026",
|
|
46
46
|
"source": "apps/web/package.json#dependencies",
|
|
47
47
|
"docsUrl": "https://tanstack.com/table/latest"
|
|
48
48
|
},
|
|
@@ -50,7 +50,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
50
50
|
"name": "@tanstack/react-form",
|
|
51
51
|
"displayName": "TanStack Form",
|
|
52
52
|
"version": "v2.0.0-alpha.2",
|
|
53
|
-
"lastUpdate": "Sep
|
|
53
|
+
"lastUpdate": "Sep 11, 2026",
|
|
54
54
|
"source": "apps/web/package.json#dependencies",
|
|
55
55
|
"docsUrl": "https://tanstack.com/form/latest"
|
|
56
56
|
},
|
|
@@ -58,7 +58,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
58
58
|
"name": "@tanstack/charts",
|
|
59
59
|
"displayName": "TanStack Charts",
|
|
60
60
|
"version": "v0.16.0",
|
|
61
|
-
"lastUpdate": "Sep
|
|
61
|
+
"lastUpdate": "Sep 11, 2026",
|
|
62
62
|
"source": "apps/admin/package.json#dependencies",
|
|
63
63
|
"docsUrl": "https://tanstack.com/charts/latest"
|
|
64
64
|
},
|
|
@@ -66,7 +66,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
66
66
|
"name": "@tanstack/react-pacer",
|
|
67
67
|
"displayName": "TanStack Pacer",
|
|
68
68
|
"version": "v0.23.0",
|
|
69
|
-
"lastUpdate": "Sep
|
|
69
|
+
"lastUpdate": "Sep 11, 2026",
|
|
70
70
|
"source": "apps/web/package.json#dependencies",
|
|
71
71
|
"docsUrl": "https://tanstack.com/pacer/latest"
|
|
72
72
|
},
|
|
@@ -74,7 +74,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
74
74
|
"name": "@tanstack/react-hotkeys",
|
|
75
75
|
"displayName": "TanStack Hotkeys",
|
|
76
76
|
"version": "v0.10.0",
|
|
77
|
-
"lastUpdate": "Sep
|
|
77
|
+
"lastUpdate": "Sep 11, 2026",
|
|
78
78
|
"source": "apps/web/package.json#dependencies",
|
|
79
79
|
"docsUrl": "https://tanstack.com/hotkeys/latest"
|
|
80
80
|
},
|
|
@@ -82,7 +82,7 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
82
82
|
"name": "@tanstack/react-virtual",
|
|
83
83
|
"displayName": "TanStack Virtual",
|
|
84
84
|
"version": "v3.14.10",
|
|
85
|
-
"lastUpdate": "Sep
|
|
85
|
+
"lastUpdate": "Sep 11, 2026",
|
|
86
86
|
"source": "apps/admin/package.json#dependencies",
|
|
87
87
|
"docsUrl": "https://tanstack.com/virtual/latest"
|
|
88
88
|
}
|
|
@@ -95,15 +95,15 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
95
95
|
"items": [
|
|
96
96
|
{
|
|
97
97
|
"name": "@allbluecn/web-app",
|
|
98
|
-
"version": "v0.
|
|
99
|
-
"lastUpdate": "Sep
|
|
98
|
+
"version": "v0.11.0",
|
|
99
|
+
"lastUpdate": "Sep 11, 2026",
|
|
100
100
|
"source": "apps/web/package.json",
|
|
101
101
|
"docsUrl": "https://github.com/allbluecn/allblue"
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
"name": "@allbluecn/kernel",
|
|
105
|
-
"version": "v0.
|
|
106
|
-
"lastUpdate": "Sep
|
|
105
|
+
"version": "v0.6.1",
|
|
106
|
+
"lastUpdate": "Sep 11, 2026",
|
|
107
107
|
"source": "packages/kernel/package.json",
|
|
108
108
|
"docsUrl": "https://github.com/allbluecn/allblue"
|
|
109
109
|
}
|
|
@@ -117,42 +117,42 @@ export const SDK_GROUPS: SdkGroup[] = [
|
|
|
117
117
|
{
|
|
118
118
|
"name": "Vite",
|
|
119
119
|
"version": "v8.1.5",
|
|
120
|
-
"lastUpdate": "Sep
|
|
120
|
+
"lastUpdate": "Sep 11, 2026",
|
|
121
121
|
"source": "apps/web/package.json#devDependencies",
|
|
122
122
|
"docsUrl": "https://vite.dev/"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "Node.js",
|
|
126
126
|
"version": "v24.19.0",
|
|
127
|
-
"lastUpdate": "Sep
|
|
127
|
+
"lastUpdate": "Sep 11, 2026",
|
|
128
128
|
"source": ".nvmrc",
|
|
129
129
|
"docsUrl": "https://nodejs.org/"
|
|
130
130
|
},
|
|
131
131
|
{
|
|
132
132
|
"name": "Prisma",
|
|
133
133
|
"version": "v7.8.0",
|
|
134
|
-
"lastUpdate": "Sep
|
|
134
|
+
"lastUpdate": "Sep 11, 2026",
|
|
135
135
|
"source": "packages/database/package.json#devDependencies",
|
|
136
136
|
"docsUrl": "https://www.prisma.io/docs"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"name": "shadcn UI",
|
|
140
140
|
"version": "v1.2.4",
|
|
141
|
-
"lastUpdate": "Sep
|
|
141
|
+
"lastUpdate": "Sep 11, 2026",
|
|
142
142
|
"source": "apps/web/package.json#dependencies",
|
|
143
143
|
"docsUrl": "https://ui.shadcn.com/"
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
"name": "TipTap",
|
|
147
147
|
"version": "v3.27.4",
|
|
148
|
-
"lastUpdate": "Sep
|
|
148
|
+
"lastUpdate": "Sep 11, 2026",
|
|
149
149
|
"source": "apps/web/package.json#dependencies",
|
|
150
150
|
"docsUrl": "https://tiptap.dev/"
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"name": "Lucide React",
|
|
154
|
-
"version": "v1.
|
|
155
|
-
"lastUpdate": "Sep
|
|
154
|
+
"version": "v1.43.0",
|
|
155
|
+
"lastUpdate": "Sep 11, 2026",
|
|
156
156
|
"source": "apps/web/package.json#dependencies",
|
|
157
157
|
"docsUrl": "https://lucide.dev/"
|
|
158
158
|
}
|
|
@@ -20,10 +20,12 @@ import { queryCollectionOptions } from '@tanstack/query-db-collection'
|
|
|
20
20
|
import { listKnowledgeFn } from '@/server/serverFns/knowledge'
|
|
21
21
|
import { getQueryClient } from '@/lib/query-client-seam'
|
|
22
22
|
|
|
23
|
+
export const KNOWLEDGE_BASES_QUERY_KEY = ['knowledge-bases'] as const
|
|
24
|
+
|
|
23
25
|
export const knowledgeBasesCollection = createCollection(
|
|
24
26
|
queryCollectionOptions({
|
|
25
27
|
id: 'knowledge-bases',
|
|
26
|
-
queryKey:
|
|
28
|
+
queryKey: KNOWLEDGE_BASES_QUERY_KEY,
|
|
27
29
|
queryFn: () => listKnowledgeFn(),
|
|
28
30
|
getKey: (item: { id: string }) => item.id,
|
|
29
31
|
queryClient: getQueryClient(),
|
|
@@ -20,10 +20,12 @@ import { queryCollectionOptions } from '@tanstack/query-db-collection'
|
|
|
20
20
|
import { listPinnedWorkspacesBriefFn } from '@/server/serverFns/workspace'
|
|
21
21
|
import { getQueryClient } from '@/lib/query-client-seam'
|
|
22
22
|
|
|
23
|
+
export const WORKSPACES_QUERY_KEY = ['workspaces'] as const
|
|
24
|
+
|
|
23
25
|
export const workspacesCollection = createCollection(
|
|
24
26
|
queryCollectionOptions({
|
|
25
27
|
id: 'workspaces',
|
|
26
|
-
queryKey:
|
|
28
|
+
queryKey: WORKSPACES_QUERY_KEY,
|
|
27
29
|
queryFn: () => listPinnedWorkspacesBriefFn(),
|
|
28
30
|
getKey: (item: { id: string }) => item.id,
|
|
29
31
|
queryClient: getQueryClient(),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 带默认超时与 JSON 解析的 fetch 封装(供客户端 SDK / 工具函数复用)。
|
|
3
|
+
*
|
|
4
|
+
* 默认超时 10s:接口调用应在 10 秒内返回;超过视为网络/服务异常,
|
|
5
|
+
* 由上层(React Query 等)触发重试或降级。
|
|
6
|
+
*
|
|
7
|
+
* 调用方可传入自己的 AbortSignal 覆盖默认超时(如与外层 Promise.race
|
|
8
|
+
* 联动,或用 AbortController 手动取消),不会被默认超时覆盖。
|
|
9
|
+
*
|
|
10
|
+
* @throws 当响应非 2xx 时抛 `Error`,消息含 URL 与 HTTP 状态码,便于日志定位。
|
|
11
|
+
*/
|
|
12
|
+
export async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> {
|
|
13
|
+
const res = await fetch(url, {
|
|
14
|
+
...init,
|
|
15
|
+
headers: {
|
|
16
|
+
"content-type": "application/json",
|
|
17
|
+
...(init?.headers ?? {}),
|
|
18
|
+
},
|
|
19
|
+
signal: init?.signal ?? AbortSignal.timeout(10_000),
|
|
20
|
+
})
|
|
21
|
+
if (!res.ok) throw new Error(`fetchJson ${url} 失败:HTTP ${res.status}`)
|
|
22
|
+
return (await res.json()) as T
|
|
23
|
+
}
|
|
@@ -15,17 +15,11 @@
|
|
|
15
15
|
// You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
import
|
|
18
|
+
// paraglide 动态 key 解析:静态具名导出(m.xxx)覆盖不到的运行时拼接 key 场景。
|
|
19
|
+
import * as messages from "@/paraglide/messages.js";
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<LayoutGrid className="size-4 text-muted-foreground" />
|
|
26
|
-
<h2 className="text-base font-semibold">{m.workspace_home_kbTitle()}</h2>
|
|
27
|
-
</div>
|
|
28
|
-
<p className="py-4 text-center text-sm text-muted-foreground">{m.workspace_home_kbPlaceholder()}</p>
|
|
29
|
-
</div>
|
|
30
|
-
);
|
|
21
|
+
/** 按消息 key 取文案;未知 key 原样返回(便于发现缺失) */
|
|
22
|
+
export function t(key: string): string {
|
|
23
|
+
const fn = (messages as unknown as Record<string, unknown>)[key];
|
|
24
|
+
return typeof fn === "function" ? (fn as () => string)() : key;
|
|
31
25
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'
|
|
2
|
+
import { uiTranslations } from 'fumadocs-ui/i18n'
|
|
3
|
+
import { zhCN } from '@fumadocs/language/zh-cn'
|
|
4
|
+
import { fumadocsI18n } from './fumadocs-i18n'
|
|
5
|
+
|
|
6
|
+
export type DocsLang = 'zh' | 'en'
|
|
7
|
+
|
|
8
|
+
const DOCS_LANGS: readonly DocsLang[] = ['zh', 'en'] as const
|
|
9
|
+
|
|
10
|
+
export function isDocsLang(v: string): v is DocsLang {
|
|
11
|
+
return DOCS_LANGS.includes(v as DocsLang)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const translations = fumadocsI18n
|
|
15
|
+
.translations()
|
|
16
|
+
.extend(uiTranslations())
|
|
17
|
+
.preset('zh', zhCN())
|
|
18
|
+
.add({
|
|
19
|
+
zh: {
|
|
20
|
+
displayName: '简体中文',
|
|
21
|
+
},
|
|
22
|
+
en: {
|
|
23
|
+
displayName: 'English',
|
|
24
|
+
},
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export function baseOptions(lang: DocsLang): BaseLayoutProps {
|
|
28
|
+
return {
|
|
29
|
+
nav: {
|
|
30
|
+
title: lang === 'en' ? 'AllBlue Docs' : 'AllBlue 使用文档',
|
|
31
|
+
url: `/${lang}/docs`,
|
|
32
|
+
},
|
|
33
|
+
links: [
|
|
34
|
+
{
|
|
35
|
+
text: lang === 'en' ? 'Back to App' : '返回应用',
|
|
36
|
+
url: '/dashboard',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/lib/sanitize.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import DOMPurify from 'isomorphic-dompurify'
|
|
2
2
|
|
|
3
|
+
// HTML 清理:默认配置(html + svg + mathMl profile 均启用),
|
|
4
|
+
// 覆盖富文本 diff、附件预览等 7 处 HTML 渲染场景。
|
|
3
5
|
export function sanitize(html: string): string {
|
|
4
6
|
return DOMPurify.sanitize(html)
|
|
5
|
-
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// SVG 清理:头像渲染(NotionAvatar)专用。
|
|
10
|
+
// 显式复用默认配置——DOMPurify 默认 profile 已包含 SVG 滤镜元素
|
|
11
|
+
// (<defs>/<filter>/<feMorphology>/<feFlood>/<feComposite>/<feMerge>/<feMergeNode>),
|
|
12
|
+
// 同时清除 <script>/on*/javascript: 等 XSS 载荷。
|
|
13
|
+
// 禁止改成 USE_PROFILES: { svg: true }——严格 SVG profile 会移除全部 fe* 滤镜元素,
|
|
14
|
+
// 头像轮廓辉光(feMorphology+feFlood+feComposite+feMerge)会失效渲染白屏。
|
|
15
|
+
// 结构完整性由 __tests__/security/xss-sanitize.test.ts 的双向断言锁定。
|
|
16
|
+
export function sanitizeSvg(svg: string): string {
|
|
17
|
+
return DOMPurify.sanitize(svg)
|
|
18
|
+
}
|
package/src/lib/session.ts
CHANGED
|
@@ -40,5 +40,13 @@ export type Session = Omit<AuthSession, 'user'> & { user?: SessionUser | null }
|
|
|
40
40
|
|
|
41
41
|
export async function getCurrentSession(request?: Request): Promise<Session | null> {
|
|
42
42
|
if (!request) return null
|
|
43
|
-
|
|
43
|
+
try {
|
|
44
|
+
return (await getSession(request, authConfig)) as Session | null
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// JWT 解密失败(AUTH_SECRET 轮换 / 跨应用同名 cookie 污染)等效于无 session:
|
|
47
|
+
// 返回 null 走未登录分支;@auth/core 会自行清理坏 cookie。
|
|
48
|
+
// 不兜底时 JWTSessionError 会炸穿所有未捕获的调用点(route beforeLoad / serverFn)。
|
|
49
|
+
console.warn('[session] 读取 session 失败(坏 cookie 或 auth 配置异常),降级为未登录:', error instanceof Error ? error.message : error)
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
44
52
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineDocs } from 'fumadocs-mdx/macro'
|
|
2
|
+
import { loader } from 'fumadocs-core/source'
|
|
3
|
+
import { fumadocsI18n } from './fumadocs-i18n'
|
|
4
|
+
|
|
5
|
+
const docs = defineDocs({
|
|
6
|
+
dir: 'content/docs',
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export const source = loader({
|
|
10
|
+
i18n: fumadocsI18n,
|
|
11
|
+
baseUrl: '/docs',
|
|
12
|
+
source: docs.toFumadocsSource(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export { docs }
|
|
@@ -54,6 +54,6 @@ describe("getVectorStore 工厂", () => {
|
|
|
54
54
|
|
|
55
55
|
it("未知 VECTOR_BACKEND 抛错", async () => {
|
|
56
56
|
process.env.VECTOR_BACKEND = "unknown_backend";
|
|
57
|
-
await expect(getVectorStore()).rejects.
|
|
57
|
+
await expect(getVectorStore()).rejects.toThrow("未知 VECTOR_BACKEND");
|
|
58
58
|
});
|
|
59
59
|
});
|
|
@@ -131,13 +131,13 @@ describe("sqliteVecStore", () => {
|
|
|
131
131
|
it("非法表名/列名抛错", async () => {
|
|
132
132
|
await expect(
|
|
133
133
|
sqliteVecStore.searchByVector([1, 0, 0, 0], "evil" as never)
|
|
134
|
-
).rejects.
|
|
134
|
+
).rejects.toThrow("非法表名");
|
|
135
135
|
await expect(
|
|
136
136
|
sqliteVecStore.deleteEmbeddings(
|
|
137
137
|
"knowledge_embeddings",
|
|
138
138
|
"evil" as never,
|
|
139
139
|
"x"
|
|
140
140
|
)
|
|
141
|
-
).rejects.
|
|
141
|
+
).rejects.toThrow("非法删除过滤列");
|
|
142
142
|
});
|
|
143
143
|
});
|