@backstage-community/plugin-mcp-chat 0.1.2 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,68 @@
1
1
  # @backstage-community/plugin-mcp-chat
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 207781a: ### Added Conversation History Feature
8
+
9
+ - **Conversation Persistence**: Chat sessions are automatically saved for authenticated users
10
+ - **Starring**: Mark important conversations as favorites for quick access
11
+ - **Search**: Filter conversations by title using client-side search
12
+ - **Delete**: Remove individual conversations or clear all history
13
+ - **AI-Generated Titles**: Conversations get auto-generated titles using the LLM (with fallback to first message)
14
+
15
+ ### Backend Improvements
16
+
17
+ - Refactored router into domain-specific modules (status, chat, conversations) for better maintainability
18
+ - Added authentication and validation middleware
19
+ - New API endpoints for conversation management (list, get, delete, star, update title)
20
+ - Added `ChatConversationStore` and `SummarizationService` to public exports
21
+ - Comprehensive unit tests for `ChatConversationStore`
22
+
23
+ ### Configuration Options
24
+
25
+ New `conversationHistory` config section with `displayLimit`, `autoSummarize`, and `summarizeTimeout` options.
26
+
27
+ ### Notes
28
+
29
+ - Guest users (`user:development/guest`) do not have conversations saved
30
+ - Conversations stored in `mcp_chat_conversations` database table with automatic migrations
31
+
32
+ ## 0.2.0
33
+
34
+ ### Minor Changes
35
+
36
+ - c330b2c: **BREAKING**: Removed SSE (Server-Sent Events) transport support
37
+
38
+ The deprecated `SSEClientTransport` has been removed in favor of `StreamableHTTPClientTransport`, which is the modern MCP standard.
39
+
40
+ **Migration:**
41
+
42
+ If you had MCP servers configured with `type: sse`, update your configuration:
43
+
44
+ ```yaml
45
+ # Before (no longer supported)
46
+ mcpServers:
47
+ - id: my-server
48
+ name: My Server
49
+ type: sse
50
+ url: 'http://example.com/sse'
51
+
52
+ # After
53
+ mcpServers:
54
+ - id: my-server
55
+ name: My Server
56
+ url: 'http://example.com/mcp' # type is auto-detected when url is present
57
+ ```
58
+
59
+ **Changes:**
60
+
61
+ - Removed `MCPServerType.SSE` enum value from both frontend and backend
62
+ - Removed SSE transport fallback logic from `MCPClientServiceImpl`
63
+ - Updated configuration schema to only accept `stdio` and `streamable-http` types
64
+ - HTTP servers are now auto-detected when a `url` field is present
65
+
3
66
  ## 0.1.2
4
67
 
5
68
  ### Patch Changes
package/README.md CHANGED
@@ -11,10 +11,11 @@ The MCP Chat plugin brings conversational AI capabilities directly into your Bac
11
11
  ## Features
12
12
 
13
13
  - 🤖 **Multi-Provider AI Support**: Works with OpenAI, Claude, Gemini, and Ollama
14
- - 🔧 **Multi-Server Support**: Connect multiple MCP servers (STDIO, SSE, Streamable HTTP)
14
+ - 🔧 **Multi-Server Support**: Connect multiple MCP servers (STDIO, Streamable HTTP)
15
15
  - 🛠️ **Tool Management**: Browse and dynamically enable/disable tools from connected MCP servers
16
16
  - 💬 **Rich Chat Interface**: Beautiful, responsive chat UI with markdown support
17
17
  - ⚡ **Quick Setup**: Configurable QuickStart prompts for common use cases
18
+ - 📜 **Conversation History**: View, search, star, and manage your chat sessions
18
19
 
19
20
  ## Supported AI Providers
20
21
 
@@ -198,7 +199,7 @@ mcpChat:
198
199
  category: Catalog
