@sweetoburrito/backstage-plugin-ai-assistant 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/api/chat.esm.js +20 -1
  2. package/dist/api/chat.esm.js.map +1 -1
  3. package/dist/api/mcp.esm.js +73 -0
  4. package/dist/api/mcp.esm.js.map +1 -0
  5. package/dist/api/summarizer.esm.js +47 -0
  6. package/dist/api/summarizer.esm.js.map +1 -0
  7. package/dist/components/AiAssistantChatModal/AiAssistantChatModal.esm.js +194 -0
  8. package/dist/components/AiAssistantChatModal/AiAssistantChatModal.esm.js.map +1 -0
  9. package/dist/components/AiAssistantChatModal/index.esm.js +2 -0
  10. package/dist/components/AiAssistantChatModal/index.esm.js.map +1 -0
  11. package/dist/components/AiAssistantPage/AiAssistantPage.esm.js +31 -8
  12. package/dist/components/AiAssistantPage/AiAssistantPage.esm.js.map +1 -1
  13. package/dist/components/Conversation/Conversation.esm.js +28 -6
  14. package/dist/components/Conversation/Conversation.esm.js.map +1 -1
  15. package/dist/components/Conversation/McpServersTab.esm.js +303 -0
  16. package/dist/components/Conversation/McpServersTab.esm.js.map +1 -0
  17. package/dist/components/Conversation/SettingsModal.esm.js +134 -0
  18. package/dist/components/Conversation/SettingsModal.esm.js.map +1 -0
  19. package/dist/components/MessageCard/FeedbackButtons.esm.js +64 -0
  20. package/dist/components/MessageCard/FeedbackButtons.esm.js.map +1 -0
  21. package/dist/components/MessageCard/MessageCard.esm.js +63 -10
  22. package/dist/components/MessageCard/MessageCard.esm.js.map +1 -1
  23. package/dist/hooks/use-chat-settings.esm.js +24 -0
  24. package/dist/hooks/use-chat-settings.esm.js.map +1 -0
  25. package/dist/hooks/use-page-summary.esm.js +27 -0
  26. package/dist/hooks/use-page-summary.esm.js.map +1 -0
  27. package/dist/index.d.ts +9 -1
  28. package/dist/index.esm.js +1 -1
  29. package/dist/plugin.esm.js +33 -2
  30. package/dist/plugin.esm.js.map +1 -1
  31. package/package.json +4 -3
@@ -53,7 +53,26 @@ const createChatService = ({
53
53
  const data = await response.json();
54
54
  return data.conversations;
55
55
  };
56
- return { getModels, getConversation, sendMessage, getConversations };
56
+ const scoreMessage = async (messageId, score) => {
57
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
58
+ await fetchApi.fetch(
59
+ `${assistantBaseUrl}/chat/message/${messageId}/score`,
60
+ {
61
+ method: "POST",
62
+ body: JSON.stringify({ score }),
63
+ headers: {
64
+ "Content-Type": "application/json"
65
+ }
66
+ }
67
+ );
68
+ };
69
+ return {
70
+ getModels,
71
+ getConversation,
72
+ sendMessage,
73
+ getConversations,
74
+ scoreMessage
75
+ };
57
76
  };
58
77
 
59
78
  export { chatApiRef, createChatService };
