@kaifusion/widget 1.0.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/dist/KaiChatWidget.d.ts +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.es.js +66504 -0
- package/dist/index.es.js.map +1 -0
- package/package.json +46 -0
- package/src/KaiChatWidget.tsx +709 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,709 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from "react";
|
|
2
|
+
import ReactMarkdown from "react-markdown";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
import remarkMath from "remark-math";
|
|
5
|
+
import rehypeKatex from "rehype-katex";
|
|
6
|
+
import rehypeRaw from "rehype-raw";
|
|
7
|
+
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
|
8
|
+
import { vscDarkPlus } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
|
9
|
+
import {
|
|
10
|
+
MessageSquare,
|
|
11
|
+
X,
|
|
12
|
+
Send,
|
|
13
|
+
Maximize2,
|
|
14
|
+
Minimize2,
|
|
15
|
+
Loader2,
|
|
16
|
+
Bot,
|
|
17
|
+
Copy,
|
|
18
|
+
CheckCircle,
|
|
19
|
+
Terminal,
|
|
20
|
+
FileText,
|
|
21
|
+
ExternalLink,
|
|
22
|
+
RefreshCcw,
|
|
23
|
+
Quote,
|
|
24
|
+
} from "lucide-react";
|
|
25
|
+
import { motion, AnimatePresence } from "motion/react";
|
|
26
|
+
import { v4 as uuidv4 } from "uuid";
|
|
27
|
+
|
|
28
|
+
interface Message {
|
|
29
|
+
id: string;
|
|
30
|
+
content: string;
|
|
31
|
+
isBot: boolean;
|
|
32
|
+
timestamp: Date;
|
|
33
|
+
isError?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface KaiChatWidgetProps {
|
|
37
|
+
authToken?: string;
|
|
38
|
+
workflowId?: string;
|
|
39
|
+
title?: string;
|
|
40
|
+
targetUrl?: string;
|
|
41
|
+
position?: "left" | "right";
|
|
42
|
+
color?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function KaiChatWidget({
|
|
46
|
+
authToken,
|
|
47
|
+
workflowId = "e64585ca-45c1-4dad-ab62-9b59e4979995",
|
|
48
|
+
title = "ApiFort AI",
|
|
49
|
+
targetUrl = "http://localhost:23056",
|
|
50
|
+
position = "right",
|
|
51
|
+
color = "#526cfe",
|
|
52
|
+
}: KaiChatWidgetProps) {
|
|
53
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
54
|
+
const [isMaximized, setIsMaximized] = useState(false);
|
|
55
|
+
const [messages, setMessages] = useState<Message[]>([
|
|
56
|
+
{
|
|
57
|
+
id: "welcome",
|
|
58
|
+
content: "Merhaba! Size nasıl yardımcı olabilirim?",
|
|
59
|
+
isBot: true,
|
|
60
|
+
timestamp: new Date(),
|
|
61
|
+
},
|
|
62
|
+
]);
|
|
63
|
+
const [input, setInput] = useState("");
|
|
64
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
65
|
+
const [copiedCode, setCopiedCode] = useState<string | null>(null);
|
|
66
|
+
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
67
|
+
const sessionIdRef = useRef(uuidv4());
|
|
68
|
+
|
|
69
|
+
const copyToClipboard = async (text: string) => {
|
|
70
|
+
try {
|
|
71
|
+
await navigator.clipboard.writeText(text);
|
|
72
|
+
setCopiedCode(text);
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
setCopiedCode(null);
|
|
75
|
+
}, 2000);
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error("Kopyalama başarısız:", err);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
83
|
+
}, [messages, isOpen]);
|
|
84
|
+
|
|
85
|
+
const sendMessage = async () => {
|
|
86
|
+
if (!input.trim() || isLoading) return;
|
|
87
|
+
|
|
88
|
+
const userMessage: Message = {
|
|
89
|
+
id: uuidv4(),
|
|
90
|
+
content: input,
|
|
91
|
+
isBot: false,
|
|
92
|
+
timestamp: new Date(),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
setMessages((prev) => [...prev, userMessage]);
|
|
96
|
+
setInput("");
|
|
97
|
+
setIsLoading(true);
|
|
98
|
+
|
|
99
|
+
// Bot mesajı için placeholder oluştur
|
|
100
|
+
const botMessageId = uuidv4();
|
|
101
|
+
setMessages((prev) => [
|
|
102
|
+
...prev,
|
|
103
|
+
{
|
|
104
|
+
id: botMessageId,
|
|
105
|
+
content: "",
|
|
106
|
+
isBot: true,
|
|
107
|
+
timestamp: new Date(),
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const token = authToken || localStorage.getItem("auth_access_token");
|
|
113
|
+
const headers: Record<string, string> = {
|
|
114
|
+
"Content-Type": "application/json",
|
|
115
|
+
Accept: "text/event-stream",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (token) {
|
|
119
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
120
|
+
headers["X-API-Key"] = token;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const response = await fetch(`${targetUrl}/api/v1/workflows/execute`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers,
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
input_text: userMessage.content,
|
|
128
|
+
session_id: sessionIdRef.current,
|
|
129
|
+
chatflow_id: sessionIdRef.current,
|
|
130
|
+
workflow_id: workflowId,
|
|
131
|
+
}),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const contentType = response.headers.get("content-type");
|
|
135
|
+
if (contentType && contentType.includes("application/json")) {
|
|
136
|
+
const data = await response.json();
|
|
137
|
+
if (data.error) {
|
|
138
|
+
setMessages((prev) =>
|
|
139
|
+
prev.map((msg) =>
|
|
140
|
+
msg.id === botMessageId
|
|
141
|
+
? { ...msg, content: data.message, isError: true }
|
|
142
|
+
: msg
|
|
143
|
+
)
|
|
144
|
+
);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!response.body) throw new Error("ReadableStream not supported");
|
|
150
|
+
|
|
151
|
+
const reader = response.body.getReader();
|
|
152
|
+
const decoder = new TextDecoder();
|
|
153
|
+
let fullText = "";
|
|
154
|
+
|
|
155
|
+
while (true) {
|
|
156
|
+
const { done, value } = await reader.read();
|
|
157
|
+
if (done) break;
|
|
158
|
+
|
|
159
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
160
|
+
const lines = chunk.split("\n");
|
|
161
|
+
|
|
162
|
+
for (const line of lines) {
|
|
163
|
+
if (line.startsWith("data: ")) {
|
|
164
|
+
const data = line.slice(6).trim();
|
|
165
|
+
if (data === "[DONE]" || !data) continue;
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const parsed = JSON.parse(data);
|
|
169
|
+
let newContent = "";
|
|
170
|
+
|
|
171
|
+
if (parsed.type === "token" && parsed.content) {
|
|
172
|
+
newContent = parsed.content;
|
|
173
|
+
} else if (parsed.type === "output" && parsed.output) {
|
|
174
|
+
newContent = parsed.output;
|
|
175
|
+
} else if (parsed.type === "complete" && parsed.result) {
|
|
176
|
+
if (typeof parsed.result === "string" && !fullText) {
|
|
177
|
+
newContent = parsed.result;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (newContent) {
|
|
182
|
+
fullText += newContent;
|
|
183
|
+
setMessages((prev) =>
|
|
184
|
+
prev.map((msg) =>
|
|
185
|
+
msg.id === botMessageId
|
|
186
|
+
? { ...msg, content: fullText }
|
|
187
|
+
: msg
|
|
188
|
+
)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
} catch (e) {
|
|
192
|
+
console.warn("Stream parse error:", e);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error("API Error:", error);
|
|
199
|
+
setMessages((prev) =>
|
|
200
|
+
prev.map((msg) =>
|
|
201
|
+
msg.id === botMessageId
|
|
202
|
+
? { ...msg, content: "Üzgünüm, bir hata oluştu.", isError: true }
|
|
203
|
+
: msg
|
|
204
|
+
)
|
|
205
|
+
);
|
|
206
|
+
} finally {
|
|
207
|
+
setIsLoading(false);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const clearHistory = () => {
|
|
212
|
+
setMessages([]);
|
|
213
|
+
sessionIdRef.current = uuidv4();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div
|
|
218
|
+
className={`fixed bottom-5 z-50 flex flex-col items-end gap-4 font-sans ${
|
|
219
|
+
position === "left" ? "left-5" : "right-5"
|
|
220
|
+
}`}
|
|
221
|
+
style={{ fontFamily: "system-ui, -apple-system, sans-serif" }}
|
|
222
|
+
>
|
|
223
|
+
{/* Overlay - Tam Ekran Modu İçin */}
|
|
224
|
+
<AnimatePresence>
|
|
225
|
+
{isOpen && isMaximized && (
|
|
226
|
+
<motion.div
|
|
227
|
+
initial={{ opacity: 0 }}
|
|
228
|
+
animate={{ opacity: 1 }}
|
|
229
|
+
exit={{ opacity: 0 }}
|
|
230
|
+
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40"
|
|
231
|
+
onClick={() => setIsMaximized(false)}
|
|
232
|
+
/>
|
|
233
|
+
)}
|
|
234
|
+
</AnimatePresence>
|
|
235
|
+
|
|
236
|
+
{/* Chat Penceresi */}
|
|
237
|
+
<AnimatePresence>
|
|
238
|
+
{isOpen && (
|
|
239
|
+
<motion.div
|
|
240
|
+
initial={{ opacity: 0, y: 20, scale: 0.95 }}
|
|
241
|
+
animate={{
|
|
242
|
+
opacity: 1,
|
|
243
|
+
y: 0,
|
|
244
|
+
scale: 1,
|
|
245
|
+
width: isMaximized ? "900px" : "400px",
|
|
246
|
+
height: isMaximized ? "85vh" : "600px",
|
|
247
|
+
}}
|
|
248
|
+
exit={{ opacity: 0, y: 20, scale: 0.95 }}
|
|
249
|
+
className={`rounded-2xl shadow-2xl flex flex-col overflow-hidden z-50 transition-all duration-300 ${
|
|
250
|
+
isMaximized
|
|
251
|
+
? "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 m-0"
|
|
252
|
+
: "relative"
|
|
253
|
+
}`}
|
|
254
|
+
>
|
|
255
|
+
{/* Header */}
|
|
256
|
+
<div
|
|
257
|
+
className="px-4 py-3 text-white flex justify-between items-center shadow-md"
|
|
258
|
+
style={{ background: color }}
|
|
259
|
+
>
|
|
260
|
+
<div className="flex items-center gap-2 font-semibold">
|
|
261
|
+
<Bot className="w-5 h-5" />
|
|
262
|
+
{title}
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex items-center gap-1">
|
|
265
|
+
<button
|
|
266
|
+
onClick={clearHistory}
|
|
267
|
+
className="p-1.5 hover:bg-white/20 rounded-full transition-colors"
|
|
268
|
+
title="Sohbeti Temizle"
|
|
269
|
+
>
|
|
270
|
+
<RefreshCcw className="w-4 h-4" />
|
|
271
|
+
</button>
|
|
272
|
+
<button
|
|
273
|
+
onClick={() => setIsMaximized(!isMaximized)}
|
|
274
|
+
className="p-1.5 hover:bg-white/20 rounded-full transition-colors"
|
|
275
|
+
>
|
|
276
|
+
{isMaximized ? (
|
|
277
|
+
<Minimize2 className="w-4 h-4" />
|
|
278
|
+
) : (
|
|
279
|
+
<Maximize2 className="w-4 h-4" />
|
|
280
|
+
)}
|
|
281
|
+
</button>
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
{/* Mesajlar */}
|
|
286
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-gray-50">
|
|
287
|
+
{messages.map((msg) => (
|
|
288
|
+
<div
|
|
289
|
+
key={msg.id}
|
|
290
|
+
className={`flex ${
|
|
291
|
+
msg.isBot ? "justify-start" : "justify-end"
|
|
292
|
+
} ${
|
|
293
|
+
msg.isBot && !msg.content && !msg.isError ? "hidden" : ""
|
|
294
|
+
}`}
|
|
295
|
+
>
|
|
296
|
+
<div
|
|
297
|
+
className={`max-w-[85%] rounded-2xl px-4 py-3 shadow-sm ${
|
|
298
|
+
msg.isBot
|
|
299
|
+
? msg.isError
|
|
300
|
+
? "bg-red-50 text-red-600 border border-red-100"
|
|
301
|
+
: "bg-white text-gray-800 border border-gray-100"
|
|
302
|
+
: "text-white"
|
|
303
|
+
}`}
|
|
304
|
+
style={!msg.isBot ? { backgroundColor: color } : {}}
|
|
305
|
+
>
|
|
306
|
+
<div className="max-w-none break-words">
|
|
307
|
+
<ReactMarkdown
|
|
308
|
+
remarkPlugins={[remarkGfm, remarkMath]}
|
|
309
|
+
rehypePlugins={[rehypeKatex, rehypeRaw]}
|
|
310
|
+
components={{
|
|
311
|
+
// Kod blokları - modern tasarım ile
|
|
312
|
+
code: ({ className, children, ...props }: any) => {
|
|
313
|
+
const match = /language-(\w+)/.exec(
|
|
314
|
+
className || ""
|
|
315
|
+
);
|
|
316
|
+
const language = match ? match[1] : "";
|
|
317
|
+
const isInline = !match;
|
|
318
|
+
const codeContent = String(children).replace(
|
|
319
|
+
/\n$/,
|
|
320
|
+
""
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
return !isInline ? (
|
|
324
|
+
<div className="relative group my-2 sm:my-4 rounded-xl shadow-lg border border-gray-200 w-full overflow-hidden">
|
|
325
|
+
{/* Header with language and copy button */}
|
|
326
|
+
<div
|
|
327
|
+
className="flex items-center justify-between px-4 py-2 text-[#f3f4f6] border-b border-[#374151]"
|
|
328
|
+
style={{
|
|
329
|
+
background:
|
|
330
|
+
"linear-gradient(to right, #1f2937, #111827)",
|
|
331
|
+
fontSize: "14px",
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
<div className="flex items-center gap-2 min-w-0 flex-1">
|
|
335
|
+
<Terminal className="w-4 h-4 text-blue-400 flex-shrink-0" />
|
|
336
|
+
<span className="font-medium capitalize truncate">
|
|
337
|
+
{language || "plaintext"}
|
|
338
|
+
</span>
|
|
339
|
+
</div>
|
|
340
|
+
<button
|
|
341
|
+
onClick={() => copyToClipboard(codeContent)}
|
|
342
|
+
className="flex items-center gap-1 px-3 py-1 bg-[#374151] hover:bg-[#4b5563] text-[#e5e7eb] rounded-md transition-all duration-200 text-xs font-medium cursor-pointer"
|
|
343
|
+
style={{ fontSize: "12px" }}
|
|
344
|
+
>
|
|
345
|
+
{copiedCode === codeContent ? (
|
|
346
|
+
<>
|
|
347
|
+
<CheckCircle className="w-3 h-3 text-green-400" />
|
|
348
|
+
<span>Kopyalandı!</span>
|
|
349
|
+
</>
|
|
350
|
+
) : (
|
|
351
|
+
<>
|
|
352
|
+
<Copy className="w-3 h-3" />
|
|
353
|
+
<span>Kopyala</span>
|
|
354
|
+
</>
|
|
355
|
+
)}
|
|
356
|
+
</button>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
{/* Syntax highlighted code */}
|
|
360
|
+
<div className="bg-gray-950 overflow-x-auto">
|
|
361
|
+
<SyntaxHighlighter
|
|
362
|
+
style={vscDarkPlus}
|
|
363
|
+
language={language || "text"}
|
|
364
|
+
customStyle={{
|
|
365
|
+
margin: 0,
|
|
366
|
+
padding: "1rem",
|
|
367
|
+
fontSize: "0.75rem",
|
|
368
|
+
lineHeight: "1.4",
|
|
369
|
+
background: "transparent",
|
|
370
|
+
borderRadius: 0,
|
|
371
|
+
}}
|
|
372
|
+
showLineNumbers={
|
|
373
|
+
codeContent.split("\n").length > 5
|
|
374
|
+
}
|
|
375
|
+
lineNumberStyle={{
|
|
376
|
+
color: "#6b7280",
|
|
377
|
+
fontSize: "0.7rem",
|
|
378
|
+
marginRight: "0.5rem",
|
|
379
|
+
userSelect: "none",
|
|
380
|
+
}}
|
|
381
|
+
wrapLongLines={false}
|
|
382
|
+
>
|
|
383
|
+
{codeContent}
|
|
384
|
+
</SyntaxHighlighter>
|
|
385
|
+
</div>
|
|
386
|
+
</div>
|
|
387
|
+
) : (
|
|
388
|
+
<code
|
|
389
|
+
className="bg-blue-50 text-blue-700 px-2 py-1 rounded-md text-sm font-mono border border-blue-200 font-medium"
|
|
390
|
+
{...props}
|
|
391
|
+
>
|
|
392
|
+
{children}
|
|
393
|
+
</code>
|
|
394
|
+
);
|
|
395
|
+
},
|
|
396
|
+
|
|
397
|
+
// Pre etiketleri için beyaz arka plan
|
|
398
|
+
pre: ({ children }: any) => (
|
|
399
|
+
<pre className="bg-white max-w-full rounded-lg text-sm ">
|
|
400
|
+
{children}
|
|
401
|
+
</pre>
|
|
402
|
+
),
|
|
403
|
+
|
|
404
|
+
// Başlıklar - modern hiyerarşi (Daha kompakt)
|
|
405
|
+
h1: ({ children }: any) => (
|
|
406
|
+
<h1 className="text-lg font-bold mb-2 text-gray-900 pb-2 border-b-2 border-gradient-to-r from-blue-500 to-purple-600 relative">
|
|
407
|
+
<span className="bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
|
408
|
+
{children}
|
|
409
|
+
</span>
|
|
410
|
+
</h1>
|
|
411
|
+
),
|
|
412
|
+
h2: ({ children }: any) => (
|
|
413
|
+
<h2 className="text-base font-bold mb-2 text-gray-900 mt-4 flex items-center gap-2">
|
|
414
|
+
<span className="w-1 h-5 bg-gradient-to-b from-blue-500 to-purple-600 rounded-full"></span>
|
|
415
|
+
{children}
|
|
416
|
+
</h2>
|
|
417
|
+
),
|
|
418
|
+
h3: ({ children }: any) => (
|
|
419
|
+
<h3 className="text-sm font-semibold mb-2 text-gray-800 mt-3 flex items-center gap-2">
|
|
420
|
+
<span className="w-1 h-4 bg-gradient-to-b from-blue-400 to-purple-500 rounded-full"></span>
|
|
421
|
+
{children}
|
|
422
|
+
</h3>
|
|
423
|
+
),
|
|
424
|
+
h4: ({ children }: any) => (
|
|
425
|
+
<h4 className="text-xs font-semibold mb-1 text-gray-700 mt-2 flex items-center gap-2">
|
|
426
|
+
<span className="w-0.5 h-3 bg-gradient-to-b from-blue-400 to-purple-500 rounded-full"></span>
|
|
427
|
+
{children}
|
|
428
|
+
</h4>
|
|
429
|
+
),
|
|
430
|
+
|
|
431
|
+
// Listeler - modern tasarım ile
|
|
432
|
+
ul: ({ children }: any) => (
|
|
433
|
+
<ul className="mb-2 space-y-0.5 text-sm">
|
|
434
|
+
{children}
|
|
435
|
+
</ul>
|
|
436
|
+
),
|
|
437
|
+
ol: ({ children }: any) => (
|
|
438
|
+
<ol className="mt-3 mb-2 space-y-0.5 counter-reset-list text-sm">
|
|
439
|
+
{children}
|
|
440
|
+
</ol>
|
|
441
|
+
),
|
|
442
|
+
li: ({ children, ...props }: any) => {
|
|
443
|
+
const isOrderedList =
|
|
444
|
+
props.className?.includes("ordered") ||
|
|
445
|
+
(typeof children === "object" &&
|
|
446
|
+
children?.props?.ordered);
|
|
447
|
+
|
|
448
|
+
return isOrderedList ? (
|
|
449
|
+
<li
|
|
450
|
+
className={`flex items-start gap-2 pl-1 leading-relaxed ${
|
|
451
|
+
!msg.isBot ? "text-white" : "text-gray-700"
|
|
452
|
+
}`}
|
|
453
|
+
>
|
|
454
|
+
<span className="flex-shrink-0 w-5 h-5 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-full flex items-center justify-center text-[10px] font-bold mt-0.5 shadow-sm counter-increment">
|
|
455
|
+
•
|
|
456
|
+
</span>
|
|
457
|
+
<div className="flex-1 pt-0.5 text-left">
|
|
458
|
+
{children}
|
|
459
|
+
</div>
|
|
460
|
+
</li>
|
|
461
|
+
) : (
|
|
462
|
+
<li
|
|
463
|
+
className={`flex items-start gap-2 pl-1 leading-relaxed ${
|
|
464
|
+
!msg.isBot ? "text-white" : "text-gray-700"
|
|
465
|
+
}`}
|
|
466
|
+
>
|
|
467
|
+
<span className="flex-shrink-0 w-1.5 h-1.5 bg-gradient-to-r from-blue-500 to-purple-600 rounded-full mt-2 shadow-sm"></span>
|
|
468
|
+
<div className="flex-1 text-left">
|
|
469
|
+
{children}
|
|
470
|
+
</div>
|
|
471
|
+
</li>
|
|
472
|
+
);
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
// Linkler
|
|
476
|
+
a: ({ href, children }: any) => (
|
|
477
|
+
<a
|
|
478
|
+
href={href}
|
|
479
|
+
className="text-blue-600 hover:text-blue-800 underline inline-flex items-center gap-0.5 transition-colors duration-200 break-all max-w-full text-sm"
|
|
480
|
+
target="_blank"
|
|
481
|
+
rel="noopener noreferrer"
|
|
482
|
+
>
|
|
483
|
+
<span className="break-all">{children}</span>
|
|
484
|
+
<ExternalLink className="w-3 h-3 flex-shrink-0" />
|
|
485
|
+
</a>
|
|
486
|
+
),
|
|
487
|
+
|
|
488
|
+
// Alıntılar - modern tasarım
|
|
489
|
+
blockquote: ({ children }: any) => (
|
|
490
|
+
<blockquote className="relative my-2 sm:my-4 p-3 sm:p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border border-blue-200 rounded-lg shadow-sm -mx-1 sm:mx-0">
|
|
491
|
+
<div className="absolute top-2 sm:top-3 left-2 sm:left-3">
|
|
492
|
+
<Quote className="w-3 h-3 sm:w-4 sm:h-4 text-blue-400 opacity-60" />
|
|
493
|
+
</div>
|
|
494
|
+
<div className="pl-4 sm:pl-6">
|
|
495
|
+
<div className="text-blue-600 text-[10px] sm:text-xs font-semibold mb-1 uppercase tracking-wide">
|
|
496
|
+
Alıntı
|
|
497
|
+
</div>
|
|
498
|
+
<div className="text-gray-700 italic leading-relaxed text-xs sm:text-sm">
|
|
499
|
+
{children}
|
|
500
|
+
</div>
|
|
501
|
+
</div>
|
|
502
|
+
<div className="absolute bottom-2 sm:bottom-3 right-2 sm:right-3">
|
|
503
|
+
<Quote className="w-2 h-2 sm:w-3 sm:h-3 text-blue-300 opacity-40 rotate-180" />
|
|
504
|
+
</div>
|
|
505
|
+
</blockquote>
|
|
506
|
+
),
|
|
507
|
+
|
|
508
|
+
// Tablolar - modern responsive tasarım
|
|
509
|
+
table: ({ children }: any) => (
|
|
510
|
+
<div className="overflow-x-auto my-2 sm:my-4 rounded-lg border border-gray-200 shadow-sm bg-white -mx-1 sm:mx-0">
|
|
511
|
+
<table className="min-w-full text-xs">
|
|
512
|
+
{children}
|
|
513
|
+
</table>
|
|
514
|
+
</div>
|
|
515
|
+
),
|
|
516
|
+
thead: ({ children }: any) => (
|
|
517
|
+
<thead className="bg-gradient-to-r from-gray-50 to-gray-100">
|
|
518
|
+
{children}
|
|
519
|
+
</thead>
|
|
520
|
+
),
|
|
521
|
+
th: ({ children }: any) => (
|
|
522
|
+
<th className="px-2 sm:px-4 py-1.5 sm:py-2 text-left font-semibold text-gray-900 border-b border-gray-200 first:rounded-tl-lg last:rounded-tr-lg">
|
|
523
|
+
<div className="flex items-center gap-1">
|
|
524
|
+
<FileText className="w-3 h-3 text-gray-500 hidden sm:block" />
|
|
525
|
+
<span className="truncate">{children}</span>
|
|
526
|
+
</div>
|
|
527
|
+
</th>
|
|
528
|
+
),
|
|
529
|
+
tbody: ({ children }: any) => (
|
|
530
|
+
<tbody className="divide-y divide-gray-100">
|
|
531
|
+
{children}
|
|
532
|
+
</tbody>
|
|
533
|
+
),
|
|
534
|
+
tr: ({ children }: any) => (
|
|
535
|
+
<tr className="hover:bg-gray-50 transition-colors duration-150">
|
|
536
|
+
{children}
|
|
537
|
+
</tr>
|
|
538
|
+
),
|
|
539
|
+
td: ({ children }: any) => (
|
|
540
|
+
<td className="px-2 sm:px-4 py-1.5 sm:py-2 text-gray-700 leading-relaxed text-xs">
|
|
541
|
+
<div
|
|
542
|
+
className="truncate max-w-[150px] sm:max-w-none"
|
|
543
|
+
title={
|
|
544
|
+
typeof children === "string" ? children : ""
|
|
545
|
+
}
|
|
546
|
+
>
|
|
547
|
+
{children}
|
|
548
|
+
</div>
|
|
549
|
+
</td>
|
|
550
|
+
),
|
|
551
|
+
|
|
552
|
+
// Paragraflar - daha iyi spacing
|
|
553
|
+
p: ({ children }: any) => (
|
|
554
|
+
<p
|
|
555
|
+
className={`m-0! last:mb-0 leading-relaxed break-words overflow-wrap-anywhere text-sm ${
|
|
556
|
+
!msg.isBot ? "text-white" : "text-gray-700"
|
|
557
|
+
}`}
|
|
558
|
+
>
|
|
559
|
+
{children}
|
|
560
|
+
</p>
|
|
561
|
+
),
|
|
562
|
+
|
|
563
|
+
// Vurgu metinleri
|
|
564
|
+
strong: ({ children }: any) => (
|
|
565
|
+
<strong
|
|
566
|
+
className={`font-bold inline ${
|
|
567
|
+
!msg.isBot ? "text-white" : "text-gray-900"
|
|
568
|
+
}`}
|
|
569
|
+
>
|
|
570
|
+
{children}
|
|
571
|
+
</strong>
|
|
572
|
+
),
|
|
573
|
+
em: ({ children }: any) => (
|
|
574
|
+
<em
|
|
575
|
+
className={`italic ${
|
|
576
|
+
!msg.isBot ? "text-white/90" : "text-gray-600"
|
|
577
|
+
}`}
|
|
578
|
+
>
|
|
579
|
+
{children}
|
|
580
|
+
</em>
|
|
581
|
+
),
|
|
582
|
+
|
|
583
|
+
// Çizgi
|
|
584
|
+
hr: () => <hr className="border-gray-300 my-4" />,
|
|
585
|
+
|
|
586
|
+
// Resimler - responsive
|
|
587
|
+
img: ({ src, alt }: any) => (
|
|
588
|
+
<img
|
|
589
|
+
src={src}
|
|
590
|
+
alt={alt}
|
|
591
|
+
className="max-w-full h-auto rounded-lg shadow-sm my-2"
|
|
592
|
+
loading="lazy"
|
|
593
|
+
/>
|
|
594
|
+
),
|
|
595
|
+
|
|
596
|
+
// Matematik formülleri için
|
|
597
|
+
div: ({ className, children, ...props }: any) => {
|
|
598
|
+
if (className === "math math-display") {
|
|
599
|
+
return (
|
|
600
|
+
<div className="my-4 text-center bg-gray-50 p-3 rounded-lg border">
|
|
601
|
+
<div className={className} {...props}>
|
|
602
|
+
{children}
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
);
|
|
606
|
+
}
|
|
607
|
+
return (
|
|
608
|
+
<div className={className} {...props}>
|
|
609
|
+
{children}
|
|
610
|
+
</div>
|
|
611
|
+
);
|
|
612
|
+
},
|
|
613
|
+
|
|
614
|
+
// Inline matematik
|
|
615
|
+
span: ({ className, children, ...props }: any) => {
|
|
616
|
+
if (className === "math math-inline") {
|
|
617
|
+
return (
|
|
618
|
+
<span className="bg-gray-100 px-1 py-0.5 rounded text-sm font-mono">
|
|
619
|
+
<span className={className} {...props}>
|
|
620
|
+
{children}
|
|
621
|
+
</span>
|
|
622
|
+
</span>
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
return (
|
|
626
|
+
<span className={className} {...props}>
|
|
627
|
+
{children}
|
|
628
|
+
</span>
|
|
629
|
+
);
|
|
630
|
+
},
|
|
631
|
+
}}
|
|
632
|
+
>
|
|
633
|
+
{msg.content}
|
|
634
|
+
</ReactMarkdown>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
))}
|
|
639
|
+
{isLoading && (
|
|
640
|
+
<div className="flex justify-start">
|
|
641
|
+
<div className="bg-white border border-gray-100 rounded-2xl px-4 py-3 shadow-sm flex items-center gap-2">
|
|
642
|
+
<span className="text-xs text-gray-500">Yazıyor</span>
|
|
643
|
+
<div className="flex space-x-1">
|
|
644
|
+
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.3s]"></div>
|
|
645
|
+
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-bounce [animation-delay:-0.15s]"></div>
|
|
646
|
+
<div className="w-1.5 h-1.5 bg-gray-400 rounded-full animate-bounce"></div>
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
</div>
|
|
650
|
+
)}
|
|
651
|
+
<div ref={messagesEndRef} />
|
|
652
|
+
</div>
|
|
653
|
+
|
|
654
|
+
{/* Input Alanı */}
|
|
655
|
+
<div className="p-4 bg-white border-t border-gray-100">
|
|
656
|
+
<div className="flex gap-2 items-center bg-gray-50 rounded-full px-4 py-2 border border-gray-200 focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500 transition-all">
|
|
657
|
+
<input
|
|
658
|
+
type="text"
|
|
659
|
+
value={input}
|
|
660
|
+
onChange={(e) => setInput(e.target.value)}
|
|
661
|
+
onKeyDown={(e) => e.key === "Enter" && sendMessage()}
|
|
662
|
+
placeholder="Mesajınızı yazın..."
|
|
663
|
+
className="flex-1 bg-transparent border-none focus:ring-0 outline-none text-sm py-1"
|
|
664
|
+
disabled={isLoading}
|
|
665
|
+
/>
|
|
666
|
+
<button
|
|
667
|
+
onClick={sendMessage}
|
|
668
|
+
disabled={isLoading || !input.trim()}
|
|
669
|
+
className={`p-2 rounded-full transition-all ${
|
|
670
|
+
input.trim() && !isLoading
|
|
671
|
+
? "text-blue-600 hover:bg-blue-50"
|
|
672
|
+
: "text-gray-400"
|
|
673
|
+
}`}
|
|
674
|
+
>
|
|
675
|
+
{isLoading ? (
|
|
676
|
+
<Loader2 className="w-5 h-5 animate-spin" />
|
|
677
|
+
) : (
|
|
678
|
+
<Send className="w-5 h-5" />
|
|
679
|
+
)}
|
|
680
|
+
</button>
|
|
681
|
+
</div>
|
|
682
|
+
<div className="text-center mt-2">
|
|
683
|
+
<span className="text-[10px] text-gray-400">
|
|
684
|
+
Powered by Agenticgro
|
|
685
|
+
</span>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
</motion.div>
|
|
689
|
+
)}
|
|
690
|
+
</AnimatePresence>
|
|
691
|
+
|
|
692
|
+
<motion.button
|
|
693
|
+
initial={{ scale: 0 }}
|
|
694
|
+
animate={{ scale: 1 }}
|
|
695
|
+
whileHover={{ scale: 1.1 }}
|
|
696
|
+
whileTap={{ scale: 0.9 }}
|
|
697
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
698
|
+
className="w-14 h-14 rounded-full shadow-xl flex items-center justify-center text-white transition-shadow hover:shadow-2xl"
|
|
699
|
+
style={{ backgroundColor: color }}
|
|
700
|
+
>
|
|
701
|
+
{isOpen ? (
|
|
702
|
+
<X className="w-7 h-7" />
|
|
703
|
+
) : (
|
|
704
|
+
<MessageSquare className="w-7 h-7" />
|
|
705
|
+
)}
|
|
706
|
+
</motion.button>
|
|
707
|
+
</div>
|
|
708
|
+
);
|
|
709
|
+
}
|