199
200
  ```
200
201
 
201
- For more advanced MCP server configuration examples (including STDIO, Streamable HTTP, SSE, custom scripts, and arguments), see [SERVER_CONFIGURATION](../../docs/SERVER_CONFIGURATION.md).
202
+ For more advanced MCP server configuration examples (including STDIO, Streamable HTTP, custom scripts, and arguments), see [SERVER_CONFIGURATION](../../docs/SERVER_CONFIGURATION.md).
202
203
 
203
204
  ### Environment Variables
204
205
 
@@ -226,9 +227,17 @@ export KUBECONFIG="/path/to/your/kubeconfig.yaml"
226
227
  - Tool management controls for enabling/disabling specific servers
227
228
 
228
229
  3. **Start Chatting**: Begin a conversation by:
230
+
229
231
  - Selecting from the provided quick prompts, or
230
232
  - Typing your own queries directly into the chat input field
231
233
 
234
+ 4. **Manage Conversation History**: Access your chat history from the right sidebar:
235
+ - View past conversations ordered by recent activity
236
+ - Star important conversations for quick access
237
+ - Search through conversation titles
238
+ - Delete conversations you no longer need
239
+ - Click any conversation to restore and continue it
240
+
232
241
  ### Example Queries
233
242
 
234
243
  | Query | MCP Server Required | Purpose |
@@ -7,7 +7,7 @@ class McpChat {
7
7
  this.discoveryApi = options.discoveryApi;
8
8
  this.fetchApi = options.fetchApi;
9
9
  }
10
- async sendChatMessage(messages, enabledTools = [], signal) {
10
+ async sendChatMessage(messages, enabledTools = [], signal, conversationId) {
11
11
  const baseUrl = await this.discoveryApi.getBaseUrl("mcp-chat");
12
12
  const response = await this.fetchApi.fetch(`${baseUrl}/chat`, {
13
13
  method: "POST",
@@ -16,7 +16,8 @@ class McpChat {
16
16
  },
17
17
  body: JSON.stringify({
18
18
  messages,
19
- enabledTools
19
+ enabledTools,
20
+ conversationId
20
21
  }),
21
22
  signal
22
23
  });
@@ -49,6 +50,45 @@ class McpChat {
49
50
  }
50
51
  return response.json();
51
52
  }
53
+ async getConversations() {
54
+ const baseUrl = await this.discoveryApi.getBaseUrl("mcp-chat");
55
+ const response = await this.fetchApi.fetch(`${baseUrl}/conversations`);
56
+ if (!response.ok) {
57
+ throw await ResponseError.fromResponse(response);
58
+ }
59
+ return response.json();
60
+ }
61
+ async getConversationById(id) {
62
+ const baseUrl = await this.discoveryApi.getBaseUrl("mcp-chat");
63
+ const response = await this.fetchApi.fetch(
64
+ `${baseUrl}/conversations/${id}`
65
+ );
66
+ if (!response.ok) {
67
+ throw await ResponseError.fromResponse(response);
68
+ }
69
+ return response.json();
70
+ }
71
+ async deleteConversation(id) {
72
+ const baseUrl = await this.discoveryApi.getBaseUrl("mcp-chat");
73
+ const response = await this.fetchApi.fetch(
74
+ `${baseUrl}/conversations/${id}`,
75
+ { method: "DELETE" }
76
+ );
77
+ if (!response.ok) {
78
+ throw await ResponseError.fromResponse(response);
79
+ }
80
+ }
81
+ async toggleConversationStar(id) {
82
+ const baseUrl = await this.discoveryApi.getBaseUrl("mcp-chat");
83
+ const response = await this.fetchApi.fetch(
84
+ `${baseUrl}/conversations/${id}/star`,
85
+ { method: "PATCH" }
86
+ );
87
+ if (!response.ok) {
88
+ throw await ResponseError.fromResponse(response);
89
+ }
90
+ return response.json();
91
+ }
52
92
  }
53
93
 
54
94
  export { McpChat };
@@ -1 +1 @@
1
- {"version":3,"file":"McpChatApi.esm.js","sources":["../../src/api/McpChatApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport {\n ChatMessage,\n ChatResponse,\n MCPServerStatusData,\n ProviderStatusData,\n ToolsResponse,\n} from '../types';\n\n/**\n * @public\n */\nexport interface McpChatApi {\n sendChatMessage(\n messages: ChatMessage[],\n enabledTools?: string[],\n signal?: AbortSignal,\n ): Promise<ChatResponse>;\n getMCPServerStatus(): Promise<MCPServerStatusData>;\n getAvailableTools(): Promise<ToolsResponse>;\n getProviderStatus(): Promise<ProviderStatusData>;\n}\n\nexport class McpChat implements McpChatApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n }\n\n async sendChatMessage(\n messages: ChatMessage[],\n enabledTools: string[] = [],\n signal?: AbortSignal,\n ): Promise<ChatResponse> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/chat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n messages,\n enabledTools,\n }),\n signal,\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async getMCPServerStatus(): Promise<MCPServerStatusData> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n const response = await this.fetchApi.fetch(`${baseUrl}/mcp/status`);\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n return response.json();\n }\n\n async getAvailableTools(): Promise<ToolsResponse> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/tools`);\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async getProviderStatus(): Promise<ProviderStatusData> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/provider/status`);\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n}\n"],"names":[],"mappings":";;AAwCO,MAAM,OAAA,CAA8B;AAAA,EACxB,YAAA;AAAA,EACA,QAAA;AAAA,EAEjB,YAAY,OAAA,EAA6D;AACvE,IAAA,IAAA,CAAK,eAAe,OAAA,CAAQ,YAAA;AAC5B,IAAA,IAAA,CAAK,WAAW,OAAA,CAAQ,QAAA;AAAA;AAC1B,EAEA,MAAM,eAAA,CACJ,QAAA,EACA,YAAA,GAAyB,IACzB,MAAA,EACuB;AACvB,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,YAAA,CAAa,WAAW,UAAU,CAAA;AAE7D,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,KAAA,CAAA,EAAS;AAAA,MAC5D,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB;AAAA,OAClB;AAAA,MACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,QACnB,QAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,MACD;AAAA,KACD,CAAA;AAED,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAA,OAAO,SAAS,IAAA,EAAK;AAAA;AACvB,EAEA,MAAM,kBAAA,GAAmD;AACvD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,YAAA,CAAa,WAAW,UAAU,CAAA;AAC7D,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,WAAA,CAAa,CAAA;AAClE,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA;AAEjD,IAAA,OAAO,SAAS,IAAA,EAAK;AAAA;AACvB,EAEA,MAAM,iBAAA,GAA4C;AAChD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,YAAA,CAAa,WAAW,UAAU,CAAA;AAE7D,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,MAAA,CAAQ,CAAA;AAE7D,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAA,OAAO,SAAS,IAAA,EAAK;AAAA;AACvB,EAEA,MAAM,iBAAA,GAAiD;AACrD,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,YAAA,CAAa,WAAW,UAAU,CAAA;AAE7D,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,SAAS,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,gBAAA,CAAkB,CAAA;AAEvE,IAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,MAAA,MAAM,MAAM,aAAA,CAAc,YAAA,CAAa,QAAQ,CAAA;AAAA;AAGjD,IAAA,OAAO,SAAS,IAAA,EAAK;AAAA;AAEzB;;;;"}
1
+ {"version":3,"file":"McpChatApi.esm.js","sources":["../../src/api/McpChatApi.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api';\nimport { ResponseError } from '@backstage/errors';\nimport {\n ChatMessage,\n ChatResponse,\n ConversationRecord,\n ConversationsResponse,\n MCPServerStatusData,\n ProviderStatusData,\n ToolsResponse,\n} from '../types';\n\n/**\n * @public\n */\nexport interface McpChatApi {\n sendChatMessage(\n messages: ChatMessage[],\n enabledTools?: string[],\n signal?: AbortSignal,\n conversationId?: string,\n ): Promise<ChatResponse>;\n getMCPServerStatus(): Promise<MCPServerStatusData>;\n getAvailableTools(): Promise<ToolsResponse>;\n getProviderStatus(): Promise<ProviderStatusData>;\n getConversations(): Promise<ConversationsResponse>;\n getConversationById(id: string): Promise<ConversationRecord>;\n deleteConversation(id: string): Promise<void>;\n toggleConversationStar(id: string): Promise<{ isStarred: boolean }>;\n}\n\nexport class McpChat implements McpChatApi {\n private readonly discoveryApi: DiscoveryApi;\n private readonly fetchApi: FetchApi;\n\n constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }) {\n this.discoveryApi = options.discoveryApi;\n this.fetchApi = options.fetchApi;\n }\n\n async sendChatMessage(\n messages: ChatMessage[],\n enabledTools: string[] = [],\n signal?: AbortSignal,\n conversationId?: string,\n ): Promise<ChatResponse> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/chat`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n messages,\n enabledTools,\n conversationId,\n }),\n signal,\n });\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async getMCPServerStatus(): Promise<MCPServerStatusData> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n const response = await this.fetchApi.fetch(`${baseUrl}/mcp/status`);\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n return response.json();\n }\n\n async getAvailableTools(): Promise<ToolsResponse> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/tools`);\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async getProviderStatus(): Promise<ProviderStatusData> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/provider/status`);\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async getConversations(): Promise<ConversationsResponse> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(`${baseUrl}/conversations`);\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async getConversationById(id: string): Promise<ConversationRecord> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/conversations/${id}`,\n );\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n\n async deleteConversation(id: string): Promise<void> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/conversations/${id}`,\n { method: 'DELETE' },\n );\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n }\n\n async toggleConversationStar(id: string): Promise<{ isStarred: boolean }> {\n const baseUrl = await this.discoveryApi.getBaseUrl('mcp-chat');\n\n const response = await this.fetchApi.fetch(\n `${baseUrl}/conversations/${id}/star`,\n { method: 'PATCH' },\n );\n\n if (!response.ok) {\n throw await ResponseError.fromResponse(response);\n }\n\n return response.json();\n }\n}\n"],"names":[],"mappings":"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"}
@@ -12,7 +12,14 @@ import { QuickStart } from './QuickStart.esm.js';
12
12
  import { TypingIndicator } from './TypingIndicator.esm.js';