@@ -1 +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 {\n Conversation,\n Message,\n} 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 getConversations: () => Promise<Conversation[]>;\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 const getConversations: ChatApi['getConversations'] = async () => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const response = await fetchApi.fetch(\n `${assistantBaseUrl}/chat/conversations`,\n );\n\n const data = await response.json();\n\n return data.conversations as Conversation[];\n };\n\n return { getModels, getConversation, sendMessage, getConversations };\n};\n"],"names":[],"mappings":";;AA6BO,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,MAAM,mBAAgD,YAAY;AAChE,IAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,UAAA,CAAW,cAAc,CAAA;AAErE,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,KAAA;AAAA,MAC9B,GAAG,gBAAgB,CAAA,mBAAA;AAAA,KACrB;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,IAAA,OAAO,IAAA,CAAK,aAAA;AAAA,EACd,CAAA;AAEA,EAAA,OAAO,EAAE,SAAA,EAAW,eAAA,EAAiB,WAAA,EAAa,gBAAA,EAAiB;AACrE;;;;"}
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 {\n Conversation,\n Message,\n} 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 getConversations: () => Promise<Conversation[]>;\n scoreMessage: (messageId: string, score: number) => Promise<void>;\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 const getConversations: ChatApi['getConversations'] = async () => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const response = await fetchApi.fetch(\n `${assistantBaseUrl}/chat/conversations`,\n );\n\n const data = await response.json();\n\n return data.conversations as Conversation[];\n };\n\n const scoreMessage: ChatApi['scoreMessage'] = async (messageId, score) => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n await fetchApi.fetch(\n `${assistantBaseUrl}/chat/message/${messageId}/score`,\n {\n method: 'POST',\n body: JSON.stringify({ score }),\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n };\n\n return {\n getModels,\n getConversation,\n sendMessage,\n getConversations,\n scoreMessage,\n };\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { createApiRef } from '@backstage/core-plugin-api';
2
+
3
+ const mcpApiRef = createApiRef({
4
+ id: "plugin.ai-assistant.mcp"
5
+ });
6
+ const createMcpService = ({ fetchApi, discoveryApi }) => {
7
+ const getUserDefinedMcpConfigs = async () => {
8
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
9
+ const response = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`);
10
+ const data = await response.json();
11
+ return data;
12
+ };
13
+ const createMcpConfig = async (config) => {
14
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
15
+ const res = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`, {
16
+ method: "POST",
17
+ body: JSON.stringify(config),
18
+ headers: {
19
+ "Content-Type": "application/json"
20
+ }
21
+ });
22
+ if (!res.ok) {
23
+ const errorData = await res.json();
24
+ const { error } = errorData;
25
+ throw new Error(
26
+ `Failed to create MCP config: ${error || res.statusText}`
27
+ );
28
+ }
29
+ };
30
+ const updateMcpConfig = async (config) => {
31
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
32
+ const res = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`, {
33
+ method: "PATCH",
34
+ body: JSON.stringify(config),
35
+ headers: {
36
+ "Content-Type": "application/json"
37
+ }
38
+ });
39
+ if (!res.ok) {
40
+ const errorData = await res.json();
41
+ const { error } = errorData;
42
+ throw new Error(
43
+ `Failed to update MCP config: ${error || res.statusText}`
44
+ );
45
+ }
46
+ };
47
+ const deleteMcpConfig = async (configName) => {
48
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
49
+ const res = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`, {
50
+ method: "DELETE",
51
+ body: JSON.stringify({ name: configName }),
52
+ headers: {
53
+ "Content-Type": "application/json"
54
+ }
55
+ });
56
+ if (!res.ok) {
57
+ const errorData = await res.json();
58
+ const { error } = errorData;
59
+ throw new Error(
60
+ `Failed to delete MCP config: ${error || res.statusText}`
61
+ );
62
+ }
63
+ };
64
+ return {
65
+ getUserDefinedMcpConfigs,
66
+ createMcpConfig,
67
+ updateMcpConfig,
68
+ deleteMcpConfig
69
+ };
70
+ };
71
+
72
+ export { createMcpService, mcpApiRef };
73
+ //# sourceMappingURL=mcp.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.esm.js","sources":["../../src/api/mcp.ts"],"sourcesContent":["import { createApiRef } from '@backstage/core-plugin-api';\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { McpServerConfig } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nexport type McpApi = Awaited<ReturnType<typeof createMcpService>>;\n\ntype McpApiOptions = {\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n};\n\nexport const mcpApiRef = createApiRef<McpApi>({\n id: 'plugin.ai-assistant.mcp',\n});\n\nexport const createMcpService = ({ fetchApi, discoveryApi }: McpApiOptions) => {\n const getUserDefinedMcpConfigs = async (): Promise<{ names: string[] }> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const response = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`);\n const data = await response.json();\n return data;\n };\n\n const createMcpConfig = async (config: McpServerConfig): Promise<void> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const res = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`, {\n method: 'POST',\n body: JSON.stringify(config),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n\n const { error } = errorData;\n\n throw new Error(\n `Failed to create MCP config: ${error || res.statusText}`,\n );\n }\n };\n\n const updateMcpConfig = async (config: McpServerConfig): Promise<void> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const res = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`, {\n method: 'PATCH',\n body: JSON.stringify(config),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n\n const { error } = errorData;\n\n throw new Error(\n `Failed to update MCP config: ${error || res.statusText}`,\n );\n }\n };\n\n const deleteMcpConfig = async (configName: string): Promise<void> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const res = await fetchApi.fetch(`${assistantBaseUrl}/mcp/config`, {\n method: 'DELETE',\n body: JSON.stringify({ name: configName }),\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n if (!res.ok) {\n const errorData = await res.json();\n\n const { error } = errorData;\n\n throw new Error(\n `Failed to delete MCP config: ${error || res.statusText}`,\n );\n }\n };\n\n return {\n getUserDefinedMcpConfigs,\n createMcpConfig,\n updateMcpConfig,\n deleteMcpConfig,\n };\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { createApiRef } from '@backstage/core-plugin-api';
2
+
3
+ const summarizerApiRef = createApiRef({
4
+ id: "plugin.ai-assistant.summarizer"
5
+ });
6
+ const createSummarizerService = ({
7
+ fetchApi,
8
+ discoveryApi
9
+ }) => {
10
+ const summarizeContent = async (content) => {
11
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
12
+ const response = await fetchApi.fetch(
13
+ `${assistantBaseUrl}/summary/content`,
14
+ {
15
+ method: "POST",
16
+ body: JSON.stringify({ content }),
17
+ headers: {
18
+ "Content-Type": "application/json"
19
+ }
20
+ }
21
+ );
22
+ const data = await response.json();
23
+ return data.summary;
24
+ };
25
+ const summarizeConversation = async (messages) => {
26
+ const assistantBaseUrl = await discoveryApi.getBaseUrl("ai-assistant");
27
+ const response = await fetchApi.fetch(
28
+ `${assistantBaseUrl}/summary/conversation`,
29
+ {
30
+ method: "POST",
31
+ body: JSON.stringify({ messages }),
32
+ headers: {
33
+ "Content-Type": "application/json"
34
+ }
35
+ }
36
+ );
37
+ const data = await response.json();
38
+ return data.summary;
39
+ };
40
+ return {
41
+ summarizeContent,
42
+ summarizeConversation
43
+ };
44
+ };
45
+
46
+ export { createSummarizerService, summarizerApiRef };
47
+ //# sourceMappingURL=summarizer.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summarizer.esm.js","sources":["../../src/api/summarizer.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\nexport type SummarizerApi = Awaited<ReturnType<typeof createSummarizerService>>;\n\ntype SummarizerApiOptions = {\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n};\n\nexport const summarizerApiRef = createApiRef<SummarizerApi>({\n id: 'plugin.ai-assistant.summarizer',\n});\n\nexport const createSummarizerService = ({\n fetchApi,\n discoveryApi,\n}: SummarizerApiOptions) => {\n const summarizeContent = async (content: string): Promise<string> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n\n const response = await fetchApi.fetch(\n `${assistantBaseUrl}/summary/content`,\n {\n method: 'POST',\n body: JSON.stringify({ content }),\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n\n const data = await response.json();\n return data.summary as string;\n };\n\n const summarizeConversation = async (\n messages: Message[],\n ): Promise<string> => {\n const assistantBaseUrl = await discoveryApi.getBaseUrl('ai-assistant');\n const response = await fetchApi.fetch(\n `${assistantBaseUrl}/summary/conversation`,\n {\n method: 'POST',\n body: JSON.stringify({ messages }),\n headers: {\n 'Content-Type': 'application/json',\n },\n },\n );\n\n const data = await response.json();\n return data.summary as string;\n };\n\n return {\n summarizeContent,\n summarizeConversation,\n };\n};\n"],"names":[],"mappings":";;AAWO,MAAM,mBAAmB,YAAA,CAA4B;AAAA,EAC1D,EAAA,EAAI;AACN,CAAC;AAEM,MAAM,0BAA0B,CAAC;AAAA,EACtC,QAAA;AAAA,EACA;AACF,CAAA,KAA4B;AAC1B,EAAA,MAAM,gBAAA,GAAmB,OAAO,OAAA,KAAqC;AACnE,IAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,UAAA,CAAW,cAAc,CAAA;AAErE,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,KAAA;AAAA,MAC9B,GAAG,gBAAgB,CAAA,gBAAA,CAAA;AAAA,MACnB;AAAA,QACE,MAAA,EAAQ,MAAA;AAAA,QACR,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,SAAS,CAAA;AAAA,QAChC,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB;AACF,KACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd,CAAA;AAEA,EAAA,MAAM,qBAAA,GAAwB,OAC5B,QAAA,KACoB;AACpB,IAAA,MAAM,gBAAA,GAAmB,MAAM,YAAA,CAAa,UAAA,CAAW,cAAc,CAAA;AACrE,IAAA,MAAM,QAAA,GAAW,MAAM,QAAA,CAAS,KAAA;AAAA,MAC9B,GAAG,gBAAgB,CAAA,qBAAA,CAAA;AAAA,MACnB;AAAA,QACE,MAAA,EAAQ,MAAA;AAAA,QACR,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,EAAE,UAAU,CAAA;AAAA,QACjC,OAAA,EAAS;AAAA,UACP,cAAA,EAAgB;AAAA;AAClB;AACF,KACF;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AACjC,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,gBAAA;AAAA,IACA;AAAA,GACF;AACF;;;;"}
@@ -0,0 +1,194 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+ import { useState, useEffect, useMemo } from 'react';
3
+ import { Conversation } from '../Conversation/Conversation.esm.js';
4
+ import '@mui/material/Dialog';
5
+ import '@mui/material/DialogTitle';
6
+ import '@mui/material/DialogContent';
7
+ import '@mui/material/DialogActions';
8
+ import Button from '@mui/material/Button';
9
+ import '@mui/material/Tab';
10
+ import '@mui/material/Tabs';
11
+ import Box from '@mui/material/Box';
12
+ import Typography from '@mui/material/Typography';
13
+ import Stack from '@mui/material/Stack';
14
+ import IconButton from '@mui/material/IconButton';
15
+ import '@mui/icons-material/Close';
16
+ import '@mui/icons-material/Settings';
17
+ import '@backstage/core-plugin-api';
18
+ import 'react-use';
19
+ import '@mui/material/TextField';
20
+ import '@mui/material/Paper';
21
+ import '@mui/material/Divider';
22
+ import '@mui/material/Card';
23
+ import '@mui/material/CardContent';
24
+ import '@mui/material/CardActions';
25
+ import '@mui/material/Alert';
26
+ import '@mui/icons-material/Delete';
27
+ import AddIcon from '@mui/icons-material/Add';
28
+ import Tooltip from '@mui/material/Tooltip';
29
+ import '../../api/mcp.esm.js';
30
+ import Modal from '@mui/material/Modal';
31
+ import Chip from '@mui/material/Chip';
32
+ import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';
33
+ import { useChatSettings } from '../../hooks/use-chat-settings.esm.js';
34
+ import { useLocation } from 'react-router-dom';
35
+ import { usePageSummary } from '../../hooks/use-page-summary.esm.js';
36
+
37
+ const AiAssistantChatModal = ({
38
+ open: controlledOpen,
39
+ onClose: controlledOnClose,
40
+ conversationId: controlledConversationId,
41
+ setConversationId: controlledSetConversationId
42
+ }) => {
43
+ const [modalOpen, setModalOpen] = useState(false);
44
+ const [modalConversationId, setModalConversationId] = useState();
45
+ const { modalVisible, setModalVisible, setSummaryEnabled, summaryEnabled } = useChatSettings();
46
+ const location = useLocation();
47
+ const { summary, loading, error } = usePageSummary();
48
+ useEffect(() => {
49
+ setModalVisible(true);
50
+ }, [location, setModalVisible, setSummaryEnabled]);
51
+ useEffect(() => {
52
+ if (!modalOpen) return;
53
+ setSummaryEnabled(true);
54
+ }, [modalOpen, setSummaryEnabled]);
55
+ const additionalSystemMessages = useMemo(() => {
56
+ if (!summaryEnabled || !summary) return [];
57
+ const path = location.pathname;
58
+ const title = document.title;
59
+ const content = `The user is currently on the page titled "${title}" located at "${path}". The page has the following context: ${summary}. Use this context to inform your responses where relevant.`;
60
+ return [
61
+ {
62
+ role: "system",
63
+ content,
64
+ metadata: {},
65
+ score: 0
66
+ }
67
+ ];
68
+ }, [summaryEnabled, summary, location.pathname]);
69
+ if (!modalVisible) {
70
+ return /* @__PURE__ */ jsx(Fragment, {});
71
+ }
72
+ const getChipColor = () => {
73
+ if (loading) return "primary";
74
+ if (error) return "error";
75
+ if (summary) return "success";
76
+ return void 0;
77
+ };
78
+ const getChipLabel = () => {
79
+ if (loading) return "Loading page context...";
80
+ if (error) return "Error loading page context";
81
+ if (summary) return "Page Context Loaded";
82
+ return "No page context available";
83
+ };
84
+ const isOpen = controlledOpen !== void 0 ? controlledOpen : modalOpen;
85
+ const conversationId = controlledConversationId !== void 0 ? controlledConversationId : modalConversationId;
86
+ const setConversationId = controlledSetConversationId || setModalConversationId;
87
+ const handleModalOpen = () => {
88
+ if (controlledOpen === void 0) {
89
+ setModalOpen(true);
90
+ }
91
+ };
92
+ const handleModalClose = () => {
93
+ if (controlledOnClose) {
94
+ controlledOnClose();
95
+ } else {
96
+ setModalOpen(false);
97
+ }
98
+ };
99
+ const openNewModalChat = () => {
100
+ setConversationId(void 0);
101
+ };
102
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
103
+ /* @__PURE__ */ jsx(
104
+ Box,
105
+ {
106
+ sx: {
107
+ position: "absolute",
108
+ bottom: (theme) => theme.spacing(2),
109
+ right: (theme) => theme.spacing(2),
110
+ borderRadius: "50%",
111
+ boxShadow: (theme) => theme.shadows[4],
112
+ bgcolor: (theme) => theme.palette.background.paper,
113
+ border: (theme) => `1px solid ${theme.palette.primary.main}`,
114
+ width: 50,
115
+ height: 50,
116
+ padding: (theme) => theme.spacing(1),
117
+ display: "flex",
118
+ justifyContent: "center",
119
+ alignItems: "center",
120
+ zIndex: (theme) => theme.zIndex.modal - 1,
121
+ ":hover": {
122
+ cursor: "pointer",
123
+ boxShadow: (theme) => theme.shadows[6],
124
+ bgcolor: (theme) => theme.palette.background.default
125
+ }
126
+ },
127
+ onClick: handleModalOpen,
128
+ children: /* @__PURE__ */ jsx(AutoAwesomeIcon, {})
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx(Modal, { open: isOpen, onClose: handleModalClose, children: /* @__PURE__ */ jsx(
132
+ Box,
133
+ {
134
+ sx: {
135
+ position: "absolute",
136
+ top: "50%",
137
+ left: "50%",
138
+ transform: "translate(-50%, -50%)",
139
+ width: "60vw",
140
+ height: "60vh",
141
+ bgcolor: "background.paper",
142
+ border: "1px solid #000",
143
+ padding: 2,
144
+ display: "flex",
145
+ flexDirection: "column"
146
+ },
147
+ children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, flex: 1, height: "100%", children: [
148
+ /* @__PURE__ */ jsxs(
149
+ Stack,
150
+ {
151
+ direction: "row",
152
+ spacing: 2,
153
+ justifyContent: "space-between",
154
+ alignItems: "center",
155
+ children: [
156
+ /* @__PURE__ */ jsx(Typography, { variant: "h6", component: "h2", children: "AI Assistant" }),
157
+ summaryEnabled && /* @__PURE__ */ jsx(
158
+ Tooltip,
159
+ {
160
+ title: summary ?? "No summary available",
161
+ placement: "bottom",
162
+ children: /* @__PURE__ */ jsx(
163
+ Chip,
164
+ {
165
+ label: getChipLabel(),
166
+ color: getChipColor(),
167
+ variant: "outlined"
168
+ }
169
+ )
170
+ }
171
+ ),
172
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, children: [
173
+ /* @__PURE__ */ jsx(Tooltip, { title: "New Chat", placement: "bottom", children: /* @__PURE__ */ jsx(IconButton, { onClick: openNewModalChat, children: /* @__PURE__ */ jsx(AddIcon, {}) }) }),
174
+ /* @__PURE__ */ jsx(Button, { variant: "outlined", onClick: handleModalClose, children: "Close" })
175
+ ] })
176
+ ]
177
+ }
178
+ ),
179
+ /* @__PURE__ */ jsx(
180
+ Conversation,
181
+ {
182
+ conversationId,
183
+ setConversationId,
184
+ additionalSystemMessages
185
+ }
186
+ )
187
+ ] })
188
+ }
189
+ ) })
190
+ ] });
191
+ };
192
+
193
+ export { AiAssistantChatModal };
194
+ //# sourceMappingURL=AiAssistantChatModal.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AiAssistantChatModal.esm.js","sources":["../../../src/components/AiAssistantChatModal/AiAssistantChatModal.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Conversation } from '../Conversation';\nimport Stack from '@mui/material/Stack';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport Modal from '@mui/material/Modal';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport AddIcon from '@mui/icons-material/Add';\nimport AutoAwesomeIcon from '@mui/icons-material/AutoAwesome';\nimport { useChatSettings } from '../../hooks/use-chat-settings';\nimport { useLocation } from 'react-router-dom';\nimport { usePageSummary } from '../../hooks/use-page-summary';\nimport { Message } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\n\nexport interface AiAssistantChatModalProps {\n open?: boolean;\n onClose?: () => void;\n conversationId?: string;\n setConversationId?: (id: string | undefined) => void;\n}\n\nexport const AiAssistantChatModal = ({\n open: controlledOpen,\n onClose: controlledOnClose,\n conversationId: controlledConversationId,\n setConversationId: controlledSetConversationId,\n}: AiAssistantChatModalProps) => {\n const [modalOpen, setModalOpen] = useState(false);\n const [modalConversationId, setModalConversationId] = useState<string>();\n\n const { modalVisible, setModalVisible, setSummaryEnabled, summaryEnabled } =\n useChatSettings();\n\n const location = useLocation();\n\n const { summary, loading, error } = usePageSummary();\n\n // Update visibility based on route changes\n useEffect(() => {\n setModalVisible(true);\n }, [location, setModalVisible, setSummaryEnabled]);\n\n useEffect(() => {\n if (!modalOpen) return;\n setSummaryEnabled(true);\n }, [modalOpen, setSummaryEnabled]);\n\n const additionalSystemMessages: Message[] = useMemo(() => {\n if (!summaryEnabled || !summary) return [];\n\n const path = location.pathname;\n const title = document.title;\n const content = `The user is currently on the page titled \"${title}\" located at \"${path}\". The page has the following context: ${summary}. Use this context to inform your responses where relevant.`;\n\n return [\n {\n role: 'system',\n content,\n metadata: {},\n score: 0,\n },\n ];\n }, [summaryEnabled, summary, location.pathname]);\n\n // If the context says the modal is not visible, don't render anything\n if (!modalVisible) {\n return <></>;\n }\n\n const getChipColor = () => {\n if (loading) return 'primary';\n if (error) return 'error';\n if (summary) return 'success';\n return undefined;\n };\n\n const getChipLabel = () => {\n if (loading) return 'Loading page context...';\n if (error) return 'Error loading page context';\n if (summary) return 'Page Context Loaded';\n return 'No page context available';\n };\n\n // Use controlled props if provided, otherwise use internal state\n const isOpen = controlledOpen !== undefined ? controlledOpen : modalOpen;\n const conversationId =\n controlledConversationId !== undefined\n ? controlledConversationId\n : modalConversationId;\n const setConversationId =\n controlledSetConversationId || setModalConversationId;\n\n const handleModalOpen = () => {\n if (controlledOpen === undefined) {\n setModalOpen(true);\n }\n };\n\n const handleModalClose = () => {\n if (controlledOnClose) {\n controlledOnClose();\n } else {\n setModalOpen(false);\n }\n };\n\n const openNewModalChat = () => {\n setConversationId(undefined);\n };\n\n return (\n <>\n <Box\n sx={{\n position: 'absolute',\n bottom: theme => theme.spacing(2),\n right: theme => theme.spacing(2),\n borderRadius: '50%',\n boxShadow: theme => theme.shadows[4],\n bgcolor: theme => theme.palette.background.paper,\n border: theme => `1px solid ${theme.palette.primary.main}`,\n width: 50,\n height: 50,\n padding: theme => theme.spacing(1),\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n zIndex: theme => theme.zIndex.modal - 1,\n ':hover': {\n cursor: 'pointer',\n boxShadow: theme => theme.shadows[6],\n bgcolor: theme => theme.palette.background.default,\n },\n }}\n onClick={handleModalOpen}\n >\n <AutoAwesomeIcon />\n </Box>\n\n <Modal open={isOpen} onClose={handleModalClose}>\n <Box\n sx={{\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n width: '60vw',\n height: '60vh',\n bgcolor: 'background.paper',\n border: '1px solid #000',\n padding: 2,\n display: 'flex',\n flexDirection: 'column',\n }}\n >\n <Stack spacing={2} flex={1} height=\"100%\">\n <Stack\n direction=\"row\"\n spacing={2}\n justifyContent=\"space-between\"\n alignItems=\"center\"\n >\n <Typography variant=\"h6\" component=\"h2\">\n AI Assistant\n </Typography>\n {summaryEnabled && (\n <Tooltip\n title={summary ?? 'No summary available'}\n placement=\"bottom\"\n >\n <Chip\n label={getChipLabel()}\n color={getChipColor()}\n variant=\"outlined\"\n />\n </Tooltip>\n )}\n <Stack direction=\"row\" spacing={1}>\n <Tooltip title=\"New Chat\" placement=\"bottom\">\n <IconButton onClick={openNewModalChat}>\n <AddIcon />\n </IconButton>\n </Tooltip>\n <Button variant=\"outlined\" onClick={handleModalClose}>\n Close\n </Button>\n </Stack>\n </Stack>\n <Conversation\n conversationId={conversationId}\n setConversationId={setConversationId}\n additionalSystemMessages={additionalSystemMessages}\n />\n </Stack>\n </Box>\n </Modal>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { AiAssistantChatModal } from './AiAssistantChatModal.esm.js';
2
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,27 +1,50 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
2
+ import { useEffect, useState } from 'react';
3
3
  import { Conversation } from '../Conversation/Conversation.esm.js';
