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