@allbluecn/web-app 0.4.21 → 0.5.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.
Files changed (84) hide show
  1. package/package.json +25 -33
  2. package/src/components/command/command-palette.tsx +1 -1
  3. package/src/components/knowledge/knowledge-toolbar.tsx +29 -1
  4. package/src/components/layout/sidebar-03/app-sidebar.tsx +3 -4
  5. package/src/components/layout/sidebar-03/nav-notifications.tsx +135 -52
  6. package/src/components/layout/sidebar-03/user-menu.tsx +4 -2
  7. package/src/components/notifications/settings-notifications.tsx +198 -0
  8. package/src/components/notifications/team-notifications.tsx +343 -0
  9. package/src/components/settings/personal-team/team/team-client.tsx +51 -12
  10. package/src/components/settings/personal-team/team/team-invitations.tsx +181 -181
  11. package/src/components/shared/wave-physics-loader.tsx +155 -0
  12. package/src/components/story/__tests__/dependency-type-badge.test.ts +89 -0
  13. package/src/components/story/constants.ts +114 -51
  14. package/src/components/story/detail/story-detail-body.tsx +13 -3
  15. package/src/components/story/detail/story-detail-toolbar/children.tsx +49 -274
  16. package/src/components/story/detail/story-detail-toolbar/dependencies.tsx +13 -45
  17. package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +465 -0
  18. package/src/components/story/detail/story-detail-toolbar.tsx +84 -3
  19. package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +152 -1
  20. package/src/components/story/display/use-paged-story-groups.ts +370 -28
  21. package/src/components/story/inline-editors/story-select-dialog.tsx +88 -12
  22. package/src/components/story/peek-panel.tsx +8 -1
  23. package/src/components/story/relations/story-ref-row.tsx +319 -0
  24. package/src/components/story/story-list-view.tsx +3 -1
  25. package/src/components/story/story-panel-header.tsx +4 -1
  26. package/src/components/story/types.ts +17 -18
  27. package/src/components/story/views/story-list/batch-toolbar.tsx +12 -1
  28. package/src/components/story/views/story-list/index.tsx +16 -5
  29. package/src/components/story/views/story-list/row.tsx +5 -4
  30. package/src/components/ui/separator.tsx +1 -1
  31. package/src/hooks/use-notifications.ts +4 -1
  32. package/src/lib/__tests__/datetime.test.ts +16 -1
  33. package/src/lib/__tests__/rag.test.ts +37 -2
  34. package/src/lib/changelog/sdk-versions.ts +30 -30
  35. package/src/lib/commands/registry.ts +1 -1
  36. package/src/lib/commands/types.ts +3 -0
  37. package/src/lib/datetime.ts +1 -0
  38. package/src/lib/rag.ts +9 -2
  39. package/src/routeTree.gen.ts +23 -0
  40. package/src/routes/_nav/notifications/route.lazy.tsx +22 -0
  41. package/src/routes/_nav/notifications/route.tsx +30 -0
  42. package/src/routes/_nav/settings/notifications/route.lazy.tsx +6 -5
  43. package/src/routes/api/notifications/stream.ts +4 -0
  44. package/src/server/ai-ports.ts +1 -1
  45. package/src/server/knowledge-public/github-sync.ts +1 -1
  46. package/src/server/notifications/__tests__/notification-service.test.ts +189 -0
  47. package/src/server/notifications/notification-service.ts +137 -27
  48. package/src/server/serverFns/__tests__/knowledge-index.test.ts +178 -0
  49. package/src/server/serverFns/__tests__/team-invitation.test.ts +73 -7
  50. package/src/server/serverFns/__tests__/team-join-request.test.ts +5 -3
  51. package/src/server/serverFns/knowledge-index-utils.ts +104 -0
  52. package/src/server/serverFns/knowledge-index.ts +6 -67
  53. package/src/server/serverFns/knowledge.ts +8 -2
  54. package/src/server/serverFns/locale.ts +15 -3
  55. package/src/server/serverFns/notifications.ts +18 -4
  56. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +239 -0
  57. package/src/server/serverFns/story/__tests__/story-groups.test.ts +19 -1
  58. package/src/server/serverFns/story/index.ts +1 -2
  59. package/src/server/serverFns/story/story-dependencies.ts +107 -163
  60. package/src/server/serverFns/story/story-groups.ts +23 -3
  61. package/src/server/serverFns/story/story-transfer.ts +5 -3
  62. package/src/server/serverFns/team.ts +51 -2
  63. package/src/components/knowledge/knowledge-settings-panel.tsx +0 -148
  64. package/src/components/notifications/notification-list.tsx +0 -232
  65. package/src/components/notifications/notification-preferences.tsx +0 -156
  66. package/src/components/story/relation-badge.tsx +0 -57
  67. package/src/components/story/relations/dependency-panel.tsx +0 -190
  68. package/src/components/story/relations/hierarchy-panel.tsx +0 -180
  69. package/src/components/story/relations/tab.tsx +0 -212
  70. package/src/config/sidebar-notifications.ts +0 -50
  71. package/src/lib/openai-client.ts +0 -62
  72. package/src/plugins/.gen-manifest.json +0 -13
  73. package/src/plugins/modules.gen.ts +0 -20
  74. package/src/plugins/plugins.gen.ts +0 -20
  75. package/src/plugins/ui.gen.ts +0 -8
  76. package/src/routes/_nav/inspiration/route.tsx +0 -8
  77. package/src/routes/_nav/prd/$id/route.tsx +0 -9
  78. package/src/routes/_nav/prd/index.tsx +0 -9
  79. package/src/routes/_nav/prd/route.tsx +0 -8
  80. package/src/routes/_nav/tags/$id/route.tsx +0 -8
  81. package/src/routes/_nav/tags/index.tsx +0 -8
  82. package/src/routes/_nav/tags/route.tsx +0 -8
  83. package/src/routes/_nav/tags/settings/route.tsx +0 -9
  84. package/src/routes/share/prd/$prdId/route.tsx +0 -10
