@codedevin/dify-chat
Version:
A beautiful and configurable chatbot widget for Dify integration with multiple display modes
1,414 lines • 66.3 kB
JavaScript
var Re = Object.defineProperty;
var je = (e, r, s) => r in e ? Re(e, r, { enumerable: !0, configurable: !0, writable: !0, value: s }) : e[r] = s;
var fe = (e, r, s) => je(e, typeof r != "symbol" ? r + "" : r, s);
import { jsx as t, jsxs as u, Fragment as xe } from "react/jsx-runtime";
import De, { useState as T, useRef as Y, useCallback as W, memo as ve, useEffect as q, useMemo as Be, isValidElement as $e, Suspense as He, forwardRef as qe, useImperativeHandle as Oe } from "react";
import { createPortal as Ue } from "react-dom";
import { clsx as V } from "clsx";
import { AnimatePresence as ne, motion as X } from "framer-motion";
import { Check as We, Clipboard as Ke, X as Ce, RotateCcw as ze, Paperclip as Ve, ArrowUp as Je, Minimize2 as Xe, Maximize2 as Ye, ArrowDown as Ge, MessageCircle as Te } from "lucide-react";
import { marked as Qe } from "marked";
import Pe from "react-markdown";
import Ie from "remark-gfm";
class Ze {
constructor(r) {
fe(this, "config");
fe(this, "conversationId", null);
fe(this, "currentTaskId", null);
this.config = r;
}
getHeaders() {
return {
Authorization: `Bearer ${this.config.apiKey}`,
"Content-Type": "application/json"
};
}
setConversationId(r) {
this.conversationId = r;
}
getConversationId() {
return this.conversationId;
}
async sendMessage(r, s) {
const o = {
inputs: this.config.inputs || {},
query: r,
user: this.config.userId || "anonymous",
conversation_id: this.conversationId,
response_mode: "blocking",
files: (s == null ? void 0 : s.map((p) => ({
type: p.type,
transfer_method: p.id ? "local_file" : "remote_url",
upload_file_id: p.id,
url: p.url
}))) || []
}, n = await fetch(`${this.config.baseUrl}/chat-messages`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(o)
});
if (!n.ok) {
const p = await n.json().catch(() => ({ message: "Request failed" }));
throw new Error(p.message || "Failed to send message");
}
const l = await n.json();
return l.conversation_id && !this.conversationId && (this.conversationId = l.conversation_id), l;
}
async sendMessageStream(r, s, o) {
var _;
const n = {
inputs: this.config.inputs || {},
query: r,
user: this.config.userId || "anonymous",
conversation_id: this.conversationId,
response_mode: "streaming",
files: (s == null ? void 0 : s.map((g) => ({
type: g.type,
transfer_method: g.id ? "local_file" : "remote_url",
upload_file_id: g.id,
url: g.url
}))) || []
}, l = await fetch(`${this.config.baseUrl}/chat-messages`, {
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(n)
});
if (!l.ok) {
const g = await l.json().catch(() => ({ message: "Request failed" }));
throw new Error(g.message || "Failed to send message");
}
const p = (_ = l.body) == null ? void 0 : _.getReader();
if (!p)
throw new Error("Failed to get response stream");
const y = new TextDecoder();
let F = "";
try {
for (; ; ) {
const { done: g, value: N } = await p.read();
if (g) break;
F += y.decode(N, { stream: !0 });
const P = F.split(`
`);
F = P.pop() || "";
for (const I of P)
if (I.trim() !== "") {
if (I.startsWith("data: ")) {
const M = I.slice(6).trim();
if (M === "[DONE]") break;
try {
const x = JSON.parse(M);
x.conversation_id && !this.conversationId && (this.conversationId = x.conversation_id), x.task_id && (this.currentTaskId = x.task_id), o == null || o(x);
} catch {
}
} else if (I.startsWith("event: "))
continue;
}
}
} finally {
p.releaseLock();
}
}
async uploadFile(r) {
const s = new FormData();
s.append("file", r), s.append("user", this.config.userId || "anonymous");
const o = await fetch(`${this.config.baseUrl}/files/upload`, {
method: "POST",
headers: {
Authorization: `Bearer ${this.config.apiKey}`
},
body: s
});
if (!o.ok) {
const l = await o.json().catch(() => ({ message: "Upload failed" }));
throw new Error(l.message || "Failed to upload file");
}
return {
id: (await o.json()).id,
type: this.getFileType(r.type),
name: r.name,
size: r.size
};
}
getFileType(r) {
return r.startsWith("image/") ? "image" : r.startsWith("audio/") ? "audio" : r.startsWith("video/") ? "video" : "document";
}
async stopMessage() {
if (!this.currentTaskId)
throw new Error("No active task to stop");
const r = {
user: this.config.userId || "anonymous"
}, s = await fetch(
`${this.config.baseUrl}/chat-messages/${this.currentTaskId}/stop`,
{
method: "POST",
headers: this.getHeaders(),
body: JSON.stringify(r)
}
);
if (!s.ok) {
const o = await s.json().catch(() => ({ message: "Failed to stop message" }));
throw new Error(o.message || "Failed to stop message");
}
this.currentTaskId = null;
}
getCurrentTaskId() {
return this.currentTaskId;
}
resetConversation() {
this.conversationId = null, this.currentTaskId = null;
}
}
const et = ({
config: e,
onMessage: r,
onError: s,
enableStreaming: o = !0
}) => {
const [n, l] = T([]), [p, y] = T(!1), [F, _] = T(null), [g, N] = T(!1), P = Y(new Ze(e)), I = () => Math.random().toString(36).substring(2, 15), M = (h) => {
try {
const f = h.match(
/'error':\s*\{\s*'message':\s*"([^"]+)"/
);
if (f && f[1])
return f[1];
const v = h.match(/"message":\s*"([^"]+)"/);
return v && v[1] ? v[1] : h;
} catch {
return h;
}
}, x = W(
(h) => {
l((f) => [...f, h]), r == null || r(h);
},
[r]
), S = W(
(h) => {
l((f) => {
const v = [...f], c = v[v.length - 1];
if (c && c.role === "assistant") {
const m = {
...c,
content: h,
timestamp: Date.now()
// Update timestamp to ensure change detection
};
v[v.length - 1] = m, r == null || r(m);
}
return v;
});
},
[r]
), R = W(
async (h, f) => {
if (!h.trim() && !(f != null && f.length)) return;
_(null), y(!0);
const v = {
id: I(),
role: "user",
content: h.trim(),
timestamp: Date.now(),
attachments: f
};
x(v);
try {
if (o) {
let c = "", m = !1;
await P.current.sendMessageStream(
h,
f,
(i) => {
var w;
switch (i.event) {
case "message":
if (i.answer) {
if (!m) {
const O = {
id: I(),
role: "assistant",
content: "",
timestamp: Date.now()
};
x(O), m = !0;
}
c += i.answer, S(c);
}
break;
case "agent_message":
if (i.answer) {
if (!m) {
const O = {
id: I(),
role: "assistant",
content: "",
timestamp: Date.now()
};
x(O), m = !0;
}
c += i.answer, S(c);
}
break;
case "message_end":
(w = i.metadata) != null && w.usage;
break;
case "workflow_started":
break;
case "workflow_finished":
break;
case "node_started":
break;
case "node_finished":
break;
case "error":
y(!1);
const b = M(
i.message || "Stream error occurred"
), C = new Error(b);
throw _(C), s == null || s(C), C;
default:
break;
}
}
), y(!1);
} else {
const c = await P.current.sendMessage(
h,
f
), m = {
id: I(),
role: "assistant",
content: c.answer,
timestamp: Date.now()
};
x(m), y(!1);
}
} catch (c) {
const m = c instanceof Error ? c : new Error("Unknown error");
_(m), s == null || s(m), y(!1), l((i) => {
const w = i[i.length - 1];
return w && w.role === "assistant" && !w.content.trim() ? i.slice(0, -1) : i;
});
}
},
[o, x, S, s]
), j = W(
async (h) => {
try {
return await P.current.uploadFile(h);
} catch (f) {
const v = f instanceof Error ? f : new Error("Upload failed");
throw _(v), s == null || s(v), v;
}
},
[s]
), z = W(() => {
l([]), _(null);
}, []), E = W(() => {
P.current.resetConversation(), z();
}, [z]), B = W(async () => {
if (!(!p || g)) {
N(!0);
try {
await P.current.stopMessage(), y(!1);
} catch (h) {
const f = h instanceof Error ? h : new Error("Failed to stop message");
_(f), s == null || s(f);
} finally {
N(!1);
}
}
}, [p, g, s]);
return {
messages: n,
isLoading: p,
error: F,
isStopping: g,
sendMessage: R,
stopMessage: B,
uploadFile: j,
clearMessages: z,
resetConversation: E
};
}, we = ({
size: e = 16,
className: r = ""
}) => /* @__PURE__ */ t(
"svg",
{
width: e,
height: e,
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
className: r,
children: /* @__PURE__ */ t(
"path",
{
d: "M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 22.5l-.394-1.933a2.25 2.25 0 00-1.423-1.423L12.75 18.75l1.933-.394a2.25 2.25 0 001.423-1.423L16.5 15l.394 1.933a2.25 2.25 0 001.423 1.423l1.933.394-1.933.394a2.25 2.25 0 00-1.423 1.423z",
stroke: "currentColor",
strokeWidth: "1.5",
strokeLinecap: "round",
strokeLinejoin: "round"
}
)
}
);
function Z(...e) {
return V(e);
}
const Se = ({ className: e }) => /* @__PURE__ */ u(
"svg",
{
className: e,
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ t("rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }),
/* @__PURE__ */ t("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })
]
}
), tt = ({ className: e }) => /* @__PURE__ */ t(
"svg",
{
className: e,
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: /* @__PURE__ */ t("path", { d: "M20 6 9 17l-5-5" })
}
), ue = (e) => typeof e == "string" ? e : Array.isArray(e) ? e.map(ue).join("") : $e(e) ? ue(e.props.children) : "", Fe = ve(
({ children: e, className: r, language: s, ...o }) => {
const [n, l] = T(null), [p, y] = T(!1), F = ue(e), _ = async () => {
try {
await navigator.clipboard.writeText(F), y(!0), setTimeout(() => y(!1), 2e3);
} catch (N) {
console.error("Failed to copy code:", N);
}
};
q(() => {
(async () => {
try {
const { codeToTokens: P, bundledLanguages: I } = await import("shiki"), M = ue(e);
if (!(s in I)) {
l(
/* @__PURE__ */ t(
"pre",
{
...o,
className: Z(
"my-0 overflow-x-auto w-full rounded-b-xl bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 border-0 p-4",
r
),
children: /* @__PURE__ */ t("code", { className: "whitespace-pre-wrap", children: e })
}
)
);
return;
}
const { tokens: x } = await P(M, {
lang: s,
themes: {
light: "github-light",
dark: "github-dark"
}
});
l(
/* @__PURE__ */ t(
"pre",
{
...o,
className: Z(
"my-0 overflow-x-auto w-full rounded-b-xl bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 border-0 p-4",
r
),
children: /* @__PURE__ */ t("code", { className: "whitespace-pre-wrap", children: x.map((S, R) => /* @__PURE__ */ u(
"span",
{
children: [
S.map((j, z) => {
const E = typeof j.htmlStyle == "string" ? void 0 : j.htmlStyle;
return /* @__PURE__ */ t(
"span",
{
style: E,
children: j.content
},
`token-${// biome-ignore lint/suspicious/noArrayIndexKey: Needed for react key
z}`
);
}),
R !== x.length - 1 && `
`
]
},
`line-${// biome-ignore lint/suspicious/noArrayIndexKey: Needed for react key
R}`
)) })
}
)
);
} catch (P) {
console.error("Failed to highlight code:", P), l(
/* @__PURE__ */ t(
"pre",
{
...o,
className: Z(
"my-0 overflow-x-auto w-full rounded-b-xl bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 border-0 p-4",
r
),
children: /* @__PURE__ */ t("code", { className: "whitespace-pre-wrap", children: e })
}
)
);
}
})();
}, [e, s]);
const g = /* @__PURE__ */ t(
"pre",
{
...o,
className: Z(
"my-0 overflow-x-auto w-full rounded-b-xl bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 border-0 p-4",
r
),
children: /* @__PURE__ */ t("code", { className: "whitespace-pre-wrap", children: e })
}
);
return /* @__PURE__ */ u("div", { className: "relative group my-4 w-full", children: [
/* @__PURE__ */ u("div", { className: "flex items-center justify-between px-4 py-2 bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 rounded-t-xl w-full", children: [
/* @__PURE__ */ t("span", { className: "text-sm text-zinc-600 dark:text-zinc-300 font-medium", children: s }),
/* @__PURE__ */ t(
"button",
{
onClick: _,
className: "flex items-center gap-2 px-2 py-1 text-xs text-zinc-500 hover:text-zinc-700 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:text-zinc-200 dark:hover:bg-zinc-800 rounded transition-colors",
title: p ? "Copied!" : "Copy code",
children: p ? /* @__PURE__ */ u(xe, { children: [
/* @__PURE__ */ t(tt, { className: "w-3 h-3" }),
"Copied"
] }) : /* @__PURE__ */ u(xe, { children: [
/* @__PURE__ */ t(Se, { className: "w-3 h-3" }),
"Copy"
] })
}
)
] }),
n || g
] });
}
);
Fe.displayName = "HighlightedPre";
const _e = ({
children: e,
language: r,
className: s,
...o
}) => /* @__PURE__ */ t(
He,
{
fallback: /* @__PURE__ */ u("div", { className: "relative group my-4 w-full", children: [
/* @__PURE__ */ u("div", { className: "flex items-center justify-between px-4 py-2 bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 rounded-t-xl w-full", children: [
/* @__PURE__ */ t("span", { className: "text-sm text-zinc-600 dark:text-zinc-300 font-medium", children: r }),
/* @__PURE__ */ u(
"button",
{
className: "flex items-center gap-2 px-2 py-1 text-xs text-zinc-500 dark:text-zinc-400 rounded",
disabled: !0,
children: [
/* @__PURE__ */ t(Se, { className: "w-3 h-3" }),
"Copy"
]
}
)
] }),
/* @__PURE__ */ t(
"pre",
{
...o,
className: Z(
"my-0 overflow-x-auto w-full rounded-b-xl bg-[#f9f9f9] text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 border-0 p-4",
s
),
children: /* @__PURE__ */ t("code", { className: "whitespace-pre-wrap", children: e })
}
)
] }),
children: /* @__PURE__ */ t(Fe, { language: r, className: s, ...o, children: e })
}
);
_e.displayName = "CodeBlock";
const Me = {
h1: ({ children: e, ...r }) => /* @__PURE__ */ t("h1", { className: "mt-2 scroll-m-20 text-4xl font-bold", ...r, children: e }),
h2: ({ children: e, ...r }) => /* @__PURE__ */ t(
"h2",
{
className: "mt-8 scroll-m-20 border-b pb-2 text-2xl font-semibold tracking-tight first:mt-0",
...r,
children: e
}
),
h3: ({ children: e, ...r }) => /* @__PURE__ */ t(
"h3",
{
className: "mt-4 scroll-m-20 text-xl font-semibold tracking-tight",
...r,
children: e
}
),
h4: ({ children: e, ...r }) => /* @__PURE__ */ t(
"h4",
{
className: "mt-4 scroll-m-20 text-lg font-semibold tracking-tight",
...r,
children: e
}
),
h5: ({ children: e, ...r }) => /* @__PURE__ */ t(
"h5",
{
className: "mt-4 scroll-m-20 text-lg font-semibold tracking-tight",
...r,
children: e
}
),
h6: ({ children: e, ...r }) => /* @__PURE__ */ t(
"h6",
{
className: "mt-4 scroll-m-20 text-base font-semibold tracking-tight",
...r,
children: e
}
),
p: ({ children: e, ...r }) => /* @__PURE__ */ t("p", { className: "leading-6 [&:not(:first-child)]:mt-4", ...r, children: e }),
strong: ({ children: e, ...r }) => /* @__PURE__ */ t("span", { className: "font-semibold", ...r, children: e }),
a: ({
children: e,
...r
}) => /* @__PURE__ */ t(
"a",
{
className: "font-medium underline underline-offset-4",
target: "_blank",
rel: "noreferrer",
...r,
children: e
}
),
ol: ({ children: e, ...r }) => /* @__PURE__ */ t("ol", { className: "my-4 ml-6 list-decimal", ...r, children: e }),
ul: ({ children: e, ...r }) => /* @__PURE__ */ t("ul", { className: "my-4 ml-6 list-disc", ...r, children: e }),
li: ({ children: e, ...r }) => /* @__PURE__ */ t("li", { className: "mt-2", ...r, children: e }),
blockquote: ({
children: e,
...r
}) => /* @__PURE__ */ t("blockquote", { className: "mt-4 border-l-2 pl-6 italic", ...r, children: e }),
hr: (e) => /* @__PURE__ */ t("hr", { className: "my-4 md:my-8", ...e }),
table: ({ children: e, ...r }) => /* @__PURE__ */ t("div", { className: "my-6 w-full overflow-y-auto", children: /* @__PURE__ */ t(
"table",
{
className: "relative w-full overflow-hidden border-none text-sm",
...r,
children: e
}
) }),
tr: ({ children: e, ...r }) => /* @__PURE__ */ t("tr", { className: "last:border-b-none m-0 border-b", ...r, children: e }),
th: ({ children: e, ...r }) => /* @__PURE__ */ t(
"th",
{
className: "px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right",
...r,
children: e
}
),
td: ({ children: e, ...r }) => /* @__PURE__ */ t(
"td",
{
className: "px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right",
...r,
children: e
}
),
img: ({ alt: e, ...r }) => (
// biome-ignore lint/a11y/useAltText: alt is not required
/* @__PURE__ */ t("img", { className: "rounded-md", alt: e, ...r })
),
code: ({ children: e, node: r, className: s, ...o }) => {
const n = /language-(\w+)/.exec(s || "");
return n ? /* @__PURE__ */ t(_e, { language: n[1], className: s, ...o, children: e }) : /* @__PURE__ */ t(
"code",
{
className: Z(
"rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-sm",
s
),
...o,
children: e
}
);
},
pre: ({ children: e }) => /* @__PURE__ */ t(xe, { children: e })
};
function rt(e) {
return e ? Qe.lexer(e).map((s) => s.raw) : [];
}
const Ae = ve(
({ content: e, className: r }) => /* @__PURE__ */ t("div", { className: r, children: /* @__PURE__ */ t(Pe, { remarkPlugins: [Ie], components: Me, children: e }) }),
(e, r) => e.content === r.content
);
Ae.displayName = "MemoizedMarkdownBlock";
const st = ve(
({ content: e, id: r, className: s }) => Be(
() => rt(e || ""),
[e]
).map((n, l) => /* @__PURE__ */ t(
Ae,
{
content: n,
className: s
},
`${r}-block_${// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
l}`
))
);
st.displayName = "MarkdownContent";
const ot = ({ children: e }) => /* @__PURE__ */ t("div", { className: "markdown-content", children: /* @__PURE__ */ t(Pe, { remarkPlugins: [Ie], components: Me, children: e }) }), nt = ({
message: e,
showAvatar: r = !0,
assistantAvatar: s,
userAvatar: o
}) => {
const [n, l] = T(!1), p = async () => {
try {
await navigator.clipboard.writeText(e.content), l(!0), setTimeout(() => l(!1), 2e3);
} catch {
}
};
return /* @__PURE__ */ t(ne, { children: /* @__PURE__ */ t(
X.div,
{
"data-testid": `message-${e.role}`,
className: "w-full mx-auto max-w-3xl px-4 group/message",
initial: { y: 5, opacity: 0 },
animate: { y: 0, opacity: 1 },
"data-role": e.role,
children: /* @__PURE__ */ u(
"div",
{
className: V(
"flex gap-4 w-full group-data-[role=user]/message:ml-auto group-data-[role=user]/message:max-w-2xl",
{
"group-data-[role=user]/message:w-fit": !0
}
),
children: [
e.role === "assistant" && r && /* @__PURE__ */ t("div", { className: "size-8 flex items-center rounded-full justify-center ring-1 shrink-0 ring-border bg-background", children: /* @__PURE__ */ t("div", { className: "translate-y-px", children: s ? /* @__PURE__ */ t(
"img",
{
src: s,
alt: "Assistant",
className: "h-4 w-4 rounded-full object-cover"
}
) : /* @__PURE__ */ t(we, { size: 14, className: "text-muted-foreground" }) }) }),
/* @__PURE__ */ u("div", { className: "flex flex-col gap-4 w-full", children: [
e.attachments && e.attachments.length > 0 && /* @__PURE__ */ t(
"div",
{
"data-testid": "message-attachments",
className: "flex flex-row justify-end gap-2",
children: e.attachments.map((y, F) => /* @__PURE__ */ u(
"div",
{
className: "flex items-center gap-2 text-xs bg-muted rounded-lg px-3 py-2",
children: [
/* @__PURE__ */ t("span", { children: "📎" }),
/* @__PURE__ */ t("span", { className: "truncate max-w-[120px]", children: y.name }),
/* @__PURE__ */ u("span", { className: "text-xs opacity-60", children: [
"(",
Math.round(y.size / 1024),
"KB)"
] })
]
},
F
))
}
),
/* @__PURE__ */ t("div", { className: "flex flex-row gap-2 items-start", children: /* @__PURE__ */ t(
"div",
{
"data-testid": "message-content",
className: V("flex flex-col gap-4 message-text", {
"bg-muted px-3 py-2 rounded-xl": e.role === "user",
"": e.role === "assistant"
}),
children: /* @__PURE__ */ t(ot, { children: e.content })
}
) }),
e.role === "assistant" && /* @__PURE__ */ t("div", { className: "flex flex-row gap-2", children: /* @__PURE__ */ t(
"button",
{
className: "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground py-1 px-2 h-fit text-muted-foreground",
onClick: p,
title: n ? "Copied!" : "Copy message",
"data-state": "closed",
children: n ? /* @__PURE__ */ t(We, { className: "h-4 w-4 " }) : /* @__PURE__ */ t(Ke, { className: "h-4 w-4" })
}
) })
] })
]
}
)
}
) });
}, at = ({ showAvatar: e = !0, assistantAvatar: r }) => /* @__PURE__ */ t(
X.div,
{
"data-testid": "message-assistant-loading",
className: "w-full mx-auto max-w-3xl px-4 group/message",
initial: { y: 5, opacity: 0 },
animate: { y: 0, opacity: 1, transition: { delay: 0.5 } },
"data-role": "assistant",
children: /* @__PURE__ */ u("div", { className: "flex gap-4 w-full", children: [
e && /* @__PURE__ */ t("div", { className: "size-8 flex items-center rounded-full justify-center ring-1 shrink-0 ring-border bg-background", children: /* @__PURE__ */ t("div", { className: "translate-y-px", children: r ? /* @__PURE__ */ t(
"img",
{
src: r,
alt: "Assistant",
className: "h-4 w-4 rounded-full object-cover"
}
) : /* @__PURE__ */ t(we, { size: 14, className: " text-muted-foreground" }) }) }),
/* @__PURE__ */ t("div", { className: "flex flex-col gap-4 w-full mt-[0.7rem]", children: /* @__PURE__ */ t("div", { className: "flex flex-row gap-2 items-center", children: /* @__PURE__ */ u("div", { className: "flex space-x-1 items-center", children: [
/* @__PURE__ */ t("div", { className: "w-2 h-2 bg-muted-foreground rounded-full animate-pulse" }),
/* @__PURE__ */ t(
"div",
{
className: "w-2 h-2 bg-muted-foreground rounded-full animate-pulse",
style: { animationDelay: "0.2s" }
}
),
/* @__PURE__ */ t(
"div",
{
className: "w-2 h-2 bg-muted-foreground rounded-full animate-pulse",
style: { animationDelay: "0.4s" }
}
)
] }) }) })
] })
}
), it = ({ size: e = 14 }) => /* @__PURE__ */ t(
"svg",
{
width: e,
height: e,
viewBox: "0 0 24 24",
fill: "currentColor",
xmlns: "http://www.w3.org/2000/svg",
children: /* @__PURE__ */ t("rect", { x: "6", y: "6", width: "12", height: "12", rx: "1", ry: "1" })
}
), lt = ({
onSendMessage: e,
onUploadFile: r,
placeholder: s = "Type your message...",
disabled: o = !1,
allowFileUpload: n = !1,
allowedFileTypes: l,
maxFileSize: p = 15 * 1024 * 1024,
// 15MB
status: y = "ready",
onStop: F,
autoFocus: _ = !1,
initialMessage: g = "",
showResetButton: N = !1,
onReset: P
}) => {
const I = n ? l || ["image/png", "image/jpeg"] : [], [M, x] = T(g), [S, R] = T([]), [j, z] = T(!1), E = Y(null), B = Y(null), h = W(async () => {
!M.trim() && S.length === 0 || o || y !== "ready" || (e(M.trim(), S), x(""), R([]), B.current && (B.current.style.height = "auto"));
}, [M, S, o, y, e]), f = (i) => {
i.key === "Enter" && !i.shiftKey && !i.nativeEvent.isComposing && (i.preventDefault(), y !== "ready" || h());
}, v = async (i) => {
const w = Array.from(i.target.files || []);
if (!(!w.length || !r)) {
z(!0);
try {
for (const b of w) {
if (b.size > p)
throw new Error(
`File ${b.name} is too large. Maximum size is ${Math.round(
p / 1024 / 1024
)}MB`
);
if (!I.some((d) => d.startsWith(".") ? b.name.toLowerCase().endsWith(d.toLowerCase()) : b.type.match(d.replace("*", ".*"))))
throw new Error(`File type ${b.type} is not allowed`);
const O = await r(b);
R((d) => [...d, O]);
}
} catch {
} finally {
z(!1), E.current && (E.current.value = "");
}
}
}, c = (i) => {
R((w) => w.filter((b, C) => C !== i));
}, m = (i) => {
x(i.target.value);
const w = i.target;
w.style.height = "auto", w.style.height = Math.min(w.scrollHeight, 120) + "px";
};
return /* @__PURE__ */ u("div", { className: "relative w-full flex flex-col gap-4", children: [
/* @__PURE__ */ t(
"input",
{
type: "file",
className: "fixed -top-4 -left-4 size-0.5 opacity-0 pointer-events-none",
ref: E,
multiple: !0,
accept: I.join(","),
onChange: v,
tabIndex: -1
}
),
S.length > 0 && /* @__PURE__ */ t("div", { className: "flex flex-wrap gap-2 px-4", children: S.map((i, w) => /* @__PURE__ */ u(
"div",
{
className: "flex items-center gap-2 bg-muted rounded-lg px-3 py-2 text-sm",
children: [
/* @__PURE__ */ t("span", { children: "📎" }),
/* @__PURE__ */ t("span", { className: "truncate max-w-[120px]", children: i.name }),
/* @__PURE__ */ t(
"button",
{
onClick: () => c(w),
className: "text-muted-foreground hover:text-foreground",
children: /* @__PURE__ */ t(Ce, { className: "h-3 w-3" })
}
)
]
},
w
)) }),
/* @__PURE__ */ u("div", { className: "relative", children: [
/* @__PURE__ */ t(
"textarea",
{
"data-testid": "multimodal-input",
ref: B,
placeholder: s,
value: M,
onChange: m,
onKeyDown: f,
className: V(
"flex w-full border border-input px-3 py-2 ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 min-h-[24px] max-h-[calc(75dvh)] overflow-hidden resize-none rounded-2xl bg-muted pb-10 text-sm text-foreground input-text overflow-y-auto"
),
rows: 2,
autoFocus: _,
disabled: o
}
),
/* @__PURE__ */ u("div", { className: "absolute bottom-0 p-2 w-fit flex flex-row justify-start gap-1", children: [
N && P && /* @__PURE__ */ t(
"button",
{
"data-testid": "reset-button",
className: "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground py-1 px-2 h-fit text-muted-foreground rounded-md button-text",
disabled: y !== "ready" || o,
onClick: P,
title: "Reset conversation",
"data-state": "closed",
children: /* @__PURE__ */ t(ze, { size: 14 })
}
),
n && r && /* @__PURE__ */ t(
"button",
{
"data-testid": "attachments-button",
className: "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground py-1 px-2 h-fit text-muted-foreground rounded-md rounded-bl-lg button-text",
disabled: y !== "ready" || o,
onClick: () => {
var i;
return (i = E.current) == null ? void 0 : i.click();
},
title: "Attach file",
"data-state": "closed",
children: /* @__PURE__ */ t(Ve, { size: 14, className: "-rotate-45" })
}
)
] }),
/* @__PURE__ */ t("div", { className: "absolute bottom-0 right-0 p-2 w-fit flex flex-row justify-end", children: y === "submitted" || y === "streaming" ? /* @__PURE__ */ t(
"button",
{
"data-testid": "stop-button",
className: "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-primary/90 rounded-full p-1.5 h-fit border dark:border-zinc-600 button-text",
onClick: F,
title: "Stop generation",
"data-state": "closed",
children: /* @__PURE__ */ t(it, { size: 14 })
}
) : /* @__PURE__ */ t(
"button",
{
"data-testid": "send-button",
className: "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground hover:bg-primary/90 rounded-full p-1.5 h-fit border dark:border-zinc-600 button-text",
onClick: h,
disabled: o || !M.trim() && S.length === 0 || j,
title: "Send message",
"data-state": "closed",
children: /* @__PURE__ */ t(Je, { size: 14 })
}
) })
] }),
j && /* @__PURE__ */ t("div", { className: "px-4 text-sm text-muted-foreground", children: "Uploading files..." })
] });
}, ke = qe(
({
config: e,
theme: r,
displayMode: s = "embedded",
placeholder: o = "Type your message...",
title: n = "Chat Assistant",
subtitle: l,
avatar: p,
className: y,
style: F,
onMessage: _,
onError: g,
onFullscreenChange: N,
maxHeight: P = 500,
maxWidth: I = 400,
showHeader: M = !0,
showAvatar: x = !0,
allowFileUpload: S = !1,
allowedFileTypes: R,
maxFileSize: j = 15 * 1024 * 1024,
autoFocus: z = !1,
disabled: E = !1,
initialMessage: B = ""
}, h) => {
var Ne;
const f = Y(null), v = Y(null), c = Y(null), m = S ? R || ["image/png", "image/jpeg"] : [], [i, w] = T(!1), [b, C] = T(!1), O = () => C((a) => !a);
q(() => {
N == null || N(b);
}, [b, N]);
const {
messages: d,
isLoading: $,
error: K,
sendMessage: pe,
stopMessage: ae,
uploadFile: ie,
resetConversation: Q
} = et({
config: e,
onMessage: _,
onError: g,
enableStreaming: !0
}), [G, ee] = T(0), [te, le] = T(!0);
q(() => {
const a = c.current;
if (!a || !te) return;
const L = d.length > G;
($ || L) && requestAnimationFrame(() => {
a.scrollTop = a.scrollHeight;
}), ee(d.length);
}, [d, $, te]), q(() => {
const a = c.current;
if (!a) return;
const L = () => {
const { scrollTop: se, scrollHeight: oe, clientHeight: ye } = a, be = oe - se - ye < 50;
w(!be && d.length > 0);
};
return a.addEventListener("scroll", L, { passive: !0 }), () => a.removeEventListener("scroll", L);
}, [d.length]);
const ce = () => {
const a = c.current;
if (!a)
return {
scrollTop: 0,
scrollHeight: 0,
clientHeight: 0,
isAtTop: !0,
isAtBottom: !0,
isNearBottom: !0
};
const { scrollTop: L, scrollHeight: se, clientHeight: oe } = a, ye = L === 0, be = L + oe >= se, Ee = se - L - oe < 50;
return {
scrollTop: L,
scrollHeight: se,
clientHeight: oe,
isAtTop: ye,
isAtBottom: be,
isNearBottom: Ee
};
}, de = () => {
requestAnimationFrame(() => {
const a = c.current;
a && (a.scrollTop = 0);
});
}, re = () => {
requestAnimationFrame(() => {
const a = c.current;
a && (a.scrollTop = a.scrollHeight);
});
}, he = (a) => {
requestAnimationFrame(() => {
const L = c.current;
L && (L.scrollTop = a);
});
}, k = (a) => {
requestAnimationFrame(() => {
const L = c.current;
L && (L.scrollTop += a);
});
}, A = () => {
le(!0);
}, H = () => {
le(!1);
}, U = () => te;
Oe(h, () => ({
getScrollInfo: ce,
scrollToTop: de,
scrollToBottom: re,
scrollTo: he,
scrollBy: k,
enableAutoScroll: A,
disableAutoScroll: H,
isAutoScrollEnabled: U
}));
const D = (r == null ? void 0 : r.background) && (r.background.includes("9%") || r.background.includes("4.9%") || r.background.includes("3.9%"));
q(() => {
if (v.current) {
const a = v.current;
D ? a.classList.add("dark") : a.classList.remove("dark"), r && (r.primary && a.style.setProperty("--primary", r.primary), r.secondary && a.style.setProperty("--secondary", r.secondary), r.background && a.style.setProperty("--background", r.background), r.text && a.style.setProperty("--foreground", r.text), r.border && a.style.setProperty("--border", r.border), r.borderRadius && a.style.setProperty("--radius", r.borderRadius), r.fontFamily && (a.style.fontFamily = r.fontFamily));
}
}, [r, D]), q(() => {
const a = (L) => {
L.key === "Escape" && b && C(!1);
};
return document.addEventListener("keydown", a), () => {
document.removeEventListener("keydown", a);
};
}, [b]);
const J = {
...F,
...b ? {
zIndex: 999999,
width: "100vw",
height: "100vh",
maxHeight: "none",
maxWidth: "none",
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0
} : {
maxHeight: P,
maxWidth: I
}
}, ge = /* @__PURE__ */ u(
"div",
{
ref: v,
className: V(
"dify-chatbot dify-chatbot-container flex flex-col min-w-0 bg-background p-[2px]",
{
"h-full border border-border rounded-lg": !b && s === "embedded",
dark: D
},
y
),
style: J,
children: [
M && /* @__PURE__ */ u("div", { className: "flex items-center justify-between p-4 border-b bg-card", children: [
/* @__PURE__ */ u("div", { className: "flex items-center gap-3", children: [
x && p && /* @__PURE__ */ t(
"img",
{
src: p,
alt: "Assistant",
className: "h-8 w-8 rounded-full object-cover"
}
),
/* @__PURE__ */ u("div", { children: [
/* @__PURE__ */ t("h3", { className: "font-semibold text-card-foreground", children: n }),
l && /* @__PURE__ */ t("p", { className: "text-sm text-muted-foreground", children: l })
] })
] }),
/* @__PURE__ */ u("div", { className: "flex items-center gap-1", children: [
/* @__PURE__ */ t(
"button",
{
onClick: Q,
title: "Reset conversation",
className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground p-1 rounded-md",
children: /* @__PURE__ */ t(ze, { className: "h-4 w-4" })
}
),
/* @__PURE__ */ t(
"button",
{
onClick: O,
title: b ? "Exit fullscreen" : "Enter fullscreen",
className: "border border-input bg-background hover:bg-accent hover:text-accent-foreground p-1 rounded-md",
children: b ? /* @__PURE__ */ t(Xe, { className: "h-4 w-4" }) : /* @__PURE__ */ t(Ye, { className: "h-4 w-4" })
}
)
] })
] }),
/* @__PURE__ */ u(
"div",
{
ref: c,
className: "dify-chatbot-messages flex flex-col min-w-0 gap-6 flex-1 overflow-y-auto pt-4 relative",
style: {
overscrollBehavior: "contain",
touchAction: "pan-y"
},
children: [
d.length === 0 && /* @__PURE__ */ t("div", { className: "flex-1 flex items-center justify-center", children: /* @__PURE__ */ u("div", { className: "text-center text-muted-foreground", children: [
/* @__PURE__ */ t("div", { className: "mb-4", children: /* @__PURE__ */ t(
we,
{
size: 48,
className: "mx-auto text-muted-foreground/30"
}
) }),
/* @__PURE__ */ t("p", { className: "text-lg font-medium text-foreground", children: "How can I help you today?" }),
l && /* @__PURE__ */ t("p", { className: "text-sm mt-2 text-muted-foreground", children: l })
] }) }),
d.map((a) => /* @__PURE__ */ t(
nt,
{
message: a,
showAvatar: x,
assistantAvatar: p
},
a.id
)),
$ && d.length > 0 && ((Ne = d[d.length - 1]) == null ? void 0 : Ne.role) === "user" && /* @__PURE__ */ t(
at,
{
showAvatar: x,
assistantAvatar: p
}
),
K && /* @__PURE__ */ t("div", { className: "w-full mx-auto max-w-3xl px-4", children: /* @__PURE__ */ u("div", { className: "text-center text-destructive text-sm py-2 bg-destructive/10 rounded-md", children: [
"Error: ",
K.message
] }) }),
/* @__PURE__ */ t(
"div",
{
ref: f,
className: "shrink-0 min-w-[24px] min-h-[24px]"
}
)
]
}
),
/* @__PURE__ */ t(
"form",
{
className: "flex mx-auto px-4 bg-background pb-4 md:pb-6 gap-2 w-full md:max-w-3xl",
onSubmit: (a) => a.preventDefault(),
children: /* @__PURE__ */ u("div", { className: "relative w-full flex flex-col gap-4", children: [
i && /* @__PURE__ */ t("div", { className: "absolute left-1/2 bottom-28 -translate-x-1/2 z-50", children: /* @__PURE__ */ t(
"button",
{
onClick: re,
className: "inline-flex items-center justify-center border border-input bg-background hover:bg-accent hover:text-accent-foreground h-8 w-8 rounded-full",
children: /* @__PURE__ */ t(Ge, { className: "h-4 w-4" })
}
) }),
/* @__PURE__ */ t(
lt,
{
onSendMessage: pe,
onUploadFile: S ? ie : void 0,
placeholder: o,
disabled: E,
allowFileUpload: S,
allowedFileTypes: m,
maxFileSize: j,
autoFocus: z,
initialMessage: B,
status: $ ? "streaming" : "ready",
onStop: ae,
showResetButton: !M,
onReset: Q
}
)
] })
}
)
]
}
);
return b ? Ue(ge, document.body) : ge;
}
);
ke.displayName = "DifyChatbot";
const ct = {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline"
}, dt = {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10"
}, Le = De.forwardRef(
({ className: e, variant: r = "default", size: s = "default", ...o }, n) => /* @__PURE__ */ t(
"button",
{
className: V(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
ct[r],
dt[s],
e
),
ref: n,
...o
}
)
);
Le.displayName = "Button";
const kt = ({
config: e,
theme: r,
placeholder: s = "Type your message...",
title: o = "Chat Assistant",
subtitle: n,
avatar: l,
className: p,
style: y,
onMessage: F,
onError: _,
maxHeight: g = 500,
maxWidth: N = 400,
showHeader: P = !0,
showAvatar: I = !0,
allowFileUpload: M = !1,
allowedFileTypes: x,
maxFileSize: S = 15 * 1024 * 1024,
autoFocus: R = !1,
disabled: j = !1,
position: z = "bottom-right",
offset: E = { x: 0, y: 0 },
trigger: B,
defaultOpen: h = !1,
onOpenChange: f,
triggerIcon: v,
triggerText: c
}) => {
const [m, i] = T(h);
q(() => {
f == null || f(m);
}, [m, f]);
const w = () => {
i(!m);
}, b = () => "dify-chatbot dify-floating-chatbot ", C = () => {
const d = E.x, $ = E.y;
switch (z) {
case "bottom-right":
return {
bottom: 20 + $,
right: 20 + d,
zIndex: 999999999,
position: "fixed"
};
case "bottom-left":
return {
bottom: 20 + $,
left: 20 + d,
zIndex: 999999999,
position: "fixed"
};
case "top-right":
return {
top: 20 + $,
right: 20 + d,
zIndex: 999999999,
position: "fixed"
};
case "top-left":
return {
top: 20 + $,
left: 20 + d,
zIndex: 999999999,
position: "fixed"
};
default:
return {
bottom: 20,
right: 20,
zIndex: 999999999,
position: "fixed"
};
}
}, O = () => {
switch (z) {
case "bottom-right":
case "bottom-left":
return { y: 20, opacity: 0 };
case "top-right":
case "top-left":
return { y: -20, opacity: 0 };
default:
return { y: 20, opacity: 0 };
}
};
return /* @__PURE__ */ u("div", { className: b(), style: C(), children: [
/* @__PURE__ */ t(ne, { children: m && /* @__PURE__ */ t(
X.div,
{
initial: O(),
animate: { y: 0, opacity: 1 },
exit: O(),
transition: { duration: 0.2, ease: "easeOut" },
className: V("mb-4", z.includes("top") && "mb-0 mt-4"),
style: {
width: N,
height: g
},
children: /* @__PURE__ */ t(
ke,
{
config: e,
theme: r,
displayMode: "embedded",
placeholder: s,
title: o,
subtitle: n,
avatar: l,
className: p,
style: y,
onMessage: F,
onError: _,
maxHeight: g,
maxWidth: N,
showHeader: P,
showAvatar: I,
allowFileUpload: M,
allowedFileTypes: x,
maxFileSize: S,
autoFocus: R,
disabled: j,
preventExternalScroll: !0
}
)
}
) })