4
- import { useAsync, useList } from 'react-use';
5
- import { chatApiRef } from '../../api/chat.esm.js';
6
- import { useTheme } from '@mui/material/styles';
4
+ import '@mui/material/Dialog';
5
+ import '@mui/material/DialogTitle';
6
+ import '@mui/material/DialogContent';
7
+ import '@mui/material/DialogActions';
8
+ import '@mui/material/Button';
9
+ import '@mui/material/Tab';
10
+ import '@mui/material/Tabs';
11
+ import Box from '@mui/material/Box';
12
+ import Typography from '@mui/material/Typography';
7
13
  import Stack from '@mui/material/Stack';
8
14
  import IconButton from '@mui/material/IconButton';
15
+ import '@mui/icons-material/Close';
16
+ import '@mui/icons-material/Settings';
17
+ import { useApi } from '@backstage/core-plugin-api';
18
+ import { useAsync, useList } from 'react-use';
19
+ import '@mui/material/TextField';
20
+ import '@mui/material/Paper';
21
+ import '@mui/material/Divider';
22
+ import '@mui/material/Card';
23
+ import '@mui/material/CardContent';
24
+ import '@mui/material/CardActions';
25
+ import '@mui/material/Alert';
26
+ import '@mui/icons-material/Delete';
27
+ import AddIcon from '@mui/icons-material/Add';
9
28
  import Tooltip from '@mui/material/Tooltip';
