@gendive/chatllm 0.6.6 → 0.6.7

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.
@@ -273,6 +273,10 @@ interface HeaderProps {
273
273
  onSettingsOpen: () => void;
274
274
  onSidebarToggle: () => void;
275
275
  sidebarOpen: boolean;
276
+ /** 모델 선택기 표시 여부 */
277
+ showModelSelector?: boolean;
278
+ /** 설정 버튼 표시 여부 */
279
+ showSettings?: boolean;
276
280
  }
277
281
  interface EmptyStateProps {
278
282
  greeting: string;
@@ -273,6 +273,10 @@ interface HeaderProps {
273
273
  onSettingsOpen: () => void;
274
274
  onSidebarToggle: () => void;
275
275
  sidebarOpen: boolean;
276
+ /** 모델 선택기 표시 여부 */
277
+ showModelSelector?: boolean;
278
+ /** 설정 버튼 표시 여부 */
279
+ showSettings?: boolean;
276
280
  }
277
281
  interface EmptyStateProps {
278
282
  greeting: string;
@@ -1073,7 +1073,9 @@ var ChatHeader = ({
1073
1073
  onModelChange,
1074
1074
  onSettingsOpen,
1075
1075
  onSidebarToggle,
1076
- sidebarOpen
1076
+ sidebarOpen,
1077
+ showModelSelector = true,
1078
+ showSettings = true
1077
1079
  }) => {
1078
1080
  const [modelDropdownOpen, setModelDropdownOpen] = (0, import_react2.useState)(false);
1079
1081
  const currentModel = models.find((m) => m.id === model);
@@ -1130,7 +1132,7 @@ var ChatHeader = ({
1130
1132
  )
1131
1133
  ] }),
1132
1134
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1133
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative" }, children: [
1135
+ showModelSelector && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { position: "relative" }, children: [
1134
1136
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1135
1137
  "button",
1136
1138
  {
@@ -1297,7 +1299,7 @@ var ChatHeader = ({
1297
1299
  )
1298
1300
  ] })
1299
1301
  ] }),
1300
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1302
+ showSettings && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1301
1303
  "button",
1302
1304
  {
1303
1305
  onClick: onSettingsOpen,
@@ -2062,53 +2064,99 @@ var CodeBlock = ({ language, code }) => {
2062
2064
  var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2063
2065
  const [isHovered, setIsHovered] = import_react5.default.useState(false);
2064
2066
  const [copyState, setCopyState] = import_react5.default.useState("idle");
2067
+ const imgRef = import_react5.default.useRef(null);
2065
2068
  const handleCopyImage = async () => {
2066
2069
  setCopyState("copying");
2067
2070
  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";
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...");
2100
+ }
2101
+ }
2102
+ try {
2103
+ const response = await fetch(src, { mode: "cors" });
2104
+ if (response.ok) {
2105
+ 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
+ }
2114
+ const tempImg = new Image();
2115
+ const blobUrl = URL.createObjectURL(blob);
2076
2116
  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) => {
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
+ }
2127
+ ctx.drawImage(tempImg, 0, 0);
2128
+ const pngBlob = await new Promise((res) => {
2129
+ canvas.toBlob(res, "image/png");
2130
+ });
2131
+ URL.revokeObjectURL(blobUrl);
2088
2132
  if (pngBlob) {
2089
- imageBlob = pngBlob;
2133
+ await navigator.clipboard.write([
2134
+ new ClipboardItem({ "image/png": pngBlob })
2135
+ ]);
2136
+ setCopyState("copied");
2137
+ setTimeout(() => setCopyState("idle"), 2e3);
2090
2138
  resolve();
2091
2139
  } else {
2092
- reject(new Error("Failed to convert to PNG"));
2140
+ reject(new Error("Failed to create PNG blob"));
2093
2141
  }
2094
- }, "image/png");
2142
+ } catch (err) {
2143
+ reject(err);
2144
+ }
2145
+ };
2146
+ tempImg.onerror = () => {
2147
+ URL.revokeObjectURL(blobUrl);
2148
+ reject(new Error("Failed to load image"));
2095
2149
  };
2096
- img.onerror = () => reject(new Error("Failed to load image"));
2097
- img.src = URL.createObjectURL(blob);
2150
+ tempImg.src = blobUrl;
2098
2151
  });
2152
+ return;
2099
2153
  }
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);
2154
+ } catch {
2155
+ console.log("Fetch method failed, using URL fallback");
2111
2156
  }
2157
+ await navigator.clipboard.writeText(src);
2158
+ setCopyState("copied");
2159
+ setTimeout(() => setCopyState("idle"), 2e3);
2112
2160
  } catch (error) {
2113
2161
  console.error("Failed to copy image:", error);
2114
2162
  setCopyState("error");
@@ -2151,9 +2199,11 @@ var ImageWithCopyButton = ({ src, alt, imageKey }) => {
2151
2199
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2152
2200
  "img",
2153
2201
  {
2202
+ ref: imgRef,
2154
2203
  src,
2155
2204
  alt,
2156
2205
  className: "chatllm-image",
2206
+ crossOrigin: "anonymous",
2157
2207
  style: {
2158
2208
  maxWidth: "100%",
2159
2209
  borderRadius: "8px",
@@ -4521,7 +4571,9 @@ var ChatUI = ({
4521
4571
  onModelChange: setModel,
4522
4572
  onSettingsOpen: openSettings,
4523
4573
  onSidebarToggle: toggleSidebar,
4524
- sidebarOpen
4574
+ sidebarOpen,
4575
+ showModelSelector,
4576
+ showSettings
4525
4577
  }
4526
4578
  ),
4527
4579
  messages.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(