@gendive/chatllm 0.6.8 → 0.6.9

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.
@@ -2065,97 +2065,80 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2065
2065
  const [isHovered, setIsHovered] = import_react5.default.useState(false);
2066
2066
  const [copyState, setCopyState] = import_react5.default.useState("idle");
2067
2067
  const imgRef = import_react5.default.useRef(null);
2068
- const handleCopyImage = async () => {
2069
- setCopyState("copying");
2070
- try {
2071
- if (!navigator.clipboard || !navigator.clipboard.write) {
2072
- await navigator.clipboard.writeText(src);
2073
- setCopyState("copied");
2074
- setTimeout(() => setCopyState("idle"), 2e3);
2075
- return;
2076
- }
2077
- const img = imgRef.current;
2078
- if (img && img.complete && img.naturalWidth > 0) {
2079
- try {
2080
- const canvas = document.createElement("canvas");
2081
- canvas.width = img.naturalWidth;
2082
- canvas.height = img.naturalHeight;
2083
- const ctx = canvas.getContext("2d");
2084
- if (ctx) {
2085
- ctx.drawImage(img, 0, 0);
2086
- const blob = await new Promise((resolve) => {
2087
- canvas.toBlob(resolve, "image/png");
2088
- });
2089
- if (blob) {
2090
- await navigator.clipboard.write([
2091
- new ClipboardItem({ "image/png": blob })
2092
- ]);
2093
- setCopyState("copied");
2094
- setTimeout(() => setCopyState("idle"), 2e3);
2095
- return;
2096
- }
2097
- }
2098
- } catch {
2099
- console.log("Canvas method failed (CORS), trying fetch...");
2068
+ const getImageBlob = async () => {
2069
+ const img = imgRef.current;
2070
+ if (img && img.complete && img.naturalWidth > 0) {
2071
+ try {
2072
+ const canvas = document.createElement("canvas");
2073
+ canvas.width = img.naturalWidth;
2074
+ canvas.height = img.naturalHeight;
2075
+ const ctx = canvas.getContext("2d");
2076
+ if (ctx) {
2077
+ ctx.drawImage(img, 0, 0);
2078
+ const blob = await new Promise((resolve) => {
2079
+ canvas.toBlob(resolve, "image/png");
2080
+ });
2081
+ if (blob) return blob;
2100
2082
  }
2083
+ } catch {
2084
+ console.log("Canvas method failed (CORS), trying fetch...");
2101
2085
  }
2086
+ }
2087
+ if (!src.startsWith("data:")) {
2102
2088
  try {
2103
2089
  const response = await fetch(src, { mode: "cors" });
2104
2090
  if (response.ok) {
2105
2091
  const blob = await response.blob();
2106
- if (blob.type === "image/png") {
2107
- await navigator.clipboard.write([
2108
- new ClipboardItem({ "image/png": blob })
2109
- ]);
2110
- setCopyState("copied");
2111
- setTimeout(() => setCopyState("idle"), 2e3);
2112
- return;
2113
- }
2092
+ if (blob.type === "image/png") return blob;
2114
2093
  const tempImg = new Image();
2115
2094
  const blobUrl = URL.createObjectURL(blob);
2116
- await new Promise((resolve, reject) => {
2117
- tempImg.onload = async () => {
2118
- try {
2119
- const canvas = document.createElement("canvas");
2120
- canvas.width = tempImg.naturalWidth;
2121
- canvas.height = tempImg.naturalHeight;
2122
- const ctx = canvas.getContext("2d");
2123
- if (!ctx) {
2124
- reject(new Error("Failed to get canvas context"));
2125
- return;
2126
- }
2095
+ return new Promise((resolve) => {
2096
+ tempImg.onload = () => {
2097
+ const canvas = document.createElement("canvas");
2098
+ canvas.width = tempImg.naturalWidth;
2099
+ canvas.height = tempImg.naturalHeight;
2100
+ const ctx = canvas.getContext("2d");
2101
+ if (ctx) {
2127
2102
  ctx.drawImage(tempImg, 0, 0);
2128
- const pngBlob = await new Promise((res) => {
2129
- canvas.toBlob(res, "image/png");
2130
- });
2103
+ canvas.toBlob((pngBlob) => {
2104
+ URL.revokeObjectURL(blobUrl);
2105
+ resolve(pngBlob);
2106
+ }, "image/png");
2107
+ } else {
2131
2108
  URL.revokeObjectURL(blobUrl);
2132
- if (pngBlob) {
2133
- await navigator.clipboard.write([
2134
- new ClipboardItem({ "image/png": pngBlob })
2135
- ]);
2136
- setCopyState("copied");
2137
- setTimeout(() => setCopyState("idle"), 2e3);
2138
- resolve();
2139
- } else {
2140
- reject(new Error("Failed to create PNG blob"));
2141
- }
2142
- } catch (err) {
2143
- reject(err);
2109
+ resolve(null);
2144
2110
  }
2145
2111
  };
2146
2112
  tempImg.onerror = () => {
2147
2113
  URL.revokeObjectURL(blobUrl);
2148
- reject(new Error("Failed to load image"));
2114
+ resolve(null);
2149
2115
  };
2150
2116
  tempImg.src = blobUrl;
2151
2117
  });
2152
- return;
2153
2118
  }
2154
2119
  } catch {
2155
- console.log("Fetch method failed, using URL fallback");
2120
+ console.log("Fetch method failed");
2121
+ }
2122
+ }
2123
+ return null;
2124
+ };
2125
+ const handleCopy = async () => {
2126
+ setCopyState("copying");
2127
+ try {
2128
+ if (!navigator.clipboard?.write) {
2129
+ await navigator.clipboard.writeText(src);
2130
+ setCopyState("copied");
2131
+ setTimeout(() => setCopyState("idle"), 2e3);
2132
+ return;
2133
+ }
2134
+ const blob = await getImageBlob();
2135
+ if (blob) {
2136
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
2137
+ setCopyState("copied");
2138
+ } else {
2139
+ await navigator.clipboard.writeText(src);
2140
+ setCopyState("copied");
2156
2141
  }
2157
- await navigator.clipboard.writeText(src);
2158
- setCopyState("copied");
2159
2142
  setTimeout(() => setCopyState("idle"), 2e3);
2160
2143
  } catch (error) {
2161
2144
  console.error("Failed to copy image:", error);
@@ -2163,27 +2146,34 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2163
2146
  setTimeout(() => setCopyState("idle"), 2e3);
2164
2147
  }
2165
2148
  };
2166
- const getCopyButtonText = () => {
2167
- switch (copyState) {
2168
- case "copying":
2169
- return "\uBCF5\uC0AC \uC911...";
2170
- case "copied":
2171
- return "\uBCF5\uC0AC\uB428!";
2172
- case "error":
2173
- return "\uBCF5\uC0AC \uC2E4\uD328";
2174
- default:
2175
- return "\uC774\uBBF8\uC9C0 \uBCF5\uC0AC";
2149
+ const handleDownload = async () => {
2150
+ try {
2151
+ const blob = await getImageBlob();
2152
+ const url = blob ? URL.createObjectURL(blob) : src;
2153
+ const link = document.createElement("a");
2154
+ link.href = url;
2155
+ link.download = alt || `image-${Date.now()}.png`;
2156
+ document.body.appendChild(link);
2157
+ link.click();
2158
+ document.body.removeChild(link);
2159
+ if (blob) URL.revokeObjectURL(url);
2160
+ } catch (error) {
2161
+ console.error("Failed to download image:", error);
2162
+ window.open(src, "_blank");
2176
2163
  }
2177
2164
  };
2178
- const getCopyButtonColor = () => {
2179
- switch (copyState) {
2180
- case "copied":
2181
- return "var(--chatllm-success, #22c55e)";
2182
- case "error":
2183
- return "var(--chatllm-error, #ef4444)";
2184
- default:
2185
- return "var(--chatllm-text, #ffffff)";
2186
- }
2165
+ const toolbarButtonStyle = {
2166
+ display: "flex",
2167
+ alignItems: "center",
2168
+ justifyContent: "center",
2169
+ width: "36px",
2170
+ height: "36px",
2171
+ backgroundColor: "transparent",
2172
+ border: "none",
2173
+ borderRadius: "8px",
2174
+ cursor: "pointer",
2175
+ color: "var(--chatllm-text-muted, #9ca3af)",
2176
+ transition: "background-color 0.2s, color 0.2s"
2187
2177
  };
2188
2178
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2189
2179
  "div",
@@ -2213,50 +2203,71 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2213
2203
  }
2214
2204
  ),
2215
2205
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
2216
- "button",
2206
+ "div",
2217
2207
  {
2218
- onClick: handleCopyImage,
2219
- disabled: copyState === "copying",
2220
2208
  style: {
2221
2209
  position: "absolute",
2222
- top: "8px",
2223
- right: "8px",
2210
+ bottom: "0",
2211
+ left: "0",
2212
+ right: "0",
2224
2213
  display: "flex",
2225
2214
  alignItems: "center",
2226
- gap: "6px",
2227
- padding: "6px 12px",
2228
- backgroundColor: "rgba(0, 0, 0, 0.7)",
2229
- color: getCopyButtonColor(),
2230
- border: "none",
2231
- borderRadius: "6px",
2232
- fontSize: "12px",
2233
- fontWeight: 500,
2234
- cursor: copyState === "copying" ? "wait" : "pointer",
2235
- opacity: isHovered || copyState !== "idle" ? 1 : 0,
2236
- transform: isHovered || copyState !== "idle" ? "translateY(0)" : "translateY(-4px)",
2215
+ gap: "4px",
2216
+ padding: "8px 12px",
2217
+ backgroundColor: "var(--chatllm-bg, #ffffff)",
2218
+ borderTop: "1px solid var(--chatllm-border-light, #e5e7eb)",
2219
+ borderRadius: "0 0 8px 8px",
2220
+ opacity: isHovered ? 1 : 0,
2221
+ transform: isHovered ? "translateY(0)" : "translateY(8px)",
2237
2222
  transition: "opacity 0.2s ease, transform 0.2s ease",
2238
- backdropFilter: "blur(4px)",
2239
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.2)"
2223
+ pointerEvents: isHovered ? "auto" : "none"
2240
2224
  },
2241
2225
  children: [
2242
2226
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2243
- "svg",
2227
+ "button",
2244
2228
  {
2245
- width: "14",
2246
- height: "14",
2247
- viewBox: "0 0 24 24",
2248
- fill: "none",
2249
- stroke: "currentColor",
2250
- strokeWidth: "2",
2251
- strokeLinecap: "round",
2252
- strokeLinejoin: "round",
2253
- 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: [
2229
+ onClick: handleCopy,
2230
+ disabled: copyState === "copying",
2231
+ style: {
2232
+ ...toolbarButtonStyle,
2233
+ color: copyState === "copied" ? "var(--chatllm-success, #22c55e)" : copyState === "error" ? "var(--chatllm-error, #ef4444)" : "var(--chatllm-text-muted, #9ca3af)"
2234
+ },
2235
+ title: copyState === "copied" ? "\uBCF5\uC0AC\uB428!" : copyState === "error" ? "\uBCF5\uC0AC \uC2E4\uD328" : "\uBCF5\uC0AC\uD558\uAE30",
2236
+ onMouseOver: (e) => {
2237
+ e.currentTarget.style.backgroundColor = "var(--chatllm-bg-hover, #f3f4f6)";
2238
+ e.currentTarget.style.color = "var(--chatllm-text, #1f2937)";
2239
+ },
2240
+ onMouseOut: (e) => {
2241
+ e.currentTarget.style.backgroundColor = "transparent";
2242
+ e.currentTarget.style.color = copyState === "copied" ? "var(--chatllm-success, #22c55e)" : "var(--chatllm-text-muted, #9ca3af)";
2243
+ },
2244
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", 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: [
2254
2245
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
2255
2246
  /* @__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" })
2256
- ] })
2247
+ ] }) })
2257
2248
  }
2258
2249
  ),
2259
- getCopyButtonText()
2250
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2251
+ "button",
2252
+ {
2253
+ onClick: handleDownload,
2254
+ style: toolbarButtonStyle,
2255
+ title: "\uB2E4\uC6B4\uB85C\uB4DC",
2256
+ onMouseOver: (e) => {
2257
+ e.currentTarget.style.backgroundColor = "var(--chatllm-bg-hover, #f3f4f6)";
2258
+ e.currentTarget.style.color = "var(--chatllm-text, #1f2937)";
2259
+ },
2260
+ onMouseOut: (e) => {
2261
+ e.currentTarget.style.backgroundColor = "transparent";
2262
+ e.currentTarget.style.color = "var(--chatllm-text-muted, #9ca3af)";
2263
+ },
2264
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2265
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
2266
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("polyline", { points: "7 10 12 15 17 10" }),
2267
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
2268
+ ] })
2269
+ }
2270
+ )
2260
2271
  ]
2261
2272
  }
2262
2273
  )