@nqminds/mcp-client 1.0.7 → 1.0.8

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/dist/MCPChat.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import type { MCPChatProps } from "./types";
3
- export declare function MCPChat({ companyNumber, apiEndpoint, customStyles, className }: MCPChatProps): React.JSX.Element;
3
+ export declare function MCPChat({ companyNumber, apiEndpoint, customStyles, className, }: MCPChatProps): React.JSX.Element;
4
4
  //# sourceMappingURL=MCPChat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MCPChat.d.ts","sourceRoot":"","sources":["../src/MCPChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAE3D,OAAO,KAAK,EAAyB,YAAY,EAAe,MAAM,SAAS,CAAC;AAEhF,wBAAgB,OAAO,CAAC,EACtB,aAAa,EACb,WAA6B,EAC7B,YAAiB,EACjB,SAAc,EACf,EAAE,YAAY,qBAmVd"}
1
+ {"version":3,"file":"MCPChat.d.ts","sourceRoot":"","sources":["../src/MCPChat.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAyB,YAAY,EAAe,MAAM,SAAS,CAAC;AA+ChF,wBAAgB,OAAO,CAAC,EACtB,aAAa,EACb,WAA6B,EAC7B,YAAiB,EACjB,SAAc,GACf,EAAE,YAAY,qBAgfd"}
package/dist/MCPChat.js CHANGED
@@ -1,66 +1,107 @@
1
1
  "use client";
2
- import React, { useState, useRef, useEffect } from "react";
2
+ import React, { useState, useRef, useEffect, useCallback } from "react";
3
3
  import ReactMarkdown from "react-markdown";
4
- export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customStyles = {}, className = "" }) {
4
+ import remarkGfm from "remark-gfm";
5
+ const DEFAULT_ACTIONS = [
6
+ {
7
+ label: "Company overview",
8
+ icon: "🏢",
9
+ prompt: "Please provide a comprehensive company overview. Include key business information, company structure, size, registered details, and any notable financial highlights.",
10
+ },
11
+ {
12
+ label: "Competitor analysis",
13
+ icon: "⚔️",
14
+ prompt: "Please perform a competitor analysis. Identify the main competitors in this sector, assess market positioning, and highlight competitive advantages or disadvantages.",
15
+ },
16
+ {
17
+ label: "Risk analysis",
18
+ icon: "⚠️",
19
+ prompt: "Please perform a thorough risk analysis. Identify key business risks, financial risks, operational risks, and any concerning patterns in the data.",
20
+ },
21
+ {
22
+ label: "Online sentiment",
23
+ icon: "🌐",
24
+ prompt: "Please assess the online sentiment and public perception. Look for relevant news coverage, reviews, reputational issues, and overall public standing.",
25
+ },
26
+ {
27
+ label: "SWOT analysis",
28
+ icon: "📊",
29
+ prompt: "Please perform a full SWOT analysis. Cover Strengths, Weaknesses, Opportunities, and Threats in detail.",
30
+ },
31
+ {
32
+ label: "Research key people",
33
+ icon: "👤",
34
+ prompt: "Please research the key people associated with this company. Include directors, persons of significant control, major shareholders, and relevant background information.",
35
+ },
36
+ ];
37
+ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customStyles = {}, className = "", }) {
5
38
  const [messages, setMessages] = useState([]);
6
39
  const [input, setInput] = useState("");
7
40
  const [isLoading, setIsLoading] = useState(false);
8
41
  const [thinkingSteps, setThinkingSteps] = useState([]);
9
- const [isExpanded, setIsExpanded] = useState(false);
42
+ const [isOpen, setIsOpen] = useState(false);
43
+ const [preparingAction, setPreparingAction] = useState(null);
44
+ const [directPromptOpen, setDirectPromptOpen] = useState(false);
45
+ const [directPromptText, setDirectPromptText] = useState("");
46
+ const [theme, setTheme] = useState(() => {
47
+ if (typeof window !== "undefined") {
48
+ return localStorage.getItem("mcp-chat-theme") ?? "light";
49
+ }
50
+ return "light";
51
+ });
10
52
  const messagesEndRef = useRef(null);
11
53
  const thinkingEndRef = useRef(null);
54
+ const inputRef = useRef(null);
55
+ const directPromptRef = useRef(null);
12
56
  const abortControllerRef = useRef(null);
13
- // Merge custom styles with default CSS variables
14
- const containerStyle = {
15
- ...customStyles,
16
- };
17
57
  const scrollToBottom = () => {
18
58
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
19
59
  };
20
60
  useEffect(() => {
21
61
  scrollToBottom();
22
62
  }, [messages]);
63
+ // Focus input when panel opens in "chat now" mode
64
+ useEffect(() => {
65
+ if (isOpen) {
66
+ setTimeout(() => inputRef.current?.focus(), 150);
67
+ }
68
+ }, [isOpen]);
23
69
  const cancelRequest = () => {
24
70
  if (abortControllerRef.current) {
25
71
  abortControllerRef.current.abort();
26
72
  abortControllerRef.current = null;
27
73
  setIsLoading(false);
28
74
  setThinkingSteps([]);
29
- // Remove any streaming message that was in progress
75
+ setPreparingAction(null);
30
76
  setMessages((prev) => prev.filter((m) => !m.isStreaming));
31
77
  }
32
78
  };
33
- const handleSubmit = async (e) => {
34
- e.preventDefault();
35
- if (!input.trim() || isLoading)
79
+ const sendMessage = useCallback(async (text, hidden = false) => {
80
+ if (!text.trim() || isLoading)
36
81
  return;
37
- // Set loading state immediately to show cancel button
38
82
  setIsLoading(true);
39
83
  const userMessage = {
40
84
  role: "user",
41
- content: input.trim(),
85
+ content: text.trim(),
42
86
  timestamp: new Date(),
87
+ hidden,
43
88
  };
44
89
  setMessages((prev) => [...prev, userMessage]);
45
90
  setInput("");
46
91
  setThinkingSteps([]);
47
- // Add initial thinking step
48
92
  let thinkingStepCounter = 0;
49
93
  const addThinkingStep = (message) => {
50
94
  setThinkingSteps((prev) => [
51
95
  ...prev,
52
96
  { id: `${Date.now()}-${thinkingStepCounter++}`, message, timestamp: new Date() },
53
97
  ]);
54
- // Auto-scroll thinking window
55
98
  setTimeout(() => {
56
99
  thinkingEndRef.current?.scrollIntoView({ behavior: "smooth" });
57
100
  }, 50);
58
101
  };
59
- // Create abort controller for this request
60
102
  const abortController = new AbortController();
61
103
  abortControllerRef.current = abortController;
62
104
  try {
63
- // Call your API route that communicates with MCP
64
105
  const response = await fetch(apiEndpoint, {
65
106
  method: "POST",
66
107
  headers: { "Content-Type": "application/json" },
@@ -70,15 +111,12 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
70
111
  }),
71
112
  signal: abortController.signal,
72
113
  });
73
- if (!response.ok) {
114
+ if (!response.ok)
74
115
  throw new Error("Failed to get response");
75
- }
76
116
  const reader = response.body?.getReader();
77
117
  const decoder = new TextDecoder();
78
- if (!reader) {
118
+ if (!reader)
79
119
  throw new Error("No response body");
80
- }
81
- // Create a streaming message
82
120
  const streamingMessage = {
83
121
  role: "assistant",
84
122
  content: "",
@@ -98,21 +136,18 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
98
136
  for (const line of lines) {
99
137
  if (line.startsWith("data: ")) {
100
138
  const data = line.slice(6);
101
- if (data === "[DONE]") {
139
+ if (data === "[DONE]")
102
140
  continue;
103
- }
104
141
  try {
105
142
  const parsed = JSON.parse(data);
106
143
  if (parsed.type === "thinking") {
107
144
  addThinkingStep(parsed.message || "Processing...");
108
145
  }
109
146
  else if (parsed.type === "content") {
110
- // Update the streaming message with new content
111
147
  setMessages((prev) => {
112
148
  const updated = [...prev];
113
149
  const lastIndex = updated.length - 1;
114
150
  if (lastIndex >= 0 && updated[lastIndex].isStreaming) {
115
- // Create a new message object instead of mutating
116
151
  updated[lastIndex] = {
117
152
  ...updated[lastIndex],
118
153
  content: updated[lastIndex].content + (parsed.chunk || ""),
@@ -122,22 +157,16 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
122
157
  });
123
158
  }
124
159
  else if (parsed.type === "done") {
125
- // Mark streaming as complete
126
160
  setMessages((prev) => {
127
161
  const updated = [...prev];
128
162
  const lastIndex = updated.length - 1;
129
163
  if (lastIndex >= 0 && updated[lastIndex].isStreaming) {
130
- // Create a new message object instead of mutating
131
- updated[lastIndex] = {
132
- ...updated[lastIndex],
133
- isStreaming: false,
134
- };
164
+ updated[lastIndex] = { ...updated[lastIndex], isStreaming: false };
135
165
  }
136
166
  return updated;
137
167
  });
138
168
  }
139
169
  else if (parsed.type === "error") {
140
- // Handle error from stream
141
170
  throw new Error(parsed.message || "Stream error");
142
171
  }
143
172
  }
@@ -150,34 +179,70 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
150
179
  }
151
180
  }
152
181
  catch (error) {
153
- // Don't show error message if request was cancelled
154
- if (error instanceof Error && error.name === 'AbortError') {
182
+ if (error instanceof Error && error.name === "AbortError") {
155
183
  console.log("Request was cancelled");
156
184
  return;
157
185
  }
158
186
  console.error("Error:", error);
159
- const errorMessage = {
160
- role: "assistant",
161
- content: "Sorry, I encountered an error. Please try again.",
162
- timestamp: new Date(),
163
- };
164
- setMessages((prev) => {
165
- // Remove streaming message if it exists
166
- const filtered = prev.filter((m) => !m.isStreaming);
167
- return [...filtered, errorMessage];
168
- });
187
+ setMessages((prev) => [
188
+ ...prev.filter((m) => !m.isStreaming),
189
+ {
190
+ role: "assistant",
191
+ content: "Sorry, I encountered an error. Please try again.",
192
+ timestamp: new Date(),
193
+ },
194
+ ]);
169
195
  }
170
196
  finally {
171
- // Clean up abort controller
172
197
  abortControllerRef.current = null;
173
198
  setIsLoading(false);
174
- // Clear thinking steps after a brief delay
199
+ setPreparingAction(null);
175
200
  setTimeout(() => setThinkingSteps([]), 2000);
176
201
  }
202
+ }, [apiEndpoint, companyNumber, isLoading]);
203
+ const handleSubmit = async (e) => {
204
+ e.preventDefault();
205
+ await sendMessage(input, false);
206
+ };
207
+ const handleActionButton = async (action) => {
208
+ const prompt = companyNumber
209
+ ? `${action.prompt}\n\nCompany number: ${companyNumber}`
210
+ : action.prompt;
211
+ setPreparingAction(action.label);
212
+ // messages will be added by sendMessage → switches view automatically
213
+ setTimeout(() => sendMessage(prompt, true), 50);
214
+ };
215
+ const handleChatNow = () => {
216
+ // Just focus the input — no prompt sent
217
+ setTimeout(() => inputRef.current?.focus(), 50);
218
+ };
219
+ const openDirectPrompt = (prefill = "") => {
220
+ setDirectPromptText(prefill);
221
+ setDirectPromptOpen(true);
222
+ setTimeout(() => {
223
+ if (directPromptRef.current) {
224
+ directPromptRef.current.focus();
225
+ directPromptRef.current.selectionStart = prefill.length;
226
+ directPromptRef.current.selectionEnd = prefill.length;
227
+ }
228
+ }, 80);
229
+ };
230
+ const sendDirectPrompt = async () => {
231
+ if (!directPromptText.trim())
232
+ return;
233
+ setDirectPromptOpen(false);
234
+ // Send as a visible message so you can see exactly what went to the agent
235
+ await sendMessage(directPromptText, false);
236
+ setDirectPromptText("");
237
+ };
238
+ const toggleTheme = () => {
239
+ const next = theme === "dark" ? "light" : "dark";
240
+ setTheme(next);
241
+ if (typeof window !== "undefined")
242
+ localStorage.setItem("mcp-chat-theme", next);
177
243
  };
178
244
  const clearChat = async () => {
179
245
  setMessages([]);
180
- // Also clear server-side conversation history
181
246
  try {
182
247
  await fetch(apiEndpoint, {
183
248
  method: "DELETE",
@@ -185,43 +250,83 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
185
250
  body: JSON.stringify({ sessionId: "default" }),
186
251
  });
187
252
  }
188
- catch (error) {
189
- console.error("Failed to clear server conversation:", error);
253
+ catch (err) {
254
+ console.error("Failed to clear server conversation:", err);
190
255
  }
191
256
  };
192
- if (!isExpanded) {
193
- return (React.createElement("div", { className: `mcp-chat-trigger ${className}`, style: containerStyle },
194
- React.createElement("button", { onClick: () => setIsExpanded(true), className: "mcp-chat-trigger-button" }, "\uD83D\uDCAC Ask AI Assistant")));
195
- }
196
- return (React.createElement("div", { className: `mcp-chat-container ${className}`, style: containerStyle },
197
- React.createElement("div", { className: "mcp-chat-header" },
198
- React.createElement("h3", { className: "mcp-chat-title" }, "AI Assistant"),
199
- React.createElement("div", { className: "mcp-chat-header-actions" },
200
- messages.length > 0 && (React.createElement("button", { onClick: clearChat, className: "mcp-chat-button mcp-chat-button-secondary" }, "Clear")),
201
- React.createElement("button", { onClick: () => setIsExpanded(false), className: "mcp-chat-button mcp-chat-button-secondary" }, "\u2715"))),
202
- React.createElement("div", { className: "mcp-chat-messages" },
203
- messages.length === 0 && (React.createElement("div", { className: "mcp-chat-welcome" },
204
- React.createElement("p", { className: "mcp-chat-welcome-title" }, "\uD83D\uDC4B Hi! I can help you with company information."),
205
- companyNumber && (React.createElement("p", { className: "mcp-chat-welcome-subtitle" },
206
- "Currently viewing: ",
207
- React.createElement("strong", null, companyNumber))),
208
- React.createElement("p", { className: "mcp-chat-welcome-subtitle" }, "Try asking:"),
209
- React.createElement("ul", { className: "mcp-chat-suggestions" }, companyNumber && (React.createElement(React.Fragment, null,
210
- React.createElement("li", null, "\u201CGet company info\u201D"),
211
- React.createElement("li", null, "\u201CShow me financial data from 2020\u201D")))))),
212
- messages.map((msg, idx) => (React.createElement("div", { key: idx, className: `mcp-chat-message ${msg.role === "user" ? "mcp-chat-message-user" : "mcp-chat-message-assistant"}` },
213
- React.createElement("div", { className: "mcp-chat-message-bubble" },
214
- msg.role === "assistant" ? (React.createElement("div", { className: "mcp-chat-message-content markdown-content" },
215
- React.createElement(ReactMarkdown, null, msg.content))) : (React.createElement("div", { className: "mcp-chat-message-content" }, msg.content)),
216
- React.createElement("div", { className: "mcp-chat-message-timestamp" }, msg.timestamp.toLocaleTimeString()))))),
217
- isLoading && thinkingSteps.length > 0 && (React.createElement("div", { className: "mcp-chat-message mcp-chat-message-assistant" },
218
- React.createElement("div", { className: "mcp-chat-thinking" },
219
- React.createElement("div", { className: "mcp-chat-thinking-title" }, "\uD83D\uDCAD Processing..."),
220
- React.createElement("div", { className: "mcp-chat-thinking-steps" },
221
- thinkingSteps.map((step) => (React.createElement("div", { key: step.id, className: "mcp-chat-thinking-step" }, step.message))),
222
- React.createElement("div", { ref: thinkingEndRef }))))),
223
- React.createElement("div", { ref: messagesEndRef })),
224
- React.createElement("form", { onSubmit: handleSubmit, className: "mcp-chat-input-form" },
225
- React.createElement("input", { type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "Ask a question...", className: "mcp-chat-input", disabled: isLoading }),
226
- isLoading ? (React.createElement("button", { type: "button", onClick: cancelRequest, className: "mcp-chat-button mcp-chat-button-secondary" }, "Cancel")) : (React.createElement("button", { type: "submit", className: "mcp-chat-button mcp-chat-button-primary", disabled: !input.trim() }, "Send")))));
257
+ const visibleMessages = messages.filter((m) => !m.hidden);
258
+ const showHome = visibleMessages.length === 0 && !isLoading;
259
+ return (React.createElement("div", { className: `mcp-root ${className}`, style: customStyles, "data-theme": theme },
260
+ !isOpen && (React.createElement("button", { className: "mcp-float-icon", onClick: () => setIsOpen(true), "aria-label": "Open AI assistant", title: "Open AI assistant" }, "\uD83E\uDD16")),
261
+ isOpen && (React.createElement("div", { className: "mcp-overlay" },
262
+ React.createElement("div", { className: "mcp-panel" },
263
+ React.createElement("div", { className: "mcp-chat-header" },
264
+ React.createElement("div", { className: "mcp-chat-header-left" },
265
+ React.createElement("span", { className: "mcp-chat-header-icon" }, "\uD83E\uDD16"),
266
+ React.createElement("h3", { className: "mcp-chat-title" }, "AI Assistant"),
267
+ companyNumber && (React.createElement("span", { className: "mcp-chat-header-company" }, companyNumber))),
268
+ React.createElement("div", { className: "mcp-chat-header-actions" },
269
+ React.createElement("button", { onClick: () => openDirectPrompt(), className: "mcp-chat-button mcp-btn-dev", title: "Open direct-prompt editor (dev tool)" }, "\uD83D\uDD27 Direct"),
270
+ React.createElement("button", { onClick: toggleTheme, className: "mcp-btn-theme", "aria-label": theme === "dark" ? "Switch to light mode" : "Switch to dark mode", title: theme === "dark" ? "Light mode" : "Dark mode" }, theme === "dark" ? "☀️" : "🌙"),
271
+ visibleMessages.length > 0 && (React.createElement("button", { onClick: clearChat, className: "mcp-chat-button mcp-chat-button-secondary", title: "Clear conversation" }, "Clear")),
272
+ React.createElement("button", { onClick: () => setIsOpen(false), className: "mcp-chat-button mcp-chat-button-secondary mcp-btn-icon", "aria-label": "Minimise", title: "Minimise" }, "\u25BC"))),
273
+ directPromptOpen && (React.createElement("div", { className: "mcp-direct-overlay" },
274
+ React.createElement("div", { className: "mcp-direct-panel" },
275
+ React.createElement("div", { className: "mcp-direct-header" },
276
+ React.createElement("span", { className: "mcp-direct-title" }, "\uD83D\uDD27 Direct Prompt"),
277
+ React.createElement("button", { className: "mcp-chat-button mcp-chat-button-secondary mcp-btn-icon", onClick: () => setDirectPromptOpen(false), title: "Close" }, "\u2715")),
278
+ React.createElement("div", { className: "mcp-direct-presets" },
279
+ React.createElement("span", { className: "mcp-direct-presets-label" }, "Load preset:"),
280
+ DEFAULT_ACTIONS.map((a) => (React.createElement("button", { key: a.label, className: "mcp-direct-preset-chip", onClick: () => openDirectPrompt(companyNumber
281
+ ? `${a.prompt}\n\nCompany number: ${companyNumber}`
282
+ : a.prompt), title: a.label },
283
+ a.icon,
284
+ " ",
285
+ a.label)))),
286
+ React.createElement("textarea", { ref: directPromptRef, className: "mcp-direct-textarea", value: directPromptText, onChange: (e) => setDirectPromptText(e.target.value), placeholder: "Type or paste any prompt here \u2014 it will be sent verbatim to the agent and shown in the chat\u2026", rows: 12, onKeyDown: (e) => {
287
+ // Ctrl/Cmd+Enter to send
288
+ if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
289
+ e.preventDefault();
290
+ sendDirectPrompt();
291
+ }
292
+ } }),
293
+ React.createElement("div", { className: "mcp-direct-footer" },
294
+ React.createElement("span", { className: "mcp-direct-hint" }, "Ctrl+Enter to send"),
295
+ React.createElement("div", { className: "mcp-direct-footer-actions" },
296
+ React.createElement("button", { className: "mcp-chat-button mcp-chat-button-secondary", onClick: () => setDirectPromptText("") }, "Clear"),
297
+ React.createElement("button", { className: "mcp-chat-button mcp-btn-dev", onClick: sendDirectPrompt, disabled: !directPromptText.trim() || isLoading }, "Send to agent \u2192")))))),
298
+ preparingAction && (React.createElement("div", { className: "mcp-preparing-banner" },
299
+ React.createElement("span", { className: "mcp-preparing-spinner" }),
300
+ "Preparing ",
301
+ React.createElement("strong", null, preparingAction),
302
+ " \u2014 this may take a moment\u2026")),
303
+ React.createElement("div", { className: "mcp-chat-messages" },
304
+ showHome && (React.createElement("div", { className: "mcp-home" },
305
+ React.createElement("p", { className: "mcp-home-title" }, "\uD83D\uDC4B What would you like to know?"),
306
+ companyNumber && (React.createElement("p", { className: "mcp-home-subtitle" },
307
+ "Company: ",
308
+ React.createElement("strong", null, companyNumber))),
309
+ React.createElement("div", { className: "mcp-action-grid" }, DEFAULT_ACTIONS.map((action) => (React.createElement("button", { key: action.label, className: "mcp-action-card", onClick: () => handleActionButton(action) },
310
+ React.createElement("span", { className: "mcp-action-card-icon" }, action.icon),
311
+ React.createElement("span", { className: "mcp-action-card-label" }, action.label))))),
312
+ React.createElement("button", { className: "mcp-chat-now-button", onClick: handleChatNow }, "\uD83D\uDCAC Or just chat\u2026"))),
313
+ visibleMessages.map((msg, idx) => (React.createElement("div", { key: idx, className: `mcp-chat-message ${msg.role === "user" ? "mcp-chat-message-user" : "mcp-chat-message-assistant"}` },
314
+ React.createElement("div", { className: "mcp-chat-message-bubble" },
315
+ msg.role === "assistant" ? (React.createElement("div", { className: "mcp-chat-message-content markdown-content" },
316
+ React.createElement(ReactMarkdown, { remarkPlugins: [remarkGfm] }, msg.content))) : (React.createElement("div", { className: "mcp-chat-message-content" }, msg.content)),
317
+ React.createElement("div", { className: "mcp-chat-message-timestamp" }, msg.timestamp.toLocaleTimeString()))))),
318
+ isLoading && (React.createElement("div", { className: "mcp-chat-message mcp-chat-message-assistant" },
319
+ React.createElement("div", { className: "mcp-chat-thinking" },
320
+ React.createElement("div", { className: "mcp-chat-thinking-title" },
321
+ React.createElement("span", { className: "mcp-preparing-spinner" }),
322
+ thinkingSteps.length === 0
323
+ ? " Thinking…"
324
+ : " Processing…"),
325
+ thinkingSteps.length > 0 && (React.createElement("div", { className: "mcp-chat-thinking-steps" },
326
+ thinkingSteps.map((step) => (React.createElement("div", { key: step.id, className: "mcp-chat-thinking-step" }, step.message))),
327
+ React.createElement("div", { ref: thinkingEndRef })))))),
328
+ React.createElement("div", { ref: messagesEndRef })),
329
+ React.createElement("form", { onSubmit: handleSubmit, className: "mcp-chat-input-form" },
330
+ React.createElement("input", { ref: inputRef, type: "text", value: input, onChange: (e) => setInput(e.target.value), placeholder: "Ask a question\u2026", className: "mcp-chat-input", disabled: isLoading }),
331
+ isLoading ? (React.createElement("button", { type: "button", onClick: cancelRequest, className: "mcp-chat-button mcp-chat-button-secondary" }, "Cancel")) : (React.createElement("button", { type: "submit", className: "mcp-chat-button mcp-chat-button-primary", disabled: !input.trim() }, "Send"))))))));
227
332
  }
@@ -1 +1 @@
1
- {"version":3,"file":"openai-client.d.ts","sourceRoot":"","sources":["../src/openai-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,eAAe;YAwErB,mBAAmB;IAoB3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAoMrH,YAAY,IAAI,IAAI;IAMd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
1
+ {"version":3,"file":"openai-client.d.ts","sourceRoot":"","sources":["../src/openai-client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,eAAe;YA4ErB,mBAAmB;IAoB3B,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAAE,WAAW,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IA+MrH,YAAY,IAAI,IAAI;IAMd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAG/B"}
@@ -52,13 +52,17 @@ CRITICAL CONTEXT AWARENESS RULES:
52
52
 
53
53
  RESPONSE FORMATTING RULES:
54
54
  - NEVER show raw JSON data to users unless they explicitly ask for "JSON", "raw data", or similar
55
- - Always present data in natural, human-readable format using clear sentences and bullet points
55
+ - Use rich Markdown formatting the UI renders it fully (bold, italic, headings, tables, code blocks)
56
+ - Use **bold** for key facts, names, amounts, and important values
57
+ - Use ## and ### headings to organise longer responses into clear sections
58
+ - Use tables whenever comparing multiple entities or showing structured data (e.g. list of officers, financial figures across years, search results) — prefer tables over bullet lists for multi-field data
59
+ - Use bullet lists only for genuinely unordered or enumerable items (e.g. a list of risks, a list of SIC codes) — do NOT default to bullets for everything
56
60
  - Convert dates to readable format (e.g., "15 March 2023" instead of "2023-03-15")
57
- - Format addresses as natural text, not as structured fields
58
- - Present lists of items as bulleted lists or comma-separated values
59
- - Use tables/structured format only when it significantly improves readability
60
- - When showing company officers or PSCs, write like: "John Smith (born March 1975, British nationality)" rather than showing field names
61
- - Only include the most relevant information - don't dump all available fields
61
+ - Format addresses as natural inline text, not as structured fields
62
+ - When showing company officers or PSCs, use a table with columns like Name, Role, Nationality, DOB rather than a bullet per person
63
+ - When showing financial figures, use a table with Year / Metric / Value columns
64
+ - Only include the most relevant information don't dump all available fields
65
+ - Avoid walls of bullet points; use prose sentences for narrative context and reserve lists/tables for structured data
62
66
 
63
67
  When responding:
64
68
  - Be concise and direct
@@ -193,7 +197,20 @@ When responding:
193
197
  const functionArgs = typeof functionCall.arguments === 'string'
194
198
  ? JSON.parse(functionCall.arguments)
195
199
  : functionCall.arguments;
196
- onThinking?.(`🔧 Using tool: ${functionName}`);
200
+ // Build a descriptive thinking message with key arguments
201
+ let toolDesc = functionName;
202
+ if (functionName === "fetch_webpage" && functionArgs.url) {
203
+ try {
204
+ toolDesc = `fetch_webpage → ${new URL(functionArgs.url).hostname}`;
205
+ }
206
+ catch {
207
+ toolDesc = `fetch_webpage → ${functionArgs.url}`;
208
+ }
209
+ }
210
+ else if (functionName === "web_search" && functionArgs.query) {
211
+ toolDesc = `web_search → "${functionArgs.query}"`;
212
+ }
213
+ onThinking?.(`🔧 ${toolDesc}`);
197
214
  try {
198
215
  // Execute the tool via MCP
199
216
  const result = await this.client.callTool({
@@ -1,7 +1,8 @@
1
1
  /* MCP Chat Component Styles */
2
2
 
3
- /* CSS Variables for customization */
4
- :root {
3
+ /* ── Dark theme (default) ──────────────────────────────────────────────────── */
4
+ .mcp-root {
5
+ display: contents;
5
6
  --mcp-primary-color: #4ea1ff;
6
7
  --mcp-bg: #0b0d12;
7
8
  --mcp-card-bg: #121621;
@@ -15,56 +16,148 @@
15
16
  --mcp-box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
16
17
  }
17
18
 
18
- /* Trigger Button (collapsed state) */
19
- .mcp-chat-trigger {
20
- position: fixed;
21
- bottom: 24px;
22
- right: 24px;
23
- z-index: 1000;
19
+ /* ── Light theme ────────────────────────────────────────────────────────────── */
20
+ .mcp-root[data-theme="light"] {
21
+ --mcp-primary-color: #1d6fe8;
22
+ --mcp-bg: #f5f7fb;
23
+ --mcp-card-bg: #ffffff;
24
+ --mcp-text: #111827;
25
+ --mcp-text-secondary: #6b7280;
26
+ --mcp-border: #e2e8f0;
27
+ --mcp-box-shadow: 0 12px 40px rgba(0, 0, 0, 0.12);
24
28
  }
25
29
 
26
- .mcp-chat-trigger-button {
27
- padding: 16px 32px;
28
- font-size: 18px;
29
- border-radius: 28px;
30
- box-shadow: var(--mcp-box-shadow);
31
- font-weight: 600;
30
+ /* ── Overlay backdrop adjustments per theme ─────────────────────────────────── */
31
+ .mcp-root[data-theme="light"] .mcp-overlay {
32
+ background: rgba(100, 116, 139, 0.35);
33
+ }
34
+
35
+ .mcp-root[data-theme="light"] .mcp-direct-overlay {
36
+ background: rgba(241, 245, 249, 0.92);
37
+ }
38
+
39
+ .mcp-root[data-theme="light"] .mcp-direct-panel {
40
+ box-shadow: 0 8px 40px rgba(245, 158, 11, 0.12);
41
+ }
42
+
43
+ .mcp-root[data-theme="light"] .mcp-chat-message-user .mcp-chat-message-bubble {
44
+ background: var(--mcp-primary-color);
45
+ color: #fff;
46
+ }
47
+
48
+ .mcp-root[data-theme="light"] .mcp-chat-message-assistant .mcp-chat-message-bubble {
49
+ background: #f0f4ff;
50
+ border-color: #d1dafe;
51
+ color: var(--mcp-text);
52
+ }
53
+
54
+ .mcp-root[data-theme="light"] .mcp-preparing-banner {
55
+ background: rgba(29, 111, 232, 0.08);
56
+ }
57
+
58
+ .mcp-root[data-theme="light"] .mcp-chat-thinking {
59
+ background: #f0f4ff;
60
+ border-color: #d1dafe;
61
+ }
62
+
63
+ .mcp-root[data-theme="light"] .mcp-btn-dev {
64
+ border-color: #d97706;
65
+ color: #d97706;
66
+ }
67
+
68
+ .mcp-root[data-theme="light"] .mcp-btn-dev:hover:not(:disabled) {
69
+ background: #d97706;
70
+ color: #fff;
71
+ }
72
+
73
+ /* ───────────────────────────────────────────────
74
+ Theme-toggle button
75
+ ─────────────────────────────────────────────── */
76
+ .mcp-btn-theme {
77
+ background: transparent;
78
+ border: 1px solid var(--mcp-border);
79
+ color: var(--mcp-text-secondary);
80
+ font-size: 16px;
81
+ padding: 6px 10px;
82
+ border-radius: 8px;
83
+ cursor: pointer;
84
+ line-height: 1;
85
+ transition: border-color 0.2s, color 0.2s, background 0.2s;
86
+ }
87
+
88
+ .mcp-btn-theme:hover {
89
+ border-color: var(--mcp-primary-color);
90
+ color: var(--mcp-primary-color);
91
+ background: rgba(78, 161, 255, 0.06);
92
+ }
93
+
94
+ /* ───────────────────────────────────────────────
95
+ Floating icon (fixed, bottom-right)
96
+ ─────────────────────────────────────────────── */
97
+ .mcp-float-icon {
98
+ position: fixed;
99
+ bottom: 28px;
100
+ right: 28px;
101
+ z-index: 1100;
102
+ width: 56px;
103
+ height: 56px;
104
+ border-radius: 50%;
32
105
  background: var(--mcp-primary-color);
33
- color: white;
34
106
  border: none;
35
107
  cursor: pointer;
36
- font-family: inherit;
108
+ font-size: 26px;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ box-shadow: 0 4px 20px rgba(78, 161, 255, 0.4);
37
113
  transition: transform 0.2s, box-shadow 0.2s;
38
114
  }
39
115
 
40
- .mcp-chat-trigger-button:hover {
41
- transform: translateY(-2px);
42
- box-shadow: 0 8px 30px rgba(0, 0, 0, 0.5);
116
+ .mcp-float-icon:hover {
117
+ transform: scale(1.1);
118
+ box-shadow: 0 8px 30px rgba(78, 161, 255, 0.5);
43
119
  }
44
120
 
45
- .mcp-chat-trigger-button:active {
46
- transform: translateY(0);
121
+ /* ───────────────────────────────────────────────
122
+ Overlay backdrop + panel
123
+ ─────────────────────────────────────────────── */
124
+ .mcp-overlay {
125
+ position: fixed;
126
+ inset: 0;
127
+ z-index: 1200;
128
+ display: flex;
129
+ align-items: center;
130
+ justify-content: center;
131
+ /* semi-transparent backdrop */
132
+ background: rgba(0, 0, 0, 0.45);
47
133
  }
48
134
 
49
- /* Main Container (expanded state) */
50
- .mcp-chat-container {
51
- position: fixed;
52
- bottom: 24px;
53
- right: 24px;
54
- width: 550px;
55
- max-width: calc(100vw - 48px);
56
- height: 700px;
57
- max-height: calc(100vh - 48px);
135
+ .mcp-panel {
136
+ position: relative;
137
+ width: 90vw;
138
+ height: 90vh;
139
+ /* leave room for top nav (assume ~64px) */
140
+ max-height: calc(100vh - 80px);
58
141
  background: var(--mcp-bg);
59
142
  border: 1px solid var(--mcp-border);
60
143
  border-radius: var(--mcp-border-radius);
61
144
  box-shadow: var(--mcp-box-shadow);
62
145
  display: flex;
63
146
  flex-direction: column;
64
- z-index: 1000;
147
+ overflow: hidden;
148
+ animation: mcp-panel-in 0.2s ease;
149
+ /* push it slightly down so the top nav stays visible */
150
+ margin-top: 64px;
65
151
  }
66
152
 
67
- /* Header */
153
+ @keyframes mcp-panel-in {
154
+ from { opacity: 0; transform: scale(0.97) translateY(12px); }
155
+ to { opacity: 1; transform: scale(1) translateY(0); }
156
+ }
157
+
158
+ /* ───────────────────────────────────────────────
159
+ Header
160
+ ─────────────────────────────────────────────── */
68
161
  .mcp-chat-header {
69
162
  padding: 18px 24px;
70
163
  border-bottom: 1px solid var(--mcp-border);
@@ -74,6 +167,19 @@
74
167
  background: var(--mcp-card-bg);
75
168
  border-top-left-radius: var(--mcp-border-radius);
76
169
  border-top-right-radius: var(--mcp-border-radius);
170
+ flex-shrink: 0;
171
+ }
172
+
173
+ .mcp-chat-header-left {
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 10px;
177
+ overflow: hidden;
178
+ }
179
+
180
+ .mcp-chat-header-icon {
181
+ font-size: 22px;
182
+ flex-shrink: 0;
77
183
  }
78
184
 
79
185
  .mcp-chat-title {
@@ -81,14 +187,30 @@
81
187
  font-size: 20px;
82
188
  font-weight: 600;
83
189
  color: var(--mcp-text);
190
+ white-space: nowrap;
191
+ }
192
+
193
+ .mcp-chat-header-company {
194
+ font-size: 13px;
195
+ color: var(--mcp-text-secondary);
196
+ background: rgba(255,255,255,0.06);
197
+ padding: 3px 10px;
198
+ border-radius: 20px;
199
+ white-space: nowrap;
200
+ overflow: hidden;
201
+ text-overflow: ellipsis;
202
+ max-width: 240px;
84
203
  }
85
204
 
86
205
  .mcp-chat-header-actions {
87
206
  display: flex;
88
207
  gap: 10px;
208
+ flex-shrink: 0;
89
209
  }
90
210
 
91
- /* Buttons */
211
+ /* ───────────────────────────────────────────────
212
+ Buttons
213
+ ─────────────────────────────────────────────── */
92
214
  .mcp-chat-button {
93
215
  padding: 8px 16px;
94
216
  font-size: 14px;
@@ -127,7 +249,44 @@
127
249
  background: rgba(255, 255, 255, 0.05);
128
250
  }
129
251
 
130
- /* Messages Area */
252
+ .mcp-btn-icon {
253
+ padding: 8px 12px;
254
+ font-size: 13px;
255
+ }
256
+
257
+ /* ───────────────────────────────────────────────
258
+ "Preparing" banner
259
+ ─────────────────────────────────────────────── */
260
+ .mcp-preparing-banner {
261
+ display: flex;
262
+ align-items: center;
263
+ gap: 10px;
264
+ padding: 10px 20px;
265
+ background: rgba(78, 161, 255, 0.1);
266
+ border-bottom: 1px solid var(--mcp-border);
267
+ font-size: 14px;
268
+ color: var(--mcp-text-secondary);
269
+ flex-shrink: 0;
270
+ }
271
+
272
+ .mcp-preparing-spinner {
273
+ display: inline-block;
274
+ width: 14px;
275
+ height: 14px;
276
+ border: 2px solid rgba(78, 161, 255, 0.3);
277
+ border-top-color: var(--mcp-primary-color);
278
+ border-radius: 50%;
279
+ animation: mcp-spin 0.7s linear infinite;
280
+ flex-shrink: 0;
281
+ }
282
+
283
+ @keyframes mcp-spin {
284
+ to { transform: rotate(360deg); }
285
+ }
286
+
287
+ /* ───────────────────────────────────────────────
288
+ Messages area
289
+ ─────────────────────────────────────────────── */
131
290
  .mcp-chat-messages {
132
291
  flex: 1;
133
292
  overflow-y: auto;
@@ -137,33 +296,115 @@
137
296
  gap: 16px;
138
297
  }
139
298
 
140
- /* Welcome Message */
141
- .mcp-chat-welcome {
299
+ /* ───────────────────────────────────────────────
300
+ Home screen (action cards shown inside panel)
301
+ ─────────────────────────────────────────────── */
302
+ .mcp-home {
303
+ display: flex;
304
+ flex-direction: column;
305
+ align-items: center;
306
+ gap: 24px;
307
+ padding: 24px 0;
308
+ flex: 1;
309
+ }
310
+
311
+ .mcp-home-title {
312
+ font-size: 22px;
313
+ font-weight: 600;
314
+ color: var(--mcp-text);
315
+ margin: 0;
142
316
  text-align: center;
317
+ }
318
+
319
+ .mcp-home-subtitle {
320
+ font-size: 15px;
143
321
  color: var(--mcp-text-secondary);
144
- margin-top: 60px;
322
+ margin: -16px 0 0;
323
+ text-align: center;
145
324
  }
146
325
 
147
- .mcp-chat-welcome-title {
148
- font-size: 18px;
149
- font-weight: 500;
326
+ .mcp-action-grid {
327
+ display: grid;
328
+ grid-template-columns: repeat(3, 1fr);
329
+ gap: 16px;
330
+ width: 100%;
331
+ max-width: 860px;
332
+ }
333
+
334
+ @media (max-width: 700px) {
335
+ .mcp-action-grid {
336
+ grid-template-columns: repeat(2, 1fr);
337
+ }
338
+ }
339
+
340
+ @media (max-width: 420px) {
341
+ .mcp-action-grid {
342
+ grid-template-columns: 1fr;
343
+ }
344
+ }
345
+
346
+ .mcp-action-card {
347
+ display: flex;
348
+ flex-direction: column;
349
+ align-items: center;
350
+ justify-content: center;
351
+ gap: 10px;
352
+ padding: 22px 16px;
353
+ background: var(--mcp-card-bg);
354
+ border: 1px solid var(--mcp-border);
355
+ border-radius: var(--mcp-border-radius);
150
356
  color: var(--mcp-text);
357
+ cursor: pointer;
358
+ font-family: inherit;
359
+ font-size: 15px;
360
+ font-weight: 500;
361
+ text-align: center;
362
+ transition: border-color 0.2s, transform 0.15s, box-shadow 0.2s;
363
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
151
364
  }
152
365
 
153
- .mcp-chat-welcome-subtitle {
154
- font-size: 16px;
155
- margin-top: 12px;
366
+ .mcp-action-card:hover {
367
+ border-color: var(--mcp-primary-color);
368
+ transform: translateY(-2px);
369
+ box-shadow: 0 6px 20px rgba(78, 161, 255, 0.15);
156
370
  }
157
371
 
158
- .mcp-chat-suggestions {
159
- text-align: left;
372
+ .mcp-action-card:active {
373
+ transform: translateY(0);
374
+ }
375
+
376
+ .mcp-action-card-icon {
377
+ font-size: 28px;
378
+ line-height: 1;
379
+ }
380
+
381
+ .mcp-action-card-label {
382
+ color: var(--mcp-text);
383
+ font-size: 14px;
384
+ font-weight: 500;
385
+ }
386
+
387
+ .mcp-chat-now-button {
388
+ padding: 12px 36px;
160
389
  font-size: 15px;
161
- line-height: 2;
162
- margin-top: 24px;
163
- padding-left: 20px;
390
+ font-weight: 600;
391
+ font-family: inherit;
392
+ border-radius: 28px;
393
+ background: transparent;
394
+ color: var(--mcp-primary-color);
395
+ border: 1px solid var(--mcp-primary-color);
396
+ cursor: pointer;
397
+ transition: background 0.2s, color 0.2s;
398
+ }
399
+
400
+ .mcp-chat-now-button:hover {
401
+ background: var(--mcp-primary-color);
402
+ color: white;
164
403
  }
165
404
 
166
- /* Message Bubbles */
405
+ /* ───────────────────────────────────────────────
406
+ Message bubbles
407
+ ─────────────────────────────────────────────── */
167
408
  .mcp-chat-message {
168
409
  display: flex;
169
410
  max-width: 85%;
@@ -175,6 +416,7 @@
175
416
 
176
417
  .mcp-chat-message-assistant {
177
418
  align-self: flex-start;
419
+ max-width: 90%;
178
420
  }
179
421
 
180
422
  .mcp-chat-message-bubble {
@@ -191,6 +433,7 @@
191
433
  background: var(--mcp-card-bg);
192
434
  border: 1px solid var(--mcp-border);
193
435
  color: var(--mcp-text);
436
+ width: 100%;
194
437
  }
195
438
 
196
439
  .mcp-chat-message-content {
@@ -205,66 +448,186 @@
205
448
  margin-top: 6px;
206
449
  }
207
450
 
208
- /* Markdown Content */
451
+ /* ───────────────────────────────────────────────
452
+ Markdown
453
+ ─────────────────────────────────────────────── */
209
454
  .markdown-content {
210
455
  font-size: 15px;
211
456
  line-height: 1.6;
457
+ color: inherit;
458
+ }
459
+
460
+ /* Paragraphs */
461
+ .markdown-content p { margin: 0 0 12px 0; }
462
+ .markdown-content p:last-child { margin-bottom: 0; }
463
+
464
+ /* Headings */
465
+ .markdown-content h1,
466
+ .markdown-content h2,
467
+ .markdown-content h3,
468
+ .markdown-content h4,
469
+ .markdown-content h5,
470
+ .markdown-content h6 {
471
+ font-weight: 700;
472
+ line-height: 1.3;
473
+ margin: 16px 0 8px 0;
474
+ color: var(--mcp-text);
212
475
  }
213
-
214
- .markdown-content p {
215
- margin: 0 0 12px 0;
216
- }
217
-
218
- .markdown-content p:last-child {
219
- margin-bottom: 0;
220
- }
221
-
476
+ .markdown-content h1 { font-size: 1.4em; }
477
+ .markdown-content h2 { font-size: 1.25em; }
478
+ .markdown-content h3 { font-size: 1.1em; }
479
+ .markdown-content h4,
480
+ .markdown-content h5,
481
+ .markdown-content h6 { font-size: 1em; }
482
+ .markdown-content h1:first-child,
483
+ .markdown-content h2:first-child,
484
+ .markdown-content h3:first-child { margin-top: 0; }
485
+
486
+ /* Bold & italic */
487
+ .markdown-content strong { font-weight: 700; }
488
+ .markdown-content em { font-style: italic; }
489
+ .markdown-content strong em,
490
+ .markdown-content em strong { font-weight: 700; font-style: italic; }
491
+
492
+ /* Strikethrough (GFM) */
493
+ .markdown-content del { text-decoration: line-through; opacity: 0.7; }
494
+
495
+ /* Lists */
222
496
  .markdown-content ul,
223
- .markdown-content ol {
497
+ .markdown-content ol { margin: 0 0 12px 0; padding-left: 22px; }
498
+ .markdown-content li { margin: 4px 0; }
499
+ .markdown-content li > p { margin-bottom: 4px; }
500
+ /* Task lists (GFM) */
501
+ .markdown-content input[type="checkbox"] {
502
+ margin-right: 6px;
503
+ accent-color: var(--mcp-primary-color);
504
+ }
505
+
506
+ /* Links */
507
+ .markdown-content a {
508
+ color: var(--mcp-primary-color);
509
+ text-decoration: underline;
510
+ text-underline-offset: 2px;
511
+ word-break: break-all;
512
+ }
513
+ .markdown-content a:hover { opacity: 0.8; }
514
+
515
+ /* Blockquotes */
516
+ .markdown-content blockquote {
224
517
  margin: 0 0 12px 0;
225
- padding-left: 20px;
518
+ padding: 8px 14px;
519
+ border-left: 3px solid var(--mcp-primary-color);
520
+ background: rgba(78, 161, 255, 0.07);
521
+ border-radius: 0 6px 6px 0;
522
+ color: var(--mcp-text-secondary);
523
+ font-style: italic;
226
524
  }
525
+ .markdown-content blockquote p:last-child { margin-bottom: 0; }
227
526
 
228
- .markdown-content li {
229
- margin: 4px 0;
527
+ /* Horizontal rule */
528
+ .markdown-content hr {
529
+ border: none;
530
+ border-top: 1px solid var(--mcp-border);
531
+ margin: 16px 0;
230
532
  }
231
533
 
534
+ /* Inline code */
232
535
  .markdown-content code {
233
536
  background: rgba(255, 255, 255, 0.1);
234
537
  padding: 2px 6px;
235
538
  border-radius: 4px;
236
- font-size: 14px;
237
- font-family: ui-monospace, monospace;
539
+ font-size: 13px;
540
+ font-family: ui-monospace, 'Cascadia Code', 'Fira Mono', monospace;
238
541
  }
239
542
 
543
+ /* Code blocks */
240
544
  .markdown-content pre {
241
545
  background: rgba(255, 255, 255, 0.05);
242
- padding: 12px;
546
+ padding: 12px 16px;
243
547
  border-radius: 8px;
244
548
  overflow-x: auto;
245
549
  margin: 0 0 12px 0;
550
+ border: 1px solid var(--mcp-border);
246
551
  }
247
-
248
552
  .markdown-content pre code {
249
553
  background: none;
250
554
  padding: 0;
555
+ font-size: 13px;
556
+ }
557
+
558
+ /* Tables (GFM) */
559
+ .markdown-content table {
560
+ width: 100%;
561
+ border-collapse: collapse;
562
+ margin: 0 0 14px 0;
563
+ font-size: 14px;
564
+ display: block;
565
+ overflow-x: auto;
566
+ }
567
+ .markdown-content thead { background: rgba(78, 161, 255, 0.12); }
568
+ .markdown-content th {
569
+ text-align: left;
570
+ font-weight: 600;
571
+ padding: 8px 12px;
572
+ border: 1px solid var(--mcp-border);
573
+ white-space: nowrap;
574
+ color: var(--mcp-text);
575
+ }
576
+ .markdown-content td {
577
+ padding: 7px 12px;
578
+ border: 1px solid var(--mcp-border);
579
+ vertical-align: top;
580
+ color: var(--mcp-text);
581
+ }
582
+ .markdown-content tbody tr:nth-child(even) {
583
+ background: rgba(255, 255, 255, 0.03);
584
+ }
585
+ .markdown-content tbody tr:hover {
586
+ background: rgba(78, 161, 255, 0.06);
587
+ }
588
+
589
+ /* Light theme overrides for markdown */
590
+ .mcp-root[data-theme="light"] .markdown-content code {
591
+ background: rgba(0, 0, 0, 0.06);
592
+ }
593
+ .mcp-root[data-theme="light"] .markdown-content pre {
594
+ background: rgba(0, 0, 0, 0.03);
595
+ }
596
+ .mcp-root[data-theme="light"] .markdown-content thead {
597
+ background: rgba(29, 111, 232, 0.08);
598
+ }
599
+ .mcp-root[data-theme="light"] .markdown-content tbody tr:nth-child(even) {
600
+ background: rgba(0, 0, 0, 0.02);
601
+ }
602
+ .mcp-root[data-theme="light"] .markdown-content tbody tr:hover {
603
+ background: rgba(29, 111, 232, 0.05);
604
+ }
605
+ .mcp-root[data-theme="light"] .markdown-content blockquote {
606
+ background: rgba(29, 111, 232, 0.05);
251
607
  }
252
608
 
253
- /* Thinking Steps */
609
+ /* ───────────────────────────────────────────────
610
+ Thinking steps
611
+ ─────────────────────────────────────────────── */
254
612
  .mcp-chat-thinking {
255
613
  padding: 14px 18px;
256
614
  border-radius: 12px;
257
615
  background: var(--mcp-card-bg);
258
616
  border: 1px solid var(--mcp-border);
259
617
  width: 100%;
618
+ display: flex;
619
+ flex-direction: column;
620
+ gap: 8px;
260
621
  }
261
622
 
262
623
  .mcp-chat-thinking-title {
263
624
  font-size: 14px;
264
625
  font-weight: 600;
265
- margin-bottom: 8px;
266
626
  opacity: 0.8;
267
627
  color: var(--mcp-text);
628
+ display: flex;
629
+ align-items: center;
630
+ gap: 8px;
268
631
  }
269
632
 
270
633
  .mcp-chat-thinking-steps {
@@ -285,7 +648,9 @@
285
648
  border-bottom: none;
286
649
  }
287
650
 
288
- /* Input Form */
651
+ /* ───────────────────────────────────────────────
652
+ Input form
653
+ ─────────────────────────────────────────────── */
289
654
  .mcp-chat-input-form {
290
655
  padding: 20px;
291
656
  border-top: 1px solid var(--mcp-border);
@@ -294,6 +659,7 @@
294
659
  background: var(--mcp-card-bg);
295
660
  border-bottom-left-radius: var(--mcp-border-radius);
296
661
  border-bottom-right-radius: var(--mcp-border-radius);
662
+ flex-shrink: 0;
297
663
  }
298
664
 
299
665
  .mcp-chat-input {
@@ -317,10 +683,12 @@
317
683
  cursor: not-allowed;
318
684
  }
319
685
 
320
- /* Scrollbar styling */
686
+ /* ───────────────────────────────────────────────
687
+ Scrollbars
688
+ ─────────────────────────────────────────────── */
321
689
  .mcp-chat-messages::-webkit-scrollbar,
322
690
  .mcp-chat-thinking-steps::-webkit-scrollbar {
323
- width: 8px;
691
+ width: 6px;
324
692
  }
325
693
 
326
694
  .mcp-chat-messages::-webkit-scrollbar-track,
@@ -338,3 +706,146 @@
338
706
  .mcp-chat-thinking-steps::-webkit-scrollbar-thumb:hover {
339
707
  background: var(--mcp-text-secondary);
340
708
  }
709
+
710
+ /* ───────────────────────────────────────────────
711
+ Dev button
712
+ ─────────────────────────────────────────────── */
713
+ .mcp-btn-dev {
714
+ background: transparent;
715
+ border: 1px dashed #f59e0b;
716
+ color: #f59e0b;
717
+ font-size: 12px;
718
+ padding: 6px 12px;
719
+ border-radius: 8px;
720
+ cursor: pointer;
721
+ font-family: inherit;
722
+ font-weight: 600;
723
+ transition: background 0.2s, color 0.2s;
724
+ }
725
+
726
+ .mcp-btn-dev:hover:not(:disabled) {
727
+ background: #f59e0b;
728
+ color: #0b0d12;
729
+ }
730
+
731
+ .mcp-btn-dev:disabled {
732
+ opacity: 0.4;
733
+ cursor: not-allowed;
734
+ }
735
+
736
+ /* ───────────────────────────────────────────────
737
+ Direct-prompt overlay (sits inside .mcp-panel)
738
+ ─────────────────────────────────────────────── */
739
+ .mcp-direct-overlay {
740
+ position: absolute;
741
+ inset: 0;
742
+ z-index: 10;
743
+ background: rgba(11, 13, 18, 0.92);
744
+ display: flex;
745
+ align-items: center;
746
+ justify-content: center;
747
+ padding: 24px;
748
+ box-sizing: border-box;
749
+ border-radius: var(--mcp-border-radius);
750
+ }
751
+
752
+ .mcp-direct-panel {
753
+ width: 100%;
754
+ max-width: 780px;
755
+ background: var(--mcp-card-bg);
756
+ border: 1px dashed #f59e0b;
757
+ border-radius: var(--mcp-border-radius);
758
+ display: flex;
759
+ flex-direction: column;
760
+ gap: 0;
761
+ overflow: hidden;
762
+ box-shadow: 0 8px 40px rgba(245, 158, 11, 0.15);
763
+ }
764
+
765
+ .mcp-direct-header {
766
+ display: flex;
767
+ justify-content: space-between;
768
+ align-items: center;
769
+ padding: 14px 18px;
770
+ border-bottom: 1px solid var(--mcp-border);
771
+ background: rgba(245, 158, 11, 0.06);
772
+ }
773
+
774
+ .mcp-direct-title {
775
+ font-size: 15px;
776
+ font-weight: 700;
777
+ color: #f59e0b;
778
+ letter-spacing: 0.02em;
779
+ }
780
+
781
+ .mcp-direct-presets {
782
+ display: flex;
783
+ flex-wrap: wrap;
784
+ gap: 8px;
785
+ align-items: center;
786
+ padding: 12px 16px;
787
+ border-bottom: 1px solid var(--mcp-border);
788
+ background: rgba(255, 255, 255, 0.02);
789
+ }
790
+
791
+ .mcp-direct-presets-label {
792
+ font-size: 12px;
793
+ color: var(--mcp-text-secondary);
794
+ white-space: nowrap;
795
+ }
796
+
797
+ .mcp-direct-preset-chip {
798
+ font-size: 12px;
799
+ padding: 4px 10px;
800
+ border-radius: 20px;
801
+ border: 1px solid var(--mcp-border);
802
+ background: transparent;
803
+ color: var(--mcp-text-secondary);
804
+ cursor: pointer;
805
+ font-family: inherit;
806
+ transition: border-color 0.15s, color 0.15s;
807
+ white-space: nowrap;
808
+ }
809
+
810
+ .mcp-direct-preset-chip:hover {
811
+ border-color: #f59e0b;
812
+ color: #f59e0b;
813
+ }
814
+
815
+ .mcp-direct-textarea {
816
+ width: 100%;
817
+ box-sizing: border-box;
818
+ padding: 16px;
819
+ font-size: 14px;
820
+ font-family: ui-monospace, 'Cascadia Code', monospace;
821
+ line-height: 1.6;
822
+ background: var(--mcp-bg);
823
+ color: var(--mcp-text);
824
+ border: none;
825
+ border-bottom: 1px solid var(--mcp-border);
826
+ resize: vertical;
827
+ min-height: 220px;
828
+ outline: none;
829
+ }
830
+
831
+ .mcp-direct-textarea:focus {
832
+ background: rgba(255, 255, 255, 0.02);
833
+ }
834
+
835
+ .mcp-direct-footer {
836
+ display: flex;
837
+ justify-content: space-between;
838
+ align-items: center;
839
+ padding: 12px 16px;
840
+ }
841
+
842
+ .mcp-direct-hint {
843
+ font-size: 12px;
844
+ color: var(--mcp-text-secondary);
845
+ opacity: 0.7;
846
+ }
847
+
848
+ .mcp-direct-footer-actions {
849
+ display: flex;
850
+ gap: 8px;
851
+ }
package/dist/types.d.ts CHANGED
@@ -6,6 +6,8 @@ export interface Message {
6
6
  content: string;
7
7
  timestamp: Date;
8
8
  isStreaming?: boolean;
9
+ /** Hidden messages are sent to the AI but not shown in the chat bubble list */
10
+ hidden?: boolean;
9
11
  }
10
12
  export interface ThinkingStep {
11
13
  id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqminds/mcp-client",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "Reusable MCP client component with AI chat interface",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -42,7 +42,8 @@
42
42
  "peerDependencies": {
43
43
  "react": "^18.0.0 || ^19.0.0",
44
44
  "react-dom": "^18.0.0 || ^19.0.0",
45
- "react-markdown": "^9.0.0"
45
+ "react-markdown": "^9.0.0",
46
+ "remark-gfm": "^4.0.1"
46
47
  },
47
48
  "dependencies": {
48
49
  "@modelcontextprotocol/sdk": "^1.0.4",
@@ -54,6 +55,7 @@
54
55
  "react": "^19.0.0",
55
56
  "react-dom": "^19.0.0",
56
57
  "react-markdown": "^9.1.0",
58
+ "remark-gfm": "^4.0.1",
57
59
  "typescript": "^5.0.0"
58
60
  }
59
61
  }