@nqminds/mcp-client 1.0.5 → 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 +1 -1
- package/dist/MCPChat.d.ts.map +1 -1
- package/dist/MCPChat.js +190 -87
- package/dist/openai-client.d.ts.map +1 -1
- package/dist/openai-client.js +58 -2
- package/dist/styles/MCPChat.css +583 -72
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
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
|
package/dist/MCPChat.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MCPChat.d.ts","sourceRoot":"","sources":["../src/MCPChat.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
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
|
-
|
|
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 [
|
|
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
|
-
|
|
75
|
+
setPreparingAction(null);
|
|
30
76
|
setMessages((prev) => prev.filter((m) => !m.isStreaming));
|
|
31
77
|
}
|
|
32
78
|
};
|
|
33
|
-
const
|
|
34
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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,45 +250,83 @@ export function MCPChat({ companyNumber, apiEndpoint = "/api/mcp/chat", customSt
|
|
|
185
250
|
body: JSON.stringify({ sessionId: "default" }),
|
|
186
251
|
});
|
|
187
252
|
}
|
|
188
|
-
catch (
|
|
189
|
-
console.error("Failed to clear server conversation:",
|
|
253
|
+
catch (err) {
|
|
254
|
+
console.error("Failed to clear server conversation:", err);
|
|
190
255
|
}
|
|
191
256
|
};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
React.createElement("
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
React.createElement("
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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"))))))));
|
|
229
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;
|
|
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"}
|
package/dist/openai-client.js
CHANGED
|
@@ -33,6 +33,47 @@ export class MCPClientOpenAI {
|
|
|
33
33
|
}, {
|
|
34
34
|
capabilities: {},
|
|
35
35
|
});
|
|
36
|
+
// Initialize conversation with system message
|
|
37
|
+
this.conversationHistory = [
|
|
38
|
+
{
|
|
39
|
+
type: "message",
|
|
40
|
+
role: "system",
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "input_text",
|
|
44
|
+
text: `You are a helpful assistant with access to Companies House data through specialized tools.
|
|
45
|
+
|
|
46
|
+
CRITICAL CONTEXT AWARENESS RULES:
|
|
47
|
+
1. Carefully track ALL entities you mention in your responses (company numbers, names, people, dates, etc.)
|
|
48
|
+
2. When the user refers to "that company," "the person," "those results," or uses similar references, ALWAYS look back at what you just discussed in the immediately preceding messages
|
|
49
|
+
3. If you mentioned specific company numbers, names, or other identifiers, remember them for follow-up questions
|
|
50
|
+
4. Before saying "I don't have a record of X," review your recent responses to check if you did mention it
|
|
51
|
+
5. Maintain awareness of the conversation flow - if you just provided information about something, the user's next question likely refers to it
|
|
52
|
+
|
|
53
|
+
RESPONSE FORMATTING RULES:
|
|
54
|
+
- NEVER show raw JSON data to users unless they explicitly ask for "JSON", "raw data", or similar
|
|
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
|
|
60
|
+
- Convert dates to readable format (e.g., "15 March 2023" instead of "2023-03-15")
|
|
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
|
|
66
|
+
|
|
67
|
+
When responding:
|
|
68
|
+
- Be concise and direct
|
|
69
|
+
- Use tools to fetch accurate, up-to-date Companies House data
|
|
70
|
+
- Track key identifiers (company numbers, PSC names, etc.) across the conversation
|
|
71
|
+
- If unclear what the user is referring to, check your previous response first before asking for clarification
|
|
72
|
+
- Never expose internal implementation details like "MCP Server" or tool names to users`,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
];
|
|
36
77
|
}
|
|
37
78
|
async compactConversation() {
|
|
38
79
|
try {
|
|
@@ -156,7 +197,20 @@ export class MCPClientOpenAI {
|
|
|
156
197
|
const functionArgs = typeof functionCall.arguments === 'string'
|
|
157
198
|
? JSON.parse(functionCall.arguments)
|
|
158
199
|
: functionCall.arguments;
|
|
159
|
-
|
|
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}`);
|
|
160
214
|
try {
|
|
161
215
|
// Execute the tool via MCP
|
|
162
216
|
const result = await this.client.callTool({
|
|
@@ -229,7 +283,9 @@ export class MCPClientOpenAI {
|
|
|
229
283
|
return finalResponse;
|
|
230
284
|
}
|
|
231
285
|
clearHistory() {
|
|
232
|
-
|
|
286
|
+
// Keep the system message (first item) when clearing history
|
|
287
|
+
const systemMessage = this.conversationHistory[0];
|
|
288
|
+
this.conversationHistory = systemMessage ? [systemMessage] : [];
|
|
233
289
|
}
|
|
234
290
|
async cleanup() {
|
|
235
291
|
await this.client.close();
|