@blank-utils/llm 0.2.3 → 0.2.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.
@@ -56542,10 +56542,10 @@ ${fake_token_around_image}` + `${global_img_token}` + image_token.repeat(image_s
56542
56542
  import {
56543
56543
  createContext,
56544
56544
  useContext,
56545
- useState as useState2,
56545
+ useState as useState3,
56546
56546
  useCallback as useCallback2,
56547
56547
  useEffect as useEffect3,
56548
- useMemo,
56548
+ useMemo as useMemo2,
56549
56549
  useRef as useRef3
56550
56550
  } from "react";
56551
56551
 
@@ -57065,15 +57065,15 @@ async function createLLM(config = {}) {
57065
57065
  }
57066
57066
 
57067
57067
  // src/react/components.tsx
57068
- import { useRef as useRef2, useEffect as useEffect2, useState } from "react";
57068
+ import { useRef as useRef2, useEffect as useEffect2, useState as useState2, useMemo } from "react";
57069
57069
 
57070
57070
  // src/react/chat-input.tsx
57071
- import { useRef, useEffect, useCallback } from "react";
57072
- import { jsxDEV } from "react/jsx-dev-runtime";
57071
+ import { useRef, useEffect, useCallback, useState } from "react";
57072
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
57073
57073
  function SendIcon() {
57074
57074
  return /* @__PURE__ */ jsxDEV("svg", {
57075
- width: "16",
57076
- height: "16",
57075
+ width: "14",
57076
+ height: "14",
57077
57077
  viewBox: "0 0 24 24",
57078
57078
  fill: "none",
57079
57079
  stroke: "currentColor",
@@ -57081,30 +57081,88 @@ function SendIcon() {
57081
57081
  strokeLinecap: "round",
57082
57082
  strokeLinejoin: "round",
57083
57083
  children: [
57084
- /* @__PURE__ */ jsxDEV("path", {
57085
- d: "M22 2L11 13"
57084
+ /* @__PURE__ */ jsxDEV("line", {
57085
+ x1: "5",
57086
+ y1: "12",
57087
+ x2: "19",
57088
+ y2: "12"
57086
57089
  }, undefined, false, undefined, this),
57087
- /* @__PURE__ */ jsxDEV("path", {
57088
- d: "M22 2L15 22L11 13L2 9L22 2Z"
57090
+ /* @__PURE__ */ jsxDEV("polyline", {
57091
+ points: "12 5 19 12 12 19"
57089
57092
  }, undefined, false, undefined, this)
57090
57093
  ]
57091
57094
  }, undefined, true, undefined, this);
57092
57095
  }
57093
57096
  function StopIcon() {
57094
57097
  return /* @__PURE__ */ jsxDEV("svg", {
57095
- width: "16",
57096
- height: "16",
57098
+ width: "14",
57099
+ height: "14",
57097
57100
  viewBox: "0 0 24 24",
57098
57101
  fill: "currentColor",
57099
57102
  children: /* @__PURE__ */ jsxDEV("rect", {
57100
- x: "4",
57101
- y: "4",
57102
- width: "16",
57103
- height: "16",
57104
- rx: "2"
57103
+ x: "6",
57104
+ y: "6",
57105
+ width: "12",
57106
+ height: "12"
57105
57107
  }, undefined, false, undefined, this)
57106
57108
  }, undefined, false, undefined, this);
57107
57109
  }
57110
+ function ImageIcon() {
57111
+ return /* @__PURE__ */ jsxDEV("svg", {
57112
+ width: "14",
57113
+ height: "14",
57114
+ viewBox: "0 0 24 24",
57115
+ fill: "none",
57116
+ stroke: "currentColor",
57117
+ strokeWidth: "2",
57118
+ strokeLinecap: "round",
57119
+ strokeLinejoin: "round",
57120
+ children: [
57121
+ /* @__PURE__ */ jsxDEV("rect", {
57122
+ x: "3",
57123
+ y: "3",
57124
+ width: "18",
57125
+ height: "18",
57126
+ rx: "0",
57127
+ ry: "0"
57128
+ }, undefined, false, undefined, this),
57129
+ /* @__PURE__ */ jsxDEV("circle", {
57130
+ cx: "8.5",
57131
+ cy: "8.5",
57132
+ r: "1.5"
57133
+ }, undefined, false, undefined, this),
57134
+ /* @__PURE__ */ jsxDEV("polyline", {
57135
+ points: "21 15 16 10 5 21"
57136
+ }, undefined, false, undefined, this)
57137
+ ]
57138
+ }, undefined, true, undefined, this);
57139
+ }
57140
+ function XIcon() {
57141
+ return /* @__PURE__ */ jsxDEV("svg", {
57142
+ width: "10",
57143
+ height: "10",
57144
+ viewBox: "0 0 24 24",
57145
+ fill: "none",
57146
+ stroke: "currentColor",
57147
+ strokeWidth: "3",
57148
+ strokeLinecap: "round",
57149
+ strokeLinejoin: "round",
57150
+ children: [
57151
+ /* @__PURE__ */ jsxDEV("line", {
57152
+ x1: "18",
57153
+ y1: "6",
57154
+ x2: "6",
57155
+ y2: "18"
57156
+ }, undefined, false, undefined, this),
57157
+ /* @__PURE__ */ jsxDEV("line", {
57158
+ x1: "6",
57159
+ y1: "6",
57160
+ x2: "18",
57161
+ y2: "18"
57162
+ }, undefined, false, undefined, this)
57163
+ ]
57164
+ }, undefined, true, undefined, this);
57165
+ }
57108
57166
  var STYLE_ID = "__llm-chat-input-styles";
57109
57167
  function injectStyles(theme) {
57110
57168
  if (typeof document === "undefined")
@@ -57112,82 +57170,192 @@ function injectStyles(theme) {
57112
57170
  const existing = document.getElementById(STYLE_ID);
57113
57171
  if (existing)
57114
57172
  existing.remove();
57115
- const isDark = theme === "dark";
57173
+ const d = theme === "dark";
57174
+ const border = d ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.12)";
57175
+ const borderFocus = d ? "rgba(255,255,255,0.35)" : "rgba(0,0,0,0.35)";
57176
+ const bg2 = d ? "#000000" : "#ffffff";
57177
+ const text = d ? "#ffffff" : "#000000";
57178
+ const textMuted = d ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.3)";
57179
+ const textSecondary = d ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
57180
+ const monoFont = `ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace`;
57181
+ const sansFont = `-apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", Roboto, Helvetica, Arial, sans-serif`;
57116
57182
  const css = `
57117
57183
  .llm-ci {
57118
57184
  display: flex;
57119
57185
  flex-direction: column;
57120
- border-radius: 16px;
57121
- border: 1px solid ${isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.12)"};
57122
- background: ${isDark ? "rgba(17,17,17,0.95)" : "rgba(255,255,255,0.98)"};
57123
- padding: 8px;
57124
- transition: border-color 0.2s, box-shadow 0.2s;
57186
+ border: 1px solid ${border};
57187
+ background: ${bg2};
57188
+ padding: 12px;
57189
+ transition: border-color 0.15s;
57125
57190
  position: relative;
57126
57191
  }
57127
57192
  .llm-ci:focus-within {
57128
- border-color: ${isDark ? "rgba(56,189,248,0.4)" : "rgba(59,130,246,0.4)"};
57129
- box-shadow: 0 0 0 2px ${isDark ? "rgba(56,189,248,0.1)" : "rgba(59,130,246,0.1)"};
57193
+ border-color: ${borderFocus};
57194
+ }
57195
+
57196
+ /* Attached Images */
57197
+ .llm-ci-images {
57198
+ display: flex;
57199
+ flex-wrap: wrap;
57200
+ gap: 6px;
57201
+ margin-bottom: 8px;
57202
+ }
57203
+ .llm-ci-image-preview {
57204
+ position: relative;
57205
+ width: 52px;
57206
+ height: 52px;
57207
+ overflow: hidden;
57208
+ border: 1px solid ${border};
57209
+ }
57210
+ .llm-ci-image-preview img {
57211
+ width: 100%;
57212
+ height: 100%;
57213
+ object-fit: cover;
57214
+ }
57215
+ .llm-ci-image-remove {
57216
+ position: absolute;
57217
+ top: 2px;
57218
+ right: 2px;
57219
+ width: 16px;
57220
+ height: 16px;
57221
+ background: ${d ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.8)"};
57222
+ color: ${text};
57223
+ border: none;
57224
+ display: flex;
57225
+ align-items: center;
57226
+ justify-content: center;
57227
+ cursor: pointer;
57228
+ opacity: 0;
57229
+ transition: opacity 0.1s;
57130
57230
  }
57231
+ .llm-ci-image-preview:hover .llm-ci-image-remove {
57232
+ opacity: 1;
57233
+ }
57234
+
57235
+ /* Textarea */
57131
57236
  .llm-ci textarea {
57132
57237
  width: 100%;
57133
- min-height: 40px;
57134
- max-height: 160px;
57238
+ min-height: 24px;
57239
+ max-height: 200px;
57135
57240
  resize: none;
57136
57241
  border: none;
57137
57242
  outline: none;
57138
57243
  background: transparent;
57139
- color: ${isDark ? "#e5e5e5" : "#1a1a1a"};
57244
+ color: ${text};
57140
57245
  font-size: 14px;
57141
57246
  line-height: 1.5;
57142
- padding: 8px 12px;
57143
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
57247
+ padding: 0;
57248
+ margin: 0;
57249
+ font-family: ${sansFont};
57144
57250
  scrollbar-width: thin;
57145
57251
  }
57146
57252
  .llm-ci textarea::placeholder {
57147
- color: ${isDark ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.35)"};
57253
+ color: ${textMuted};
57148
57254
  }
57255
+
57256
+ /* Toolbar */
57149
57257
  .llm-ci-toolbar {
57150
57258
  display: flex;
57151
57259
  align-items: center;
57152
57260
  justify-content: space-between;
57153
- padding: 4px 4px 0;
57261
+ margin-top: 8px;
57262
+ min-height: 28px;
57154
57263
  }
57155
- .llm-ci-actions {
57264
+ .llm-ci-left-actions {
57156
57265
  display: flex;
57157
57266
  align-items: center;
57158
57267
  gap: 2px;
57159
57268
  }
57160
- .llm-ci-send {
57269
+ .llm-ci-right-actions {
57270
+ display: flex;
57271
+ align-items: center;
57272
+ gap: 8px;
57273
+ }
57274
+
57275
+ /* Icon Buttons */
57276
+ .llm-ci-btn-icon {
57161
57277
  display: flex;
57162
57278
  align-items: center;
57163
57279
  justify-content: center;
57164
- width: 32px;
57165
- height: 32px;
57166
- border-radius: 50%;
57280
+ width: 28px;
57281
+ height: 28px;
57167
57282
  border: none;
57283
+ background: transparent;
57284
+ color: ${textSecondary};
57285
+ cursor: pointer;
57286
+ transition: color 0.1s;
57287
+ }
57288
+ .llm-ci-btn-icon:hover {
57289
+ color: ${text};
57290
+ }
57291
+
57292
+ /* Send / Stop — flat, monospace, uppercase */
57293
+ .llm-ci-send {
57294
+ display: flex;
57295
+ align-items: center;
57296
+ justify-content: center;
57297
+ height: 28px;
57298
+ padding: 0 12px;
57299
+ border: 1px solid ${border};
57300
+ font-size: 10px;
57301
+ font-weight: 400;
57302
+ font-family: ${monoFont};
57303
+ text-transform: uppercase;
57304
+ letter-spacing: 0.08em;
57168
57305
  cursor: pointer;
57169
- transition: all 0.15s ease;
57170
- flex-shrink: 0;
57306
+ transition: all 0.1s;
57307
+ gap: 6px;
57308
+ background: transparent;
57309
+ color: ${textSecondary};
57171
57310
  }
57172
57311
  .llm-ci-send--active {
57173
- background: ${isDark ? "#38bdf8" : "#3b82f6"};
57174
- color: white;
57312
+ background: ${text};
57313
+ color: ${bg2};
57314
+ border-color: ${text};
57175
57315
  }
57176
57316
  .llm-ci-send--active:hover {
57177
- background: ${isDark ? "#0ea5e9" : "#2563eb"};
57178
- transform: scale(1.05);
57317
+ opacity: 0.8;
57179
57318
  }
57180
57319
  .llm-ci-send--disabled {
57181
- background: ${isDark ? "rgba(255,255,255,0.05)" : "rgba(0,0,0,0.05)"};
57182
- color: ${isDark ? "rgba(255,255,255,0.2)" : "rgba(0,0,0,0.2)"};
57320
+ color: ${textMuted};
57321
+ border-color: ${d ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)"};
57183
57322
  cursor: not-allowed;
57184
57323
  }
57185
57324
  .llm-ci-send--stop {
57186
- background: ${isDark ? "rgba(239,68,68,0.15)" : "rgba(239,68,68,0.1)"};
57187
- color: ${isDark ? "#f87171" : "#ef4444"};
57325
+ background: transparent;
57326
+ border-color: ${border};
57327
+ color: ${textSecondary};
57188
57328
  }
57189
57329
  .llm-ci-send--stop:hover {
57190
- background: ${isDark ? "rgba(239,68,68,0.25)" : "rgba(239,68,68,0.2)"};
57330
+ border-color: ${borderFocus};
57331
+ color: ${text};
57332
+ }
57333
+
57334
+ /* Model Selector Slot */
57335
+ .llm-ci-model-selector {
57336
+ margin-right: auto;
57337
+ }
57338
+
57339
+ /* Hidden file input */
57340
+ .llm-ci-file-input {
57341
+ display: none;
57342
+ }
57343
+
57344
+ /* Drag Overlay */
57345
+ .llm-ci-drag-overlay {
57346
+ position: absolute;
57347
+ inset: 0;
57348
+ background: ${d ? "rgba(0,0,0,0.9)" : "rgba(255,255,255,0.9)"};
57349
+ display: flex;
57350
+ align-items: center;
57351
+ justify-content: center;
57352
+ z-index: 10;
57353
+ border: 1px dashed ${textSecondary};
57354
+ color: ${textSecondary};
57355
+ font-size: 11px;
57356
+ font-family: ${monoFont};
57357
+ text-transform: uppercase;
57358
+ letter-spacing: 0.08em;
57191
57359
  }
57192
57360
  `;