29
+ import '../../api/mcp.esm.js';
30
+ import { chatApiRef } from '../../api/chat.esm.js';
31
+ import { useTheme } from '@mui/material/styles';
10
32
  import Drawer from '@mui/material/Drawer';
11
33
  import List from '@mui/material/List';
12
34
  import ListItemButton from '@mui/material/ListItemButton';
13
35
  import ListItem from '@mui/material/ListItem';
14
- import Box from '@mui/material/Box';
15
- import Typography from '@mui/material/Typography';
16
36
  import MenuIcon from '@mui/icons-material/Menu';
17
- import AddIcon from '@mui/icons-material/Add';
18
- import { useApi } from '@backstage/core-plugin-api';
19
37
  import { signalApiRef } from '@backstage/plugin-signals-react';
38
+ import { useChatSettings } from '../../hooks/use-chat-settings.esm.js';
20
39
 
21
40
  const AiAssistantPage = () => {
22
41
  const chatApi = useApi(chatApiRef);
23
42
  const signalApi = useApi(signalApiRef);
24
43
  const theme = useTheme();
44
+ const chatSettings = useChatSettings();
45
+ useEffect(() => {
46
+ chatSettings.setModalVisible(false);
47
+ }, [chatSettings]);
25
48
  const [conversationId, setConversationId] = useState();
26
49
  const { value: conversationHistory } = useAsync(
27
50
  () => chatApi.getConversations(),
@@ -1 +1 @@
1
- {"version":3,"file":"AiAssistantPage.esm.js","sources":["../../../src/components/AiAssistantPage/AiAssistantPage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Conversation } from '../Conversation';\nimport type { Conversation as ConversationType } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { useAsync, useList } from 'react-use';\nimport { chatApiRef } from '../../api/chat';\nimport { useTheme } from '@mui/material/styles';\n\nimport Stack from '@mui/material/Stack';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport Drawer from '@mui/material/Drawer';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItem from '@mui/material/ListItem';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\n\nimport MenuIcon from '@mui/icons-material/Menu';\nimport AddIcon from '@mui/icons-material/Add';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { signalApiRef } from '@backstage/plugin-signals-react';\n\nexport const AiAssistantPage = () => {\n const chatApi = useApi(chatApiRef);\n const signalApi = useApi(signalApiRef);\n\n const theme = useTheme();\n\n const [conversationId, setConversationId] = useState<string>();\n\n const { value: conversationHistory } = useAsync(\n () => chatApi.getConversations(),\n [chatApi],\n );\n\n const [conversations, { set, updateAt }] = useList<ConversationType>([]);\n\n useEffect(() => {\n if (!conversationHistory) {\n return;\n }\n\n set(conversationHistory);\n }, [conversationHistory, set]);\n\n useEffect(() => {\n const subscription = signalApi.subscribe<{\n conversation: ConversationType;\n }>(`ai-assistant.chat.conversation-details-update`, ({ conversation }) => {\n set(currentConversations => {\n const index = currentConversations.findIndex(\n c => c.id === conversation.id,\n );\n\n if (index !== -1) {\n const newConversations = [...currentConversations];\n newConversations[index] = conversation;\n return newConversations;\n }\n return [conversation, ...currentConversations];\n });\n });\n\n return () => subscription.unsubscribe();\n }, [signalApi, set, updateAt]);\n\n const [open, setOpen] = useState(false);\n\n const toggleDrawer = (drawerOpen: boolean) => () => {\n setOpen(drawerOpen);\n };\n\n const openNewChat = () => {\n setConversationId(undefined);\n };\n\n return (\n <>\n <Stack\n padding={2}\n spacing={2}\n flex={1}\n height=\"100vh\"\n maxHeight=\"100vh\"\n boxSizing=\"border-box\"\n >\n <Stack direction=\"row\" spacing={2} justifyContent=\"flex-end\">\n <Tooltip title=\"New Chat\">\n <IconButton onClick={openNewChat}>\n <AddIcon />\n </IconButton>\n </Tooltip>\n\n {conversations.length > 0 && (\n <Tooltip title=\"Chat History\">\n <IconButton onClick={toggleDrawer(true)}>\n <MenuIcon />\n </IconButton>\n </Tooltip>\n )}\n </Stack>\n <Conversation\n conversationId={conversationId}\n setConversationId={setConversationId}\n />\n </Stack>\n <Drawer anchor=\"right\" open={open} onClose={toggleDrawer(false)}>\n <Box\n sx={{ width: 300 }}\n role=\"presentation\"\n onClick={toggleDrawer(false)}\n >\n <List>\n {conversations.map(conversation => (\n <ListItem\n key={conversation.id}\n sx={{\n fontSize: theme.typography.body1.fontSize,\n }}\n >\n <ListItemButton\n sx={{\n justifyContent: 'flex-start !important',\n padding: `${theme.spacing(1)} !important`,\n borderRadius: `${theme.spacing(1)} !important`,\n backgroundColor:\n conversationId === conversation.id\n ? `${theme.palette.action.selected} !important`\n : 'transparent !important',\n }}\n onClick={() => setConversationId(conversation.id)}\n >\n <Typography\n variant=\"body1\"\n sx={{\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n width: '100%',\n }}\n >\n {conversation.title}\n </Typography>\n </ListItemButton>\n </ListItem>\n ))}\n </List>\n </Box>\n </Drawer>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AiAssistantPage.esm.js","sources":["../../../src/components/AiAssistantPage/AiAssistantPage.tsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Conversation } from '../Conversation';\nimport type { Conversation as ConversationType } from '@sweetoburrito/backstage-plugin-ai-assistant-common';\nimport { useAsync, useList } from 'react-use';\nimport { chatApiRef } from '../../api/chat';\nimport { useTheme } from '@mui/material/styles';\n\nimport Stack from '@mui/material/Stack';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport Drawer from '@mui/material/Drawer';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItem from '@mui/material/ListItem';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\n\nimport MenuIcon from '@mui/icons-material/Menu';\nimport AddIcon from '@mui/icons-material/Add';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { signalApiRef } from '@backstage/plugin-signals-react';\nimport { useChatSettings } from '../../hooks/use-chat-settings';\n\nexport const AiAssistantPage = () => {\n const chatApi = useApi(chatApiRef);\n const signalApi = useApi(signalApiRef);\n\n const theme = useTheme();\n\n const chatSettings = useChatSettings();\n\n useEffect(() => {\n chatSettings.setModalVisible(false);\n }, [chatSettings]);\n\n const [conversationId, setConversationId] = useState<string>();\n\n const { value: conversationHistory } = useAsync(\n () => chatApi.getConversations(),\n [chatApi],\n );\n\n const [conversations, { set, updateAt }] = useList<ConversationType>([]);\n\n useEffect(() => {\n if (!conversationHistory) {\n return;\n }\n\n set(conversationHistory);\n }, [conversationHistory, set]);\n\n useEffect(() => {\n const subscription = signalApi.subscribe<{\n conversation: ConversationType;\n }>(`ai-assistant.chat.conversation-details-update`, ({ conversation }) => {\n set(currentConversations => {\n const index = currentConversations.findIndex(\n c => c.id === conversation.id,\n );\n\n if (index !== -1) {\n const newConversations = [...currentConversations];\n newConversations[index] = conversation;\n return newConversations;\n }\n return [conversation, ...currentConversations];\n });\n });\n\n return () => subscription.unsubscribe();\n }, [signalApi, set, updateAt]);\n\n const [open, setOpen] = useState(false);\n\n const toggleDrawer = (drawerOpen: boolean) => () => {\n setOpen(drawerOpen);\n };\n\n const openNewChat = () => {\n setConversationId(undefined);\n };\n\n return (\n <>\n <Stack\n padding={2}\n spacing={2}\n flex={1}\n height=\"100vh\"\n maxHeight=\"100vh\"\n boxSizing=\"border-box\"\n >\n <Stack direction=\"row\" spacing={2} justifyContent=\"flex-end\">\n <Tooltip title=\"New Chat\">\n <IconButton onClick={openNewChat}>\n <AddIcon />\n </IconButton>\n </Tooltip>\n\n {conversations.length > 0 && (\n <Tooltip title=\"Chat History\">\n <IconButton onClick={toggleDrawer(true)}>\n <MenuIcon />\n </IconButton>\n </Tooltip>\n )}\n </Stack>\n <Conversation\n conversationId={conversationId}\n setConversationId={setConversationId}\n />\n </Stack>\n <Drawer anchor=\"right\" open={open} onClose={toggleDrawer(false)}>\n <Box\n sx={{ width: 300 }}\n role=\"presentation\"\n onClick={toggleDrawer(false)}\n >\n <List>\n {conversations.map(conversation => (\n <ListItem\n key={conversation.id}\n sx={{\n fontSize: theme.typography.body1.fontSize,\n }}\n >\n <ListItemButton\n sx={{\n justifyContent: 'flex-start !important',\n padding: `${theme.spacing(1)} !important`,\n borderRadius: `${theme.spacing(1)} !important`,\n backgroundColor:\n conversationId === conversation.id\n ? `${theme.palette.action.selected} !important`\n : 'transparent !important',\n }}\n onClick={() => setConversationId(conversation.id)}\n >\n <Typography\n variant=\"body1\"\n sx={{\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n width: '100%',\n }}\n >\n {conversation.title}\n </Typography>\n </ListItemButton>\n </ListItem>\n ))}\n </List>\n </Box>\n </Drawer>\n </>\n );\n};\n"],"names":[],"mappings":"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"}
@@ -10,12 +10,15 @@ import Stack from '@mui/material/Stack';
10
10
  import Autocomplete from '@mui/material/Autocomplete';
11
11
  import Paper from '@mui/material/Paper';
12
12
  import NorthIcon from '@mui/icons-material/North';
13
+ import SettingsIcon from '@mui/icons-material/Settings';
13
14
  import Button from '@mui/material/Button';
14
15
  import { MessageCard } from '../MessageCard/MessageCard.esm.js';
16
+ import { SettingsModal } from './SettingsModal.esm.js';
15
17
 
16
18
  const Conversation = ({
17
19
  conversationId,
18
- setConversationId
20
+ setConversationId,
21
+ additionalSystemMessages
19
22
  }) => {
20
23
  const chatApi = useApi(chatApiRef);
21
24
  const errorApi = useApi(errorApiRef);
@@ -26,6 +29,7 @@ const Conversation = ({
26
29
  "modelId",
27
30
  void 0
28
31
  );
32
+ const [settingsModalOpen, setSettingsModalOpen] = useState(false);
29
33
  const { value: models, loading: loadingModels } = useAsync(
30
34
  () => chatApi.getModels(),
31
35
  [chatApi]
@@ -84,7 +88,7 @@ const Conversation = ({
84
88
  }, [models, modelId, setModelId]);
85
89
  const [{ loading: sending }, sendMessage] = useAsyncFn(async () => {
86
90
  const newMessages = [
87
- { role: "human", content: input, metadata: {} }
91
+ { role: "human", content: input, metadata: {}, score: 0 }
88
92
  ];
89
93
  if (!modelId) {
90
94
  errorApi.post({
@@ -99,7 +103,7 @@ const Conversation = ({
99
103
  const response = await chatApi.sendMessage({
100
104
  conversationId,
101
105
  modelId,
102
- messages: newMessages
106
+ messages: additionalSystemMessages ? [...additionalSystemMessages, ...newMessages] : newMessages
103
107
  });
104
108
  setConversationId(response.conversationId);
105
109
  return response;
@@ -157,10 +161,10 @@ const Conversation = ({
157
161
  },
158
162
  message.id ?? idx
159
163
  )),
160
- (messages[messages.length - 1]?.role === "human" || messages[messages.length - 1]?.role === "tool") && /* @__PURE__ */ jsx(
164
+ messages[messages.length - 1] && messages[messages.length - 1]?.role !== "ai" && /* @__PURE__ */ jsx(
161
165
  MessageCard,
162
166
  {
163
- message: { content: "", role: "ai", metadata: {} },
167
+ message: { content: "", role: "ai", metadata: {}, score: 0 },
164
168
  loading: true
165
169
  }
166
170
  ),
@@ -205,6 +209,17 @@ const Conversation = ({
205
209
  renderInput: (params) => /* @__PURE__ */ jsx(TextField, { ...params, label: "Models" })
206
210
  }
207
211
  ),
212
+ /* @__PURE__ */ jsx(
213
+ Button,
214
+ {
215
+ disabled: sending,
216
+ variant: "contained",
217
+ color: "info",
218
+ title: "Settings",
219
+ onClick: () => setSettingsModalOpen(true),
220
+ children: /* @__PURE__ */ jsx(SettingsIcon, {})
221
+ }
222
+ ),
208
223
  /* @__PURE__ */ jsx(
209
224
  Button,
210
225
  {
@@ -216,7 +231,14 @@ const Conversation = ({
216
231
  )
217
232
  ]
218
233
  }
219
- ) })
234
+ ) }),
235
+ /* @__PURE__ */ jsx(
236
+ SettingsModal,
237
+ {
238
+ open: settingsModalOpen,
239
+ onClose: () => setSettingsModalOpen(false)
240
+ }
241
+ )
220
242
  ]
221
243
  }
222
244
  );