@@ -1,180 +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 * as React from "react";
19
- import { Plus, Minus, GitBranch } from "lucide-react";
20
- import { useTranslation } from "react-i18next";
21
- import { Button } from "@/components/ui/button";
22
- import { StorySearchPopover } from "../search-popover";
23
- import { useServerFn } from "@tanstack/react-start";
24
- import { addChildFn, removeChildFn, getChildrenCountFn, getStoryDetailFn } from "@/server/serverFns/story";
25
- import { toast } from "sonner";
26
- import { type StoryBrief, StoryListItem } from "../types";
27
-
28
- export interface HierarchyPanelProps {
29
- storyId: string;
30
- data: {
31
- parent: StoryBrief | null;
32
- children: StoryListItem[];
33
- hasChildren: boolean;
34
- };
35
- onUpdate: (updates: Partial<{ parent: StoryBrief | null; children: StoryListItem[]; hasChildren: boolean }>) => void;
36
- }
37
-
38
- export function HierarchyPanel({ storyId, data, onUpdate }: HierarchyPanelProps) {
39
- const { t } = useTranslation("story");
40
- const [addingChild, setAddingChild] = React.useState(false);
41
- const addChild = useServerFn(addChildFn);
42
- const removeChild = useServerFn(removeChildFn);
43
- const getChildrenCount = useServerFn(getChildrenCountFn);
44
- const getStoryDetail = useServerFn(getStoryDetailFn);
45
-
46
- const handleAddChild = async (childId: string) => {
47
- try {
48
- await addChild({ data: { parentId: storyId, childId } });
49
- toast.success(t("hierarchy.childAdded"));
50
-
51
- const [{ count }, storyDetail] = await Promise.all([
52
- getChildrenCount({ data: { parentId: storyId } }),
53
- getStoryDetail({ data: { storyId: childId } }),
54
- ]);
55
-
56
- onUpdate({
57
- children: [...data.children, storyDetail as StoryListItem],
58
- hasChildren: count > 0,
59
- });
60
-
61
- setAddingChild(false);
62
- } catch (error: any) {
63
- console.error(t("hierarchy.addChildFailed"), error);
64
- toast.error(error.message || t("hierarchy.addChildFailed"));
65
- }
66
- };
67
-
68
- const handleRemoveChild = async (childId: string) => {
69
- try {
70
- await removeChild({ data: { childId } });
71
- toast.success(t("hierarchy.childRemoved"));
72
-
73
- const { count } = await getChildrenCount({ data: { parentId: storyId } });
74
- onUpdate({
75
- children: data.children.filter((c) => c.id !== childId),
76
- hasChildren: count > 0,
77
- });
78
- } catch (error: any) {
79
- console.error(t("hierarchy.removeChildFailed"), error);
80
- toast.error(error.message || t("hierarchy.removeChildFailed"));
81
- }
82
- };
83
-
84
- return (
85
- <div className="space-y-4">
86
- <div>
87
- <div className="text-sm font-medium mb-2">{t("hierarchy.parent")}</div>
88
- {data.parent ? (
89
- <div className="flex items-center justify-between p-3 bg-muted rounded-md">
90
- <div className="flex items-center gap-2">
91
- <GitBranch className="h-4 w-4 text-muted-foreground" />
92
- <span className="text-sm">{data.parent.title}</span>
93
- </div>
94
- <Button
95
- type="button"
96
- variant="ghost"
97
- size="sm"
98
- onClick={() => handleRemoveChild(storyId)}
99
- >
100
- <Minus className="h-4 w-4" />
101
- </Button>
102
- </div>
103
- ) : (
104
- <div className="flex items-center justify-between">
105
- <span className="text-sm text-muted-foreground">{t("hierarchy.noParent")}</span>
106
- <StorySearchPopover
107
- onSelect={handleAddChild}
108
- placeholder={t("hierarchy.setParent")}
109
- trigger={
110
- <Button type="button" variant="outline" size="sm">
111
- <Plus className="h-4 w-4 mr-2" />
112
- {t("hierarchy.setParent")}
113
- </Button>
114
- }
115
- />
116
- </div>
117
- )}
118
- </div>
119
-
120
- <div>
121
- <div className="flex items-center justify-between mb-2">
122
- <div className="text-sm font-medium">{t("hierarchy.children")}</div>
123
- <Button
124
- type="button"
125
- variant="outline"
126
- size="sm"
127
- onClick={() => setAddingChild(!addingChild)}
128
- >
129
- <Plus className="h-4 w-4 mr-2" />
130
- {t("hierarchy.addChild")}
131
- </Button>
132
- </div>
133
-
134
- {addingChild && (
135
- <div className="mb-3">
136
- <StorySearchPopover
137
- onSelect={handleAddChild}
138
- placeholder={t("hierarchy.searchStory")}
139
- excludeIds={[storyId, ...data.children.map((c) => c.id)]}
140
- trigger={
141
- <Button type="button" variant="outline" size="sm" className="w-full">
142
- <Plus className="h-4 w-4 mr-2" />
143
- {t("hierarchy.selectChild")}
144
- </Button>
145
- }
146
- />
147
- </div>
148
- )}
149
-
150
- {data.children.length > 0 ? (
151
- <div className="space-y-2">
152
- {data.children.map((child) => (
153
- <div
154
- key={child.id}
155
- className="flex items-center justify-between p-3 bg-muted rounded-md"
156
- >
157
- <div className="flex items-center gap-2">
158
- <GitBranch className="h-4 w-4 text-muted-foreground" />
159
- <span className="text-sm">{child.name}</span>
160
- </div>
161
- <Button
162
- type="button"
163
- variant="ghost"
164
- size="sm"
165
- onClick={() => handleRemoveChild(child.id)}
166
- >
167
- <Minus className="h-4 w-4" />
168
- </Button>
169
- </div>
170
- ))}
171
- </div>
172
- ) : (
173
- <div className="text-sm text-muted-foreground text-center py-4">
174
- {t("hierarchy.noChildren")}
175
- </div>
176
- )}
177
- </div>
178
- </div>
179
- );
180
- }
@@ -1,212 +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 * as React from "react";
19
- import { useTranslation } from "react-i18next";
20
- import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
21
- import { RELATIONS_ACCORDION_ITEMS } from "../constants";
22
- import { HierarchyPanel } from "./hierarchy-panel";
23
- import { DependencyPanel } from "./dependency-panel";
24
- import { LinksPanel } from "./links-panel";
25
- import { AttachmentsPanel } from "./attachments-panel";
26
- import { KnowledgePanel } from "./knowledge-panel";
27
- import { type RelationsData } from "../types";
28
- import {
29
- listChildStoriesFn,
30
- getDependenciesFn,
31
- getLinksFn,
32
- getAttachmentsFn,
33
- getLinkedPrdsFn,
34
- getLinkedDocsFn,
35
- } from "@/server/serverFns/story";
36
- import { toast } from "sonner";
37
-
38
- export interface RelationsTabProps {
39
- storyId: string;
40
- initialData?: RelationsData;
41
- }
42
-
43
- export function RelationsTab({ storyId, initialData }: RelationsTabProps) {
44
- const { t } = useTranslation("story");
45
- const [data, setData] = React.useState<RelationsData>(
46
- initialData || {
47
- hierarchy: { parent: null, children: [], hasChildren: false },
48
- dependencies: { blocking: [], blockedBy: [], relatesTo: [] },
49
- links: [],
50
- attachments: [],
51
- knowledge: { prds: [], docs: [] },
52
- }
53
- );
54
- const [loading, setLoading] = React.useState(false);
55
-
56
- const refreshAll = React.useCallback(async () => {
57
- setLoading(true);
58
- try {
59
- const [childrenStories, deps, links, attachments, prds, docs] = await Promise.all([
60
- listChildStoriesFn({ data: { parentId: storyId } }),
61
- getDependenciesFn({ data: { storyId } }),
62
- getLinksFn({ data: { storyId } }),
63
- getAttachmentsFn({ data: { storyId } }),
64
- getLinkedPrdsFn({ data: { storyId } }),
65
- getLinkedDocsFn({ data: { storyId } }),
66
- ]);
67
-
68
- setData({
69
- hierarchy: {
70
- parent: null,
71
- children: childrenStories,
72
- hasChildren: childrenStories.length > 0,
73
- },
74
- dependencies: deps as RelationsData["dependencies"],
75
- links,
76
- attachments,
77
- knowledge: {
78
- prds,
79
- docs,
80
- },
81
- });
82
- } catch (error) {
83
- console.error(t("relationsTab.refreshFailed"), error);
84
- toast.error(t("relationsTab.refreshFailed"));
85
- } finally {
86
- setLoading(false);
87
- }
88
- }, [storyId]);
89
-
90
- React.useEffect(() => {
91
- if (!initialData) {
92
- refreshAll();
93
- }
94
- }, [initialData, refreshAll]);
95
-
96
- const updateHierarchy = React.useCallback(
97
- (updates: Partial<RelationsData["hierarchy"]>) => {
98
- setData((prev) => ({
99
- ...prev,
100
- hierarchy: { ...prev.hierarchy, ...updates },
101
- }));
102
- },
103
- []
104
- );
105
-
106
- const updateDependencies = React.useCallback(
107
- (updates: Partial<RelationsData["dependencies"]>) => {
108
- setData((prev) => ({
109
- ...prev,
110
- dependencies: { ...prev.dependencies, ...updates },
111
- }));
112
- },
113
- []
114
- );
115
-
116
- const updateLinks = React.useCallback(
117
- (newLinks: RelationsData["links"]) => {
118
- setData((prev) => ({ ...prev, links: newLinks }));
119
- },
120
- []
121
- );
122
-
123
- const updateAttachments = React.useCallback(
124
- (newAttachments: RelationsData["attachments"]) => {
125
- setData((prev) => ({ ...prev, attachments: newAttachments }));
126
- },
127
- []
128
- );
129
-
130
- const updateKnowledge = React.useCallback(
131
- (updates: Partial<RelationsData["knowledge"]>) => {
132
- setData((prev) => ({
133
- ...prev,
134
- knowledge: { ...prev.knowledge, ...updates },
135
- }));
136
- },
137
- []
138
- );
139
-
140
- return (
141
- <div className="space-y-4">
142
- <div className="flex items-center justify-between">
143
- <h3 className="text-lg font-semibold">{t("relationsTab.title")}</h3>
144
- <button
145
- type="button"
146
- onClick={refreshAll}
147
- disabled={loading}
148
- className="text-sm text-muted-foreground hover:text-foreground disabled:opacity-50"
149
- >
150
- {t(loading ? "relationsTab.refreshing" : "relationsTab.refresh")}
151
- </button>
152
- </div>
153
-
154
- <Accordion type="multiple" defaultValue={Object.values(RELATIONS_ACCORDION_ITEMS)} className="w-full">
155
- <AccordionItem value={RELATIONS_ACCORDION_ITEMS.HIERARCHY}>
156
- <AccordionTrigger>{t("relationsTab.hierarchy")}</AccordionTrigger>
157
- <AccordionContent>
158
- <HierarchyPanel
159
- storyId={storyId}
160
- data={data.hierarchy}
161
- onUpdate={updateHierarchy}
162
- />
163
- </AccordionContent>
164
- </AccordionItem>
165
-
166
- <AccordionItem value={RELATIONS_ACCORDION_ITEMS.DEPENDENCIES}>
167
- <AccordionTrigger>{t("relationsTab.dependencies")}</AccordionTrigger>
168
- <AccordionContent>
169
- <DependencyPanel
170
- storyId={storyId}
171
- data={data.dependencies}
172
- onUpdate={updateDependencies}
173
- />
174
- </AccordionContent>
175
- </AccordionItem>
176
-
177
- <AccordionItem value={RELATIONS_ACCORDION_ITEMS.LINKS}>
178
- <AccordionTrigger>{t("relationsTab.links")}</AccordionTrigger>
179
- <AccordionContent>
180
- <LinksPanel
181
- storyId={storyId}
182
- data={data.links}
183
- onUpdate={updateLinks}
184
- />
185
- </AccordionContent>
186
- </AccordionItem>
187
-
188
- <AccordionItem value={RELATIONS_ACCORDION_ITEMS.ATTACHMENTS}>
189
- <AccordionTrigger>{t("relationsTab.attachments")}</AccordionTrigger>
190
- <AccordionContent>
191
- <AttachmentsPanel
192
- storyId={storyId}
193
- data={data.attachments}
194
- onUpdate={updateAttachments}
195
- />
196
- </AccordionContent>
197
- </AccordionItem>
198
-
199
- <AccordionItem value={RELATIONS_ACCORDION_ITEMS.KNOWLEDGE}>
200
- <AccordionTrigger>{t("relationsTab.knowledge")}</AccordionTrigger>
201
- <AccordionContent>
202
- <KnowledgePanel
203
- storyId={storyId}
204
- data={data.knowledge}
205
- onUpdate={updateKnowledge}
206
- />
207
- </AccordionContent>
208
- </AccordionItem>
209
- </Accordion>
210
- </div>
211
- );
212
- }
@@ -1,50 +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 { getBoringAvatarUrl } from "@/lib/avatar";
19
-
20
- export type Notification = {
21
- id: string;
22
- avatar: string;
23
- fallback: string;
24
- text: string;
25
- time: string;
26
- };
27
-
28
- export const notifications: Notification[] = [
29
- {
30
- id: "1",
31
- avatar: getBoringAvatarUrl("order-1", { size: 40, square: true }),
32
- fallback: "OM",
33
- text: "New order received.",
34
- time: "10m ago",
35
- },
36
- {
37
- id: "2",
38
- avatar: getBoringAvatarUrl("server-upgrade", { size: 40, square: true }),
39
- fallback: "JL",
40
- text: "Server upgrade completed.",
41
- time: "1h ago",
42
- },
43
- {
44
- id: "3",
45
- avatar: getBoringAvatarUrl("new-user- signup", { size: 40, square: true }),
46
- fallback: "HH",
47
- text: "New user signed up.",
48
- time: "2h ago",
49
- },
50
- ];
@@ -1,62 +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 OpenAI from "openai"
19
-
20
- const PLACEHOLDER_KEYS = new Set(["sk-your-api-key-here", ""])
21
-
22
- function resolveApiKey(): string | null {
23
- const openai = process.env.OPENAI_API_KEY
24
- if (openai && !PLACEHOLDER_KEYS.has(openai)) return openai
25
-
26
- const openrouter = process.env.OPENROUTER_API_KEY
27
- if (openrouter && !PLACEHOLDER_KEYS.has(openrouter)) return openrouter
28
-
29
- return null
30
- }
31
-
32
- function resolveBaseURL(): string | undefined {
33
- return process.env.AI_BASE_URL || undefined
34
- }
35
-
36
- const globalForOpenAI = globalThis as unknown as {
37
- openAIClient: OpenAI | undefined
38
- }
39
-
40
- export function getOpenAIClient(opts?: { apiKey?: string; baseUrl?: string }): OpenAI {
41
- const { apiKey, baseUrl } = opts || {}
42
-
43
- if (apiKey) {
44
- const url = baseUrl || resolveBaseURL()
45
- return new OpenAI({ apiKey, ...(url ? { baseURL: url } : {}) })
46
- }
47
-
48
- if (globalForOpenAI.openAIClient) return globalForOpenAI.openAIClient
49
-
50
- const envKey = resolveApiKey()
51
- if (!envKey) {
52
- throw new Error("OPENAI_API_KEY or OPENROUTER_API_KEY not configured")
53
- }
54
-
55
- const url = resolveBaseURL()
56
- globalForOpenAI.openAIClient = new OpenAI({ apiKey: envKey, ...(url ? { baseURL: url } : {}) })
57
- return globalForOpenAI.openAIClient
58
- }
59
-
60
- export function hasOpenAIKey(): boolean {
61
- return resolveApiKey() !== null
62
- }
@@ -1,13 +0,0 @@
1
- {
2
- "routeFiles": [
3
- "src/routes/_nav/tags/route.tsx",
4
- "src/routes/_nav/tags/index.tsx",
5
- "src/routes/_nav/tags/$id/route.tsx",
6
- "src/routes/_nav/tags/settings/route.tsx",
7
- "src/routes/_nav/inspiration/route.tsx",
8
- "src/routes/_nav/prd/route.tsx",
9
- "src/routes/_nav/prd/index.tsx",
10
- "src/routes/_nav/prd/$id/route.tsx",
11
- "src/routes/share/prd/$prdId/route.tsx"
12
- ]
13
- }
@@ -1,20 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import type { ProFeature } from '@allbluecn/kernel'
3
-
4
- export const activePluginIds = ['@allbluecn/tags', '@allbluecn/inspiration', '@allbluecn/prd'] as const
5
- export type ActivePluginId = (typeof activePluginIds)[number]
6
-
7
- export const implementedModules: Record<ProFeature, boolean> = {
8
- billing: false,
9
- hostedAi: false,
10
- sso: false,
11
- audit: false,
12
- permissions: false,
13
- license: false,
14
- versionHistory: false,
15
- automation: false,
16
- templates: false,
17
- analytics: false,
18
- globalSearch: false,
19
- compliance: false,
20
- }
@@ -1,20 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- // profile: oss (validated: 3 plugins)
3
- // 拓扑序:@allbluecn/tags -> @allbluecn/inspiration -> @allbluecn/prd
4
- import tags from '@allbluecn/plugins-core/tags'
5
- import inspiration from '@allbluecn/plugins-core/inspiration'
6
- import prd from '@allbluecn/plugins-core/prd'
7
- import { defineProfile, type ProfileDefinition } from '@allbluecn/kernel'
8
-
9
- export const profile: ProfileDefinition = defineProfile({
10
- id: 'oss',
11
- plugins: [tags, inspiration, prd],
12
- })
13
-
14
- export const pluginIds: string[] = ['@allbluecn/tags', '@allbluecn/inspiration', '@allbluecn/prd']
15
-
16
- export const pluginMiddleware: unknown[] = [
17
- ...tags.contributes.serverMiddleware ?? [],
18
- ...inspiration.contributes.serverMiddleware ?? [],
19
- ...prd.contributes.serverMiddleware ?? [],
20
- ]
@@ -1,8 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { NavTag } from '@allbluecn/plugins-core/tags/ui'
3
- import { type ComponentType } from 'react'
4
- import type { SettingsTabContribution } from '@allbluecn/kernel'
5
-
6
- export const navItems: ComponentType[] = [NavTag]
7
- export const settingsTabs: SettingsTabContribution[] = []
8
- export const dashboardWidgets: ComponentType[] = []
@@ -1,8 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { inspiration_route_0 } from '@allbluecn/plugins-core/inspiration/routes'
5
-
6
- export const Route = createFileRoute('/_nav/inspiration')({
7
- component: inspiration_route_0.Component as any,
8
- })
@@ -1,9 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { prd_route_2 } from '@allbluecn/plugins-core/prd/routes'
5
-
6
- export const Route = createFileRoute('/_nav/prd/$id')({
7
- beforeLoad: ((ctx: PluginRouteContext) => prd_route_2.beforeLoad(ctx)) as any,
8
- component: prd_route_2.Component as any,
9
- })
@@ -1,9 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { prd_route_1 } from '@allbluecn/plugins-core/prd/routes'
5
-
6
- export const Route = createFileRoute('/_nav/prd/')({
7
- loader: ((ctx: PluginRouteContext) => prd_route_1.loader(ctx)) as any,
8
- component: prd_route_1.Component as any,
9
- })
@@ -1,8 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { prd_route_0 } from '@allbluecn/plugins-core/prd/routes'
5
-
6
- export const Route = createFileRoute('/_nav/prd')({
7
- component: prd_route_0.Component as any,
8
- })
@@ -1,8 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { tags_route_2 } from '@allbluecn/plugins-core/tags/routes'
5
-
6
- export const Route = createFileRoute('/_nav/tags/$id')({
7
- component: tags_route_2.Component as any,
8
- })
@@ -1,8 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { tags_route_1 } from '@allbluecn/plugins-core/tags/routes'
5
-
6
- export const Route = createFileRoute('/_nav/tags/')({
7
- component: tags_route_1.Component as any,
8
- })
@@ -1,8 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { tags_route_0 } from '@allbluecn/plugins-core/tags/routes'
5
-
6
- export const Route = createFileRoute('/_nav/tags')({
7
- component: tags_route_0.Component as any,
8
- })
@@ -1,9 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { tags_route_3 } from '@allbluecn/plugins-core/tags/routes'
5
-
6
- export const Route = createFileRoute('/_nav/tags/settings')({
7
- loader: ((ctx: PluginRouteContext) => tags_route_3.loader(ctx)) as any,
8
- component: tags_route_3.Component as any,
9
- })
@@ -1,10 +0,0 @@
1
- // AUTO-GENERATED by @allbluecn/kernel — DO NOT EDIT
2
- import { createFileRoute } from '@tanstack/react-router'
3
- import type { PluginRouteContext } from '@allbluecn/kernel'
4
- import { prd_route_3 } from '@allbluecn/plugins-core/prd/routes'
5
-
6
- export const Route = createFileRoute('/share/prd/$prdId')({
7
- beforeLoad: ((ctx: PluginRouteContext) => prd_route_3.beforeLoad(ctx)) as any,
8
- validateSearch: prd_route_3.validateSearch as any,
9
- component: prd_route_3.Component as any,
10
- })