57193
57361
  const style = document.createElement("style");
@@ -57203,12 +57371,18 @@ function ChatInput({
57203
57371
  disabled = false,
57204
57372
  isGenerating = false,
57205
57373
  placeholder = "Type a message...",
57206
- maxRows = 5,
57374
+ maxRows = 8,
57207
57375
  actions,
57208
57376
  theme = "dark",
57209
- className
57377
+ className,
57378
+ images = [],
57379
+ onImageAdd,
57380
+ onImageRemove,
57381
+ modelSelector
57210
57382
  }) {
57211
57383
  const textareaRef = useRef(null);
57384
+ const fileInputRef = useRef(null);
57385
+ const [isDragging, setIsDragging] = useState(false);
57212
57386
  useEffect(() => {
57213
57387
  injectStyles(theme);
57214
57388
  }, [theme]);
@@ -57218,19 +57392,68 @@ function ChatInput({
57218
57392
  return;
57219
57393
  textarea.style.height = "auto";
57220
57394
  const lineHeight = 21;
57221
- const maxHeight = lineHeight * maxRows + 16;
57395
+ const maxHeight = lineHeight * maxRows + 24;
57222
57396
  const newHeight = Math.min(textarea.scrollHeight, maxHeight);
57223
57397
  textarea.style.height = `${newHeight}px`;
57224
57398
  }, [value, maxRows]);
57225
57399
  const handleKeyDown = useCallback((e) => {
57226
57400
  if (e.key === "Enter" && !e.shiftKey) {
57227
57401
  e.preventDefault();
57228
- if (!disabled && value.trim()) {
57402
+ if (!disabled && (value.trim() || images.length > 0)) {
57229
57403
  onSend();
57230
57404
  }
57231
57405
  }
57232
- }, [disabled, value, onSend]);
57233
- const hasValue = value.trim().length > 0;
57406
+ }, [disabled, value, images.length, onSend]);
57407
+ const processFile = useCallback((file) => {
57408
+ if (!file.type.startsWith("image/"))
57409
+ return;
57410
+ const reader = new FileReader;
57411
+ reader.onload = (e) => {
57412
+ if (e.target?.result && typeof e.target.result === "string") {
57413
+ const id2 = Math.random().toString(36).substring(7);
57414
+ onImageAdd?.({
57415
+ id: id2,
57416
+ dataUrl: e.target.result,
57417
+ file,
57418
+ name: file.name
57419
+ });
57420
+ }
57421
+ };
57422
+ reader.readAsDataURL(file);
57423
+ }, [onImageAdd]);
57424
+ const handlePaste = useCallback((e) => {
57425
+ const items = e.clipboardData.items;
57426
+ for (let i = 0;i < items.length; i++) {
57427
+ const item = items[i];
57428
+ if (item && item.type.indexOf("image") !== -1) {
57429
+ const file = item.getAsFile();
57430
+ if (file)
57431
+ processFile(file);
57432
+ }
57433
+ }
57434
+ }, [processFile]);
57435
+ const handleDrop = useCallback((e) => {
57436
+ e.preventDefault();
57437
+ setIsDragging(false);
57438
+ const files = e.dataTransfer.files;
57439
+ for (let i = 0;i < files.length; i++) {
57440
+ const file = files.item(i);
57441
+ if (file)
57442
+ processFile(file);
57443
+ }
57444
+ }, [processFile]);
57445
+ const handleFileSelect = useCallback((e) => {
57446
+ const files = e.target.files;
57447
+ if (!files)
57448
+ return;
57449
+ for (let i = 0;i < files.length; i++) {
57450
+ const file = files.item(i);
57451
+ if (file)
57452
+ processFile(file);
57453
+ }
57454
+ e.target.value = "";
57455
+ }, [processFile]);
57456
+ const hasValue = value.trim().length > 0 || images.length > 0;
57234
57457
  const canSend = hasValue && !disabled && !isGenerating;
