@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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
const execFileSyncMock = vi.hoisted(() => vi.fn())
|
|
4
|
+
const execSyncMock = vi.hoisted(() => vi.fn())
|
|
5
|
+
|
|
6
|
+
vi.mock('child_process', async (importOriginal) => {
|
|
7
|
+
const actual = await importOriginal<typeof import('child_process')>()
|
|
8
|
+
return {
|
|
9
|
+
...actual,
|
|
10
|
+
execFileSync: execFileSyncMock,
|
|
11
|
+
execSync: execSyncMock,
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
import { detectAgents, getApiAgent } from '../cli-detector'
|
|
16
|
+
|
|
17
|
+
describe('getApiAgent', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
vi.resetModules()
|
|
20
|
+
delete process.env.OPENAI_API_KEY
|
|
21
|
+
delete process.env.AI_MODEL
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('未设置 API key 时返回 undefined', async () => {
|
|
25
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
26
|
+
expect(fn()).toBeUndefined()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('API key 为占位符时返回 undefined', async () => {
|
|
30
|
+
process.env.OPENAI_API_KEY = 'sk-your-api-key-here'
|
|
31
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
32
|
+
expect(fn()).toBeUndefined()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('API key 有效时返回 agent 信息', async () => {
|
|
36
|
+
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
37
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
38
|
+
const agent = fn()
|
|
39
|
+
expect(agent).toBeDefined()
|
|
40
|
+
expect(agent?.id).toBe('openai-api')
|
|
41
|
+
expect(agent?.available).toBe(true)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('使用 AI_MODEL 环境变量显示模型名', async () => {
|
|
45
|
+
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
46
|
+
process.env.AI_MODEL = 'gpt-4'
|
|
47
|
+
const { getApiAgent: fn } = await import('../cli-detector')
|
|
48
|
+
expect(fn()?.name).toContain('gpt-4')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('detectAgents 安全探测', () => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
execFileSyncMock.mockReset()
|
|
55
|
+
execSyncMock.mockReset()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('用 execFileSync 数组参数探测版本(无 shell 拼接)', async () => {
|
|
59
|
+
execFileSyncMock.mockReturnValueOnce('v1.2.3')
|
|
60
|
+
const agents = await detectAgents()
|
|
61
|
+
const claude = agents.find((a) => a.id === 'claude-code')!
|
|
62
|
+
expect(claude.available).toBe(true)
|
|
63
|
+
expect(claude.version).toBe('v1.2.3')
|
|
64
|
+
expect(execFileSyncMock).toHaveBeenCalledWith(
|
|
65
|
+
expect.any(String),
|
|
66
|
+
expect.arrayContaining(['--version']),
|
|
67
|
+
expect.objectContaining({ timeout: expect.any(Number), encoding: 'utf-8' })
|
|
68
|
+
)
|
|
69
|
+
expect(execSyncMock).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('--version 失败时回退 version 子命令(两步探测,无 shell ||)', async () => {
|
|
73
|
+
execFileSyncMock
|
|
74
|
+
.mockImplementationOnce(() => { throw new Error('not found') })
|
|
75
|
+
.mockReturnValueOnce('2.0.0')
|
|
76
|
+
const agents = await detectAgents()
|
|
77
|
+
const first = agents[0]!
|
|
78
|
+
expect(first.available).toBe(true)
|
|
79
|
+
expect(first.version).toBe('2.0.0')
|
|
80
|
+
const calls = execFileSyncMock.mock.calls
|
|
81
|
+
expect(calls[0]?.[1]).toEqual(['--version'])
|
|
82
|
+
expect(calls[1]?.[1]).toEqual(['version'])
|
|
83
|
+
expect(calls[0]![0]).toBe(calls[1]![0])
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('两步都失败标记不可用且不抛错', async () => {
|
|
87
|
+
execFileSyncMock.mockImplementation(() => { throw new Error('not found') })
|
|
88
|
+
const agents = await detectAgents()
|
|
89
|
+
expect(agents).toHaveLength(16)
|
|
90
|
+
expect(agents.every((a) => a.available === false)).toBe(true)
|
|
91
|
+
expect(agents.every((a) => a.version === '')).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('版本串截断到 50 字符且去除首尾空白', async () => {
|
|
95
|
+
execFileSyncMock.mockReturnValue(` ${'x'.repeat(80)} \n`)
|
|
96
|
+
const agents = await detectAgents()
|
|
97
|
+
expect(agents[0]!.version).toBe('x'.repeat(50))
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -50,8 +50,8 @@ export async function executeViaAgent(
|
|
|
50
50
|
return data.choices[0]?.message?.content || ""
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const {
|
|
54
|
-
const result =
|
|
53
|
+
const { execFileSync } = await import("child_process")
|
|
54
|
+
const result = execFileSync(agent.binary, ["-p", prompt], {
|
|
55
55
|
timeout: 60000,
|
|
56
56
|
encoding: "utf-8",
|
|
57
57
|
})
|
|
@@ -36,18 +36,27 @@ const KNOWN_AGENTS: { id: string; name: string; binary: string }[] = [
|
|
|
36
36
|
{ id: "mcp-cli", name: "MCP CLI", binary: "mcp" },
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
function probeVersion(
|
|
40
|
+
execFileSync: (file: string, args: string[], options: { timeout: number; encoding: "utf-8" }) => string,
|
|
41
|
+
binary: string,
|
|
42
|
+
): string {
|
|
43
|
+
const opts = { timeout: 3000, encoding: "utf-8" } as const
|
|
44
|
+
try {
|
|
45
|
+
return execFileSync(binary, ["--version"], opts).trim().slice(0, 50)
|
|
46
|
+
} catch {
|
|
42
47
|
try {
|
|
43
|
-
|
|
44
|
-
timeout: 3000,
|
|
45
|
-
encoding: "utf-8",
|
|
46
|
-
}).trim().slice(0, 50)
|
|
47
|
-
return { id, name, binary, version, available: true }
|
|
48
|
+
return execFileSync(binary, ["version"], opts).trim().slice(0, 50)
|
|
48
49
|
} catch {
|
|
49
|
-
return
|
|
50
|
+
return ""
|
|
50
51
|
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function detectAgents(): Promise<CLIAgentInfo[]> {
|
|
56
|
+
const { execFileSync } = await import("child_process")
|
|
57
|
+
return KNOWN_AGENTS.map(({ id, name, binary }) => {
|
|
58
|
+
const version = probeVersion(execFileSync, binary)
|
|
59
|
+
return { id, name, binary, version, available: version !== "" }
|
|
51
60
|
})
|
|
52
61
|
}
|
|
53
62
|
|
package/src/start.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
import { createStart, createCsrfMiddleware, createMiddleware } from '@tanstack/react-start'
|
|
19
|
+
import { securityHeadersMiddleware } from '@/server/middlewares/security-headers'
|
|
19
20
|
import { requestContextMiddleware } from '@/server/middlewares/request'
|
|
20
21
|
import { auditRequestMiddleware } from '@/server/middlewares/audit'
|
|
21
22
|
import { neonKeepaliveMiddleware } from '@/server/keepalive/middleware'
|
|
@@ -97,6 +98,6 @@ export const startInstance = createStart(async () => {
|
|
|
97
98
|
} as any)
|
|
98
99
|
}
|
|
99
100
|
return {
|
|
100
|
-
requestMiddleware: [requestContextMiddleware, neonKeepaliveMiddleware, aiPortsMiddleware, apiCorsMiddleware, csrfMiddleware, auditRequestMiddleware],
|
|
101
|
+
requestMiddleware: [securityHeadersMiddleware, requestContextMiddleware, neonKeepaliveMiddleware, aiPortsMiddleware, apiCorsMiddleware, csrfMiddleware, auditRequestMiddleware],
|
|
101
102
|
}
|
|
102
103
|
})
|
package/src/styles/globals.css
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
@import './_variables.scss';
|
|
2
2
|
@import './_keyframe-animations.scss';
|
|
3
3
|
@import "tailwindcss";
|
|
4
|
+
@import 'fumadocs-ui/css/neutral.css';
|
|
5
|
+
@import 'fumadocs-ui/css/preset.css';
|
|
4
6
|
@import "@fontsource-variable/geist";
|
|
5
7
|
@import "@fontsource-variable/geist-mono";
|
|
6
8
|
@import "./typeset.css";
|
|
@@ -245,6 +247,120 @@
|
|
|
245
247
|
--color-5: oklch(90.7% 0.231 133);
|
|
246
248
|
}
|
|
247
249
|
|
|
250
|
+
/* 知识库快捷文档标签莫兰迪色板(LABEL_COLORS 消费,light 原色 / dark 从种子色派生) */
|
|
251
|
+
:root {
|
|
252
|
+
--kb-row-mix: 45%;
|
|
253
|
+
--kb-seed-mrd: #9CAF97;
|
|
254
|
+
--kb-label-mrd-bg: #E8EFE5;
|
|
255
|
+
--kb-label-mrd-border: #9CAF97;
|
|
256
|
+
--kb-label-mrd-text: #5C705A;
|
|
257
|
+
--kb-seed-brd: #9FB2C4;
|
|
258
|
+
--kb-label-brd-bg: #E5EBF1;
|
|
259
|
+
--kb-label-brd-border: #9FB2C4;
|
|
260
|
+
--kb-label-brd-text: #54687A;
|
|
261
|
+
--kb-seed-feasibility: #C4B896;
|
|
262
|
+
--kb-label-feasibility-bg: #F1EDDF;
|
|
263
|
+
--kb-label-feasibility-border: #C4B896;
|
|
264
|
+
--kb-label-feasibility-text: #7A6E4C;
|
|
265
|
+
--kb-seed-competitor: #C4A48E;
|
|
266
|
+
--kb-label-competitor-bg: #F1E8E2;
|
|
267
|
+
--kb-label-competitor-border: #C4A48E;
|
|
268
|
+
--kb-label-competitor-text: #7A5F4C;
|
|
269
|
+
--kb-seed-charter: #B0A0B8;
|
|
270
|
+
--kb-label-charter-bg: #EDE7F0;
|
|
271
|
+
--kb-label-charter-border: #B0A0B8;
|
|
272
|
+
--kb-label-charter-text: #6A5A72;
|
|
273
|
+
--kb-seed-infoArchitecture: #96B8B0;
|
|
274
|
+
--kb-label-infoArchitecture-bg: #E2EFEC;
|
|
275
|
+
--kb-label-infoArchitecture-border: #96B8B0;
|
|
276
|
+
--kb-label-infoArchitecture-text: #4E6E68;
|
|
277
|
+
--kb-seed-techArchitecture: #8FA3AD;
|
|
278
|
+
--kb-label-techArchitecture-bg: #E1E8EB;
|
|
279
|
+
--kb-label-techArchitecture-border: #8FA3AD;
|
|
280
|
+
--kb-label-techArchitecture-text: #4E626C;
|
|
281
|
+
--kb-seed-dataStructure: #C09898;
|
|
282
|
+
--kb-label-dataStructure-bg: #F0E4E4;
|
|
283
|
+
--kb-label-dataStructure-border: #C09898;
|
|
284
|
+
--kb-label-dataStructure-text: #755050;
|
|
285
|
+
--kb-seed-integration: #B3A48E;
|
|
286
|
+
--kb-label-integration-bg: #EEE9E0;
|
|
287
|
+
--kb-label-integration-border: #B3A48E;
|
|
288
|
+
--kb-label-integration-text: #6E6046;
|
|
289
|
+
--kb-seed-copywriting: #C4A0A8;
|
|
290
|
+
--kb-label-copywriting-bg: #F2E7E9;
|
|
291
|
+
--kb-label-copywriting-border: #C4A0A8;
|
|
292
|
+
--kb-label-copywriting-text: #77545C;
|
|
293
|
+
--kb-seed-dictionary: #A8A88E;
|
|
294
|
+
--kb-label-dictionary-bg: #EBEBE0;
|
|
295
|
+
--kb-label-dictionary-border: #A8A88E;
|
|
296
|
+
--kb-label-dictionary-text: #68684C;
|
|
297
|
+
--kb-seed-legal: #8FA8B3;
|
|
298
|
+
--kb-label-legal-bg: #DCE8EC;
|
|
299
|
+
--kb-label-legal-border: #8FA8B3;
|
|
300
|
+
--kb-label-legal-text: #4C626C;
|
|
301
|
+
--kb-seed-compliance: #9FA88C;
|
|
302
|
+
--kb-label-compliance-bg: #E4E8DC;
|
|
303
|
+
--kb-label-compliance-border: #9FA88C;
|
|
304
|
+
--kb-label-compliance-text: #5E664E;
|
|
305
|
+
--kb-seed-kbDictionary: #B09A8C;
|
|
306
|
+
--kb-label-kbDictionary-bg: #EAE4DC;
|
|
307
|
+
--kb-label-kbDictionary-border: #B09A8C;
|
|
308
|
+
--kb-label-kbDictionary-text: #6E5A4C;
|
|
309
|
+
--kb-seed-website: #98A4B8;
|
|
310
|
+
--kb-label-website-bg: #E0E4EA;
|
|
311
|
+
--kb-label-website-border: #98A4B8;
|
|
312
|
+
--kb-label-website-text: #545E70;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.dark {
|
|
316
|
+
--kb-row-mix: 65%;
|
|
317
|
+
--kb-label-mrd-bg: color-mix(in srgb, var(--kb-seed-mrd) 20%, #1d1f1c);
|
|
318
|
+
--kb-label-mrd-border: color-mix(in srgb, var(--kb-seed-mrd) 45%, #1d1f1c);
|
|
319
|
+
--kb-label-mrd-text: color-mix(in srgb, var(--kb-seed-mrd) 60%, #dfe3dc);
|
|
320
|
+
--kb-label-brd-bg: color-mix(in srgb, var(--kb-seed-brd) 20%, #1c1e21);
|
|
321
|
+
--kb-label-brd-border: color-mix(in srgb, var(--kb-seed-brd) 45%, #1c1e21);
|
|
322
|
+
--kb-label-brd-text: color-mix(in srgb, var(--kb-seed-brd) 60%, #dce1e7);
|
|
323
|
+
--kb-label-feasibility-bg: color-mix(in srgb, var(--kb-seed-feasibility) 20%, #1f1d16);
|
|
324
|
+
--kb-label-feasibility-border: color-mix(in srgb, var(--kb-seed-feasibility) 45%, #1f1d16);
|
|
325
|
+
--kb-label-feasibility-text: color-mix(in srgb, var(--kb-seed-feasibility) 60%, #e6e2d5);
|
|
326
|
+
--kb-label-competitor-bg: color-mix(in srgb, var(--kb-seed-competitor) 20%, #1f1b18);
|
|
327
|
+
--kb-label-competitor-border: color-mix(in srgb, var(--kb-seed-competitor) 45%, #1f1b18);
|
|
328
|
+
--kb-label-competitor-text: color-mix(in srgb, var(--kb-seed-competitor) 60%, #e6ddd6);
|
|
329
|
+
--kb-label-charter-bg: color-mix(in srgb, var(--kb-seed-charter) 20%, #1d1b1f);
|
|
330
|
+
--kb-label-charter-border: color-mix(in srgb, var(--kb-seed-charter) 45%, #1d1b1f);
|
|
331
|
+
--kb-label-charter-text: color-mix(in srgb, var(--kb-seed-charter) 60%, #e2dde6);
|
|
332
|
+
--kb-label-infoArchitecture-bg: color-mix(in srgb, var(--kb-seed-infoArchitecture) 20%, #1b1e1d);
|
|
333
|
+
--kb-label-infoArchitecture-border: color-mix(in srgb, var(--kb-seed-infoArchitecture) 45%, #1b1e1d);
|
|
334
|
+
--kb-label-infoArchitecture-text: color-mix(in srgb, var(--kb-seed-infoArchitecture) 60%, #dbe4e1);
|
|
335
|
+
--kb-label-techArchitecture-bg: color-mix(in srgb, var(--kb-seed-techArchitecture) 20%, #1b1d1f);
|
|
336
|
+
--kb-label-techArchitecture-border: color-mix(in srgb, var(--kb-seed-techArchitecture) 45%, #1b1d1f);
|
|
337
|
+
--kb-label-techArchitecture-text: color-mix(in srgb, var(--kb-seed-techArchitecture) 60%, #dbe1e5);
|
|
338
|
+
--kb-label-dataStructure-bg: color-mix(in srgb, var(--kb-seed-dataStructure) 20%, #1f1b1b);
|
|
339
|
+
--kb-label-dataStructure-border: color-mix(in srgb, var(--kb-seed-dataStructure) 45%, #1f1b1b);
|
|
340
|
+
--kb-label-dataStructure-text: color-mix(in srgb, var(--kb-seed-dataStructure) 60%, #e6dcdc);
|
|
341
|
+
--kb-label-integration-bg: color-mix(in srgb, var(--kb-seed-integration) 20%, #1e1c18);
|
|
342
|
+
--kb-label-integration-border: color-mix(in srgb, var(--kb-seed-integration) 45%, #1e1c18);
|
|
343
|
+
--kb-label-integration-text: color-mix(in srgb, var(--kb-seed-integration) 60%, #e3ded4);
|
|
344
|
+
--kb-label-copywriting-bg: color-mix(in srgb, var(--kb-seed-copywriting) 20%, #1f1b1c);
|
|
345
|
+
--kb-label-copywriting-border: color-mix(in srgb, var(--kb-seed-copywriting) 45%, #1f1b1c);
|
|
346
|
+
--kb-label-copywriting-text: color-mix(in srgb, var(--kb-seed-copywriting) 60%, #e6dce0);
|
|
347
|
+
--kb-label-dictionary-bg: color-mix(in srgb, var(--kb-seed-dictionary) 20%, #1d1d19);
|
|
348
|
+
--kb-label-dictionary-border: color-mix(in srgb, var(--kb-seed-dictionary) 45%, #1d1d19);
|
|
349
|
+
--kb-label-dictionary-text: color-mix(in srgb, var(--kb-seed-dictionary) 60%, #e2e2da);
|
|
350
|
+
--kb-label-legal-bg: color-mix(in srgb, var(--kb-seed-legal) 20%, #1a1e20);
|
|
351
|
+
--kb-label-legal-border: color-mix(in srgb, var(--kb-seed-legal) 45%, #1a1e20);
|
|
352
|
+
--kb-label-legal-text: color-mix(in srgb, var(--kb-seed-legal) 60%, #d9e2e7);
|
|
353
|
+
--kb-label-compliance-bg: color-mix(in srgb, var(--kb-seed-compliance) 20%, #1c1e1a);
|
|
354
|
+
--kb-label-compliance-border: color-mix(in srgb, var(--kb-seed-compliance) 45%, #1c1e1a);
|
|
355
|
+
--kb-label-compliance-text: color-mix(in srgb, var(--kb-seed-compliance) 60%, #dee2d6);
|
|
356
|
+
--kb-label-kbDictionary-bg: color-mix(in srgb, var(--kb-seed-kbDictionary) 20%, #1e1c1a);
|
|
357
|
+
--kb-label-kbDictionary-border: color-mix(in srgb, var(--kb-seed-kbDictionary) 45%, #1e1c1a);
|
|
358
|
+
--kb-label-kbDictionary-text: color-mix(in srgb, var(--kb-seed-kbDictionary) 60%, #e3ddd6);
|
|
359
|
+
--kb-label-website-bg: color-mix(in srgb, var(--kb-seed-website) 20%, #1b1d21);
|
|
360
|
+
--kb-label-website-border: color-mix(in srgb, var(--kb-seed-website) 45%, #1b1d21);
|
|
361
|
+
--kb-label-website-text: color-mix(in srgb, var(--kb-seed-website) 60%, #dbe0e8);
|
|
362
|
+
}
|
|
363
|
+
|
|
248
364
|
@layer base {
|
|
249
365
|
* {
|
|
250
366
|
border-color: var(--color-border);
|
|
@@ -15,17 +15,18 @@
|
|
|
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
|
-
// Vitest
|
|
19
|
-
//
|
|
20
|
-
// `Assertion
|
|
21
|
-
//
|
|
18
|
+
// Vitest 5 类型兼容层:Vitest 5 将 expect 断言内联进主包(不再依赖 @vitest/expect),
|
|
19
|
+
// `declare module 'vitest'` 的接口增强直接生效。但 Vitest 5 的 `Assertion` 接口
|
|
20
|
+
// 使用双类型参数 `Assertion<R, T>`(R = matcher 返回类型,T = received 值类型),
|
|
21
|
+
// 而 @testing-library/jest-dom 官方 types/vitest.d.ts 仍声明单参数 `Assertion<T = any>`,
|
|
22
|
+
// 泛型参数个数不一致导致声明合并报 TS2428。这里以正确的双参数签名重写增强,
|
|
22
23
|
// 使 `toBeInTheDocument` / `toBeDisabled` 等 jest-dom matcher 类型可用。
|
|
23
24
|
// 运行时 matcher 注册在 src/__tests__/setup.ts 的 `import '@testing-library/jest-dom/vitest'`。
|
|
24
25
|
import type { TestingLibraryMatchers } from '@testing-library/jest-dom/matchers'
|
|
25
26
|
|
|
26
|
-
declare module '
|
|
27
|
-
interface Assertion<T =
|
|
28
|
-
extends TestingLibraryMatchers<
|
|
27
|
+
declare module 'vitest' {
|
|
28
|
+
interface Assertion<R extends void | Promise<void> = void, T = unknown>
|
|
29
|
+
extends TestingLibraryMatchers<R, T> {}
|
|
29
30
|
interface AsymmetricMatchersContaining
|
|
30
31
|
extends TestingLibraryMatchers<any, any> {}
|
|
31
32
|
}
|
package/vite.shared.ts
CHANGED
|
@@ -139,8 +139,25 @@ export function createWebViteConfig(opts: WebViteConfigOptions) {
|
|
|
139
139
|
},
|
|
140
140
|
server: {
|
|
141
141
|
port: 3000,
|
|
142
|
-
|
|
142
|
+
host: true,
|
|
143
|
+
// host.docker.internal:容器内安全扫描(ZAP/Nuclei,见
|
|
144
|
+
// scripts/security/)经该地址访问宿主 dev server;Vite 默认拒绝
|
|
145
|
+
// 非白名单 Host 头(返回 403 "Blocked request")。仅 dev server 生效。
|
|
146
|
+
allowedHosts: ['localhost', 'host.docker.internal'],
|
|
143
147
|
cors: { origin: false },
|
|
148
|
+
// Vite 内部中间件(/src/*、/@fs/*、.map)的响应绕过 TanStack Start
|
|
149
|
+
// requestMiddleware,故在此层补基础安全头;CSP/HSTS 仍由 middleware
|
|
150
|
+
// 统一注入(apps/web/src/server/middlewares/security-headers.ts),
|
|
151
|
+
// 生产构建无 Vite dev server,middleware 即唯一来源。
|
|
152
|
+
headers: {
|
|
153
|
+
'X-Content-Type-Options': 'nosniff',
|
|
154
|
+
'X-Frame-Options': 'DENY',
|
|
155
|
+
'Cross-Origin-Resource-Policy': 'same-origin',
|
|
156
|
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
|
157
|
+
'Permissions-Policy':
|
|
158
|
+
'camera=(), microphone=(), geolocation=(), payment=(), usb=(), fullscreen=(self)',
|
|
159
|
+
'Referrer-Policy': 'strict-origin-when-cross-origin',
|
|
160
|
+
},
|
|
144
161
|
},
|
|
145
162
|
plugins,
|
|
146
163
|
resolve: {
|
package/public/file.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/window.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
// AllBlue - 理想之海
|
|
3
|
-
// Copyright (C) 2026 AllBlue Contributors
|
|
4
|
-
//
|
|
5
|
-
// This program is free software: you can redistribute it and/or modify
|
|
6
|
-
// it under the terms of the GNU Affero General Public License as published by
|
|
7
|
-
// the Free Software Foundation, either version 3 of the License, or
|
|
8
|
-
// (at your option) any later version.
|
|
9
|
-
//
|
|
10
|
-
// This program is distributed in the hope that it will be useful,
|
|
11
|
-
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
// GNU Affero General Public License for more details.
|
|
14
|
-
//
|
|
15
|
-
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
-
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
-
|
|
18
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
19
|
-
|
|
20
|
-
describe('getApiAgent', () => {
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
vi.resetModules()
|
|
23
|
-
delete process.env.OPENAI_API_KEY
|
|
24
|
-
delete process.env.AI_MODEL
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('returns undefined when no API key set', async () => {
|
|
28
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
29
|
-
expect(getApiAgent()).toBeUndefined()
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
it('returns undefined when API key is placeholder', async () => {
|
|
33
|
-
process.env.OPENAI_API_KEY = 'sk-your-api-key-here'
|
|
34
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
35
|
-
expect(getApiAgent()).toBeUndefined()
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it('returns agent info when API key is valid', async () => {
|
|
39
|
-
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
40
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
41
|
-
const agent = getApiAgent()
|
|
42
|
-
expect(agent).toBeDefined()
|
|
43
|
-
expect(agent?.id).toBe('openai-api')
|
|
44
|
-
expect(agent?.available).toBe(true)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('uses AI_MODEL env in display name', async () => {
|
|
48
|
-
process.env.OPENAI_API_KEY = 'sk-real-key'
|
|
49
|
-
process.env.AI_MODEL = 'gpt-4'
|
|
50
|
-
const { getApiAgent } = await import('@/shared/ai-cli/cli-detector')
|
|
51
|
-
expect(getApiAgent()?.name).toContain('gpt-4')
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
describe('detectAgents', () => {
|
|
56
|
-
beforeEach(() => {
|
|
57
|
-
vi.resetModules()
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
it('returns 16 known agents with executable check', async () => {
|
|
61
|
-
const { detectAgents } = await import('@/shared/ai-cli/cli-detector')
|
|
62
|
-
const agents = await detectAgents()
|
|
63
|
-
expect(agents).toHaveLength(16)
|
|
64
|
-
agents.forEach(a => {
|
|
65
|
-
expect(a.id).toBeDefined()
|
|
66
|
-
expect(a.binary).toBeDefined()
|
|
67
|
-
})
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
it('handles all agents failing gracefully', async () => {
|
|
71
|
-
vi.doMock('child_process', () => ({
|
|
72
|
-
execSync: vi.fn(() => { throw new Error('not found') }),
|
|
73
|
-
}))
|
|
74
|
-
|
|
75
|
-
const { detectAgents } = await import('@/shared/ai-cli/cli-detector')
|
|
76
|
-
const agents = await detectAgents()
|
|
77
|
-
expect(agents).toHaveLength(16)
|
|
78
|
-
agents.forEach(a => expect(a.available).toBe(false))
|
|
79
|
-
})
|
|
80
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/src/components/workspace/{workspace-member-item.tsx → members/workspace-member-item.tsx}
RENAMED
|
File without changes
|
/package/src/components/workspace/{workspace-project-list.tsx → projects/workspace-project-list.tsx}
RENAMED
|
File without changes
|