13
13
 
14
14
  const ChatContainer = forwardRef(
15
- ({ sidebarCollapsed, mcpServers, messages, onMessagesChange }, ref) => {
15
+ ({
16
+ sidebarCollapsed,
17
+ mcpServers,
18
+ messages,
19
+ onMessagesChange,
20
+ conversationId,
21
+ onConversationUpdated
22
+ }, ref) => {
16
23
  const theme = useTheme();
17
24
  const mcpChatApi = useApi(mcpChatApiRef);
18
25
  const [inputValue, setInputValue] = useState("");
@@ -74,13 +81,17 @@ const ChatContainer = forwardRef(
74
81
  const response = await mcpChatApi.sendChatMessage(
75
82
  apiMessages,
76
83
  enabledTools,
77
- abortControllerRef.current.signal
84
+ abortControllerRef.current.signal,
85
+ conversationId
78
86
  );
79
87
  if (abortControllerRef.current?.signal.aborted) {
80
88
  return;
81
89
  }
82
90
  setIsTyping(false);
83
91
  abortControllerRef.current = null;
92
+ if (response.conversationId && onConversationUpdated) {
93
+ onConversationUpdated(response.conversationId);
94
+ }
84
95
  const botResponse = {
85
96
  id: (Date.now() + 1).toString(),
86
97
  text: response.content,
@@ -1 +1 @@
1
- {"version":3,"file":"ChatContainer.esm.js","sources":["../../../src/components/ChatContainer/ChatContainer.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n useEffect,\n useRef,\n useState,\n useImperativeHandle,\n forwardRef,\n} from 'react';\nimport Box from '@mui/material/Box';\nimport IconButton from '@mui/material/IconButton';\nimport TextField from '@mui/material/TextField';\nimport SendIcon from '@mui/icons-material/Send';\nimport { useTheme } from '@mui/material/styles';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { mcpChatApiRef } from '../../api';\nimport type { ChatMessage as ApiChatMessage } from '../../types';\nimport { ChatMessage } from './ChatMessage';\nimport { QuickStart } from './QuickStart';\nimport { TypingIndicator } from './TypingIndicator';\n\ninterface Message {\n id: string;\n text: string;\n isUser: boolean;\n timestamp: Date;\n tools?: string[];\n toolsUsed?: string[];\n toolResponses?: any[]; // Store the actual tool response objects\n}\n\ninterface MCPServer {\n id: string;\n name: string;\n enabled: boolean;\n type?: string;\n hasUrl?: boolean;\n hasNpxCommand?: boolean;\n hasScriptPath?: boolean;\n}\n\ninterface ChatContainerProps {\n sidebarCollapsed: boolean;\n mcpServers: MCPServer[];\n messages: Message[];\n onMessagesChange: (messages: Message[]) => void;\n}\n\nexport interface ChatContainerRef {\n cancelOngoingRequest: () => void;\n}\n\nexport const ChatContainer = forwardRef<ChatContainerRef, ChatContainerProps>(\n ({ sidebarCollapsed, mcpServers, messages, onMessagesChange }, ref) => {\n const theme = useTheme();\n const mcpChatApi = useApi(mcpChatApiRef);\n const [inputValue, setInputValue] = useState('');\n const [isTyping, setIsTyping] = useState(false);\n const messagesEndRef = useRef<null | HTMLDivElement>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n };\n\n // Function to cancel ongoing requests\n const cancelOngoingRequest = () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n setIsTyping(false);\n }\n };\n\n // Expose the cancel function through ref\n useImperativeHandle(\n ref,\n () => ({\n cancelOngoingRequest,\n }),\n [],\n );\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n // Cleanup: cancel any ongoing requests when component unmounts\n useEffect(() => {\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n // Shared function to send messages to API\n const sendMessageToAPI = async (messageText: string) => {\n // Cancel any existing request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Create new AbortController for this request\n abortControllerRef.current = new AbortController();\n\n const newMessage: Message = {\n id: Date.now().toString(),\n text: messageText,\n isUser: true,\n timestamp: new Date(),\n };\n onMessagesChange([...messages, newMessage]);\n setIsTyping(true);\n\n try {\n // Convert messages to API format including the new message\n const apiMessages: ApiChatMessage[] = [\n ...messages.map(msg => ({\n role: msg.isUser ? ('user' as const) : ('assistant' as const),\n content: msg.text,\n })),\n {\n role: 'user' as const,\n content: messageText,\n },\n ];\n\n // Get enabled tools from MCP servers\n // Backend uses server IDs to filter tools (tool.serverId matches serverConfig.id)\n const enabledTools = mcpServers\n .filter(server => server.enabled)\n .map(server => server.id);\n\n const response = await mcpChatApi.sendChatMessage(\n apiMessages,\n enabledTools,\n abortControllerRef.current.signal,\n );\n\n // Check if request was aborted\n if (abortControllerRef.current?.signal.aborted) {\n return;\n }\n\n setIsTyping(false);\n abortControllerRef.current = null;\n\n const botResponse: Message = {\n id: (Date.now() + 1).toString(),\n text: response.content,\n isUser: false,\n timestamp: new Date(),\n tools: response.toolResponses?.map(tool => tool.toolName) || [],\n toolsUsed: response.toolsUsed || [],\n toolResponses: response.toolResponses || [],\n };\n onMessagesChange([...messages, newMessage, botResponse]);\n } catch (err) {\n // Check if error is due to abortion\n if (err instanceof Error && err.name === 'AbortError') {\n // eslint-disable-next-line no-console\n console.error('Request was cancelled');\n return;\n }\n\n setIsTyping(false);\n abortControllerRef.current = null;\n // eslint-disable-next-line no-console\n console.error('Failed to send message:', err);\n\n let errorMessage =\n 'Sorry, I encountered an error processing your request.';\n\n if (err instanceof Error) {\n if (err.message.includes('404')) {\n errorMessage =\n 'The MCP Chat service is not available. Please check if the backend is running.';\n } else if (err.message.includes('Network')) {\n errorMessage =\n 'Network error. Please check your connection and try again.';\n } else {\n errorMessage = `Error: ${err.message}`;\n }\n }\n\n const errorResponse: Message = {\n id: (Date.now() + 1).toString(),\n text: errorMessage,\n isUser: false,\n timestamp: new Date(),\n tools: [],\n toolsUsed: [],\n toolResponses: [],\n };\n onMessagesChange([...messages, newMessage, errorResponse]);\n }\n };\n\n const handleSuggestionClick = async (suggestion: string) => {\n await sendMessageToAPI(suggestion);\n };\n\n const handleSendMessage = async () => {\n if (inputValue.trim()) {\n const messageText = inputValue;\n setInputValue(''); // Clear input immediately\n await sendMessageToAPI(messageText);\n }\n };\n\n const handleKeyPress = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n handleSendMessage();\n }\n };\n\n return (\n <Box\n sx={{\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n marginRight: sidebarCollapsed ? '60px' : '400px',\n transition: 'margin-right 0.3s ease',\n }}\n >\n {messages.length === 0 ? (\n <QuickStart onSuggestionClick={handleSuggestionClick} />\n ) : (\n <Box\n sx={{\n flex: 1,\n overflowY: 'auto',\n padding: theme.spacing(6),\n paddingBottom: theme.spacing(10),\n paddingRight: theme.spacing(14),\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(1),\n backgroundColor: theme.palette.background.default,\n scrollbarGutter: 'stable',\n }}\n >\n {messages.map(message => (\n <ChatMessage key={message.id} message={message} />\n ))}\n {isTyping && <TypingIndicator />}\n <div ref={messagesEndRef} />\n </Box>\n )}\n\n <Box\n sx={{\n marginLeft: '14rem',\n position: 'fixed',\n bottom: 0,\n left: 0,\n right: sidebarCollapsed ? '60px' : '400px',\n padding: theme.spacing(2),\n borderTop: `1px solid ${theme.palette.divider}`,\n borderLeft: `1px solid ${theme.palette.divider}`,\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n backgroundColor: theme.palette.background.paper,\n zIndex: 1000,\n transition: 'right 0.3s ease',\n }}\n >\n <TextField\n sx={{\n marginLeft: theme.spacing(5),\n marginRight: theme.spacing(5),\n flex: 1,\n '& .MuiOutlinedInput-root': {\n borderRadius: theme.spacing(3),\n backgroundColor:\n theme.palette.mode === 'dark'\n ? 'rgba(255, 255, 255, 0.05)'\n : 'rgba(0, 0, 0, 0.05)',\n },\n }}\n placeholder=\"Message Assistant...\"\n variant=\"outlined\"\n multiline\n maxRows={4}\n value={inputValue}\n onChange={e => setInputValue(e.target.value)}\n onKeyPress={handleKeyPress}\n size=\"small\"\n disabled={isTyping}\n color=\"primary\"\n />\n <IconButton\n sx={{\n backgroundColor:\n !inputValue.trim() || isTyping\n ? theme.palette.action.disabledBackground\n : theme.palette.primary.main,\n color:\n !inputValue.trim() || isTyping\n ? theme.palette.text.disabled\n : theme.palette.primary.contrastText,\n '&:hover': {\n backgroundColor:\n !inputValue.trim() || isTyping\n ? theme.palette.action.disabledBackground\n : theme.palette.primary.dark,\n },\n '&:disabled': {\n backgroundColor: theme.palette.action.disabledBackground,\n color: theme.palette.text.disabled,\n },\n }}\n onClick={handleSendMessage}\n disabled={!inputValue.trim() || isTyping}\n >\n <SendIcon />\n </IconButton>\n </Box>\n </Box>\n );\n },\n);\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChatContainer.esm.js","sources":["../../../src/components/ChatContainer/ChatContainer.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n useEffect,\n useRef,\n useState,\n useImperativeHandle,\n forwardRef,\n} from 'react';\nimport Box from '@mui/material/Box';\nimport IconButton from '@mui/material/IconButton';\nimport TextField from '@mui/material/TextField';\nimport SendIcon from '@mui/icons-material/Send';\nimport { useTheme } from '@mui/material/styles';\nimport { useApi } from '@backstage/core-plugin-api';\nimport { mcpChatApiRef } from '../../api';\nimport type { ChatMessage as ApiChatMessage } from '../../types';\nimport { ChatMessage } from './ChatMessage';\nimport { QuickStart } from './QuickStart';\nimport { TypingIndicator } from './TypingIndicator';\n\ninterface Message {\n id: string;\n text: string;\n isUser: boolean;\n timestamp: Date;\n tools?: string[];\n toolsUsed?: string[];\n toolResponses?: any[]; // Store the actual tool response objects\n}\n\ninterface MCPServer {\n id: string;\n name: string;\n enabled: boolean;\n type?: string;\n hasUrl?: boolean;\n hasNpxCommand?: boolean;\n hasScriptPath?: boolean;\n}\n\ninterface ChatContainerProps {\n sidebarCollapsed: boolean;\n mcpServers: MCPServer[];\n messages: Message[];\n onMessagesChange: (messages: Message[]) => void;\n conversationId?: string;\n onConversationUpdated?: (conversationId: string) => void;\n}\n\nexport interface ChatContainerRef {\n cancelOngoingRequest: () => void;\n}\n\nexport const ChatContainer = forwardRef<ChatContainerRef, ChatContainerProps>(\n (\n {\n sidebarCollapsed,\n mcpServers,\n messages,\n onMessagesChange,\n conversationId,\n onConversationUpdated,\n },\n ref,\n ) => {\n const theme = useTheme();\n const mcpChatApi = useApi(mcpChatApiRef);\n const [inputValue, setInputValue] = useState('');\n const [isTyping, setIsTyping] = useState(false);\n const messagesEndRef = useRef<null | HTMLDivElement>(null);\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const scrollToBottom = () => {\n messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n };\n\n // Function to cancel ongoing requests\n const cancelOngoingRequest = () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n abortControllerRef.current = null;\n setIsTyping(false);\n }\n };\n\n // Expose the cancel function through ref\n useImperativeHandle(\n ref,\n () => ({\n cancelOngoingRequest,\n }),\n [],\n );\n\n useEffect(() => {\n scrollToBottom();\n }, [messages]);\n\n // Cleanup: cancel any ongoing requests when component unmounts\n useEffect(() => {\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n }, []);\n\n // Shared function to send messages to API\n const sendMessageToAPI = async (messageText: string) => {\n // Cancel any existing request\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Create new AbortController for this request\n abortControllerRef.current = new AbortController();\n\n const newMessage: Message = {\n id: Date.now().toString(),\n text: messageText,\n isUser: true,\n timestamp: new Date(),\n };\n onMessagesChange([...messages, newMessage]);\n setIsTyping(true);\n\n try {\n // Convert messages to API format including the new message\n const apiMessages: ApiChatMessage[] = [\n ...messages.map(msg => ({\n role: msg.isUser ? ('user' as const) : ('assistant' as const),\n content: msg.text,\n })),\n {\n role: 'user' as const,\n content: messageText,\n },\n ];\n\n // Get enabled tools from MCP servers\n // Backend uses server IDs to filter tools (tool.serverId matches serverConfig.id)\n const enabledTools = mcpServers\n .filter(server => server.enabled)\n .map(server => server.id);\n\n const response = await mcpChatApi.sendChatMessage(\n apiMessages,\n enabledTools,\n abortControllerRef.current.signal,\n conversationId,\n );\n\n // Check if request was aborted\n if (abortControllerRef.current?.signal.aborted) {\n return;\n }\n\n setIsTyping(false);\n abortControllerRef.current = null;\n\n // Notify parent if conversation was saved\n if (response.conversationId && onConversationUpdated) {\n onConversationUpdated(response.conversationId);\n }\n\n const botResponse: Message = {\n id: (Date.now() + 1).toString(),\n text: response.content,\n isUser: false,\n timestamp: new Date(),\n tools: response.toolResponses?.map(tool => tool.toolName) || [],\n toolsUsed: response.toolsUsed || [],\n toolResponses: response.toolResponses || [],\n };\n onMessagesChange([...messages, newMessage, botResponse]);\n } catch (err) {\n // Check if error is due to abortion\n if (err instanceof Error && err.name === 'AbortError') {\n // eslint-disable-next-line no-console\n console.error('Request was cancelled');\n return;\n }\n\n setIsTyping(false);\n abortControllerRef.current = null;\n // eslint-disable-next-line no-console\n console.error('Failed to send message:', err);\n\n let errorMessage =\n 'Sorry, I encountered an error processing your request.';\n\n if (err instanceof Error) {\n if (err.message.includes('404')) {\n errorMessage =\n 'The MCP Chat service is not available. Please check if the backend is running.';\n } else if (err.message.includes('Network')) {\n errorMessage =\n 'Network error. Please check your connection and try again.';\n } else {\n errorMessage = `Error: ${err.message}`;\n }\n }\n\n const errorResponse: Message = {\n id: (Date.now() + 1).toString(),\n text: errorMessage,\n isUser: false,\n timestamp: new Date(),\n tools: [],\n toolsUsed: [],\n toolResponses: [],\n };\n onMessagesChange([...messages, newMessage, errorResponse]);\n }\n };\n\n const handleSuggestionClick = async (suggestion: string) => {\n await sendMessageToAPI(suggestion);\n };\n\n const handleSendMessage = async () => {\n if (inputValue.trim()) {\n const messageText = inputValue;\n setInputValue(''); // Clear input immediately\n await sendMessageToAPI(messageText);\n }\n };\n\n const handleKeyPress = (event: React.KeyboardEvent) => {\n if (event.key === 'Enter' && !event.shiftKey) {\n event.preventDefault();\n handleSendMessage();\n }\n };\n\n return (\n <Box\n sx={{\n flex: 1,\n display: 'flex',\n flexDirection: 'column',\n marginRight: sidebarCollapsed ? '60px' : '400px',\n transition: 'margin-right 0.3s ease',\n }}\n >\n {messages.length === 0 ? (\n <QuickStart onSuggestionClick={handleSuggestionClick} />\n ) : (\n <Box\n sx={{\n flex: 1,\n overflowY: 'auto',\n padding: theme.spacing(6),\n paddingBottom: theme.spacing(10),\n paddingRight: theme.spacing(14),\n display: 'flex',\n flexDirection: 'column',\n gap: theme.spacing(1),\n backgroundColor: theme.palette.background.default,\n scrollbarGutter: 'stable',\n }}\n >\n {messages.map(message => (\n <ChatMessage key={message.id} message={message} />\n ))}\n {isTyping && <TypingIndicator />}\n <div ref={messagesEndRef} />\n </Box>\n )}\n\n <Box\n sx={{\n marginLeft: '14rem',\n position: 'fixed',\n bottom: 0,\n left: 0,\n right: sidebarCollapsed ? '60px' : '400px',\n padding: theme.spacing(2),\n borderTop: `1px solid ${theme.palette.divider}`,\n borderLeft: `1px solid ${theme.palette.divider}`,\n display: 'flex',\n alignItems: 'center',\n gap: theme.spacing(1),\n backgroundColor: theme.palette.background.paper,\n zIndex: 1000,\n transition: 'right 0.3s ease',\n }}\n >\n <TextField\n sx={{\n marginLeft: theme.spacing(5),\n marginRight: theme.spacing(5),\n flex: 1,\n '& .MuiOutlinedInput-root': {\n borderRadius: theme.spacing(3),\n backgroundColor:\n theme.palette.mode === 'dark'\n ? 'rgba(255, 255, 255, 0.05)'\n : 'rgba(0, 0, 0, 0.05)',\n },\n }}\n placeholder=\"Message Assistant...\"\n variant=\"outlined\"\n multiline\n maxRows={4}\n value={inputValue}\n onChange={e => setInputValue(e.target.value)}\n onKeyPress={handleKeyPress}\n size=\"small\"\n disabled={isTyping}\n color=\"primary\"\n />\n <IconButton\n sx={{\n backgroundColor:\n !inputValue.trim() || isTyping\n ? theme.palette.action.disabledBackground\n : theme.palette.primary.main,\n color:\n !inputValue.trim() || isTyping\n ? theme.palette.text.disabled\n : theme.palette.primary.contrastText,\n '&:hover': {\n backgroundColor:\n !inputValue.trim() || isTyping\n ? theme.palette.action.disabledBackground\n : theme.palette.primary.dark,\n },\n '&:disabled': {\n backgroundColor: theme.palette.action.disabledBackground,\n color: theme.palette.text.disabled,\n },\n }}\n onClick={handleSendMessage}\n disabled={!inputValue.trim() || isTyping}\n >\n <SendIcon />\n </IconButton>\n </Box>\n </Box>\n );\n },\n);\n"],"names":[],"mappings":"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"}
@@ -45,6 +45,7 @@ import { useProviderStatus } from '../../hooks/useProviderStatus.esm.js';
45
45
  import { useMcpServers } from '../../hooks/useMcpServers.esm.js';
46
46
  import 'react-use/esm/useAsyncRetry';
47
47
  import '../../api/index.esm.js';
48
+ import { useConversations } from '../../hooks/useConversations.esm.js';
48
49
 
49
50
  const ChatPage = () => {
50
51
  const theme = useTheme();
@@ -54,9 +55,24 @@ const ChatPage = () => {
54
55
  error: mcpServersError,
55
56
  handleServerToggle
56
57
  } = useMcpServers();
58
+ const {
59
+ starredConversations,
60
+ recentConversations,
61
+ loading: conversationsLoading,
62
+ error: conversationsError,
63
+ searchQuery,
64
+ setSearchQuery,
65
+ clearSearch,
66
+ loadConversation,
67
+ refreshConversations,
68
+ deleteConversation,
69
+ toggleStar
70
+ } = useConversations();
57
71
  const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
58
72
  const [error, setError] = useState(null);
59
73
  const [messages, setMessages] = useState([]);
74
+ const [currentConversationId, setCurrentConversationId] = useState();
75
+ const [selectedConversationId, setSelectedConversationId] = useState();
60
76
  const chatContainerRef = useRef(null);
61
77
  const combinedError = error || mcpServersError;
62
78
  const toggleSidebar = () => {
@@ -68,10 +84,42 @@ const ChatPage = () => {
68
84
  }
69
85
  setError(null);
70
86
  setMessages([]);
87
+ setCurrentConversationId(void 0);
88
+ setSelectedConversationId(void 0);
71
89
  };
72
90
  const handleMessagesChange = (newMessages) => {
73
91
  setMessages(newMessages);
74
92
  };
93
+ const handleConversationUpdated = (conversationId) => {
94
+ setCurrentConversationId(conversationId);
95
+ if (!selectedConversationId) {
96
+ setSelectedConversationId(conversationId);
97
+ }
98
+ refreshConversations();
99
+ };
100
+ const handleSelectConversation = async (conversation) => {
101
+ try {
102
+ if (chatContainerRef.current) {
103
+ chatContainerRef.current.cancelOngoingRequest();
104
+ }
105
+ const fullConversation = await loadConversation(conversation.id);
106
+ const uiMessages = fullConversation.messages.map(
107
+ (msg, index) => ({
108
+ id: `${conversation.id}-${index}`,
109
+ text: msg.content,
110
+ isUser: msg.role === "user",
111
+ timestamp: new Date(fullConversation.updatedAt),
112
+ toolsUsed: msg.role === "assistant" ? fullConversation.toolsUsed : void 0
113
+ })
114
+ );
115
+ setMessages(uiMessages);
116
+ setSelectedConversationId(conversation.id);
117
+ setCurrentConversationId(conversation.id);
118
+ setError(null);
119
+ } catch (err) {
120
+ setError(`Failed to load conversation: ${err}`);
121
+ }
122
+ };
75
123
  return /* @__PURE__ */ jsx(Page, { themeId: "tool", children: /* @__PURE__ */ jsx(Content, { noPadding: true, children: /* @__PURE__ */ jsx(
76
124
  Box,
77
125
  {
@@ -117,7 +165,9 @@ const ChatPage = () => {
117
165
  sidebarCollapsed,
118
166
  mcpServers,
119
167
  messages,
120
- onMessagesChange: handleMessagesChange
168
+ onMessagesChange: handleMessagesChange,
169
+ conversationId: currentConversationId,
170
+ onConversationUpdated: handleConversationUpdated
121
171
  }
122
172
  ),
123
173
  /* @__PURE__ */ jsx(
@@ -128,7 +178,18 @@ const ChatPage = () => {
128
178
  onNewChat: handleNewChat,
129
179
  mcpServers,
130
180
  onServerToggle: handleServerToggle,
131
- providerStatus
181
+ providerStatus,
182
+ starredConversations,
183
+ recentConversations,
184
+ conversationsLoading,
185
+ conversationsError,
186
+ searchQuery,
187
+ onSearchChange: setSearchQuery,
188
+ onSearchClear: clearSearch,
189
+ onSelectConversation: handleSelectConversation,
190
+ onToggleStar: toggleStar,
191
+ onDeleteConversation: deleteConversation,
192
+ selectedConversationId
132
193
  }
133
194
  )
134
195
  ] })
@@ -1 +1 @@
1
- {"version":3,"file":"ChatPage.esm.js","sources":["../../../src/components/ChatPage/ChatPage.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState, useRef } from 'react';\nimport { useTheme } from '@mui/material/styles';\nimport Box from '@mui/material/Box';\nimport { Content, Page, ResponseErrorPanel } from '@backstage/core-components';\nimport { ChatContainer, type ChatContainerRef } from '../ChatContainer';\nimport { RightPane } from '../RightPane';\nimport { useProviderStatus, useMcpServers } from '../../hooks';\n\ninterface Message {\n id: string;\n text: string;\n isUser: boolean;\n timestamp: Date;\n tools?: string[];\n toolsUsed?: string[];\n toolResponses?: any[];\n}\n\nexport const ChatPage = () => {\n const theme = useTheme();\n\n const providerStatus = useProviderStatus();\n const {\n mcpServers,\n error: mcpServersError,\n handleServerToggle,\n } = useMcpServers();\n const [sidebarCollapsed, setSidebarCollapsed] = useState(true);\n\n const [error, setError] = useState<string | null>(null);\n const [messages, setMessages] = useState<Message[]>([]);\n const chatContainerRef = useRef<ChatContainerRef>(null);\n\n // Combine errors from different sources\n const combinedError = error || mcpServersError;\n\n const toggleSidebar = () => {\n setSidebarCollapsed(!sidebarCollapsed);\n };\n\n const handleNewChat = () => {\n // Cancel any ongoing request first\n if (chatContainerRef.current) {\n chatContainerRef.current.cancelOngoingRequest();\n }\n\n setError(null);\n setMessages([]);\n };\n\n const handleMessagesChange = (newMessages: Message[]) => {\n setMessages(newMessages);\n };\n\n return (\n <Page themeId=\"tool\">\n <Content noPadding>\n <Box\n sx={{\n display: 'flex',\n flexDirection: 'column',\n height: '100vh',\n backgroundColor: theme.palette.background.default,\n }}\n >\n {/* Content Area */}\n <Box\n sx={{\n display: 'flex',\n flex: 1,\n overflow: 'hidden',\n position: 'relative',\n }}\n >\n {combinedError ? (\n <Box\n sx={{\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n padding: '20px',\n }}\n >\n <ResponseErrorPanel\n error={new Error(combinedError)}\n defaultExpanded\n />\n </Box>\n ) : (\n <>\n {/* Chat Container */}\n <ChatContainer\n ref={chatContainerRef}\n sidebarCollapsed={sidebarCollapsed}\n mcpServers={mcpServers}\n messages={messages}\n onMessagesChange={handleMessagesChange}\n />\n\n {/* Sidebar - Right Side */}\n <RightPane\n sidebarCollapsed={sidebarCollapsed}\n onToggleSidebar={toggleSidebar}\n onNewChat={handleNewChat}\n mcpServers={mcpServers}\n onServerToggle={handleServerToggle}\n providerStatus={providerStatus}\n />\n </>\n )}\n </Box>\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ChatPage.esm.js","sources":["../../../src/components/ChatPage/ChatPage.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { useState, useRef } from 'react';\nimport { useTheme } from '@mui/material/styles';\nimport Box from '@mui/material/Box';\nimport { Content, Page, ResponseErrorPanel } from '@backstage/core-components';\nimport { ChatContainer, type ChatContainerRef } from '../ChatContainer';\nimport { RightPane } from '../RightPane';\nimport {\n useProviderStatus,\n useMcpServers,\n useConversations,\n} from '../../hooks';\nimport type { ConversationRecord } from '../../types';\n\ninterface Message {\n id: string;\n text: string;\n isUser: boolean;\n timestamp: Date;\n tools?: string[];\n toolsUsed?: string[];\n toolResponses?: any[];\n}\n\nexport const ChatPage = () => {\n const theme = useTheme();\n\n const providerStatus = useProviderStatus();\n const {\n mcpServers,\n error: mcpServersError,\n handleServerToggle,\n } = useMcpServers();\n const {\n starredConversations,\n recentConversations,\n loading: conversationsLoading,\n error: conversationsError,\n searchQuery,\n setSearchQuery,\n clearSearch,\n loadConversation,\n refreshConversations,\n deleteConversation,\n toggleStar,\n } = useConversations();\n\n const [sidebarCollapsed, setSidebarCollapsed] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [messages, setMessages] = useState<Message[]>([]);\n const [currentConversationId, setCurrentConversationId] = useState<\n string | undefined\n >();\n const [selectedConversationId, setSelectedConversationId] = useState<\n string | undefined\n >();\n const chatContainerRef = useRef<ChatContainerRef>(null);\n\n // Combine errors from different sources\n const combinedError = error || mcpServersError;\n\n const toggleSidebar = () => {\n setSidebarCollapsed(!sidebarCollapsed);\n };\n\n const handleNewChat = () => {\n // Cancel any ongoing request first\n if (chatContainerRef.current) {\n chatContainerRef.current.cancelOngoingRequest();\n }\n\n setError(null);\n setMessages([]);\n setCurrentConversationId(undefined);\n setSelectedConversationId(undefined);\n };\n\n const handleMessagesChange = (newMessages: Message[]) => {\n setMessages(newMessages);\n };\n\n const handleConversationUpdated = (conversationId: string) => {\n setCurrentConversationId(conversationId);\n if (!selectedConversationId) {\n setSelectedConversationId(conversationId);\n }\n // Refresh the conversation list\n refreshConversations();\n };\n\n const handleSelectConversation = async (conversation: ConversationRecord) => {\n try {\n // Cancel any ongoing request first\n if (chatContainerRef.current) {\n chatContainerRef.current.cancelOngoingRequest();\n }\n\n // Load the full conversation\n const fullConversation = await loadConversation(conversation.id);\n\n // Convert conversation messages to UI messages\n const uiMessages: Message[] = fullConversation.messages.map(\n (msg, index) => ({\n id: `${conversation.id}-${index}`,\n text: msg.content,\n isUser: msg.role === 'user',\n timestamp: new Date(fullConversation.updatedAt),\n toolsUsed:\n msg.role === 'assistant' ? fullConversation.toolsUsed : undefined,\n }),\n );\n\n setMessages(uiMessages);\n setSelectedConversationId(conversation.id);\n setCurrentConversationId(conversation.id);\n setError(null);\n } catch (err) {\n setError(`Failed to load conversation: ${err}`);\n }\n };\n\n return (\n <Page themeId=\"tool\">\n <Content noPadding>\n <Box\n sx={{\n display: 'flex',\n flexDirection: 'column',\n height: '100vh',\n backgroundColor: theme.palette.background.default,\n }}\n >\n {/* Content Area */}\n <Box\n sx={{\n display: 'flex',\n flex: 1,\n overflow: 'hidden',\n position: 'relative',\n }}\n >\n {combinedError ? (\n <Box\n sx={{\n display: 'flex',\n justifyContent: 'center',\n alignItems: 'center',\n width: '100%',\n height: '100%',\n padding: '20px',\n }}\n >\n <ResponseErrorPanel\n error={new Error(combinedError)}\n defaultExpanded\n />\n </Box>\n ) : (\n <>\n {/* Chat Container */}\n <ChatContainer\n ref={chatContainerRef}\n sidebarCollapsed={sidebarCollapsed}\n mcpServers={mcpServers}\n messages={messages}\n onMessagesChange={handleMessagesChange}\n conversationId={currentConversationId}\n onConversationUpdated={handleConversationUpdated}\n />\n\n {/* Sidebar - Right Side */}\n <RightPane\n sidebarCollapsed={sidebarCollapsed}\n onToggleSidebar={toggleSidebar}\n onNewChat={handleNewChat}\n mcpServers={mcpServers}\n onServerToggle={handleServerToggle}\n providerStatus={providerStatus}\n starredConversations={starredConversations}\n recentConversations={recentConversations}\n conversationsLoading={conversationsLoading}\n conversationsError={conversationsError}\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n onSearchClear={clearSearch}\n onSelectConversation={handleSelectConversation}\n onToggleStar={toggleStar}\n onDeleteConversation={deleteConversation}\n selectedConversationId={selectedConversationId}\n />\n </>\n )}\n </Box>\n </Box>\n </Content>\n </Page>\n );\n};\n"],"names":[],"mappings":"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"}