@sweetoburrito/backstage-plugin-ai-assistant 0.2.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/README.md +13 -0
- package/dist/api/chat.esm.js +52 -0
- package/dist/api/chat.esm.js.map +1 -0
- package/dist/components/Conversation/Conversation.esm.js +200 -0
- package/dist/components/Conversation/Conversation.esm.js.map +1 -0
- package/dist/components/Conversation/index.esm.js +2 -0
- package/dist/components/Conversation/index.esm.js.map +1 -0
- package/dist/components/MessageCard/Card.esm.js +30 -0
- package/dist/components/MessageCard/Card.esm.js.map +1 -0
- package/dist/components/MessageCard/MessageCard.esm.js +72 -0
- package/dist/components/MessageCard/MessageCard.esm.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/plugin.esm.js +30 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/routes.esm.js +8 -0
- package/dist/routes.esm.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ai-assistant
|
|
2
|
+
|
|
3
|
+
Welcome to the ai-assistant plugin!
|
|
4
|
+
|
|
5
|
+
_This plugin was created through the Backstage CLI_
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/ai-assistant](http://localhost:3000/ai-assistant).
|
|
10
|
+
|
|
11
|
+
You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
|
|
12
|
+
This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
|
|
13
|
+
It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createApiRef } from '@backstage/core-plugin-api';
|
|
2
|
+
|
|
3
|
+
const chatApiRef = createApiRef({
|
|
4
|
+
id: "plugin.ai-assistant.chat"
|
|
5
|
+
});
|
|
6
|
+
const createChatService = ({
|
|
7
|
+
fetchApi,
|
|
8
|
+
discoveryApi
|
|
9
|
+
}) => {
|
|
10
|
+
const getModels = async () => {
|
|
11
|
+
const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
|
|
12
|
+
const response = await fetchApi.fetch(`${assistantBaseUrl}/models`);
|
|
13
|
+
const data = await response.json();
|
|
14
|
+
return data.models;
|
|
15
|
+
};
|
|
16
|
+
const getConversation = async (id) => {
|
|
17
|
+
if (!id) return [];
|
|
18
|
+
const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
|
|
19
|
+
const response = await fetchApi.fetch(`${assistantBaseUrl}/chat/${id}`);
|
|
20
|
+
const data = await response.json();
|
|
21
|
+
return data.conversation;
|
|
22
|
+
};
|
|
23
|
+
const sendMessage = async ({
|
|
24
|
+
conversationId,
|
|
25
|
+
modelId,
|
|
26
|
+
messages,
|
|
27
|
+
stream
|
|
28
|
+
}) => {
|
|
29
|
+
const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
|
|
30
|
+
const response = await fetchApi.fetch(`${assistantBaseUrl}/chat/message`, {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body: JSON.stringify({
|
|
33
|
+
conversationId,
|
|
34
|
+
modelId,
|
|
35
|
+
messages,
|
|
36
|
+
stream
|
|
37
|
+
}),
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/json"
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
const data = await response.json();
|
|
43
|
+
return {
|
|
44
|
+
messages: data.messages,
|
|
45
|
+
conversationId: data.conversationId
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
return { getModels, getConversation, sendMessage };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { chatApiRef, createChatService };
|
|
52
|
+
//# sourceMappingURL=chat.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.esm.js","sources":["../../src/api/chat.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\ntype SendMessageOptions = {\n conversationId?: string;\n modelId: string;\n messages: Message[];\n stream?: boolean;\n};\n\nexport type ChatApi = {\n getModels: () => Promise<string[]>;\n getConversation: (id?: string) => Promise<Message[]>;\n sendMessage: (options: SendMessageOptions) => Promise<{\n messages: Message[];\n conversationId: string;\n }>;\n};\n\ntype ChatApiOptions = {\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n};\n\nexport const chatApiRef = createApiRef<ChatApi>({\n id: 'plugin.ai-assistant.chat',\n});\n\nexport const createChatService = ({\n fetchApi,\n discoveryApi,\n}: ChatApiOptions): ChatApi => {\n const getModels: ChatApi['getModels'] = async (): Promise<string[]> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const response = await fetchApi.fetch(`${assistantBaseUrl}/models`);\n const data = await response.json();\n return data.models;\n };\n\n const getConversation: ChatApi['getConversation'] = async id => {\n if (!id) return [];\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const response = await fetchApi.fetch(`${assistantBaseUrl}/chat/${id}`);\n\n const data = await response.json();\n\n return data.conversation as Message[];\n };\n\n const sendMessage: ChatApi['sendMessage'] = async ({\n conversationId,\n modelId,\n messages,\n stream,\n }) => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n const response = await fetchApi.fetch(`${assistantBaseUrl}/chat/message`, {\n method: 'POST',\n body: JSON.stringify({\n conversationId,\n modelId,\n messages,\n stream,\n }),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n const data = await response.json();\n\n return {\n messages: data.messages as Message[],\n conversationId: data.conversationId as string,\n };\n };\n\n return { getModels, getConversation, sendMessage };\n};\n"],"names":[],"mappings":";;AAyBO,MAAM,aAAa,YAAA,CAAsB;AAAA,EAC9C,EAAA,EAAI;AACN,CAAC;AAEM,MAAM,oBAAoB,CAAC;AAAA,EAChC,QAAA;AAAA,EACA;AACF,CAAA,KAA+B;AAC7B,EAAA,MAAM,YAAkC,YAA+B;AACrE,IAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,UAAA,CAAW,cAAc,CAAA;AAErE,IAAA,MAAM,WAAW,MAAM,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,gBAAgB,CAAA,OAAA,CAAS,CAAA;AAClE,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd,CAAA;AAEA,EAAA,MAAM,eAAA,GAA8C,OAAM,EAAA,KAAM;AAC9D,IAAA,IAAI,CAAC,EAAA,EAAI,OAAO,EAAC;AACjB,IAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,UAAA,CAAW,cAAc,CAAA;AAErE,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,KAAA,CAAM,GAAG,gBAAgB,CAAA,MAAA,EAAS,EAAE,CAAA,CAAE,CAAA;AAEtE,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,IAAA,CAAK,YAAA;AAAA,EACd,CAAA;AAEA,EAAA,MAAM,cAAsC,OAAO;AAAA,IACjD,cAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACF,KAAM;AACJ,IAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,UAAA,CAAW,cAAc,CAAA;AACrE,IAAA,MAAM,WAAW,MAAM,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,gBAAgB,CAAA,aAAA,CAAA,EAAiB;AAAA,MACxE,MAAA,EAAQ,MAAA;AAAA,MACR,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACnB,cAAA;AAAA,QACA,OAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,MACD,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB;AAAA;AAClB,KACD,CAAA;AAED,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO;AAAA,MACL,UAAU,IAAA,CAAK,QAAA;AAAA,MACf,gBAAgB,IAAA,CAAK;AAAA,KACvB;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAW,eAAA,EAAiB,WAAA,EAAY;AACnD;;;;"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
|
|
3
|
+
import { chatApiRef } from '../../api/chat.esm.js';
|
|
4
|
+
import { useLocalStorage, useAsync, useList, useAsyncFn } from 'react-use';
|
|
5
|
+
import { useState, useRef, useEffect } from 'react';
|
|
6
|
+
import { signalApiRef } from '@backstage/plugin-signals-react';
|
|
7
|
+
import Typography from '@mui/material/Typography';
|
|
8
|
+
import TextField from '@mui/material/TextField';
|
|
9
|
+
import Stack from '@mui/material/Stack';
|
|
10
|
+
import Autocomplete from '@mui/material/Autocomplete';
|
|
11
|
+
import Paper from '@mui/material/Paper';
|
|
12
|
+
import NorthIcon from '@mui/icons-material/North';
|
|
13
|
+
import Button from '@mui/material/Button';
|
|
14
|
+
import { MessageCard } from '../MessageCard/MessageCard.esm.js';
|
|
15
|
+
|
|
16
|
+
const Conversation = () => {
|
|
17
|
+
const chatApi = useApi(chatApiRef);
|
|
18
|
+
const errorApi = useApi(errorApiRef);
|
|
19
|
+
const signalApi = useApi(signalApiRef);
|
|
20
|
+
const [input, setInput] = useState("");
|
|
21
|
+
const inputRef = useRef(null);
|
|
22
|
+
const [conversationId, setConversationId] = useLocalStorage("conversationId", void 0);
|
|
23
|
+
const [modelId, setModelId] = useLocalStorage(
|
|
24
|
+
"modelId",
|
|
25
|
+
void 0
|
|
26
|
+
);
|
|
27
|
+
const [messageSubscriber, setMessageSubscriber] = useState();
|
|
28
|
+
const { value: models, loading: loadingModels } = useAsync(
|
|
29
|
+
() => chatApi.getModels(),
|
|
30
|
+
[chatApi]
|
|
31
|
+
);
|
|
32
|
+
const { value: history, loading: loadingHistory } = useAsync(
|
|
33
|
+
() => chatApi.getConversation(conversationId),
|
|
34
|
+
[chatApi, conversationId]
|
|
35
|
+
);
|
|
36
|
+
const [messages, { push, set, updateAt }] = useList([]);
|
|
37
|
+
const messagesRef = useRef(messages);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
messagesRef.current = messages;
|
|
40
|
+
}, [messages]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (!history) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
set(history);
|
|
46
|
+
}, [history, set]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (models && models.length && !modelId) {
|
|
49
|
+
setModelId(models[0]);
|
|
50
|
+
}
|
|
51
|
+
}, [models, modelId, setModelId]);
|
|
52
|
+
const [{ loading: sending, value: messageResponse }, sendMessage] = useAsyncFn(async () => {
|
|
53
|
+
const newMessages = [{ role: "user", content: input }];
|
|
54
|
+
if (!modelId) {
|
|
55
|
+
errorApi.post({
|
|
56
|
+
name: "NoModelError",
|
|
57
|
+
message: "No model has been selected for this conversation. Please select a model before sending a message"
|
|
58
|
+
});
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
setInput("");
|
|
62
|
+
inputRef.current?.focus();
|
|
63
|
+
newMessages.forEach((message) => push(message));
|
|
64
|
+
const response = await chatApi.sendMessage({
|
|
65
|
+
conversationId,
|
|
66
|
+
modelId,
|
|
67
|
+
messages: newMessages
|
|
68
|
+
});
|
|
69
|
+
setConversationId(response.conversationId);
|
|
70
|
+
response.messages.forEach((message) => push(message));
|
|
71
|
+
const mostRecentMessage = response.messages.at(-1);
|
|
72
|
+
if (mostRecentMessage) {
|
|
73
|
+
messageSubscriber?.unsubscribe();
|
|
74
|
+
const { id: messageId } = mostRecentMessage;
|
|
75
|
+
setMessageSubscriber(
|
|
76
|
+
signalApi.subscribe(
|
|
77
|
+
`ai-assistant.chat.message-stream:${messageId}`,
|
|
78
|
+
(message) => {
|
|
79
|
+
const index = messagesRef.current.findIndex(
|
|
80
|
+
(m) => m.id === message.id
|
|
81
|
+
);
|
|
82
|
+
if (index !== -1) {
|
|
83
|
+
updateAt(index, message);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return response;
|
|
90
|
+
}, [
|
|
91
|
+
input,
|
|
92
|
+
inputRef,
|
|
93
|
+
chatApi,
|
|
94
|
+
setConversationId,
|
|
95
|
+
conversationId,
|
|
96
|
+
modelId,
|
|
97
|
+
errorApi,
|
|
98
|
+
setInput
|
|
99
|
+
]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!messageResponse || conversationId === messageResponse.conversationId) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setConversationId(messageResponse.conversationId);
|
|
105
|
+
}, [messageResponse, setConversationId, conversationId]);
|
|
106
|
+
const messageEndRef = useRef(null);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
messageEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
109
|
+
}, [messages]);
|
|
110
|
+
if (loadingHistory && loadingModels) {
|
|
111
|
+
return /* @__PURE__ */ jsx(Typography, { children: "Loading..." });
|
|
112
|
+
}
|
|
113
|
+
if (!models) {
|
|
114
|
+
return /* @__PURE__ */ jsx(Typography, { children: "No models available" });
|
|
115
|
+
}
|
|
116
|
+
return /* @__PURE__ */ jsxs(
|
|
117
|
+
Stack,
|
|
118
|
+
{
|
|
119
|
+
padding: 2,
|
|
120
|
+
spacing: 2,
|
|
121
|
+
flex: 1,
|
|
122
|
+
height: "100vh",
|
|
123
|
+
boxSizing: "border-box",
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ jsx(Stack, { direction: "row", spacing: 2, alignItems: "center", children: /* @__PURE__ */ jsx(
|
|
126
|
+
Autocomplete,
|
|
127
|
+
{
|
|
128
|
+
options: models,
|
|
129
|
+
defaultValue: modelId,
|
|
130
|
+
onChange: (_, value) => setModelId(value || void 0),
|
|
131
|
+
sx: { width: 300 },
|
|
132
|
+
renderInput: (params) => /* @__PURE__ */ jsx(TextField, { ...params, label: "Models" })
|
|
133
|
+
}
|
|
134
|
+
) }),
|
|
135
|
+
messages && /* @__PURE__ */ jsxs(
|
|
136
|
+
Stack,
|
|
137
|
+
{
|
|
138
|
+
spacing: 1,
|
|
139
|
+
flex: 1,
|
|
140
|
+
sx: {
|
|
141
|
+
overflowY: "auto",
|
|
142
|
+
pr: 1,
|
|
143
|
+
"&::-webkit-scrollbar": {
|
|
144
|
+
background: "transparent"
|
|
145
|
+
},
|
|
146
|
+
"&::-webkit-scrollbar-track": {
|
|
147
|
+
background: "transparent"
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
children: [
|
|
151
|
+
messages.map((message) => /* @__PURE__ */ jsx(MessageCard, { message }, message.id)),
|
|
152
|
+
/* @__PURE__ */ jsx("div", { ref: messageEndRef })
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
),
|
|
156
|
+
/* @__PURE__ */ jsx(Paper, { elevation: 2, sx: { padding: 1 }, children: /* @__PURE__ */ jsxs(
|
|
157
|
+
Stack,
|
|
158
|
+
{
|
|
159
|
+
direction: "row",
|
|
160
|
+
spacing: 1,
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
justifyContent: "start",
|
|
163
|
+
children: [
|
|
164
|
+
/* @__PURE__ */ jsx(
|
|
165
|
+
TextField,
|
|
166
|
+
{
|
|
167
|
+
multiline: true,
|
|
168
|
+
maxRows: 3,
|
|
169
|
+
variant: "standard",
|
|
170
|
+
sx: { flex: 1 },
|
|
171
|
+
value: input,
|
|
172
|
+
onChange: (e) => setInput(e.target.value),
|
|
173
|
+
inputRef,
|
|
174
|
+
onKeyDown: (e) => {
|
|
175
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
sendMessage();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
Button,
|
|
184
|
+
{
|
|
185
|
+
variant: "contained",
|
|
186
|
+
disabled: sending || !input.trim(),
|
|
187
|
+
onClick: sendMessage,
|
|
188
|
+
children: /* @__PURE__ */ jsx(NorthIcon, {})
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
) })
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export { Conversation };
|
|
200
|
+
//# sourceMappingURL=Conversation.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Conversation.esm.js","sources":["../../../src/components/Conversation/Conversation.tsx"],"sourcesContent":["import { useApi, errorApiRef } from '@backstage/core-plugin-api';\nimport { chatApiRef } from '../../api/chat';\nimport { useAsync, useAsyncFn, useList, useLocalStorage } from 'react-use';\nimport { useEffect, useRef, useState } from 'react';\nimport {\n signalApiRef,\n SignalSubscriber,\n} from '@backstage/plugin-signals-react';\n\nimport Typography from '@mui/material/Typography';\nimport TextField from '@mui/material/TextField';\nimport Stack from '@mui/material/Stack';\nimport Autocomplete from '@mui/material/Autocomplete';\nimport Paper from '@mui/material/Paper';\nimport NorthIcon from '@mui/icons-material/North';\nimport Button from '@mui/material/Button';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { MessageCard } from '../MessageCard';\n\nexport const Conversation = () => {\n const chatApi = useApi(chatApiRef);\n const errorApi = useApi(errorApiRef);\n const signalApi = useApi(signalApiRef);\n\n const [input, setInput] = useState('');\n const inputRef = useRef<HTMLInputElement>(null);\n\n const [conversationId, setConversationId] = useLocalStorage<\n string | undefined\n >('conversationId', undefined);\n\n const [modelId, setModelId] = useLocalStorage<string | undefined>(\n 'modelId',\n undefined,\n );\n\n const [messageSubscriber, setMessageSubscriber] = useState<\n SignalSubscriber | undefined\n >();\n\n const { value: models, loading: loadingModels } = useAsync(\n () => chatApi.getModels(),\n [chatApi],\n );\n const { value: history, loading: loadingHistory } = useAsync(\n () => chatApi.getConversation(conversationId),\n [chatApi, conversationId],\n );\n\n const [messages, { push, set, updateAt }] = useList<Message>([]);\n const messagesRef = useRef(messages);\n\n useEffect(() => {\n messagesRef.current = messages;\n }, [messages]);\n\n useEffect(() => {\n if (!history) {\n return;\n }\n\n set(history);\n }, [history, set]);\n\n useEffect(() => {\n if (models && models.length && !modelId) {\n setModelId(models[0]);\n }\n }, [models, modelId, setModelId]);\n\n const [{ loading: sending, value: messageResponse }, sendMessage] =\n useAsyncFn(async () => {\n const newMessages: Message[] = [{ role: 'user', content: input }];\n\n if (!modelId) {\n errorApi.post({\n name: 'NoModelError',\n message:\n 'No model has been selected for this conversation. Please select a model before sending a message',\n });\n return undefined;\n }\n\n setInput('');\n inputRef.current?.focus();\n\n newMessages.forEach(message => push(message));\n\n const response = await chatApi.sendMessage({\n conversationId,\n modelId,\n messages: newMessages,\n });\n\n setConversationId(response.conversationId);\n\n response.messages.forEach(message => push(message));\n\n // Get last item in response.messages\n const mostRecentMessage = response.messages.at(-1);\n\n if (mostRecentMessage) {\n messageSubscriber?.unsubscribe();\n\n const { id: messageId } = mostRecentMessage;\n\n setMessageSubscriber(\n signalApi.subscribe(\n `ai-assistant.chat.message-stream:${messageId}`,\n (message: Required<Message>) => {\n const index = messagesRef.current.findIndex(\n m => m.id === message.id,\n );\n if (index !== -1) {\n updateAt(index, message);\n }\n },\n ),\n );\n }\n\n return response;\n }, [\n input,\n inputRef,\n chatApi,\n setConversationId,\n conversationId,\n modelId,\n errorApi,\n setInput,\n ]);\n\n useEffect(() => {\n if (!messageResponse || conversationId === messageResponse.conversationId) {\n return;\n }\n\n setConversationId(messageResponse.conversationId);\n }, [messageResponse, setConversationId, conversationId]);\n\n const messageEndRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n messageEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n }, [messages]);\n\n if (loadingHistory && loadingModels) {\n return <Typography>Loading...</Typography>;\n }\n\n if (!models) {\n return <Typography>No models available</Typography>;\n }\n\n return (\n <Stack\n padding={2}\n spacing={2}\n flex={1}\n height=\"100vh\"\n boxSizing=\"border-box\"\n >\n <Stack direction=\"row\" spacing={2} alignItems=\"center\">\n <Autocomplete\n options={models}\n defaultValue={modelId}\n onChange={(_, value) => setModelId(value || undefined)}\n sx={{ width: 300 }}\n renderInput={params => <TextField {...params} label=\"Models\" />}\n />\n </Stack>\n {messages && (\n <Stack\n spacing={1}\n flex={1}\n sx={{\n overflowY: 'auto',\n pr: 1,\n '&::-webkit-scrollbar': {\n background: 'transparent',\n },\n '&::-webkit-scrollbar-track': {\n background: 'transparent',\n },\n }}\n >\n {messages.map(message => (\n <MessageCard key={message.id} message={message} />\n ))}\n <div ref={messageEndRef} />\n </Stack>\n )}\n\n <Paper elevation={2} sx={{ padding: 1 }}>\n <Stack\n direction=\"row\"\n spacing={1}\n alignItems=\"center\"\n justifyContent=\"start\"\n >\n <TextField\n multiline\n maxRows={3}\n variant=\"standard\"\n sx={{ flex: 1 }}\n value={input}\n onChange={e => setInput(e.target.value)}\n inputRef={inputRef}\n onKeyDown={e => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n sendMessage();\n }\n }}\n />\n <Button\n variant=\"contained\"\n disabled={sending || !input.trim()}\n onClick={sendMessage}\n >\n <NorthIcon />\n </Button>\n </Stack>\n </Paper>\n </Stack>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import Paper from '@mui/material/Paper';
|
|
3
|
+
import Box from '@mui/material/Box';
|
|
4
|
+
import { useTheme } from '@mui/material/styles';
|
|
5
|
+
|
|
6
|
+
const Card = ({ children, role }) => {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
return /* @__PURE__ */ jsx(
|
|
9
|
+
Paper,
|
|
10
|
+
{
|
|
11
|
+
sx: {
|
|
12
|
+
alignSelf: role === "user" ? "end" : "start",
|
|
13
|
+
maxWidth: "70%",
|
|
14
|
+
width: "70%",
|
|
15
|
+
borderRadius: 2,
|
|
16
|
+
border: "double transparent",
|
|
17
|
+
borderWidth: role !== "user" ? "2px" : 0,
|
|
18
|
+
backgroundImage: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}), linear-gradient(to right, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`,
|
|
19
|
+
backgroundOrigin: "border-box",
|
|
20
|
+
backgroundClip: "content-box, border-box",
|
|
21
|
+
wordBreak: "break-word"
|
|
22
|
+
},
|
|
23
|
+
elevation: 2,
|
|
24
|
+
children: /* @__PURE__ */ jsx(Box, { p: 1, children })
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { Card };
|
|
30
|
+
//# sourceMappingURL=Card.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Card.esm.js","sources":["../../../src/components/MessageCard/Card.tsx"],"sourcesContent":["import Paper from '@mui/material/Paper';\nimport Box from '@mui/material/Box';\nimport { PropsWithChildren } from 'react';\nimport { useTheme } from '@mui/material/styles';\n\ntype CardProps = PropsWithChildren & {\n role: string;\n};\n\nexport const Card = ({ children, role }: CardProps) => {\n const theme = useTheme();\n return (\n <Paper\n sx={{\n alignSelf: role === 'user' ? 'end' : 'start',\n maxWidth: '70%',\n width: '70%',\n borderRadius: 2,\n border: 'double transparent',\n borderWidth: role !== 'user' ? '2px' : 0,\n backgroundImage: `linear-gradient(${theme.palette.background.paper}, ${theme.palette.background.paper}), linear-gradient(to right, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`,\n backgroundOrigin: 'border-box',\n backgroundClip: 'content-box, border-box',\n wordBreak: 'break-word',\n }}\n elevation={2}\n >\n <Box p={1}>{children}</Box>\n </Paper>\n );\n};\n"],"names":[],"mappings":";;;;;AASO,MAAM,IAAA,GAAO,CAAC,EAAE,QAAA,EAAU,MAAK,KAAiB;AACrD,EAAA,MAAM,QAAQ,QAAA,EAAS;AACvB,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,EAAA,EAAI;AAAA,QACF,SAAA,EAAW,IAAA,KAAS,MAAA,GAAS,KAAA,GAAQ,OAAA;AAAA,QACrC,QAAA,EAAU,KAAA;AAAA,QACV,KAAA,EAAO,KAAA;AAAA,QACP,YAAA,EAAc,CAAA;AAAA,QACd,MAAA,EAAQ,oBAAA;AAAA,QACR,WAAA,EAAa,IAAA,KAAS,MAAA,GAAS,KAAA,GAAQ,CAAA;AAAA,QACvC,eAAA,EAAiB,mBAAmB,KAAA,CAAM,OAAA,CAAQ,WAAW,KAAK,CAAA,EAAA,EAAK,MAAM,OAAA,CAAQ,UAAA,CAAW,KAAK,CAAA,6BAAA,EAAgC,KAAA,CAAM,QAAQ,OAAA,CAAQ,IAAI,KAAK,KAAA,CAAM,OAAA,CAAQ,UAAU,IAAI,CAAA,CAAA,CAAA;AAAA,QAChM,gBAAA,EAAkB,YAAA;AAAA,QAClB,cAAA,EAAgB,yBAAA;AAAA,QAChB,SAAA,EAAW;AAAA,OACb;AAAA,MACA,SAAA,EAAW,CAAA;AAAA,MAEX,QAAA,kBAAA,GAAA,CAAC,GAAA,EAAA,EAAI,CAAA,EAAG,CAAA,EAAI,QAAA,EAAS;AAAA;AAAA,GACvB;AAEJ;;;;"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import Markdown from 'react-markdown';
|
|
3
|
+
import { useTheme } from '@mui/material/styles';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { Card } from './Card.esm.js';
|
|
6
|
+
import Skeleton from '@mui/material/Skeleton';
|
|
7
|
+
import Typography from '@mui/material/Typography';
|
|
8
|
+
import CircularProgress from '@mui/material/CircularProgress';
|
|
9
|
+
import Accordion from '@mui/material/Accordion';
|
|
10
|
+
import AccordionSummary from '@mui/material/AccordionSummary';
|
|
11
|
+
import AccordionDetails from '@mui/material/AccordionDetails';
|
|
12
|
+
import Stack from '@mui/material/Stack';
|
|
13
|
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
14
|
+
|
|
15
|
+
const MessageCard = ({ message }) => {
|
|
16
|
+
const { content, role } = message;
|
|
17
|
+
const theme = useTheme();
|
|
18
|
+
const hasThinking = useMemo(() => {
|
|
19
|
+
return content.startsWith("<think>");
|
|
20
|
+
}, [content]);
|
|
21
|
+
const thinking = useMemo(() => {
|
|
22
|
+
return role === "assistant" && content.includes("<think>") && !content.includes("</think>");
|
|
23
|
+
}, [content, role]);
|
|
24
|
+
const thoughtProcess = useMemo(() => {
|
|
25
|
+
const closedMatch = content.match(/<think>(.*?)<\/think>/s);
|
|
26
|
+
if (closedMatch) return closedMatch[1].trim();
|
|
27
|
+
const openMatch = content.match(/<think>(.*)/s);
|
|
28
|
+
if (openMatch) return openMatch[1].trim();
|
|
29
|
+
return "";
|
|
30
|
+
}, [content]);
|
|
31
|
+
const response = useMemo(() => {
|
|
32
|
+
if (!hasThinking) {
|
|
33
|
+
return content;
|
|
34
|
+
}
|
|
35
|
+
const [, contentResponse] = content.split("</think>");
|
|
36
|
+
return contentResponse ?? "";
|
|
37
|
+
}, [content, hasThinking]);
|
|
38
|
+
const loading = useMemo(() => {
|
|
39
|
+
return role === "assistant" && content === "";
|
|
40
|
+
}, [content, role]);
|
|
41
|
+
if (loading) {
|
|
42
|
+
return /* @__PURE__ */ jsx(Card, { role, children: /* @__PURE__ */ jsx(
|
|
43
|
+
Skeleton,
|
|
44
|
+
{
|
|
45
|
+
variant: "text",
|
|
46
|
+
height: theme.typography.body1.fontSize,
|
|
47
|
+
width: 210
|
|
48
|
+
}
|
|
49
|
+
) });
|
|
50
|
+
}
|
|
51
|
+
return /* @__PURE__ */ jsxs(Card, { role, children: [
|
|
52
|
+
hasThinking && /* @__PURE__ */ jsxs(Accordion, { sx: { paddingX: 1 }, children: [
|
|
53
|
+
/* @__PURE__ */ jsx(AccordionSummary, { expandIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, {}), children: thinking && !response ? /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, alignItems: "center", children: [
|
|
54
|
+
/* @__PURE__ */ jsx(CircularProgress, { size: theme.typography.caption.fontSize }),
|
|
55
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", children: "Thinking" })
|
|
56
|
+
] }) : /* @__PURE__ */ jsx(Typography, { variant: "caption", children: "Thought Process" }) }),
|
|
57
|
+
/* @__PURE__ */ jsx(AccordionDetails, { children: thoughtProcess ? /* @__PURE__ */ jsx(Markdown, { children: thoughtProcess }) : /* @__PURE__ */ jsx(
|
|
58
|
+
Skeleton,
|
|
59
|
+
{
|
|
60
|
+
variant: "text",
|
|
61
|
+
height: theme.typography.caption.fontSize,
|
|
62
|
+
width: 40
|
|
63
|
+
}
|
|
64
|
+
) })
|
|
65
|
+
] }),
|
|
66
|
+
/* @__PURE__ */ jsx(Markdown, { children: response }),
|
|
67
|
+
/* @__PURE__ */ jsx("br", {})
|
|
68
|
+
] });
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { MessageCard };
|
|
72
|
+
//# sourceMappingURL=MessageCard.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageCard.esm.js","sources":["../../../src/components/MessageCard/MessageCard.tsx"],"sourcesContent":["import type { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport Markdown from 'react-markdown';\nimport { useTheme } from '@mui/material/styles';\nimport { useMemo } from 'react';\nimport { Card } from './Card';\n\nimport Skeleton from '@mui/material/Skeleton';\nimport Typography from '@mui/material/Typography';\nimport CircularProgress from '@mui/material/CircularProgress';\nimport Accordion from '@mui/material/Accordion';\nimport AccordionSummary from '@mui/material/AccordionSummary';\nimport AccordionDetails from '@mui/material/AccordionDetails';\nimport Stack from '@mui/material/Stack';\n\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\n\nexport type MessageCardProps = {\n message: Message;\n};\n\nexport const MessageCard = ({ message }: MessageCardProps) => {\n const { content, role } = message;\n\n const theme = useTheme();\n\n const hasThinking = useMemo(() => {\n return content.startsWith('<think>');\n }, [content]);\n\n const thinking = useMemo<boolean>(() => {\n return (\n role === 'assistant' &&\n content.includes('<think>') &&\n !content.includes('</think>')\n );\n }, [content, role]);\n\n const thoughtProcess = useMemo(() => {\n // Try to match <think>...</think>\n const closedMatch = content.match(/<think>(.*?)<\\/think>/s);\n if (closedMatch) return closedMatch[1].trim();\n\n // If </think> is missing but <think> is present, get everything after <think>\n const openMatch = content.match(/<think>(.*)/s);\n if (openMatch) return openMatch[1].trim();\n\n return '';\n }, [content]);\n\n const response = useMemo<string>(() => {\n if (!hasThinking) {\n return content;\n }\n const [, contentResponse] = content.split('</think>');\n return contentResponse ?? '';\n }, [content, hasThinking]);\n\n const loading = useMemo(() => {\n return role === 'assistant' && content === '';\n }, [content, role]);\n\n if (loading) {\n return (\n <Card role={role}>\n <Skeleton\n variant=\"text\"\n height={theme.typography.body1.fontSize}\n width={210}\n />\n </Card>\n );\n }\n\n return (\n <Card role={role}>\n {hasThinking && (\n <Accordion sx={{ paddingX: 1 }}>\n <AccordionSummary expandIcon={<ExpandMoreIcon />}>\n {thinking && !response ? (\n <Stack direction=\"row\" spacing={1} alignItems=\"center\">\n <CircularProgress size={theme.typography.caption.fontSize} />\n <Typography variant=\"caption\">Thinking</Typography>\n </Stack>\n ) : (\n <Typography variant=\"caption\">Thought Process</Typography>\n )}\n </AccordionSummary>\n <AccordionDetails>\n {thoughtProcess ? (\n <Markdown>{thoughtProcess}</Markdown>\n ) : (\n <Skeleton\n variant=\"text\"\n height={theme.typography.caption.fontSize}\n width={40}\n />\n )}\n </AccordionDetails>\n </Accordion>\n )}\n <Markdown>{response}</Markdown>\n <br />\n </Card>\n );\n};\n"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
|
|
3
|
+
|
|
4
|
+
declare const aiAssistantPlugin: _backstage_core_plugin_api.BackstagePlugin<{
|
|
5
|
+
root: _backstage_core_plugin_api.RouteRef<undefined>;
|
|
6
|
+
}, {}, {}>;
|
|
7
|
+
declare const AiAssistantPage: () => react_jsx_runtime.JSX.Element;
|
|
8
|
+
|
|
9
|
+
export { AiAssistantPage, aiAssistantPlugin };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createPlugin, createApiFactory, fetchApiRef, discoveryApiRef, createRoutableExtension } from '@backstage/core-plugin-api';
|
|
2
|
+
import { rootRouteRef } from './routes.esm.js';
|
|
3
|
+
import { createChatService, chatApiRef } from './api/chat.esm.js';
|
|
4
|
+
|
|
5
|
+
const aiAssistantPlugin = createPlugin({
|
|
6
|
+
id: "ai-assistant",
|
|
7
|
+
routes: {
|
|
8
|
+
root: rootRouteRef
|
|
9
|
+
},
|
|
10
|
+
apis: [
|
|
11
|
+
createApiFactory({
|
|
12
|
+
api: chatApiRef,
|
|
13
|
+
deps: {
|
|
14
|
+
discoveryApi: discoveryApiRef,
|
|
15
|
+
fetchApi: fetchApiRef
|
|
16
|
+
},
|
|
17
|
+
factory: (options) => createChatService(options)
|
|
18
|
+
})
|
|
19
|
+
]
|
|
20
|
+
});
|
|
21
|
+
const AiAssistantPage = aiAssistantPlugin.provide(
|
|
22
|
+
createRoutableExtension({
|
|
23
|
+
name: "AiAssistantPage",
|
|
24
|
+
component: () => import('./components/Conversation/index.esm.js').then((m) => m.Conversation),
|
|
25
|
+
mountPoint: rootRouteRef
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export { AiAssistantPage, aiAssistantPlugin };
|
|
30
|
+
//# sourceMappingURL=plugin.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.esm.js","sources":["../src/plugin.ts"],"sourcesContent":["import {\n createPlugin,\n createRoutableExtension,\n createApiFactory,\n discoveryApiRef,\n fetchApiRef,\n} from '@backstage/core-plugin-api';\n\nimport { rootRouteRef } from './routes';\nimport { chatApiRef, createChatService } from './api/chat';\n\nexport const aiAssistantPlugin = createPlugin({\n id: 'ai-assistant',\n routes: {\n root: rootRouteRef,\n },\n apis: [\n createApiFactory({\n api: chatApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n fetchApi: fetchApiRef,\n },\n factory: options => createChatService(options),\n }),\n ],\n});\n\nexport const AiAssistantPage = aiAssistantPlugin.provide(\n createRoutableExtension({\n name: 'AiAssistantPage',\n component: () =>\n import('./components/Conversation').then(m => m.Conversation),\n mountPoint: rootRouteRef,\n }),\n);\n"],"names":[],"mappings":";;;;AAWO,MAAM,oBAAoB,YAAA,CAAa;AAAA,EAC5C,EAAA,EAAI,cAAA;AAAA,EACJ,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM;AAAA,GACR;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,gBAAA,CAAiB;AAAA,MACf,GAAA,EAAK,UAAA;AAAA,MACL,IAAA,EAAM;AAAA,QACJ,YAAA,EAAc,eAAA;AAAA,QACd,QAAA,EAAU;AAAA,OACZ;AAAA,MACA,OAAA,EAAS,CAAA,OAAA,KAAW,iBAAA,CAAkB,OAAO;AAAA,KAC9C;AAAA;AAEL,CAAC;AAEM,MAAM,kBAAkB,iBAAA,CAAkB,OAAA;AAAA,EAC/C,uBAAA,CAAwB;AAAA,IACtB,IAAA,EAAM,iBAAA;AAAA,IACN,SAAA,EAAW,MACT,OAAO,wCAA2B,EAAE,IAAA,CAAK,CAAA,CAAA,KAAK,EAAE,YAAY,CAAA;AAAA,IAC9D,UAAA,EAAY;AAAA,GACb;AACH;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.esm.js","sources":["../src/routes.ts"],"sourcesContent":["import { createRouteRef } from '@backstage/core-plugin-api';\n\nexport const rootRouteRef = createRouteRef({\n id: 'ai-assistant',\n});\n"],"names":[],"mappings":";;AAEO,MAAM,eAAe,cAAA,CAAe;AAAA,EACzC,EAAA,EAAI;AACN,CAAC;;;;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sweetoburrito/backstage-plugin-ai-assistant",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"main": "dist/index.esm.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"main": "dist/index.esm.js",
|
|
10
|
+
"types": "dist/index.d.ts"
|
|
11
|
+
},
|
|
12
|
+
"backstage": {
|
|
13
|
+
"role": "frontend-plugin",
|
|
14
|
+
"pluginId": "ai-assistant",
|
|
15
|
+
"pluginPackages": [
|
|
16
|
+
"@sweetoburrito/backstage-plugin-ai-assistant",
|
|
17
|
+
"@sweetoburrito/backstage-plugin-ai-assistant-backend",
|
|
18
|
+
"@sweetoburrito/backstage-plugin-ai-assistant-common",
|
|
19
|
+
"@sweetoburrito/backstage-plugin-ai-assistant-node"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"scripts": {
|
|
24
|
+
"start": "backstage-cli package start",
|
|
25
|
+
"build": "backstage-cli package build",
|
|
26
|
+
"lint": "backstage-cli package lint",
|
|
27
|
+
"test": "backstage-cli package test",
|
|
28
|
+
"clean": "backstage-cli package clean",
|
|
29
|
+
"prepack": "backstage-cli package prepack",
|
|
30
|
+
"postpack": "backstage-cli package postpack"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@backstage/core-components": "backstage:^",
|
|
34
|
+
"@backstage/core-plugin-api": "backstage:^",
|
|
35
|
+
"@backstage/plugin-signals": "backstage:^",
|
|
36
|
+
"@backstage/plugin-signals-react": "backstage:^",
|
|
37
|
+
"@backstage/theme": "backstage:^",
|
|
38
|
+
"@mui/icons-material": "^6.5.0",
|
|
39
|
+
"@mui/material": "^6.5.0",
|
|
40
|
+
"@sweetoburrito/backstage-plugin-ai-assistant-common": "workspace:^",
|
|
41
|
+
"react-markdown": "^10.1.0",
|
|
42
|
+
"react-use": "^17.2.4"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": "^18.0.0",
|
|
46
|
+
"react-dom": " ^18.0.0",
|
|
47
|
+
"react-router-dom": "^7.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@backstage/cli": "backstage:^",
|
|
51
|
+
"@backstage/core-app-api": "backstage:^",
|
|
52
|
+
"@backstage/dev-utils": "backstage:^"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist"
|
|
56
|
+
],
|
|
57
|
+
"typesVersions": {
|
|
58
|
+
"*": {
|
|
59
|
+
"package.json": [
|
|
60
|
+
"package.json"
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"module": "./dist/index.esm.js"
|
|
65
|
+
}
|