@drewswiredin/backstage-plugin-assistants 0.1.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/LICENSE +201 -0
- package/README.md +113 -0
- package/dist/alpha.d.ts +64 -0
- package/dist/alpha.esm.js +35 -0
- package/dist/alpha.esm.js.map +1 -0
- package/dist/api/AssistantsApi.esm.js +55 -0
- package/dist/api/AssistantsApi.esm.js.map +1 -0
- package/dist/collapsible/AssistantDetailDialog.esm.js +209 -0
- package/dist/collapsible/AssistantDetailDialog.esm.js.map +1 -0
- package/dist/collapsible/AssistantsList.esm.js +119 -0
- package/dist/collapsible/AssistantsList.esm.js.map +1 -0
- package/dist/collapsible/CollapsiblePage.esm.js +724 -0
- package/dist/collapsible/CollapsiblePage.esm.js.map +1 -0
- package/dist/collapsible/ConversationsPanel.esm.js +198 -0
- package/dist/collapsible/ConversationsPanel.esm.js.map +1 -0
- package/dist/collapsible/FullHeightRegion.esm.js +54 -0
- package/dist/collapsible/FullHeightRegion.esm.js.map +1 -0
- package/dist/collapsible/SidePane.esm.js +89 -0
- package/dist/collapsible/SidePane.esm.js.map +1 -0
- package/dist/collapsible/surface/AssistantAvatar.esm.js +98 -0
- package/dist/collapsible/surface/AssistantAvatar.esm.js.map +1 -0
- package/dist/collapsible/surface/BackstageLogo.esm.js +24 -0
- package/dist/collapsible/surface/BackstageLogo.esm.js.map +1 -0
- package/dist/collapsible/surface/ConversationSurface.esm.js +268 -0
- package/dist/collapsible/surface/ConversationSurface.esm.js.map +1 -0
- package/dist/collapsible/surface/MarkdownText.esm.js +24 -0
- package/dist/collapsible/surface/MarkdownText.esm.js.map +1 -0
- package/dist/collapsible/surface/MermaidDiagram.esm.js +245 -0
- package/dist/collapsible/surface/MermaidDiagram.esm.js.map +1 -0
- package/dist/collapsible/surface/parts.esm.js +216 -0
- package/dist/collapsible/surface/parts.esm.js.map +1 -0
- package/dist/collapsible/surface/styles/assistant-ui-markdown.css.esm.js +5 -0
- package/dist/collapsible/surface/styles/assistant-ui-markdown.css.esm.js.map +1 -0
- package/dist/collapsible/surface/styles/assistant-ui.css.esm.js +5 -0
- package/dist/collapsible/surface/styles/assistant-ui.css.esm.js.map +1 -0
- package/dist/collapsible/unreadStore.esm.js +79 -0
- package/dist/collapsible/unreadStore.esm.js.map +1 -0
- package/dist/collapsible/useConversations.esm.js +171 -0
- package/dist/collapsible/useConversations.esm.js.map +1 -0
- package/dist/index.d.ts +63 -0
- package/dist/index.esm.js +3 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js +29 -0
- package/dist/node_modules_dist/style-inject/dist/style-inject.es.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +107 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function storageKeys(assistantId) {
|
|
4
|
+
return {
|
|
5
|
+
list: `ai-chat-conversations:${assistantId}`,
|
|
6
|
+
activeId: `ai-chat-active-conversation-id:${assistantId}`
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function generateId() {
|
|
10
|
+
return `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
11
|
+
}
|
|
12
|
+
function load(listKey) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = localStorage.getItem(listKey);
|
|
15
|
+
return raw ? JSON.parse(raw) : [];
|
|
16
|
+
} catch {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function save(listKey, conversations) {
|
|
21
|
+
try {
|
|
22
|
+
localStorage.setItem(listKey, JSON.stringify(conversations));
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getLastConversationId(conversations) {
|
|
27
|
+
return [...conversations].sort(
|
|
28
|
+
(a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
|
|
29
|
+
)[0]?.id ?? null;
|
|
30
|
+
}
|
|
31
|
+
function loadActiveId(activeKey, conversations) {
|
|
32
|
+
try {
|
|
33
|
+
const storedId = localStorage.getItem(activeKey);
|
|
34
|
+
if (storedId && conversations.some((c) => c.id === storedId)) {
|
|
35
|
+
return storedId;
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
}
|
|
39
|
+
return getLastConversationId(conversations);
|
|
40
|
+
}
|
|
41
|
+
function saveActiveId(activeKey, activeId) {
|
|
42
|
+
try {
|
|
43
|
+
if (activeId) {
|
|
44
|
+
localStorage.setItem(activeKey, activeId);
|
|
45
|
+
} else {
|
|
46
|
+
localStorage.removeItem(activeKey);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function persistConversationMessages(assistantId, conversationId, messages) {
|
|
52
|
+
const { list } = storageKeys(assistantId);
|
|
53
|
+
const conversations = load(list);
|
|
54
|
+
if (!conversations.some((c) => c.id === conversationId)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
save(
|
|
58
|
+
list,
|
|
59
|
+
conversations.map(
|
|
60
|
+
(c) => c.id === conversationId ? { ...c, messages, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : c
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function loadInitialState(assistantId) {
|
|
65
|
+
const { list, activeId } = storageKeys(assistantId);
|
|
66
|
+
const conversations = load(list);
|
|
67
|
+
return {
|
|
68
|
+
assistantId,
|
|
69
|
+
conversations,
|
|
70
|
+
activeId: loadActiveId(activeId, conversations)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function useConversations(assistantId) {
|
|
74
|
+
const [state, setState] = useState(
|
|
75
|
+
() => loadInitialState(assistantId)
|
|
76
|
+
);
|
|
77
|
+
if (state.assistantId !== assistantId) {
|
|
78
|
+
setState(loadInitialState(assistantId));
|
|
79
|
+
}
|
|
80
|
+
const { conversations, activeId } = state;
|
|
81
|
+
const keys = storageKeys(state.assistantId);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
save(keys.list, conversations);
|
|
84
|
+
}, [keys.list, conversations]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
saveActiveId(keys.activeId, activeId);
|
|
87
|
+
}, [keys.activeId, activeId]);
|
|
88
|
+
const activeConversation = conversations.find((c) => c.id === activeId) ?? null;
|
|
89
|
+
const createConversation = useCallback(() => {
|
|
90
|
+
const id = generateId();
|
|
91
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
92
|
+
const conv = {
|
|
93
|
+
id,
|
|
94
|
+
title: "New Chat",
|
|
95
|
+
createdAt: now,
|
|
96
|
+
updatedAt: now,
|
|
97
|
+
pinned: false,
|
|
98
|
+
messages: []
|
|
99
|
+
};
|
|
100
|
+
setState((prev) => ({
|
|
101
|
+
...prev,
|
|
102
|
+
conversations: [conv, ...prev.conversations],
|
|
103
|
+
activeId: id
|
|
104
|
+
}));
|
|
105
|
+
return id;
|
|
106
|
+
}, []);
|
|
107
|
+
const updateMessages = useCallback((id, messages) => {
|
|
108
|
+
setState((prev) => ({
|
|
109
|
+
...prev,
|
|
110
|
+
conversations: prev.conversations.map(
|
|
111
|
+
(c) => c.id === id ? { ...c, messages, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : c
|
|
112
|
+
)
|
|
113
|
+
}));
|
|
114
|
+
}, []);
|
|
115
|
+
const renameConversation = useCallback((id, title) => {
|
|
116
|
+
setState((prev) => ({
|
|
117
|
+
...prev,
|
|
118
|
+
conversations: prev.conversations.map(
|
|
119
|
+
(c) => c.id === id ? { ...c, title, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : c
|
|
120
|
+
)
|
|
121
|
+
}));
|
|
122
|
+
}, []);
|
|
123
|
+
const pinConversation = useCallback((id) => {
|
|
124
|
+
setState((prev) => ({
|
|
125
|
+
...prev,
|
|
126
|
+
conversations: prev.conversations.map(
|
|
127
|
+
(c) => c.id === id ? { ...c, pinned: !c.pinned, updatedAt: (/* @__PURE__ */ new Date()).toISOString() } : c
|
|
128
|
+
)
|
|
129
|
+
}));
|
|
130
|
+
}, []);
|
|
131
|
+
const deleteConversation = useCallback((id) => {
|
|
132
|
+
setState((prev) => {
|
|
133
|
+
const next = prev.conversations.filter((c) => c.id !== id);
|
|
134
|
+
return {
|
|
135
|
+
...prev,
|
|
136
|
+
conversations: next,
|
|
137
|
+
activeId: prev.activeId === id ? getLastConversationId(next) : prev.activeId
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}, []);
|
|
141
|
+
const selectConversation = useCallback((id) => {
|
|
142
|
+
setState((prev) => ({ ...prev, activeId: id }));
|
|
143
|
+
}, []);
|
|
144
|
+
const setConversationModel = useCallback((id, model) => {
|
|
145
|
+
setState((prev) => ({
|
|
146
|
+
...prev,
|
|
147
|
+
conversations: prev.conversations.map(
|
|
148
|
+
(c) => c.id === id ? { ...c, model } : c
|
|
149
|
+
)
|
|
150
|
+
}));
|
|
151
|
+
}, []);
|
|
152
|
+
const sortedConversations = [...conversations].sort((a, b) => {
|
|
153
|
+
if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;
|
|
154
|
+
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
conversations: sortedConversations,
|
|
158
|
+
activeId,
|
|
159
|
+
activeConversation,
|
|
160
|
+
createConversation,
|
|
161
|
+
selectConversation,
|
|
162
|
+
updateMessages,
|
|
163
|
+
renameConversation,
|
|
164
|
+
pinConversation,
|
|
165
|
+
deleteConversation,
|
|
166
|
+
setConversationModel
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export { persistConversationMessages, useConversations };
|
|
171
|
+
//# sourceMappingURL=useConversations.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useConversations.esm.js","sources":["../../src/collapsible/useConversations.ts"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport type { UIMessage } from 'ai';\nimport type { ModelId } from '@drewswiredin/backstage-plugin-assistants-common';\n\n/**\n * A single locally-persisted chat conversation.\n *\n * @public\n */\nexport interface Conversation {\n id: string;\n title: string;\n createdAt: string;\n updatedAt: string;\n pinned: boolean;\n messages: UIMessage[];\n /**\n * The model last selected for this conversation. Restored when the chat is\n * reopened; absent means \"use the assistant's default\". The caller validates\n * it against the assistant's current allowlist and falls back to the default\n * when it's no longer available.\n */\n model?: ModelId;\n}\n\n/**\n * localStorage keys for a given assistant's conversation set. Keys are\n * namespaced by `assistantId` so each assistant owns an isolated set of\n * conversations (see the `:` suffix).\n */\nfunction storageKeys(assistantId: string) {\n return {\n list: `ai-chat-conversations:${assistantId}`,\n activeId: `ai-chat-active-conversation-id:${assistantId}`,\n };\n}\n\nfunction generateId(): string {\n return `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nfunction load(listKey: string): Conversation[] {\n try {\n const raw = localStorage.getItem(listKey);\n return raw ? (JSON.parse(raw) as Conversation[]) : [];\n } catch {\n return [];\n }\n}\n\nfunction save(listKey: string, conversations: Conversation[]) {\n try {\n localStorage.setItem(listKey, JSON.stringify(conversations));\n } catch {\n // storage full or unavailable\n }\n}\n\nfunction getLastConversationId(conversations: Conversation[]): string | null {\n return (\n [...conversations].sort(\n (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),\n )[0]?.id ?? null\n );\n}\n\nfunction loadActiveId(\n activeKey: string,\n conversations: Conversation[],\n): string | null {\n try {\n const storedId = localStorage.getItem(activeKey);\n if (storedId && conversations.some(c => c.id === storedId)) {\n return storedId;\n }\n } catch {\n // storage unavailable\n }\n return getLastConversationId(conversations);\n}\n\nfunction saveActiveId(activeKey: string, activeId: string | null) {\n try {\n if (activeId) {\n localStorage.setItem(activeKey, activeId);\n } else {\n localStorage.removeItem(activeKey);\n }\n } catch {\n // storage unavailable\n }\n}\n\n/**\n * Persist a conversation's messages directly to an assistant's namespaced\n * storage, without going through the React hook. Used by the live-thread manager\n * to save a reply that finished in the background for an assistant other than\n * the one currently on screen (whose `useConversations` state isn't mounted).\n * No-op if the conversation no longer exists.\n *\n * @public\n */\nexport function persistConversationMessages(\n assistantId: string,\n conversationId: string,\n messages: UIMessage[],\n) {\n const { list } = storageKeys(assistantId);\n const conversations = load(list);\n if (!conversations.some(c => c.id === conversationId)) {\n return;\n }\n save(\n list,\n conversations.map(c =>\n c.id === conversationId\n ? { ...c, messages, updatedAt: new Date().toISOString() }\n : c,\n ),\n );\n}\n\ninterface InternalState {\n /** The assistant these conversations belong to (kept in state so saves never\n * cross namespaces during an assistant switch). */\n assistantId: string;\n conversations: Conversation[];\n activeId: string | null;\n}\n\nfunction loadInitialState(assistantId: string): InternalState {\n const { list, activeId } = storageKeys(assistantId);\n const conversations = load(list);\n return {\n assistantId,\n conversations,\n activeId: loadActiveId(activeId, conversations),\n };\n}\n\n/**\n * The shape returned by {@link useConversations}.\n *\n * @public\n */\nexport interface ConversationsState {\n /** Conversations sorted pinned-first, then most-recently-updated. */\n conversations: Conversation[];\n activeId: string | null;\n activeConversation: Conversation | null;\n createConversation: () => string;\n selectConversation: (id: string | null) => void;\n updateMessages: (id: string, messages: UIMessage[]) => void;\n renameConversation: (id: string, title: string) => void;\n pinConversation: (id: string) => void;\n deleteConversation: (id: string) => void;\n /** Persist the model selected for a conversation (does not reorder the list). */\n setConversationModel: (id: string, model: ModelId) => void;\n}\n\n/**\n * Pattern-A localStorage conversation state (ported from Implementation 1):\n * create / select / rename / pin / delete, persisted on every change.\n *\n * Storage is namespaced by `assistantId`. The hook re-seeds itself when\n * `assistantId` changes (the assistant id is held in state alongside the data),\n * so the owning component no longer needs to remount on assistant switch — which\n * lets background chat threads outlive an assistant change.\n *\n * @public\n */\nexport function useConversations(assistantId: string): ConversationsState {\n const [state, setState] = useState<InternalState>(() =>\n loadInitialState(assistantId),\n );\n\n // Re-seed synchronously when the assistant changes. Holding assistantId in the\n // same state object keeps the persistence effects below from writing one\n // assistant's conversations under another's key during the switch render.\n if (state.assistantId !== assistantId) {\n setState(loadInitialState(assistantId));\n }\n\n const { conversations, activeId } = state;\n const keys = storageKeys(state.assistantId);\n\n // Persist on every change.\n useEffect(() => {\n save(keys.list, conversations);\n }, [keys.list, conversations]);\n\n useEffect(() => {\n saveActiveId(keys.activeId, activeId);\n }, [keys.activeId, activeId]);\n\n const activeConversation = conversations.find(c => c.id === activeId) ?? null;\n\n const createConversation = useCallback((): string => {\n const id = generateId();\n const now = new Date().toISOString();\n const conv: Conversation = {\n id,\n title: 'New Chat',\n createdAt: now,\n updatedAt: now,\n pinned: false,\n messages: [],\n };\n setState(prev => ({\n ...prev,\n conversations: [conv, ...prev.conversations],\n activeId: id,\n }));\n return id;\n }, []);\n\n const updateMessages = useCallback((id: string, messages: UIMessage[]) => {\n setState(prev => ({\n ...prev,\n conversations: prev.conversations.map(c =>\n c.id === id ? { ...c, messages, updatedAt: new Date().toISOString() } : c,\n ),\n }));\n }, []);\n\n const renameConversation = useCallback((id: string, title: string) => {\n setState(prev => ({\n ...prev,\n conversations: prev.conversations.map(c =>\n c.id === id ? { ...c, title, updatedAt: new Date().toISOString() } : c,\n ),\n }));\n }, []);\n\n const pinConversation = useCallback((id: string) => {\n setState(prev => ({\n ...prev,\n conversations: prev.conversations.map(c =>\n c.id === id\n ? { ...c, pinned: !c.pinned, updatedAt: new Date().toISOString() }\n : c,\n ),\n }));\n }, []);\n\n const deleteConversation = useCallback((id: string) => {\n setState(prev => {\n const next = prev.conversations.filter(c => c.id !== id);\n return {\n ...prev,\n conversations: next,\n activeId:\n prev.activeId === id ? getLastConversationId(next) : prev.activeId,\n };\n });\n }, []);\n\n const selectConversation = useCallback((id: string | null) => {\n setState(prev => ({ ...prev, activeId: id }));\n }, []);\n\n // Model changes don't bump updatedAt: switching model shouldn't reorder the\n // conversation list.\n const setConversationModel = useCallback((id: string, model: ModelId) => {\n setState(prev => ({\n ...prev,\n conversations: prev.conversations.map(c =>\n c.id === id ? { ...c, model } : c,\n ),\n }));\n }, []);\n\n // Sort: pinned first, then by updatedAt desc.\n const sortedConversations = [...conversations].sort((a, b) => {\n if (a.pinned !== b.pinned) return a.pinned ? -1 : 1;\n return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();\n });\n\n return {\n conversations: sortedConversations,\n activeId,\n activeConversation,\n createConversation,\n selectConversation,\n updateMessages,\n renameConversation,\n pinConversation,\n deleteConversation,\n setConversationModel,\n };\n}\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
2
|
+
import * as _backstage_frontend_plugin_api from '@backstage/frontend-plugin-api';
|
|
3
|
+
import { StatusResponse, AssistantId, ModelId } from '@drewswiredin/backstage-plugin-assistants-common';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Root route for the Assistants page (Page A — collapsible-sidebar chrome).
|
|
7
|
+
*
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
declare const rootRouteRef: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Arguments for {@link AssistantsApi.getTitle}. Mirrors the backend `POST /title`
|
|
14
|
+
* body (the shared `ChatRequest` schema): the assistant + model to title with
|
|
15
|
+
* and the opening conversation messages.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
*/
|
|
19
|
+
interface GetTitleRequest {
|
|
20
|
+
assistantId: AssistantId;
|
|
21
|
+
modelId: ModelId;
|
|
22
|
+
/** Opening UI messages; the backend builds the title prompt from these. */
|
|
23
|
+
messages: unknown[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Client for the AI Assistants backend.
|
|
27
|
+
*
|
|
28
|
+
* Resolves the backend base URL via `discoveryApi.getBaseUrl('assistants')` and
|
|
29
|
+
* issues authenticated requests via `fetchApi.fetch`. Components must never read
|
|
30
|
+
* `config 'backend.baseUrl'` or fetch in-component — they go through this API.
|
|
31
|
+
*
|
|
32
|
+
* @public
|
|
33
|
+
*/
|
|
34
|
+
interface AssistantsApi {
|
|
35
|
+
/** Fetch the browser-safe plugin status (accessible assistants, models, default). */
|
|
36
|
+
getStatus(): Promise<StatusResponse>;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve the backend base URL (`.../api/assistants`). Used by the chat
|
|
39
|
+
* transport to build the `/chat` and `/title` URLs.
|
|
40
|
+
*/
|
|
41
|
+
getBaseUrl(): Promise<string>;
|
|
42
|
+
/**
|
|
43
|
+
* The authenticated `fetch`. The chat transport needs the raw authed fetch to
|
|
44
|
+
* wire the AI SDK `DefaultChatTransport`.
|
|
45
|
+
*/
|
|
46
|
+
fetch: typeof fetch;
|
|
47
|
+
/**
|
|
48
|
+
* Generate a short conversation title via the backend `POST /title`.
|
|
49
|
+
* Best-effort by contract — the backend already falls back to a default title
|
|
50
|
+
* on generation failure; callers should additionally tolerate a rejected
|
|
51
|
+
* promise (network error) without breaking the conversation list.
|
|
52
|
+
*/
|
|
53
|
+
getTitle(request: GetTitleRequest): Promise<string>;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* API ref for the AI Assistants backend client.
|
|
57
|
+
*
|
|
58
|
+
* @public
|
|
59
|
+
*/
|
|
60
|
+
declare const assistantsApiRef: _backstage_frontend_plugin_api.ApiRef<AssistantsApi>;
|
|
61
|
+
|
|
62
|
+
export { assistantsApiRef, rootRouteRef };
|
|
63
|
+
export type { AssistantsApi, GetTitleRequest };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
function styleInject(css, ref) {
|
|
2
|
+
if ( ref === void 0 ) ref = {};
|
|
3
|
+
var insertAt = ref.insertAt;
|
|
4
|
+
|
|
5
|
+
if (!css || typeof document === 'undefined') { return; }
|
|
6
|
+
|
|
7
|
+
var head = document.head || document.getElementsByTagName('head')[0];
|
|
8
|
+
var style = document.createElement('style');
|
|
9
|
+
style.type = 'text/css';
|
|
10
|
+
|
|
11
|
+
if (insertAt === 'top') {
|
|
12
|
+
if (head.firstChild) {
|
|
13
|
+
head.insertBefore(style, head.firstChild);
|
|
14
|
+
} else {
|
|
15
|
+
head.appendChild(style);
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
head.appendChild(style);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (style.styleSheet) {
|
|
22
|
+
style.styleSheet.cssText = css;
|
|
23
|
+
} else {
|
|
24
|
+
style.appendChild(document.createTextNode(css));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { styleInject as default };
|
|
29
|
+
//# sourceMappingURL=style-inject.es.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style-inject.es.esm.js","sources":["../../../../../../node_modules/style-inject/dist/style-inject.es.js"],"sourcesContent":["function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n"],"names":[],"mappings":"AAAA,SAAS,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE;AAC/B,EAAE,KAAK,GAAG,KAAK,MAAM,GAAG,GAAG,GAAG,EAAE;AAChC,EAAE,IAAI,QAAQ,GAAG,GAAG,CAAC,QAAQ;;AAE7B,EAAE,IAAI,CAAC,GAAG,IAAI,OAAO,QAAQ,KAAK,WAAW,EAAE,EAAE,OAAO,CAAC;;AAEzD,EAAE,IAAI,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AACtE,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC;AAC7C,EAAE,KAAK,CAAC,IAAI,GAAG,UAAU;;AAEzB,EAAE,IAAI,QAAQ,KAAK,KAAK,EAAE;AAC1B,IAAI,IAAI,IAAI,CAAC,UAAU,EAAE;AACzB,MAAM,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC;AAC/C,IAAI,CAAC,MAAM;AACX,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AAC7B,IAAI;AACJ,EAAE,CAAC,MAAM;AACT,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC;AAC3B,EAAE;;AAEF,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE;AACxB,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG;AAClC,EAAE,CAAC,MAAM;AACT,IAAI,KAAK,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;AACnD,EAAE;AACF;;;;","x_google_ignoreList":[0]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\n/**\n * Root route for the Assistants page (Page A — collapsible-sidebar chrome).\n *\n * @public\n */\nexport const rootRouteRef = createRouteRef({\n id: 'assistants',\n});\n"],"names":[],"mappings":";;AAOO,MAAM,eAAe,cAAA,CAAe;AAAA,EACzC,EAAA,EAAI;AACN,CAAC;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@drewswiredin/backstage-plugin-assistants",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Frontend plugin — AI Assistants for Backstage.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"backstage": {
|
|
10
|
+
"role": "frontend-plugin",
|
|
11
|
+
"pluginId": "assistants",
|
|
12
|
+
"pluginPackages": [
|
|
13
|
+
"@drewswiredin/backstage-plugin-assistants",
|
|
14
|
+
"@drewswiredin/backstage-plugin-assistants-backend",
|
|
15
|
+
"@drewswiredin/backstage-plugin-assistants-common"
|
|
16
|
+
],
|
|
17
|
+
"features": {
|
|
18
|
+
"./alpha": "@backstage/FrontendPlugin"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.esm.js",
|
|
22
|
+
"module": "./dist/index.esm.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": "./dist/index.esm.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.esm.js"
|
|
29
|
+
},
|
|
30
|
+
"./alpha": {
|
|
31
|
+
"backstage": "@backstage/FrontendPlugin",
|
|
32
|
+
"import": "./dist/alpha.esm.js",
|
|
33
|
+
"types": "./dist/alpha.d.ts",
|
|
34
|
+
"default": "./dist/alpha.esm.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"typesVersions": {
|
|
39
|
+
"*": {
|
|
40
|
+
"alpha": [
|
|
41
|
+
"dist/alpha.d.ts"
|
|
42
|
+
],
|
|
43
|
+
"package.json": [
|
|
44
|
+
"package.json"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"start": "backstage-cli package start",
|
|
53
|
+
"build": "backstage-cli package build",
|
|
54
|
+
"lint": "backstage-cli package lint",
|
|
55
|
+
"clean": "backstage-cli package clean",
|
|
56
|
+
"prepack": "backstage-cli package prepack",
|
|
57
|
+
"postpack": "backstage-cli package postpack"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@ai-sdk/react": "^3.0.193",
|
|
61
|
+
"@assistant-ui/react": "^0.14.0",
|
|
62
|
+
"@assistant-ui/react-ai-sdk": "^1.3.0",
|
|
63
|
+
"@assistant-ui/react-markdown": "^0.14.0",
|
|
64
|
+
"@assistant-ui/react-ui": "^0.2.0",
|
|
65
|
+
"@backstage/core-components": "^0.18.0",
|
|
66
|
+
"@backstage/core-plugin-api": "^1.12.0",
|
|
67
|
+
"@backstage/frontend-plugin-api": "^0.17.0",
|
|
68
|
+
"@backstage/ui": "^0.15.0",
|
|
69
|
+
"@drewswiredin/backstage-plugin-assistants-common": "0.1.0",
|
|
70
|
+
"@material-ui/core": "^4.12.4",
|
|
71
|
+
"@material-ui/icons": "^4.11.3",
|
|
72
|
+
"@remixicon/react": "^4.5.0",
|
|
73
|
+
"ai": "^6.0.192",
|
|
74
|
+
"mermaid": "^11.0.0",
|
|
75
|
+
"react-use": "^17.5.1",
|
|
76
|
+
"remark-gfm": "^4.0.1"
|
|
77
|
+
},
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
80
|
+
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
81
|
+
"react-router-dom": "^6.0.0 || ^7.0.0"
|
|
82
|
+
},
|
|
83
|
+
"devDependencies": {
|
|
84
|
+
"@backstage/cli": "0.36.2",
|
|
85
|
+
"@types/react": "^18.0.0",
|
|
86
|
+
"react": "^18.0.0",
|
|
87
|
+
"react-dom": "^18.0.0",
|
|
88
|
+
"react-router-dom": "^6.30.2"
|
|
89
|
+
},
|
|
90
|
+
"keywords": [
|
|
91
|
+
"backstage",
|
|
92
|
+
"backstage-plugin",
|
|
93
|
+
"frontend-plugin",
|
|
94
|
+
"ai",
|
|
95
|
+
"assistant",
|
|
96
|
+
"chatbot",
|
|
97
|
+
"llm"
|
|
98
|
+
],
|
|
99
|
+
"author": "drewswiredin",
|
|
100
|
+
"repository": {
|
|
101
|
+
"type": "git",
|
|
102
|
+
"url": "https://github.com/drewswiredin/backstage-assistants.git",
|
|
103
|
+
"directory": "packages/assistants"
|
|
104
|
+
},
|
|
105
|
+
"homepage": "https://github.com/drewswiredin/backstage-assistants#readme",
|
|
106
|
+
"bugs": "https://github.com/drewswiredin/backstage-assistants/issues"
|
|
107
|
+
}
|