@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.
- package/package.json +25 -33
- package/src/components/command/command-palette.tsx +1 -1
- package/src/components/knowledge/knowledge-toolbar.tsx +29 -1
- package/src/components/layout/sidebar-03/app-sidebar.tsx +3 -4
- package/src/components/layout/sidebar-03/nav-notifications.tsx +135 -52
- package/src/components/layout/sidebar-03/user-menu.tsx +4 -2
- package/src/components/notifications/settings-notifications.tsx +198 -0
- package/src/components/notifications/team-notifications.tsx +343 -0
- package/src/components/settings/personal-team/team/team-client.tsx +51 -12
- package/src/components/settings/personal-team/team/team-invitations.tsx +181 -181
- package/src/components/shared/wave-physics-loader.tsx +155 -0
- package/src/components/story/__tests__/dependency-type-badge.test.ts +89 -0
- package/src/components/story/constants.ts +114 -51
- package/src/components/story/detail/story-detail-body.tsx +13 -3
- package/src/components/story/detail/story-detail-toolbar/children.tsx +49 -274
- package/src/components/story/detail/story-detail-toolbar/dependencies.tsx +13 -45
- package/src/components/story/detail/story-detail-toolbar/dependency-panel.tsx +465 -0
- package/src/components/story/detail/story-detail-toolbar.tsx +84 -3
- package/src/components/story/display/__tests__/use-paged-story-groups.test.ts +152 -1
- package/src/components/story/display/use-paged-story-groups.ts +370 -28
- package/src/components/story/inline-editors/story-select-dialog.tsx +88 -12
- package/src/components/story/peek-panel.tsx +8 -1
- package/src/components/story/relations/story-ref-row.tsx +319 -0
- package/src/components/story/story-list-view.tsx +3 -1
- package/src/components/story/story-panel-header.tsx +4 -1
- package/src/components/story/types.ts +17 -18
- package/src/components/story/views/story-list/batch-toolbar.tsx +12 -1
- package/src/components/story/views/story-list/index.tsx +16 -5
- package/src/components/story/views/story-list/row.tsx +5 -4
- package/src/components/ui/separator.tsx +1 -1
- package/src/hooks/use-notifications.ts +4 -1
- package/src/lib/__tests__/datetime.test.ts +16 -1
- package/src/lib/__tests__/rag.test.ts +37 -2
- package/src/lib/changelog/sdk-versions.ts +30 -30
- package/src/lib/commands/registry.ts +1 -1
- package/src/lib/commands/types.ts +3 -0
- package/src/lib/datetime.ts +1 -0
- package/src/lib/rag.ts +9 -2
- package/src/routeTree.gen.ts +23 -0
- package/src/routes/_nav/notifications/route.lazy.tsx +22 -0
- package/src/routes/_nav/notifications/route.tsx +30 -0
- package/src/routes/_nav/settings/notifications/route.lazy.tsx +6 -5
- package/src/routes/api/notifications/stream.ts +4 -0
- package/src/server/ai-ports.ts +1 -1
- package/src/server/knowledge-public/github-sync.ts +1 -1
- package/src/server/notifications/__tests__/notification-service.test.ts +189 -0
- package/src/server/notifications/notification-service.ts +137 -27
- package/src/server/serverFns/__tests__/knowledge-index.test.ts +178 -0
- package/src/server/serverFns/__tests__/team-invitation.test.ts +73 -7
- package/src/server/serverFns/__tests__/team-join-request.test.ts +5 -3
- package/src/server/serverFns/knowledge-index-utils.ts +104 -0
- package/src/server/serverFns/knowledge-index.ts +6 -67
- package/src/server/serverFns/knowledge.ts +8 -2
- package/src/server/serverFns/locale.ts +15 -3
- package/src/server/serverFns/notifications.ts +18 -4
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +239 -0
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +19 -1
- package/src/server/serverFns/story/index.ts +1 -2
- package/src/server/serverFns/story/story-dependencies.ts +107 -163
- package/src/server/serverFns/story/story-groups.ts +23 -3
- package/src/server/serverFns/story/story-transfer.ts +5 -3
- package/src/server/serverFns/team.ts +51 -2
- package/src/components/knowledge/knowledge-settings-panel.tsx +0 -148
- package/src/components/notifications/notification-list.tsx +0 -232
- package/src/components/notifications/notification-preferences.tsx +0 -156
- package/src/components/story/relation-badge.tsx +0 -57
- package/src/components/story/relations/dependency-panel.tsx +0 -190
- package/src/components/story/relations/hierarchy-panel.tsx +0 -180
- package/src/components/story/relations/tab.tsx +0 -212
- package/src/config/sidebar-notifications.ts +0 -50
- package/src/lib/openai-client.ts +0 -62
- package/src/plugins/.gen-manifest.json +0 -13
- package/src/plugins/modules.gen.ts +0 -20
- package/src/plugins/plugins.gen.ts +0 -20
- package/src/plugins/ui.gen.ts +0 -8
- package/src/routes/_nav/inspiration/route.tsx +0 -8
- package/src/routes/_nav/prd/$id/route.tsx +0 -9
- package/src/routes/_nav/prd/index.tsx +0 -9
- package/src/routes/_nav/prd/route.tsx +0 -8
- package/src/routes/_nav/tags/$id/route.tsx +0 -8
- package/src/routes/_nav/tags/index.tsx +0 -8
- package/src/routes/_nav/tags/route.tsx +0 -8
- package/src/routes/_nav/tags/settings/route.tsx +0 -9
- 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
|
-
];
|
package/src/lib/openai-client.ts
DELETED
|
@@ -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
|
-
]
|
package/src/plugins/ui.gen.ts
DELETED
|
@@ -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
|
-
})
|