57235
57458
  let sendClass = "llm-ci-send";
57236
57459
  if (isGenerating) {
@@ -57242,12 +57465,49 @@ function ChatInput({
57242
57465
  }
57243
57466
  return /* @__PURE__ */ jsxDEV("div", {
57244
57467
  className: `llm-ci${className ? ` ${className}` : ""}`,
57468
+ onDragOver: (e) => {
57469
+ e.preventDefault();
57470
+ setIsDragging(true);
57471
+ },
57472
+ onDragLeave: () => setIsDragging(false),
57473
+ onDrop: handleDrop,
57245
57474
  children: [
57475
+ isDragging && /* @__PURE__ */ jsxDEV("div", {
57476
+ className: "llm-ci-drag-overlay",
57477
+ children: "[Drop image to attach]"
57478
+ }, undefined, false, undefined, this),
57479
+ /* @__PURE__ */ jsxDEV("input", {
57480
+ ref: fileInputRef,
57481
+ type: "file",
57482
+ accept: "image/*",
57483
+ multiple: true,
57484
+ className: "llm-ci-file-input",
57485
+ onChange: handleFileSelect
57486
+ }, undefined, false, undefined, this),
57487
+ images.length > 0 && /* @__PURE__ */ jsxDEV("div", {
57488
+ className: "llm-ci-images",
57489
+ children: images.map((img) => /* @__PURE__ */ jsxDEV("div", {
57490
+ className: "llm-ci-image-preview",
57491
+ children: [
57492
+ /* @__PURE__ */ jsxDEV("img", {
57493
+ src: img.dataUrl,
57494
+ alt: "attachment"
57495
+ }, undefined, false, undefined, this),
57496
+ /* @__PURE__ */ jsxDEV("button", {
57497
+ type: "button",
57498
+ className: "llm-ci-image-remove",
57499
+ onClick: () => onImageRemove?.(img.id),
57500
+ children: /* @__PURE__ */ jsxDEV(XIcon, {}, undefined, false, undefined, this)
57501
+ }, undefined, false, undefined, this)
57502
+ ]
57503
+ }, img.id, true, undefined, this))
57504
+ }, undefined, false, undefined, this),
57246
57505
  /* @__PURE__ */ jsxDEV("textarea", {
57247
57506
  ref: textareaRef,
57248
57507
  value,
57249
57508
  onChange: (e) => onChange(e.target.value),
57250
57509
  onKeyDown: handleKeyDown,
57510
+ onPaste: handlePaste,
57251
57511
  placeholder,
57252
57512
  disabled: disabled && !isGenerating,
57253
57513
  rows: 1
@@ -57256,16 +57516,42 @@ function ChatInput({
57256
57516
  className: "llm-ci-toolbar",
57257
57517
  children: [
57258
57518
  /* @__PURE__ */ jsxDEV("div", {
57259
- className: "llm-ci-actions",
57260
- children: actions
57261
- }, undefined, false, undefined, this),
57262
- /* @__PURE__ */ jsxDEV("button", {
57263
- type: "button",
57264
- className: sendClass,
57265
- onClick: isGenerating ? onStop : onSend,
57266
- disabled: !isGenerating && !canSend,
57267
- "aria-label": isGenerating ? "Stop generation" : "Send message",
57268
- children: isGenerating ? /* @__PURE__ */ jsxDEV(StopIcon, {}, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(SendIcon, {}, undefined, false, undefined, this)
57519
+ className: "llm-ci-left-actions",
57520
+ children: [
57521
+ modelSelector && /* @__PURE__ */ jsxDEV("div", {
57522
+ className: "llm-ci-model-selector",
57523
+ children: modelSelector
57524
+ }, undefined, false, undefined, this),
57525
+ /* @__PURE__ */ jsxDEV("button", {
57526
+ type: "button",
57527
+ className: "llm-ci-btn-icon",
57528
+ onClick: () => fileInputRef.current?.click(),
57529
+ title: "Attach image (or Ctrl+V to paste)",
57530
+ children: /* @__PURE__ */ jsxDEV(ImageIcon, {}, undefined, false, undefined, this)
57531
+ }, undefined, false, undefined, this),
57532
+ actions
57533
+ ]
57534
+ }, undefined, true, undefined, this),
57535
+ /* @__PURE__ */ jsxDEV("div", {
57536
+ className: "llm-ci-right-actions",
57537
+ children: /* @__PURE__ */ jsxDEV("button", {
57538
+ type: "button",
57539
+ className: sendClass,
57540
+ onClick: isGenerating ? onStop : onSend,
57541
+ disabled: !isGenerating && !canSend,
57542
+ "aria-label": isGenerating ? "Stop generation" : "Send message",
57543
+ children: isGenerating ? /* @__PURE__ */ jsxDEV(Fragment, {
57544
+ children: [
57545
+ /* @__PURE__ */ jsxDEV(StopIcon, {}, undefined, false, undefined, this),
57546
+ " Stop"
57547
+ ]
57548
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
57549
+ children: [
57550
+ "Send ",
57551
+ /* @__PURE__ */ jsxDEV(SendIcon, {}, undefined, false, undefined, this)
57552
+ ]
57553
+ }, undefined, true, undefined, this)
57554
+ }, undefined, false, undefined, this)
57269
57555
  }, undefined, false, undefined, this)
57270
57556
  ]
57271
57557
  }, undefined, true, undefined, this)
@@ -57274,7 +57560,16 @@ function ChatInput({
57274
57560
  }
57275
57561
 
57276
57562
  // src/react/components.tsx
57277
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
57563
+ import { Streamdown } from "streamdown";
57564
+ import { code } from "@streamdown/code";
57565
+ import { mermaid } from "@streamdown/mermaid";
57566
+ import { jsxDEV as jsxDEV2, Fragment as Fragment2 } from "react/jsx-dev-runtime";
57567
+ var DEFAULT_SYSTEM_PROMPT = `You are a helpful AI assistant.
57568
+ - You can use full Markdown (bold, italic, headers, lists).
57569
+ - You can use Code Blocks with language syntax highlighting.
57570
+ - You can use Mermaid diagrams (\`\`\`mermaid ... \`\`\`).
57571
+ - You can use LaTeX math ($$ ... $$).`;
57572
+ var ALL_MODELS = { ...WEBLLM_MODELS, ...TRANSFORMERS_MODELS };
57278
57573
  function RetryIcon() {
57279
57574
  return /* @__PURE__ */ jsxDEV2("svg", {
57280
57575
  width: "14",
@@ -57295,6 +57590,21 @@ function RetryIcon() {
57295
57590
  ]
57296
57591
  }, undefined, true, undefined, this);
57297
57592
  }
57593
+ function ChevronDownIcon() {
57594
+ return /* @__PURE__ */ jsxDEV2("svg", {
57595
+ width: "12",
57596
+ height: "12",
57597
+ viewBox: "0 0 24 24",
57598
+ fill: "none",
57599
+ stroke: "currentColor",
57600
+ strokeWidth: "2",
57601
+ strokeLinecap: "round",
57602
+ strokeLinejoin: "round",
57603
+ children: /* @__PURE__ */ jsxDEV2("polyline", {
57604
+ points: "6 9 12 15 18 9"
57605
+ }, undefined, false, undefined, this)
57606
+ }, undefined, false, undefined, this);
57607
+ }
57298
57608
  var STYLE_ID2 = "__llm-chat-styles";
57299
57609
  function injectChatStyles(theme) {
57300
57610
  if (typeof document === "undefined")
@@ -57303,16 +57613,24 @@ function injectChatStyles(theme) {
57303
57613
  if (existing)
57304
57614
  existing.remove();
57305
57615
  const d = theme === "dark";
57616
+ const bg2 = d ? "#000000" : "#ffffff";
57617
+ const border = d ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.12)";
57618
+ const borderSubtle = d ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)";
57619
+ const text = d ? "#ffffff" : "#000000";
57620
+ const textSecondary = d ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.5)";
57621
+ const textTertiary = d ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.3)";
57622
+ const surfaceSubtle = d ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.03)";
57623
+ const monoFont = `ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace`;
57624
+ const sansFont = `-apple-system, BlinkMacSystemFont, "Segoe UI", "Inter", Roboto, Helvetica, Arial, sans-serif`;
57306
57625
  const css = `
57307
57626
  .llm-chat {
57308
57627
  display: flex;
57309
57628
  flex-direction: column;
57310
- border-radius: 16px;
57311
- border: 1px solid ${d ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.08)"};
57312
- background: ${d ? "#0a0a0a" : "#fafafa"};
57629
+ border: 1px solid ${border};
57630
+ background: ${bg2};
57313
57631
  overflow: hidden;
57314
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
57315
- color: ${d ? "#e5e5e5" : "#1a1a1a"};
57632
+ font-family: ${sansFont};
57633
+ color: ${text};
57316
57634
  }
57317
57635
 
57318
57636
  /* Header */
@@ -57321,34 +57639,103 @@ function injectChatStyles(theme) {
57321
57639
  align-items: center;
57322
57640
  justify-content: space-between;
57323
57641
  padding: 12px 16px;
57324
- border-bottom: 1px solid ${d ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)"};
57325
- font-size: 12px;
57326
- color: ${d ? "rgba(255,255,255,0.45)" : "rgba(0,0,0,0.45)"};
57642
+ border-bottom: 1px solid ${borderSubtle};
57327
57643
  }
57328
- .llm-chat-header-model {
57329
- font-weight: 600;
57330
- color: ${d ? "rgba(255,255,255,0.7)" : "rgba(0,0,0,0.7)"};
57644
+
57645
+ /* Model Selector */
57646
+ .llm-chat-model-select {
57647
+ position: relative;
57331
57648
  }
57332
- .llm-chat-header-status {
57649
+ .llm-chat-model-btn {
57333
57650
  display: flex;
57334
57651
  align-items: center;
57335
57652
  gap: 6px;
57653
+ background: transparent;
57654
+ border: none;
57655
+ color: ${text};
57656
+ font-weight: 400;
57657
+ font-size: 11px;
57658
+ font-family: ${monoFont};
57659
+ text-transform: uppercase;
57660
+ letter-spacing: 0.05em;
57661
+ padding: 4px 0;
57662
+ cursor: pointer;
57663
+ transition: opacity 0.1s;
57664
+ }
57665
+ .llm-chat-model-btn:hover {
57666
+ opacity: 0.6;
57667
+ }
57668
+ .llm-chat-model-menu {
57669
+ position: absolute;
57670
+ top: 100%;
57671
+ left: 0;
57672
+ margin-top: 4px;
57673
+ width: 280px;
57674
+ max-height: 300px;
57675
+ overflow-y: auto;
57676
+ background: ${d ? "#0a0a0a" : "#fafafa"};
57677
+ border: 1px solid ${border};
57678
+ z-index: 50;
57679
+ padding: 4px 0;
57680
+ scrollbar-width: thin;
57681
+ }
57682
+ .llm-chat-model-group {
57683
+ padding: 8px 12px 4px;
57684
+ font-size: 10px;
57685
+ font-weight: 400;
57686
+ color: ${textSecondary};
57687
+ text-transform: uppercase;
57688
+ letter-spacing: 0.08em;
57689
+ font-family: ${monoFont};
57690
+ }
57691
+ .llm-chat-model-item {
57692
+ display: block;
57693
+ width: 100%;
57694
+ text-align: left;
57695
+ padding: 6px 12px;
57696
+ font-size: 12px;
57697
+ font-family: ${monoFont};
57698
+ color: ${textSecondary};
57699
+ background: transparent;
57700
+ border: none;
57701
+ cursor: pointer;
57702
+ white-space: nowrap;
57703
+ overflow: hidden;
57704
+ text-overflow: ellipsis;
57705
+ transition: color 0.1s;
57706
+ }
57707
+ .llm-chat-model-item:hover {
57708
+ color: ${text};
57709
+ }
57710
+ .llm-chat-model-item--active {
57711
+ color: ${text};
57712
+ }
57713
+
57714
+ /* Status */
57715
+ .llm-chat-status {
57716
+ display: flex;
57717
+ align-items: center;
57718
+ gap: 8px;
57719
+ font-size: 10px;
57720
+ font-family: ${monoFont};
57721
+ text-transform: uppercase;
57722
+ letter-spacing: 0.08em;
57723
+ color: ${textSecondary};
57336
57724
  }
57337
57725
  .llm-chat-dot {
57338
- width: 6px;
57339
- height: 6px;
57726
+ width: 5px;
57727
+ height: 5px;
57340
57728
  border-radius: 50%;
57341
- flex-shrink: 0;
57342
57729
  }
57343
57730
  .llm-chat-dot--loading {
57344
- background: #f59e0b;
57345
- animation: llm-pulse 1.5s ease-in-out infinite;
57731
+ background: ${textSecondary};
57732
+ animation: llm-pulse 1.5s infinite;
57346
57733
  }
57347
57734
  .llm-chat-dot--ready {
57348
- background: #22c55e;
57735
+ background: ${text};
57349
57736
  }
57350
57737
  .llm-chat-dot--error {
57351
- background: #ef4444;
57738
+ background: #ff3333;
57352
57739
  }
57353
57740
 
57354
57741
  /* Progress */
@@ -57356,122 +57743,144 @@ function injectChatStyles(theme) {
57356
57743
  padding: 0 16px 8px;
57357
57744
  }
57358
57745
  .llm-chat-progress-bar {
57359
- height: 3px;
57360
- border-radius: 2px;
57361
- background: ${d ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.06)"};
57746
+ height: 1px;
57747
+ background: ${borderSubtle};
57362
57748
  overflow: hidden;
57363
57749
  }
57364
57750
  .llm-chat-progress-fill {
57365
57751
  height: 100%;
57366
- border-radius: 2px;
57367
- background: linear-gradient(90deg, #38bdf8, #818cf8);
57752
+ background: ${text};
57368
57753
  transition: width 0.3s ease;
57369
57754
  }
57370
57755
  .llm-chat-progress-text {
57371
- font-size: 11px;
57372
- color: ${d ? "rgba(255,255,255,0.35)" : "rgba(0,0,0,0.35)"};
57756
+ font-size: 10px;
57757
+ font-family: ${monoFont};
57758
+ color: ${textTertiary};
57373
57759
  margin-top: 4px;
57760
+ text-align: right;
57761
+ text-transform: uppercase;
57762
+ letter-spacing: 0.05em;
57374
57763
  }
57375
57764
 
57376
57765
  /* Messages */
57377
57766
  .llm-chat-messages {
57378
57767
  flex: 1;
57379
57768
  overflow-y: auto;
57380
- padding: 16px;
57769
+ padding: 20px 16px;
57381
57770
  display: flex;
57382
57771
  flex-direction: column;
57383
- gap: 12px;
57772
+ gap: 24px;
57384
57773
  scrollbar-width: thin;
57385
- scrollbar-color: ${d ? "rgba(255,255,255,0.1)" : "rgba(0,0,0,0.1)"} transparent;
57386
57774
  }
57387
-
57775
+
57388
57776
  /* Welcome */
57389
57777
  .llm-chat-welcome {
57390
57778
  display: flex;
57391
- align-items: center;
57779
+ flex-direction: column;
57780
+ align-items: flex-start;
57392
57781
  justify-content: center;
57393
57782
  flex: 1;
57394
- text-align: center;
57395
- color: ${d ? "rgba(255,255,255,0.25)" : "rgba(0,0,0,0.25)"};
57396
- font-size: 14px;
57397
- padding: 40px 20px;
57398
- line-height: 1.5;
57783
+ color: ${textTertiary};
57784
+ padding: 40px 0;
57785
+ }
57786
+ .llm-chat-welcome h3 {
57787
+ font-size: 11px;
57788
+ font-weight: 400;
57789
+ font-family: ${monoFont};
57790
+ text-transform: uppercase;
57791
+ letter-spacing: 0.08em;
57792
+ color: ${textSecondary};
57793
+ margin: 0 0 8px;
57794
+ }
57795
+ .llm-chat-welcome p {
57796
+ font-size: 13px;
57797
+ margin: 0;
57798
+ max-width: 360px;
57799
+ line-height: 1.6;
57800
+ color: ${textTertiary};
57399
57801
  }
57400
57802
 
57401
57803
  /* Bubble */
57402
57804
  .llm-chat-bubble {
57403
57805
  display: flex;
57404
- max-width: 80%;
57405
- animation: llm-fadein 0.2s ease;
57806
+ flex-direction: column;
57807
+ max-width: 100%;
57406
57808
  }
57407
57809
  .llm-chat-bubble--user {
57408
57810
  align-self: flex-end;
57811
+ max-width: 80%;
57409
57812
  }
57410
57813
  .llm-chat-bubble--assistant {
57411
57814
  align-self: flex-start;
57815
+ width: 100%;
57412
57816
  }
57413
- .llm-chat-bubble-content {
57817
+
57818
+ /* User message — flat, subtle bg, no radius */
57819
+ .llm-chat-user-content {
57414
57820
  padding: 10px 14px;
57415
- border-radius: 16px;
57416
57821
  font-size: 14px;
57417
- line-height: 1.55;
57822
+ line-height: 1.6;
57418
57823
  white-space: pre-wrap;
57419
- word-break: break-word;
57420
- }
57421
- .llm-chat-bubble--user .llm-chat-bubble-content {
57422
- background: linear-gradient(135deg, #38bdf8, #818cf8);
57423
- color: white;
57424
- border-bottom-right-radius: 4px;
57425
- }
57426
- .llm-chat-bubble--assistant .llm-chat-bubble-content {
57427
- background: ${d ? "rgba(255,255,255,0.06)" : "rgba(0,0,0,0.05)"};
57428
- color: ${d ? "#d4d4d4" : "#333"};
57429
- border-bottom-left-radius: 4px;
57824
+ background: ${surfaceSubtle};
57825
+ border: 1px solid ${borderSubtle};
57826
+ color: ${text};
57430
57827
  }
57431
-
57432
- /* Streaming */
57433
- .llm-chat-streaming .llm-chat-bubble-content {
57434
- border: 1px solid ${d ? "rgba(56,189,248,0.2)" : "rgba(59,130,246,0.2)"};
57828
+
57829
+ /* Assistant message */
57830
+ .llm-chat-assistant-content {
57831
+ font-size: 14px;
57832
+ line-height: 1.7;
57833
+ color: ${d ? "rgba(255,255,255,0.85)" : "rgba(0,0,0,0.85)"};
57435
57834
  }
57436
- .llm-chat-cursor {
57437
- display: inline-block;
57438
- width: 2px;
57439
- height: 14px;
57440
- background: ${d ? "#38bdf8" : "#3b82f6"};
57441
- margin-left: 2px;
57442
- vertical-align: text-bottom;
57443
- animation: llm-blink 0.8s step-end infinite;
57835
+
57836
+ /* Streamdown Overrides */
57837
+ .llm-chat-assistant-content pre {
57838
+ background: ${surfaceSubtle} !important;
57839
+ border: 1px solid ${borderSubtle} !important;
57840
+ border-radius: 0 !important;
57841
+ padding: 12px !important;
57842
+ margin: 12px 0 !important;
57843
+ }
57844
+ .llm-chat-assistant-content code {
57845
+ font-family: ${monoFont} !important;
57846
+ font-size: 13px !important;
57847
+ }
57848
+ .llm-chat-assistant-content :not(pre) > code {
57849
+ background: ${surfaceSubtle};
57850
+ border: 1px solid ${borderSubtle};
57851
+ padding: 1px 5px;
57852
+ font-size: 12.5px !important;
57444
57853
  }
57445
-
57446
- /* Pending */
57447
- .llm-chat-pending {
57854
+
57855
+ /* Attachments in message */
57856
+ .llm-chat-msg-images {
57448
57857
  display: flex;
57449
- justify-content: center;
57450
- animation: llm-fadein 0.3s ease;
57858
+ flex-wrap: wrap;
57859
+ gap: 6px;
57860
+ margin-bottom: 8px;
57861
+ justify-content: flex-end;
57451
57862
  }
57452
- .llm-chat-pending-badge {
57453
- font-size: 12px;
57454
- color: #f59e0b;
57455
- background: rgba(245,158,11,0.1);
57456
- padding: 4px 12px;
57457
- border-radius: 12px;
57863
+ .llm-chat-msg-img {
57864
+ width: 100px;
57865
+ height: 100px;
57866
+ object-fit: cover;
57867
+ border: 1px solid ${border};
57458
57868
  }
57459
57869
 
57460
57870
  /* Error */
57461
57871
  .llm-chat-error {
57462
57872
  margin: 0 16px;
57463
57873
  padding: 10px 14px;
57464
- border-radius: 12px;
57465
- background: ${d ? "rgba(239,68,68,0.08)" : "rgba(239,68,68,0.05)"};
57466
- border: 1px solid ${d ? "rgba(239,68,68,0.2)" : "rgba(239,68,68,0.15)"};
57874
+ border: 1px solid ${d ? "rgba(255,50,50,0.2)" : "rgba(200,0,0,0.15)"};
57467
57875
  display: flex;
57468
57876
  align-items: center;
57469
57877
  justify-content: space-between;
57470
- gap: 8px;
57878
+ gap: 12px;
57471
57879
  }
57472
57880
  .llm-chat-error-text {
57473
- font-size: 13px;
57474
- color: ${d ? "#f87171" : "#dc2626"};
57881
+ font-size: 12px;
57882
+ font-family: ${monoFont};
57883
+ color: ${d ? "#ff6666" : "#cc0000"};
57475
57884
  flex: 1;
57476
57885
  }
57477
57886
  .llm-chat-error-retry {
@@ -57479,63 +57888,28 @@ function injectChatStyles(theme) {
57479
57888
  align-items: center;
57480
57889
  gap: 4px;
57481
57890
  padding: 4px 10px;
57482
- border-radius: 8px;
57483
- border: 1px solid ${d ? "rgba(239,68,68,0.3)" : "rgba(239,68,68,0.2)"};
57891
+ border: 1px solid ${d ? "rgba(255,50,50,0.3)" : "rgba(200,0,0,0.2)"};
57484
57892
  background: transparent;
57485
- color: ${d ? "#f87171" : "#dc2626"};
57486
- font-size: 12px;
57893
+ color: ${d ? "#ff6666" : "#cc0000"};
57894
+ font-size: 11px;
57895
+ font-family: ${monoFont};
57896
+ text-transform: uppercase;
57897
+ letter-spacing: 0.05em;
57487
57898
  cursor: pointer;
57488
- transition: background 0.15s;
57489
- flex-shrink: 0;
57490
57899
  }
57491
57900
  .llm-chat-error-retry:hover {
57492
- background: ${d ? "rgba(239,68,68,0.12)" : "rgba(239,68,68,0.08)"};
57901
+ background: ${d ? "rgba(255,50,50,0.08)" : "rgba(200,0,0,0.04)"};
57493
57902
  }
57494
57903
 
57495
- /* Loading overlay */
57496
- .llm-chat-loading {
57497
- display: flex;
57498
- flex-direction: column;
57499
- align-items: center;
57500
- justify-content: center;
57501
- gap: 12px;
57502
- flex: 1;
57503
- padding: 40px 20px;
57504
- }
57505
- .llm-chat-spinner {
57506
- width: 28px;
57507
- height: 28px;
57508
- border: 2.5px solid ${d ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)"};
57509
- border-top-color: #38bdf8;
57510
- border-radius: 50%;
57511
- animation: llm-spin 0.7s linear infinite;
57512
- }
57513
- .llm-chat-loading-text {
57514
- font-size: 13px;
57515
- color: ${d ? "rgba(255,255,255,0.4)" : "rgba(0,0,0,0.4)"};
57516
- }
57517
-
57518
- /* Input area */
57904
+ /* Input Area */
57519
57905
  .llm-chat-input-area {
57520
- padding: 8px 12px 12px;
57521
- border-top: 1px solid ${d ? "rgba(255,255,255,0.04)" : "rgba(0,0,0,0.04)"};
57906
+ padding: 12px 16px 16px;
57907
+ border-top: 1px solid ${borderSubtle};
57522
57908
  }
57523
57909
 
57524
- /* Animations */
57525
- @keyframes llm-fadein {
57526
- from { opacity: 0; transform: translateY(4px); }
57527
- to { opacity: 1; transform: translateY(0); }
57528
- }
57529
57910
  @keyframes llm-pulse {
57530
57911
  0%, 100% { opacity: 1; }
57531
- 50% { opacity: 0.4; }
57532
- }
57533
- @keyframes llm-blink {
57534
- 0%, 100% { opacity: 1; }
57535
- 50% { opacity: 0; }
57536
- }
57537
- @keyframes llm-spin {
57538
- to { transform: rotate(360deg); }
57912
+ 50% { opacity: 0.2; }
57539
57913
  }
57540
57914
  `;
57541
57915
  const style = document.createElement("style");
@@ -57543,8 +57917,77 @@ function injectChatStyles(theme) {
57543
57917
  style.textContent = css;
57544
57918
  document.head.appendChild(style);
57545
57919
  }
57920
+ function ModelSelector({
57921
+ currentModel,
57922
+ onSelect,
57923
+ theme
57924
+ }) {
57925
+ const [isOpen, setIsOpen] = useState2(false);
57926
+ const ref = useRef2(null);
57927
+ useEffect2(() => {
57928
+ function handleClickOutside(event) {
57929
+ if (ref.current && !ref.current.contains(event.target)) {
57930
+ setIsOpen(false);
57931
+ }
57932
+ }
57933
+ document.addEventListener("mousedown", handleClickOutside);
57934
+ return () => document.removeEventListener("mousedown", handleClickOutside);
57935
+ }, []);
57936
+ const displayModel = useMemo(() => {
57937
+ if (!currentModel)
57938
+ return "Select Model";
57939
+ const id2 = currentModel.split("/").pop() || currentModel;
57940
+ return id2.length > 25 ? id2.substring(0, 25) + "..." : id2;
57941
+ }, [currentModel]);
57942
+ return /* @__PURE__ */ jsxDEV2("div", {
57943
+ className: "llm-chat-model-select",
57944
+ ref,
57945
+ children: [
57946
+ /* @__PURE__ */ jsxDEV2("button", {
57947
+ type: "button",
57948
+ className: "llm-chat-model-btn",
57949
+ onClick: () => setIsOpen(!isOpen),
57950
+ children: [
57951
+ "[",
57952
+ displayModel,
57953
+ "] ",
57954
+ /* @__PURE__ */ jsxDEV2(ChevronDownIcon, {}, undefined, false, undefined, this)
57955
+ ]
57956
+ }, undefined, true, undefined, this),
57957
+ isOpen && /* @__PURE__ */ jsxDEV2("div", {
57958
+ className: "llm-chat-model-menu",
57959
+ children: [
57960
+ /* @__PURE__ */ jsxDEV2("div", {
57961
+ className: "llm-chat-model-group",
57962
+ children: "[WebLLM]"
57963
+ }, undefined, false, undefined, this),
57964
+ Object.entries(WEBLLM_MODELS).map(([key, value]) => /* @__PURE__ */ jsxDEV2("button", {
57965
+ className: `llm-chat-model-item ${currentModel === value ? "llm-chat-model-item--active" : ""}`,
57966
+ onClick: () => {
57967
+ onSelect(value);
57968
+ setIsOpen(false);
57969
+ },
57970
+ children: key
57971
+ }, key, false, undefined, this)),
57972
+ /* @__PURE__ */ jsxDEV2("div", {
57973
+ className: "llm-chat-model-group",
57974
+ children: "[Transformers.js]"
57975
+ }, undefined, false, undefined, this),
57976
+ Object.entries(TRANSFORMERS_MODELS).map(([key, value]) => /* @__PURE__ */ jsxDEV2("button", {
57977
+ className: `llm-chat-model-item ${currentModel === value ? "llm-chat-model-item--active" : ""}`,
57978
+ onClick: () => {
57979
+ onSelect(value);
57980
+ setIsOpen(false);
57981
+ },
57982
+ children: key
57983
+ }, key, false, undefined, this))
57984
+ ]
57985
+ }, undefined, true, undefined, this)
57986
+ ]
57987
+ }, undefined, true, undefined, this);
57988
+ }
57546
57989
  function Chat2({
57547
- systemPrompt = "You are a helpful assistant. Keep responses concise and clear.",
57990
+ systemPrompt = DEFAULT_SYSTEM_PROMPT,
57548
57991
  placeholder = "Type a message...",
57549
57992
  theme = "dark",
57550
57993
  className,
@@ -57555,14 +57998,16 @@ function Chat2({
57555
57998
  onError: onErrorProp,
57556
57999
  showHeader = true,
57557
58000
  showProgress = true,
57558
- welcomeMessage = "Send a message to start chatting"
58001
+ welcomeMessage = "Ready to assist",
58002
+ onModelChange
57559
58003
  }) {
57560
- const { llm, isLoading, isReady, loadProgress, error, modelId, backend: backend2, reload } = useLLM();
57561
- const [messages, setMessages] = useState([]);
57562
- const [input, setInput] = useState("");
57563
- const [isGenerating, setIsGenerating] = useState(false);
57564
- const [streamingText, setStreamingText] = useState("");
57565
- const [pendingMessage, setPendingMessage] = useState(null);
58004
+ const { llm, isLoading, isReady, loadProgress, error, modelId, reload } = useLLM();
58005
+ const [messages, setMessages] = useState2([]);
58006
+ const [input, setInput] = useState2("");
58007
+ const [isGenerating, setIsGenerating] = useState2(false);
58008
+ const [streamingText, setStreamingText] = useState2("");
58009
+ const [pendingMessage, setPendingMessage] = useState2(null);
58010
+ const [images, setImages] = useState2([]);
57566
58011
  const messagesEndRef = useRef2(null);
57567
58012
  const abortRef = useRef2(false);
57568
58013
  const isProcessingRef = useRef2(false);
@@ -57571,21 +58016,28 @@ function Chat2({
57571
58016
  }, [theme]);
57572
58017
  useEffect2(() => {
57573
58018
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
57574
- }, [messages, streamingText]);
57575
- const generate = async (userContent, currentMessages) => {
58019
+ }, [messages, streamingText, isGenerating]);
58020
+ const generate = async (userContent, currentMessages, attachedImages = []) => {
57576
58021
  if (!llm || !isReady || isProcessingRef.current)
57577
58022
  return;
57578
58023
  isProcessingRef.current = true;
57579
- const userMsg = { role: "user", content: userContent };
58024
+ const userMsg = {
58025
+ role: "user",
58026
+ content: userContent,
58027
+ images: attachedImages
58028
+ };
57580
58029
  setMessages((prev) => [...prev, userMsg]);
58030
+ setIsGenerating(true);
58031
+ setStreamingText("");
58032
+ abortRef.current = false;
57581
58033
  const apiMessages = [];
57582
58034
  if (systemPrompt) {
57583
58035
  apiMessages.push({ role: "system", content: systemPrompt });
57584
58036
  }
57585
- apiMessages.push(...currentMessages, userMsg);
57586
- setIsGenerating(true);
57587
- setStreamingText("");
57588
- abortRef.current = false;
58037
+ currentMessages.forEach((m) => {
58038
+ apiMessages.push({ role: m.role, content: m.content });
58039
+ });
58040
+ apiMessages.push({ role: "user", content: userContent });
57589
58041
  try {
57590
58042
  const response = await llm.stream(apiMessages, (_token, fullText) => {
57591
58043
  if (abortRef.current)
@@ -57601,29 +58053,26 @@ function Chat2({
57601
58053
  } catch (err) {
57602
58054
  const error2 = err instanceof Error ? err : new Error(String(err));
57603
58055
  onErrorProp?.(error2);
58056
+ setMessages((prev) => [...prev, {
58057
+ role: "assistant",
58058
+ content: `Error: ${error2.message}`
58059
+ }]);
57604
58060
  } finally {
57605
58061
  setIsGenerating(false);
57606
58062
  isProcessingRef.current = false;
57607
58063
  }
57608
58064
  };
57609
- useEffect2(() => {
57610
- if (isReady && pendingMessage && !isProcessingRef.current) {
57611
- const msg = pendingMessage;
57612
- setPendingMessage(null);
57613
- generate(msg, messages);
57614
- }
57615
- }, [isReady, pendingMessage]);
57616
58065
  const handleSend = () => {
57617
58066
  const text = input.trim();
57618
- if (!text)
58067
+ if (!text && images.length === 0)
57619
58068
  return;
58069
+ const currentImages = [...images];
57620
58070
  setInput("");
58071
+ setImages([]);
57621
58072
  onSendProp?.(text);
57622
58073
  if (llm && isReady) {
57623
- generate(text, messages);
58074
+ generate(text, messages, currentImages);
57624
58075
  } else if (isLoading) {
57625
- const userMsg = { role: "user", content: text };
57626
- setMessages((prev) => [...prev, userMsg]);
57627
58076
  setPendingMessage(text);
57628
58077
  }
57629
58078
  };
@@ -57636,7 +58085,7 @@ function Chat2({
57636
58085
  }
57637
58086
  };
57638
58087
  const statusDotClass = error ? "llm-chat-dot llm-chat-dot--error" : isReady ? "llm-chat-dot llm-chat-dot--ready" : "llm-chat-dot llm-chat-dot--loading";
57639
- const statusText = error ? "Error" : isReady ? "Ready" : isLoading ? "Loading..." : "Idle";
58088
+ const statusText = error ? "Error" : isReady ? "Ready" : isLoading ? "Loading" : "Idle";
57640
58089
  return /* @__PURE__ */ jsxDEV2("div", {
57641
58090
  className: `llm-chat${className ? ` ${className}` : ""}`,
57642
58091
  style: { maxHeight, height: "100%" },
@@ -57644,28 +58093,34 @@ function Chat2({
57644
58093
  showHeader && /* @__PURE__ */ jsxDEV2("div", {
57645
58094
  className: "llm-chat-header",
57646
58095
  children: [
57647
- /* @__PURE__ */ jsxDEV2("span", {
57648
- className: "llm-chat-header-model",
57649
- children: [
57650
- modelId ? modelId.split("/").pop()?.substring(0, 30) : "No model",
57651
- backend2 && /* @__PURE__ */ jsxDEV2("span", {
57652
- style: { fontWeight: 400, marginLeft: 6, opacity: 0.6 },
57653
- children: [
57654
- "(",
57655
- backend2,
57656
- ")"
57657
- ]
57658
- }, undefined, true, undefined, this)
57659
- ]
57660
- }, undefined, true, undefined, this),
58096
+ onModelChange ? /* @__PURE__ */ jsxDEV2(ModelSelector, {
58097
+ currentModel: modelId,
58098
+ onSelect: onModelChange,
58099
+ theme
58100
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("div", {
58101
+ className: "llm-chat-model-select",
58102
+ children: /* @__PURE__ */ jsxDEV2("span", {
58103
+ style: {
58104
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
58105
+ fontSize: "11px",
58106
+ textTransform: "uppercase",
58107
+ letterSpacing: "0.05em"
58108
+ },
58109
+ children: [
58110
+ "[",
58111
+ modelId?.split("/").pop(),
58112
+ "]"
58113
+ ]
58114
+ }, undefined, true, undefined, this)
58115
+ }, undefined, false, undefined, this),
57661
58116
  /* @__PURE__ */ jsxDEV2("div", {
57662
- className: "llm-chat-header-status",
58117
+ className: "llm-chat-status",
57663
58118
  children: [
57664
- /* @__PURE__ */ jsxDEV2("span", {
57665
- className: statusDotClass
57666
- }, undefined, false, undefined, this),
57667
58119
  /* @__PURE__ */ jsxDEV2("span", {
57668
58120
  children: statusText
58121
+ }, undefined, false, undefined, this),
58122
+ /* @__PURE__ */ jsxDEV2("div", {
58123
+ className: statusDotClass
57669
58124
  }, undefined, false, undefined, this)
57670
58125
  ]
57671
58126
  }, undefined, true, undefined, this)
@@ -57707,50 +58162,54 @@ function Chat2({
57707
58162
  /* @__PURE__ */ jsxDEV2("div", {
57708
58163
  className: "llm-chat-messages",
57709
58164
  children: [
57710
- isLoading && messages.length === 0 && !error && /* @__PURE__ */ jsxDEV2("div", {
57711
- className: "llm-chat-loading",
58165
+ !isLoading && messages.length === 0 && !error && /* @__PURE__ */ jsxDEV2("div", {
58166
+ className: "llm-chat-welcome",
57712
58167
  children: [
57713
- /* @__PURE__ */ jsxDEV2("div", {
57714
- className: "llm-chat-spinner"
57715
- }, undefined, false, undefined, this),
57716
- /* @__PURE__ */ jsxDEV2("div", {
57717
- className: "llm-chat-loading-text",
57718
- children: loadProgress?.status || "Initializing model..."
58168
+ /* @__PURE__ */ jsxDEV2("h3", {
58169
+ children: [
58170
+ "[",
58171
+ welcomeMessage,
58172
+ "]"
58173
+ ]
58174
+ }, undefined, true, undefined, this),
58175
+ /* @__PURE__ */ jsxDEV2("p", {
58176
+ children: "Markdown, code blocks, Mermaid diagrams. Paste images with Ctrl+V."
57719
58177
  }, undefined, false, undefined, this)
57720
58178
  ]
57721
58179
  }, undefined, true, undefined, this),
57722
- !isLoading && messages.length === 0 && !error && /* @__PURE__ */ jsxDEV2("div", {
57723
- className: "llm-chat-welcome",
57724
- children: welcomeMessage
57725
- }, undefined, false, undefined, this),
57726
- messages.map((msg, i) => {
57727
- if (msg.role === "system")
57728
- return null;
57729
- return /* @__PURE__ */ jsxDEV2("div", {
57730
- className: `llm-chat-bubble llm-chat-bubble--${msg.role}`,
57731
- children: /* @__PURE__ */ jsxDEV2("div", {
57732
- className: "llm-chat-bubble-content",
58180
+ messages.map((msg, i) => /* @__PURE__ */ jsxDEV2("div", {
58181
+ className: `llm-chat-bubble llm-chat-bubble--${msg.role}`,
58182
+ children: msg.role === "user" ? /* @__PURE__ */ jsxDEV2(Fragment2, {
58183
+ children: [
58184
+ msg.images && msg.images.length > 0 && /* @__PURE__ */ jsxDEV2("div", {
58185
+ className: "llm-chat-msg-images",
58186
+ children: msg.images.map((img) => /* @__PURE__ */ jsxDEV2("img", {
58187
+ src: img.dataUrl,
58188
+ className: "llm-chat-msg-img",
58189
+ alt: "attachment"
58190
+ }, img.id, false, undefined, this))
58191
+ }, undefined, false, undefined, this),
58192
+ /* @__PURE__ */ jsxDEV2("div", {
58193
+ className: "llm-chat-user-content",
58194
+ children: msg.content
58195
+ }, undefined, false, undefined, this)
58196
+ ]
58197
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV2("div", {
58198
+ className: "llm-chat-assistant-content",
58199
+ children: /* @__PURE__ */ jsxDEV2(Streamdown, {
58200
+ plugins: { code, mermaid },
57733
58201
  children: msg.content
57734
58202
  }, undefined, false, undefined, this)
57735
- }, i, false, undefined, this);
57736
- }),
58203
+ }, undefined, false, undefined, this)
58204
+ }, i, false, undefined, this)),
57737
58205
  streamingText && /* @__PURE__ */ jsxDEV2("div", {
57738
- className: "llm-chat-bubble llm-chat-bubble--assistant llm-chat-streaming",
58206
+ className: "llm-chat-bubble llm-chat-bubble--assistant",
57739
58207
  children: /* @__PURE__ */ jsxDEV2("div", {
57740
- className: "llm-chat-bubble-content",
57741
- children: [
57742
- streamingText,
57743
- /* @__PURE__ */ jsxDEV2("span", {
57744
- className: "llm-chat-cursor"
57745
- }, undefined, false, undefined, this)
57746
- ]
57747
- }, undefined, true, undefined, this)
57748
- }, undefined, false, undefined, this),
57749
- pendingMessage !== null && /* @__PURE__ */ jsxDEV2("div", {
57750
- className: "llm-chat-pending",
57751
- children: /* @__PURE__ */ jsxDEV2("span", {
57752
- className: "llm-chat-pending-badge",
57753
- children: "⏳ Waiting for model to load..."
58208
+ className: "llm-chat-assistant-content",
58209
+ children: /* @__PURE__ */ jsxDEV2(Streamdown, {
58210
+ plugins: { code, mermaid },
58211
+ children: streamingText
58212
+ }, undefined, false, undefined, this)
57754
58213
  }, undefined, false, undefined, this)
57755
58214
  }, undefined, false, undefined, this),
57756
58215
  /* @__PURE__ */ jsxDEV2("div", {
@@ -57767,17 +58226,42 @@ function Chat2({
57767
58226
  onStop: handleStop,
57768
58227
  disabled: !isReady && !isLoading,
57769
58228
  isGenerating,
57770
- placeholder: isLoading ? "Type now, send when ready..." : placeholder,
58229
+ placeholder: isLoading ? "Model is loading..." : placeholder,
57771
58230
  theme,
57772
- actions: inputActions
58231
+ actions: inputActions,
58232
+ images,
58233
+ onImageAdd: (img) => setImages((prev) => [...prev, img]),
58234
+ onImageRemove: (id2) => setImages((prev) => prev.filter((img) => img.id !== id2))
57773
58235
  }, undefined, false, undefined, this)
57774
58236
  }, undefined, false, undefined, this)
57775
58237
  ]
57776
58238
  }, undefined, true, undefined, this);
57777
58239
  }
58240
+ function ChatApp({
58241
+ defaultModel = "qwen-2.5-0.5b",
58242
+ defaultBackend = "auto",
58243
+ autoLoad = true,
58244
+ onModelChange,
58245
+ ...chatProps
58246
+ }) {
58247
+ const [model, setModel] = useState2(defaultModel);
58248
+ const handleModelChange = (newModel) => {
58249
+ setModel(newModel);
58250
+ onModelChange?.(newModel);
58251
+ };
58252
+ return /* @__PURE__ */ jsxDEV2(LLMProvider, {
58253
+ model,
58254
+ backend: defaultBackend,
58255
+ autoLoad,
58256
+ children: /* @__PURE__ */ jsxDEV2(Chat2, {
58257
+ ...chatProps,
58258
+ onModelChange: handleModelChange
58259
+ }, undefined, false, undefined, this)
58260
+ }, model, false, undefined, this);
58261
+ }
57778
58262
 
57779
58263
  // src/react/index.tsx
57780
- import { jsxDEV as jsxDEV3, Fragment } from "react/jsx-dev-runtime";
58264
+ import { jsxDEV as jsxDEV3, Fragment as Fragment3 } from "react/jsx-dev-runtime";
57781
58265
  var LLMContext = createContext(null);
57782
58266
  function LLMProvider({
57783
58267
  children,
@@ -57787,10 +58271,10 @@ function LLMProvider({
57787
58271
  onError,
57788
58272
  ...config
57789
58273
  }) {
57790
- const [llm, setLLM] = useState2(null);
57791
- const [isLoading, setIsLoading] = useState2(false);
57792
- const [loadProgress, setLoadProgress] = useState2(null);
57793
- const [error, setError] = useState2(null);
58274
+ const [llm, setLLM] = useState3(null);
58275
+ const [isLoading, setIsLoading] = useState3(false);
58276
+ const [loadProgress, setLoadProgress] = useState3(null);
58277
+ const [error, setError] = useState3(null);
57794
58278
  const hasLoadedRef = useRef3(false);
57795
58279
  const configRef = useRef3(config);
57796
58280
  configRef.current = config;
@@ -57844,7 +58328,7 @@ function LLMProvider({
57844
58328
  }
57845
58329
  };
57846
58330
  }, [llm]);
57847
- const value = useMemo(() => ({
58331
+ const value = useMemo2(() => ({
57848
58332
  llm,
57849
58333
  isLoading,
57850
58334
  isReady: llm?.isReady ?? false,
@@ -57879,11 +58363,11 @@ function useChat(options = {}) {
57879
58363
  onFinish,
57880
58364
  onError
57881
58365
  } = options;
57882
- const [messages, setMessages] = useState2(initialMessages);
57883
- const [input, setInput] = useState2("");
57884
- const [isGenerating, setIsGenerating] = useState2(false);
57885
- const [streamingText, setStreamingText] = useState2("");
57886
- const [pendingMessage, setPendingMessage] = useState2(null);
58366
+ const [messages, setMessages] = useState3(initialMessages);
58367
+ const [input, setInput] = useState3("");
58368
+ const [isGenerating, setIsGenerating] = useState3(false);
58369
+ const [streamingText, setStreamingText] = useState3("");
58370
+ const [pendingMessage, setPendingMessage] = useState3(null);
57887
58371
  const abortRef = useRef3(false);
57888
58372
  const isProcessingRef = useRef3(false);
57889
58373
  const generateResponse = useCallback2(async (userContent, currentMessages) => {
@@ -58005,8 +58489,8 @@ function useChat(options = {}) {
58005
58489
  function useStream(options = {}) {
58006
58490
  const { llm, isReady } = useLLM();
58007
58491
  const { generateOptions, onToken, onFinish, onError } = options;
58008
- const [text, setText] = useState2("");
58009
- const [isStreaming, setIsStreaming] = useState2(false);
58492
+ const [text, setText] = useState3("");
58493
+ const [isStreaming, setIsStreaming] = useState3(false);
58010
58494
  const abortRef = useRef3(false);
58011
58495
  const stream = useCallback2(async (input) => {
58012
58496
  if (!llm || !isReady) {
@@ -58050,8 +58534,8 @@ function useStream(options = {}) {
58050
58534
  function useCompletion(options = {}) {
58051
58535
  const { llm, isReady } = useLLM();
58052
58536
  const { generateOptions } = options;
58053
- const [completion, setCompletion] = useState2("");
58054
- const [isLoading, setIsLoading] = useState2(false);
58537
+ const [completion, setCompletion] = useState3("");
58538
+ const [isLoading, setIsLoading] = useState3(false);
58055
58539
  const complete = useCallback2(async (prompt) => {
58056
58540
  if (!llm || !isReady) {
58057
58541
  return "";
@@ -58107,11 +58591,11 @@ function LLMLoading({ children, className }) {
58107
58591
  function LLMReady({ children, fallback = null }) {
58108
58592
  const { isReady, isLoading } = useLLM();
58109
58593
  if (isLoading || !isReady) {
58110
- return /* @__PURE__ */ jsxDEV3(Fragment, {
58594
+ return /* @__PURE__ */ jsxDEV3(Fragment3, {
58111
58595
  children: fallback
58112
58596
  }, undefined, false, undefined, this);
58113
58597
  }
58114
- return /* @__PURE__ */ jsxDEV3(Fragment, {
58598
+ return /* @__PURE__ */ jsxDEV3(Fragment3, {
58115
58599
  children
58116
58600
  }, undefined, false, undefined, this);
58117
58601
  }
@@ -58124,5 +58608,6 @@ export {
58124
58608
  LLMProvider,
58125
58609
  LLMLoading,
58126
58610
  ChatInput,
58611
+ ChatApp,
58127
58612
  Chat2 as Chat
58128
58613
  };