@gendive/chatllm 0.6.5 → 0.6.6

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.
@@ -1882,18 +1882,11 @@ var parseInlineElements = (text, key) => {
1882
1882
  if (match) {
1883
1883
  elements.push(
1884
1884
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1885
- "img",
1885
+ ImageWithCopyButton,
1886
1886
  {
1887
1887
  src: match[2],
1888
1888
  alt: match[1] || "",
1889
- className: "chatllm-image",
1890
- style: {
1891
- maxWidth: "100%",
1892
- borderRadius: "8px",
1893
- margin: "8px 0",
1894
- display: "block"
1895
- },
1896
- loading: "lazy"
1889
+ imageKey: `${key}-image-${index}`
1897
1890
  },
1898
1891
  `${key}-image-${index}`
1899
1892
  )
@@ -2066,6 +2059,162 @@ var CodeBlock = ({ language, code }) => {
2066
2059
  }
2067
2060
  );
2068
2061
  };
2062
+ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2063
+ const [isHovered, setIsHovered] = import_react5.default.useState(false);
2064
+ const [copyState, setCopyState] = import_react5.default.useState("idle");
2065
+ const handleCopyImage = async () => {
2066
+ setCopyState("copying");
2067
+ try {
2068
+ const response = await fetch(src);
2069
+ if (!response.ok) throw new Error("Failed to fetch image");
2070
+ const blob = await response.blob();
2071
+ if (navigator.clipboard && navigator.clipboard.write) {
2072
+ let imageBlob = blob;
2073
+ if (blob.type !== "image/png") {
2074
+ const img = new Image();
2075
+ img.crossOrigin = "anonymous";
2076
+ await new Promise((resolve, reject) => {
2077
+ img.onload = () => {
2078
+ const canvas = document.createElement("canvas");
2079
+ canvas.width = img.naturalWidth;
2080
+ canvas.height = img.naturalHeight;
2081
+ const ctx = canvas.getContext("2d");
2082
+ if (!ctx) {
2083
+ reject(new Error("Failed to get canvas context"));
2084
+ return;
2085
+ }
2086
+ ctx.drawImage(img, 0, 0);
2087
+ canvas.toBlob((pngBlob) => {
2088
+ if (pngBlob) {
2089
+ imageBlob = pngBlob;
2090
+ resolve();
2091
+ } else {
2092
+ reject(new Error("Failed to convert to PNG"));
2093
+ }
2094
+ }, "image/png");
2095
+ };
2096
+ img.onerror = () => reject(new Error("Failed to load image"));
2097
+ img.src = URL.createObjectURL(blob);
2098
+ });
2099
+ }
2100
+ await navigator.clipboard.write([
2101
+ new ClipboardItem({
2102
+ [imageBlob.type]: imageBlob
2103
+ })
2104
+ ]);
2105
+ setCopyState("copied");
2106
+ setTimeout(() => setCopyState("idle"), 2e3);
2107
+ } else {
2108
+ await navigator.clipboard.writeText(src);
2109
+ setCopyState("copied");
2110
+ setTimeout(() => setCopyState("idle"), 2e3);
2111
+ }
2112
+ } catch (error) {
2113
+ console.error("Failed to copy image:", error);
2114
+ setCopyState("error");
2115
+ setTimeout(() => setCopyState("idle"), 2e3);
2116
+ }
2117
+ };
2118
+ const getCopyButtonText = () => {
2119
+ switch (copyState) {
2120
+ case "copying":
2121
+ return "\uBCF5\uC0AC \uC911...";
2122
+ case "copied":
2123
+ return "\uBCF5\uC0AC\uB428!";
2124
+ case "error":
2125
+ return "\uBCF5\uC0AC \uC2E4\uD328";
2126
+ default:
2127
+ return "\uC774\uBBF8\uC9C0 \uBCF5\uC0AC";
2128
+ }
2129
+ };
2130
+ const getCopyButtonColor = () => {
2131
+ switch (copyState) {
2132
+ case "copied":
2133
+ return "var(--chatllm-success, #22c55e)";
2134
+ case "error":
2135
+ return "var(--chatllm-error, #ef4444)";
2136
+ default:
2137
+ return "var(--chatllm-text, #ffffff)";
2138
+ }
2139
+ };
2140
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2141
+ "div",
2142
+ {
2143
+ style: {
2144
+ position: "relative",
2145
+ display: "inline-block",
2146
+ margin: "8px 0"
2147
+ },
2148
+ onMouseEnter: () => setIsHovered(true),
2149
+ onMouseLeave: () => setIsHovered(false),
2150
+ children: [
2151
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2152
+ "img",
2153
+ {
2154
+ src,
2155
+ alt,
2156
+ className: "chatllm-image",
2157
+ style: {
2158
+ maxWidth: "100%",
2159
+ borderRadius: "8px",
2160
+ display: "block"
2161
+ },
2162
+ loading: "lazy"
2163
+ }
2164
+ ),
2165
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2166
+ "button",
2167
+ {
2168
+ onClick: handleCopyImage,
2169
+ disabled: copyState === "copying",
2170
+ style: {
2171
+ position: "absolute",
2172
+ top: "8px",
2173
+ right: "8px",
2174
+ display: "flex",
2175
+ alignItems: "center",
2176
+ gap: "6px",
2177
+ padding: "6px 12px",
2178
+ backgroundColor: "rgba(0, 0, 0, 0.7)",
2179
+ color: getCopyButtonColor(),
2180
+ border: "none",
2181
+ borderRadius: "6px",
2182
+ fontSize: "12px",
2183
+ fontWeight: 500,
2184
+ cursor: copyState === "copying" ? "wait" : "pointer",
2185
+ opacity: isHovered || copyState !== "idle" ? 1 : 0,
2186
+ transform: isHovered || copyState !== "idle" ? "translateY(0)" : "translateY(-4px)",
2187
+ transition: "opacity 0.2s ease, transform 0.2s ease",
2188
+ backdropFilter: "blur(4px)",
2189
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)"
2190
+ },
2191
+ children: [
2192
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2193
+ "svg",
2194
+ {
2195
+ width: "14",
2196
+ height: "14",
2197
+ viewBox: "0 0 24 24",
2198
+ fill: "none",
2199
+ stroke: "currentColor",
2200
+ strokeWidth: "2",
2201
+ strokeLinecap: "round",
2202
+ strokeLinejoin: "round",
2203
+ children: copyState === "copied" ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "20 6 9 17 4 12" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2204
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
2206
+ ] })
2207
+ }
2208
+ ),
2209
+ getCopyButtonText()
2210
+ ]
2211
+ }
2212
+ )
2213
+ ]
2214
+ },
2215
+ imageKey
2216
+ );
2217
+ };
2069
2218
  var ChoiceButtons = ({ choices, onChoiceClick }) => {
2070
2219
  const [hoveredIndex, setHoveredIndex] = import_react5.default.useState(null);
2071
2220
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(