openwork 0.1.1-rc.6 → 0.1.2

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/README.md CHANGED
@@ -43,6 +43,7 @@ Or configure them in-app via the settings panel.
43
43
  | --------- | ----------------------------------------------------------------- |
44
44
  | Anthropic | Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, Claude Opus 4.1, Claude Sonnet 4 |
45
45
  | OpenAI | GPT-5.2, GPT-5.1, o3, o3 Mini, o4 Mini, o1, GPT-4.1, GPT-4o |
46
+ | Google | Gemini 3 Pro Preview, Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 2.5 Flash Lite |
46
47
 
47
48
  ## Contributing
48
49
 
package/out/main/index.js CHANGED
@@ -10,6 +10,7 @@ const fs = require("fs");
10
10
  const os = require("os");
11
11
  const anthropic = require("@langchain/anthropic");
12
12
  const openai = require("@langchain/openai");
13
+ const googleGenai = require("@langchain/google-genai");
13
14
  const initSqlJs = require("sql.js");
14
15
  const langgraphCheckpoint = require("@langchain/langgraph-checkpoint");
15
16
  const node_child_process = require("node:child_process");
@@ -104,7 +105,8 @@ const OPENWORK_DIR = path.join(os.homedir(), ".openwork");
104
105
  const ENV_FILE = path.join(OPENWORK_DIR, ".env");
105
106
  const ENV_VAR_NAMES = {
106
107
  anthropic: "ANTHROPIC_API_KEY",
107
- openai: "OPENAI_API_KEY"
108
+ openai: "OPENAI_API_KEY",
109
+ google: "GOOGLE_API_KEY"
108
110
  };
109
111
  function getOpenworkDir() {
110
112
  if (!fs.existsSync(OPENWORK_DIR)) {
@@ -175,7 +177,8 @@ const store = new Store({
175
177
  });
176
178
  const PROVIDERS = [
177
179
  { id: "anthropic", name: "Anthropic" },
178
- { id: "openai", name: "OpenAI" }
180
+ { id: "openai", name: "OpenAI" },
181
+ { id: "google", name: "Google" }
179
182
  ];
180
183
  const AVAILABLE_MODELS = [
181
184
  // Anthropic Claude 4.5 series (latest as of Jan 2026)
@@ -310,6 +313,39 @@ const AVAILABLE_MODELS = [
310
313
  model: "gpt-4o-mini",
311
314
  description: "Cost-efficient variant with faster response times",
312
315
  available: true
316
+ },
317
+ // Google Gemini models
318
+ {
319
+ id: "gemini-3-pro-preview",
320
+ name: "Gemini 3 Pro Preview",
321
+ provider: "google",
322
+ model: "gemini-3-pro-preview",
323
+ description: "State-of-the-art reasoning and multimodal understanding",
324
+ available: true
325
+ },
326
+ {
327
+ id: "gemini-2.5-pro",
328
+ name: "Gemini 2.5 Pro",
329
+ provider: "google",
330
+ model: "gemini-2.5-pro",
331
+ description: "High-capability model for complex reasoning and coding",
332
+ available: true
333
+ },
334
+ {
335
+ id: "gemini-2.5-flash",
336
+ name: "Gemini 2.5 Flash",
337
+ provider: "google",
338
+ model: "gemini-2.5-flash",
339
+ description: "Lightning-fast with balance of intelligence and latency",
340
+ available: true
341
+ },
342
+ {
343
+ id: "gemini-2.5-flash-lite",
344
+ name: "Gemini 2.5 Flash Lite",
345
+ provider: "google",
346
+ model: "gemini-2.5-flash-lite",
347
+ description: "Fast, low-cost, high-performance model",
348
+ available: true
313
349
  }
314
350
  ];
315
351
  function registerModelHandlers(ipcMain) {
@@ -495,6 +531,47 @@ function registerModelHandlers(ipcMain) {
495
531
  }
496
532
  }
497
533
  );
534
+ ipcMain.handle(
535
+ "workspace:readBinaryFile",
536
+ async (_event, { threadId, filePath }) => {
537
+ const { getThread: getThread2 } = await Promise.resolve().then(() => index);
538
+ const thread = getThread2(threadId);
539
+ const metadata = thread?.metadata ? JSON.parse(thread.metadata) : {};
540
+ const workspacePath = metadata.workspacePath;
541
+ if (!workspacePath) {
542
+ return {
543
+ success: false,
544
+ error: "No workspace folder linked"
545
+ };
546
+ }
547
+ try {
548
+ const relativePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
549
+ const fullPath = path__namespace.join(workspacePath, relativePath);
550
+ const resolvedPath = path__namespace.resolve(fullPath);
551
+ const resolvedWorkspace = path__namespace.resolve(workspacePath);
552
+ if (!resolvedPath.startsWith(resolvedWorkspace)) {
553
+ return { success: false, error: "Access denied: path outside workspace" };
554
+ }
555
+ const stat = await fs__namespace$1.stat(fullPath);
556
+ if (stat.isDirectory()) {
557
+ return { success: false, error: "Cannot read directory as file" };
558
+ }
559
+ const buffer = await fs__namespace$1.readFile(fullPath);
560
+ const base64 = buffer.toString("base64");
561
+ return {
562
+ success: true,
563
+ content: base64,
564
+ size: stat.size,
565
+ modified_at: stat.mtime.toISOString()
566
+ };
567
+ } catch (e) {
568
+ return {
569
+ success: false,
570
+ error: e instanceof Error ? e.message : "Unknown error"
571
+ };
572
+ }
573
+ }
574
+ );
498
575
  }
499
576
  function getDefaultModel() {
500
577
  return store.get("defaultModel", "claude-sonnet-4-5-20250929");
@@ -1106,6 +1183,16 @@ function getModelInstance(modelId) {
1106
1183
  model,
1107
1184
  openAIApiKey: apiKey
1108
1185
  });
1186
+ } else if (model.startsWith("gemini")) {
1187
+ const apiKey = getApiKey("google");
1188
+ console.log("[Runtime] Google API key present:", !!apiKey);
1189
+ if (!apiKey) {
1190
+ throw new Error("Google API key not configured");
1191
+ }
1192
+ return new googleGenai.ChatGoogleGenerativeAI({
1193
+ model,
1194
+ apiKey
1195
+ });
1109
1196
  }
1110
1197
  return model;
1111
1198
  }
@@ -133,6 +133,9 @@ const api = {
133
133
  readFile: (threadId, filePath) => {
134
134
  return electron.ipcRenderer.invoke("workspace:readFile", { threadId, filePath });
135
135
  },
136
+ readBinaryFile: (threadId, filePath) => {
137
+ return electron.ipcRenderer.invoke("workspace:readBinaryFile", { threadId, filePath });
138
+ },
136
139
  // Listen for file changes in the workspace
137
140
  onFilesChanged: (callback) => {
138
141
  const handler = (_, data) => {
@@ -12575,6 +12575,11 @@ const Download = createLucideIcon("Download", [
12575
12575
  ["polyline", { points: "7 10 12 15 17 10", key: "2ggqvy" }],
12576
12576
  ["line", { x1: "12", x2: "12", y1: "15", y2: "3", key: "1vk2je" }]
12577
12577
  ]);
12578
+ const ExternalLink = createLucideIcon("ExternalLink", [
12579
+ ["path", { d: "M15 3h6v6", key: "1q9fwt" }],
12580
+ ["path", { d: "M10 14 21 3", key: "gplh6r" }],
12581
+ ["path", { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6", key: "a6xqqp" }]
12582
+ ]);
12578
12583
  const EyeOff = createLucideIcon("EyeOff", [
12579
12584
  [
12580
12585
  "path",
@@ -12710,6 +12715,18 @@ const GripVertical = createLucideIcon("GripVertical", [
12710
12715
  ["circle", { cx: "15", cy: "5", r: "1", key: "19l28e" }],
12711
12716
  ["circle", { cx: "15", cy: "19", r: "1", key: "f4zoj3" }]
12712
12717
  ]);
12718
+ const Hand = createLucideIcon("Hand", [
12719
+ ["path", { d: "M18 11V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2", key: "1fvzgz" }],
12720
+ ["path", { d: "M14 10V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v2", key: "1kc0my" }],
12721
+ ["path", { d: "M10 10.5V6a2 2 0 0 0-2-2a2 2 0 0 0-2 2v8", key: "10h0bg" }],
12722
+ [
12723
+ "path",
12724
+ {
12725
+ d: "M18 8a2 2 0 1 1 4 0v6a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15",
12726
+ key: "1s1gnw"
12727
+ }
12728
+ ]
12729
+ ]);
12713
12730
  const Image = createLucideIcon("Image", [
12714
12731
  ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", ry: "2", key: "1m3agn" }],
12715
12732
  ["circle", { cx: "9", cy: "9", r: "2", key: "af1f0g" }],
@@ -12730,9 +12747,20 @@ const ListTodo = createLucideIcon("ListTodo", [
12730
12747
  const LoaderCircle = createLucideIcon("LoaderCircle", [
12731
12748
  ["path", { d: "M21 12a9 9 0 1 1-6.219-8.56", key: "13zald" }]
12732
12749
  ]);
12750
+ const Maximize2 = createLucideIcon("Maximize2", [
12751
+ ["polyline", { points: "15 3 21 3 21 9", key: "mznyad" }],
12752
+ ["polyline", { points: "9 21 3 21 3 15", key: "1avn1i" }],
12753
+ ["line", { x1: "21", x2: "14", y1: "3", y2: "10", key: "ota7mn" }],
12754
+ ["line", { x1: "3", x2: "10", y1: "21", y2: "14", key: "1atl0r" }]
12755
+ ]);
12733
12756
  const MessageSquare = createLucideIcon("MessageSquare", [
12734
12757
  ["path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z", key: "1lielz" }]
12735
12758
  ]);
12759
+ const Music = createLucideIcon("Music", [
12760
+ ["path", { d: "M9 18V5l12-2v13", key: "1jmyc2" }],
12761
+ ["circle", { cx: "6", cy: "18", r: "3", key: "fqmcym" }],
12762
+ ["circle", { cx: "18", cy: "16", r: "3", key: "1hluhg" }]
12763
+ ]);
12736
12764
  const Pencil = createLucideIcon("Pencil", [
12737
12765
  [
12738
12766
  "path",
@@ -12747,6 +12775,10 @@ const Plus = createLucideIcon("Plus", [
12747
12775
  ["path", { d: "M5 12h14", key: "1ays0h" }],
12748
12776
  ["path", { d: "M12 5v14", key: "s699le" }]
12749
12777
  ]);
12778
+ const RotateCw = createLucideIcon("RotateCw", [
12779
+ ["path", { d: "M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8", key: "1p45f6" }],
12780
+ ["path", { d: "M21 3v5h-5", key: "1q7to0" }]
12781
+ ]);
12750
12782
  const Search = createLucideIcon("Search", [
12751
12783
  ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
12752
12784
  ["path", { d: "m21 21-4.3-4.3", key: "1qie3q" }]
@@ -12789,10 +12821,31 @@ const User = createLucideIcon("User", [
12789
12821
  ["path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", key: "975kel" }],
12790
12822
  ["circle", { cx: "12", cy: "7", r: "4", key: "17ys0d" }]
12791
12823
  ]);
12824
+ const Video = createLucideIcon("Video", [
12825
+ [
12826
+ "path",
12827
+ {
12828
+ d: "m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",
12829
+ key: "ftymec"
12830
+ }
12831
+ ],
12832
+ ["rect", { x: "2", y: "6", width: "14", height: "12", rx: "2", key: "158x01" }]
12833
+ ]);
12792
12834
  const X$2 = createLucideIcon("X", [
12793
12835
  ["path", { d: "M18 6 6 18", key: "1bl5f8" }],
12794
12836
  ["path", { d: "m6 6 12 12", key: "d8bk6v" }]
12795
12837
  ]);
12838
+ const ZoomIn = createLucideIcon("ZoomIn", [
12839
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
12840
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65", key: "13gj7c" }],
12841
+ ["line", { x1: "11", x2: "11", y1: "8", y2: "14", key: "1vmskp" }],
12842
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11", key: "durymu" }]
12843
+ ]);
12844
+ const ZoomOut = createLucideIcon("ZoomOut", [
12845
+ ["circle", { cx: "11", cy: "11", r: "8", key: "4ej97u" }],
12846
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65", key: "13gj7c" }],
12847
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11", key: "durymu" }]
12848
+ ]);
12796
12849
  function setRef(ref, value) {
12797
12850
  if (typeof ref === "function") {
12798
12851
  return ref(value);
@@ -22214,6 +22267,187 @@ function FileIcon$1({ name: name2 }) {
22214
22267
  return /* @__PURE__ */ jsxRuntimeExports.jsx(File, { className: "size-3.5 text-muted-foreground shrink-0" });
22215
22268
  }
22216
22269
  }
22270
+ const IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
22271
+ "png",
22272
+ "jpg",
22273
+ "jpeg",
22274
+ "gif",
22275
+ "svg",
22276
+ "webp",
22277
+ "bmp",
22278
+ "ico",
22279
+ "tiff",
22280
+ "tif"
22281
+ ]);
22282
+ const VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([
22283
+ "mp4",
22284
+ "webm",
22285
+ "ogg",
22286
+ "ogv",
22287
+ "mov",
22288
+ "avi",
22289
+ "wmv",
22290
+ "flv",
22291
+ "mkv"
22292
+ ]);
22293
+ const AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([
22294
+ "mp3",
22295
+ "wav",
22296
+ "ogg",
22297
+ "oga",
22298
+ "m4a",
22299
+ "flac",
22300
+ "aac",
22301
+ "weba"
22302
+ ]);
22303
+ const PDF_EXTENSIONS = /* @__PURE__ */ new Set(["pdf"]);
22304
+ const CODE_EXTENSIONS = /* @__PURE__ */ new Set([
22305
+ "ts",
22306
+ "tsx",
22307
+ "js",
22308
+ "jsx",
22309
+ "mjs",
22310
+ "cjs",
22311
+ "py",
22312
+ "java",
22313
+ "c",
22314
+ "cpp",
22315
+ "h",
22316
+ "hpp",
22317
+ "cs",
22318
+ "go",
22319
+ "rs",
22320
+ "rb",
22321
+ "php",
22322
+ "json",
22323
+ "xml",
22324
+ "yaml",
22325
+ "yml",
22326
+ "toml",
22327
+ "css",
22328
+ "scss",
22329
+ "sass",
22330
+ "less",
22331
+ "html",
22332
+ "htm",
22333
+ "vue",
22334
+ "svelte",
22335
+ "md",
22336
+ "mdx",
22337
+ "markdown",
22338
+ "sh",
22339
+ "bash",
22340
+ "zsh",
22341
+ "fish",
22342
+ "sql",
22343
+ "graphql",
22344
+ "proto",
22345
+ "dockerfile",
22346
+ "makefile"
22347
+ ]);
22348
+ const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
22349
+ "txt",
22350
+ "log",
22351
+ "csv",
22352
+ "tsv",
22353
+ "env",
22354
+ "gitignore",
22355
+ "editorconfig",
22356
+ "conf",
22357
+ "config",
22358
+ "ini",
22359
+ "cfg"
22360
+ ]);
22361
+ function getFileType(fileName) {
22362
+ const ext = fileName.includes(".") ? fileName.split(".").pop()?.toLowerCase() : void 0;
22363
+ if (!ext) {
22364
+ return { type: "text", canPreview: true };
22365
+ }
22366
+ if (IMAGE_EXTENSIONS.has(ext)) {
22367
+ return {
22368
+ type: "image",
22369
+ mimeType: getMimeType(ext),
22370
+ canPreview: true
22371
+ };
22372
+ }
22373
+ if (VIDEO_EXTENSIONS.has(ext)) {
22374
+ return {
22375
+ type: "video",
22376
+ mimeType: getMimeType(ext),
22377
+ canPreview: true
22378
+ };
22379
+ }
22380
+ if (AUDIO_EXTENSIONS.has(ext)) {
22381
+ return {
22382
+ type: "audio",
22383
+ mimeType: getMimeType(ext),
22384
+ canPreview: true
22385
+ };
22386
+ }
22387
+ if (PDF_EXTENSIONS.has(ext)) {
22388
+ return {
22389
+ type: "pdf",
22390
+ mimeType: "application/pdf",
22391
+ canPreview: true
22392
+ };
22393
+ }
22394
+ if (CODE_EXTENSIONS.has(ext)) {
22395
+ return {
22396
+ type: "code",
22397
+ canPreview: true
22398
+ };
22399
+ }
22400
+ if (TEXT_EXTENSIONS.has(ext)) {
22401
+ return {
22402
+ type: "text",
22403
+ canPreview: true
22404
+ };
22405
+ }
22406
+ return {
22407
+ type: "binary",
22408
+ canPreview: false
22409
+ };
22410
+ }
22411
+ function getMimeType(ext) {
22412
+ const mimeTypes = {
22413
+ // Images
22414
+ "png": "image/png",
22415
+ "jpg": "image/jpeg",
22416
+ "jpeg": "image/jpeg",
22417
+ "gif": "image/gif",
22418
+ "svg": "image/svg+xml",
22419
+ "webp": "image/webp",
22420
+ "bmp": "image/bmp",
22421
+ "ico": "image/x-icon",
22422
+ "tiff": "image/tiff",
22423
+ "tif": "image/tiff",
22424
+ // Video
22425
+ "mp4": "video/mp4",
22426
+ "webm": "video/webm",
22427
+ "ogg": "video/ogg",
22428
+ "ogv": "video/ogg",
22429
+ "mov": "video/quicktime",
22430
+ "avi": "video/x-msvideo",
22431
+ "wmv": "video/x-ms-wmv",
22432
+ "flv": "video/x-flv",
22433
+ "mkv": "video/x-matroska",
22434
+ // Audio
22435
+ "mp3": "audio/mpeg",
22436
+ "wav": "audio/wav",
22437
+ "oga": "audio/ogg",
22438
+ "m4a": "audio/mp4",
22439
+ "flac": "audio/flac",
22440
+ "aac": "audio/aac",
22441
+ "weba": "audio/webm",
22442
+ // PDF
22443
+ "pdf": "application/pdf"
22444
+ };
22445
+ return mimeTypes[ext] || "application/octet-stream";
22446
+ }
22447
+ function isBinaryFile(fileName) {
22448
+ const { type } = getFileType(fileName);
22449
+ return type === "image" || type === "video" || type === "audio" || type === "pdf" || type === "binary";
22450
+ }
22217
22451
  let ShikiError$1 = class ShikiError extends Error {
22218
22452
  constructor(message) {
22219
22453
  super(message);
@@ -32753,45 +32987,11 @@ function getLanguage(ext) {
32753
32987
  const lang2 = ext ? langMap[ext] : null;
32754
32988
  return lang2 && SUPPORTED_LANGS.has(lang2) ? lang2 : null;
32755
32989
  }
32756
- function FileViewer({ filePath }) {
32757
- const { currentThreadId, fileContents, setFileContents } = useAppStore();
32758
- const [isLoading, setIsLoading] = reactExports.useState(false);
32759
- const [error, setError] = reactExports.useState(null);
32990
+ function CodeViewer({ filePath, content: content2 }) {
32760
32991
  const [highlightedHtml, setHighlightedHtml] = reactExports.useState(null);
32761
- const content2 = fileContents[filePath];
32762
32992
  const fileName = filePath.split("/").pop() || filePath;
32763
32993
  const ext = fileName.includes(".") ? fileName.split(".").pop()?.toLowerCase() : void 0;
32764
32994
  const language = reactExports.useMemo(() => getLanguage(ext), [ext]);
32765
- reactExports.useEffect(() => {
32766
- setError(null);
32767
- setHighlightedHtml(null);
32768
- }, [filePath]);
32769
- reactExports.useEffect(() => {
32770
- async function loadFile() {
32771
- if (!currentThreadId) {
32772
- setError("No thread selected");
32773
- return;
32774
- }
32775
- if (content2 !== void 0) {
32776
- return;
32777
- }
32778
- setIsLoading(true);
32779
- setError(null);
32780
- try {
32781
- const result = await window.api.workspace.readFile(currentThreadId, filePath);
32782
- if (result.success && result.content !== void 0) {
32783
- setFileContents(filePath, result.content);
32784
- } else {
32785
- setError(result.error || "Failed to read file");
32786
- }
32787
- } catch (e) {
32788
- setError(e instanceof Error ? e.message : "Failed to read file");
32789
- } finally {
32790
- setIsLoading(false);
32791
- }
32792
- }
32793
- loadFile();
32794
- }, [currentThreadId, filePath, content2, setFileContents]);
32795
32995
  reactExports.useEffect(() => {
32796
32996
  let cancelled = false;
32797
32997
  async function highlight() {
@@ -32800,7 +33000,7 @@ function FileViewer({ filePath }) {
32800
33000
  return;
32801
33001
  }
32802
33002
  try {
32803
- console.log("[FileViewer] Starting highlight for", language);
33003
+ console.log("[CodeViewer] Starting highlight for", language);
32804
33004
  const highlighter = await getHighlighter();
32805
33005
  if (cancelled) return;
32806
33006
  const html2 = highlighter.codeToHtml(content2, {
@@ -32808,10 +33008,10 @@ function FileViewer({ filePath }) {
32808
33008
  theme: "github-dark-default"
32809
33009
  });
32810
33010
  if (cancelled) return;
32811
- console.log("[FileViewer] Highlighting complete, html length:", html2.length);
33011
+ console.log("[CodeViewer] Highlighting complete, html length:", html2.length);
32812
33012
  setHighlightedHtml(html2);
32813
33013
  } catch (e) {
32814
- console.error("[FileViewer] Shiki highlighting failed:", e);
33014
+ console.error("[CodeViewer] Shiki highlighting failed:", e);
32815
33015
  setHighlightedHtml(null);
32816
33016
  }
32817
33017
  }
@@ -32821,27 +33021,6 @@ function FileViewer({ filePath }) {
32821
33021
  };
32822
33022
  }, [content2, language]);
32823
33023
  const lineCount = content2?.split("\n").length ?? 0;
32824
- if (isLoading) {
32825
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 items-center justify-center text-muted-foreground", children: [
32826
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-6 animate-spin mr-2" }),
32827
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Loading file..." })
32828
- ] });
32829
- }
32830
- if (error) {
32831
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center text-muted-foreground gap-3 p-8", children: [
32832
- /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "size-10 text-status-critical" }),
32833
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
32834
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-foreground mb-1", children: "Failed to load file" }),
32835
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm", children: error })
32836
- ] })
32837
- ] });
32838
- }
32839
- if (content2 === void 0) {
32840
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 items-center justify-center text-muted-foreground", children: [
32841
- /* @__PURE__ */ jsxRuntimeExports.jsx(FileCode, { className: "size-6 mr-2" }),
32842
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "No content" })
32843
- ] });
32844
- }
32845
33024
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col min-h-0 overflow-hidden", children: [
32846
33025
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-4 py-2 border-b border-border bg-background/50 text-xs text-muted-foreground shrink-0", children: [
32847
33026
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: filePath }),
@@ -32865,6 +33044,400 @@ function FileViewer({ filePath }) {
32865
33044
  ) }) })
32866
33045
  ] });
32867
33046
  }
33047
+ function ImageViewer({ filePath, base64Content, mimeType }) {
33048
+ const [zoom, setZoom] = reactExports.useState(100);
33049
+ const [rotation, setRotation] = reactExports.useState(0);
33050
+ const [isPanning, setIsPanning] = reactExports.useState(false);
33051
+ const [panStart, setPanStart] = reactExports.useState({ x: 0, y: 0 });
33052
+ const [panOffset, setPanOffset] = reactExports.useState({ x: 0, y: 0 });
33053
+ const containerRef = reactExports.useRef(null);
33054
+ const fileName = filePath.split("/").pop() || filePath;
33055
+ const imageUrl = `data:${mimeType};base64,${base64Content}`;
33056
+ const handleZoomIn = () => {
33057
+ setZoom((prev) => Math.min(prev + 25, 400));
33058
+ };
33059
+ const handleZoomOut = () => {
33060
+ setZoom((prev) => Math.max(prev - 25, 25));
33061
+ };
33062
+ const handleResetZoom = () => {
33063
+ setZoom(100);
33064
+ setRotation(0);
33065
+ setPanOffset({ x: 0, y: 0 });
33066
+ };
33067
+ const handleRotate = () => {
33068
+ setRotation((prev) => (prev + 90) % 360);
33069
+ };
33070
+ const handleMouseDown = (e) => {
33071
+ if (zoom > 100) {
33072
+ setIsPanning(true);
33073
+ setPanStart({ x: e.clientX - panOffset.x, y: e.clientY - panOffset.y });
33074
+ e.preventDefault();
33075
+ }
33076
+ };
33077
+ const handleMouseMove = (e) => {
33078
+ if (isPanning && zoom > 100) {
33079
+ setPanOffset({
33080
+ x: e.clientX - panStart.x,
33081
+ y: e.clientY - panStart.y
33082
+ });
33083
+ }
33084
+ };
33085
+ const handleMouseUp = () => {
33086
+ setIsPanning(false);
33087
+ };
33088
+ const handleMouseLeave = () => {
33089
+ setIsPanning(false);
33090
+ };
33091
+ reactExports.useEffect(() => {
33092
+ if (zoom <= 100) {
33093
+ setPanOffset({ x: 0, y: 0 });
33094
+ }
33095
+ }, [zoom]);
33096
+ const canPan = zoom > 100;
33097
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col min-h-0 overflow-hidden", children: [
33098
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2 px-4 py-2 border-b border-border bg-background/50 shrink-0", children: [
33099
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground overflow-hidden", children: [
33100
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: fileName }),
33101
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground/50", children: "•" }),
33102
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Image" }),
33103
+ canPan && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
33104
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground/50", children: "•" }),
33105
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-1", children: [
33106
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Hand, { className: "size-3" }),
33107
+ "Drag to pan"
33108
+ ] })
33109
+ ] })
33110
+ ] }),
33111
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
33112
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33113
+ Button,
33114
+ {
33115
+ variant: "ghost",
33116
+ size: "sm",
33117
+ onClick: handleZoomOut,
33118
+ disabled: zoom <= 25,
33119
+ className: "h-7 px-2",
33120
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { className: "size-4" })
33121
+ }
33122
+ ),
33123
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-xs text-muted-foreground min-w-[3rem] text-center", children: [
33124
+ zoom,
33125
+ "%"
33126
+ ] }),
33127
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33128
+ Button,
33129
+ {
33130
+ variant: "ghost",
33131
+ size: "sm",
33132
+ onClick: handleZoomIn,
33133
+ disabled: zoom >= 400,
33134
+ className: "h-7 px-2",
33135
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { className: "size-4" })
33136
+ }
33137
+ ),
33138
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33139
+ Button,
33140
+ {
33141
+ variant: "ghost",
33142
+ size: "sm",
33143
+ onClick: handleRotate,
33144
+ className: "h-7 px-2",
33145
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(RotateCw, { className: "size-4" })
33146
+ }
33147
+ ),
33148
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33149
+ Button,
33150
+ {
33151
+ variant: "ghost",
33152
+ size: "sm",
33153
+ onClick: handleResetZoom,
33154
+ className: "h-7 px-2",
33155
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(Maximize2, { className: "size-4" })
33156
+ }
33157
+ )
33158
+ ] })
33159
+ ] }),
33160
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
33161
+ "div",
33162
+ {
33163
+ ref: containerRef,
33164
+ className: "flex items-center justify-center min-h-full p-8 overflow-hidden",
33165
+ onMouseDown: handleMouseDown,
33166
+ onMouseMove: handleMouseMove,
33167
+ onMouseUp: handleMouseUp,
33168
+ onMouseLeave: handleMouseLeave,
33169
+ style: {
33170
+ cursor: canPan ? isPanning ? "grabbing" : "grab" : "default",
33171
+ userSelect: "none"
33172
+ },
33173
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
33174
+ "img",
33175
+ {
33176
+ src: imageUrl,
33177
+ alt: fileName,
33178
+ className: "max-w-full h-auto transition-transform duration-200",
33179
+ style: {
33180
+ transform: `translate(${panOffset.x}px, ${panOffset.y}px) scale(${zoom / 100}) rotate(${rotation}deg)`,
33181
+ imageRendering: zoom > 100 ? "pixelated" : "auto"
33182
+ },
33183
+ draggable: false
33184
+ }
33185
+ )
33186
+ }
33187
+ ) })
33188
+ ] });
33189
+ }
33190
+ function MediaViewer({ filePath, base64Content, mimeType, mediaType }) {
33191
+ const fileName = filePath.split("/").pop() || filePath;
33192
+ const mediaUrl = `data:${mimeType};base64,${base64Content}`;
33193
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col min-h-0 overflow-hidden", children: [
33194
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-4 py-2 border-b border-border bg-background/50 text-xs text-muted-foreground shrink-0", children: [
33195
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: fileName }),
33196
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground/50", children: "•" }),
33197
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "capitalize", children: mediaType })
33198
+ ] }),
33199
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-center justify-center min-h-full p-8 gap-6", children: mediaType === "video" ? /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
33200
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Video, { className: "size-16 text-muted-foreground/30" }),
33201
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
33202
+ "video",
33203
+ {
33204
+ controls: true,
33205
+ className: "max-w-full max-h-[70vh] rounded-lg shadow-lg",
33206
+ preload: "metadata",
33207
+ children: [
33208
+ /* @__PURE__ */ jsxRuntimeExports.jsx("source", { src: mediaUrl, type: mimeType }),
33209
+ "Your browser does not support the video tag."
33210
+ ]
33211
+ }
33212
+ )
33213
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
33214
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
33215
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-32 h-32 rounded-full bg-accent/10 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Music, { className: "size-16 text-muted-foreground/50" }) }),
33216
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
33217
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-foreground", children: fileName }),
33218
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: "Audio File" })
33219
+ ] })
33220
+ ] }),
33221
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
33222
+ "audio",
33223
+ {
33224
+ controls: true,
33225
+ className: "w-full max-w-md",
33226
+ preload: "metadata",
33227
+ children: [
33228
+ /* @__PURE__ */ jsxRuntimeExports.jsx("source", { src: mediaUrl, type: mimeType }),
33229
+ "Your browser does not support the audio tag."
33230
+ ]
33231
+ }
33232
+ )
33233
+ ] }) }) })
33234
+ ] });
33235
+ }
33236
+ function PDFViewer({ filePath, base64Content }) {
33237
+ const fileName = filePath.split("/").pop() || filePath;
33238
+ const pdfUrl = `data:application/pdf;base64,${base64Content}`;
33239
+ const handleOpenExternal = () => {
33240
+ const link2 = document.createElement("a");
33241
+ link2.href = pdfUrl;
33242
+ link2.download = fileName;
33243
+ link2.click();
33244
+ };
33245
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col min-h-0 overflow-hidden", children: [
33246
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2 px-4 py-2 border-b border-border bg-background/50 shrink-0", children: [
33247
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground overflow-hidden", children: [
33248
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: fileName }),
33249
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground/50", children: "•" }),
33250
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "PDF Document" })
33251
+ ] }),
33252
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
33253
+ Button,
33254
+ {
33255
+ variant: "ghost",
33256
+ size: "sm",
33257
+ onClick: handleOpenExternal,
33258
+ className: "h-7 px-2 gap-1",
33259
+ children: [
33260
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-3" }),
33261
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs", children: "Download" })
33262
+ ]
33263
+ }
33264
+ )
33265
+ ] }),
33266
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ScrollArea, { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-col items-center min-h-full bg-muted/30", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
33267
+ "object",
33268
+ {
33269
+ data: pdfUrl,
33270
+ type: "application/pdf",
33271
+ className: "w-full h-full min-h-[600px]",
33272
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center min-h-[600px] gap-4 p-8", children: [
33273
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "size-16 text-muted-foreground/50" }),
33274
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
33275
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-foreground mb-2", children: fileName }),
33276
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground mb-4", children: "PDF preview not available in this browser" }),
33277
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { onClick: handleOpenExternal, variant: "outline", children: [
33278
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ExternalLink, { className: "size-4 mr-2" }),
33279
+ "Download PDF"
33280
+ ] })
33281
+ ] })
33282
+ ] })
33283
+ }
33284
+ ) }) })
33285
+ ] });
33286
+ }
33287
+ function BinaryFileViewer({ filePath, size: size2 }) {
33288
+ const fileName = filePath.split("/").pop() || filePath;
33289
+ const ext = fileName.includes(".") ? fileName.split(".").pop()?.toUpperCase() : "FILE";
33290
+ const formatSize2 = (bytes) => {
33291
+ if (!bytes) return "Unknown size";
33292
+ if (bytes < 1024) return `${bytes} B`;
33293
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
33294
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
33295
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
33296
+ };
33297
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col min-h-0 overflow-hidden", children: [
33298
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 px-4 py-2 border-b border-border bg-background/50 text-xs text-muted-foreground shrink-0", children: [
33299
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "truncate", children: fileName }),
33300
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground/50", children: "•" }),
33301
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Binary File" }),
33302
+ size2 && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
33303
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground/50", children: "•" }),
33304
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: formatSize2(size2) })
33305
+ ] })
33306
+ ] }),
33307
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center gap-6 p-8 text-center", children: [
33308
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-24 h-24 rounded-2xl bg-accent/10 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntimeExports.jsx(File, { className: "size-12 text-muted-foreground/50" }) }),
33309
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { children: [
33310
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-foreground mb-1", children: fileName }),
33311
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-sm text-muted-foreground mb-2", children: [
33312
+ ext,
33313
+ " file • ",
33314
+ formatSize2(size2)
33315
+ ] }),
33316
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground max-w-md", children: "This file type cannot be previewed in the viewer. You can open it with an external application." })
33317
+ ] }),
33318
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(Button, { variant: "outline", className: "gap-2", children: [
33319
+ /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "size-4" }),
33320
+ "Open Externally"
33321
+ ] })
33322
+ ] })
33323
+ ] });
33324
+ }
33325
+ function FileViewer({ filePath }) {
33326
+ const { currentThreadId, fileContents, setFileContents } = useAppStore();
33327
+ const [isLoading, setIsLoading] = reactExports.useState(false);
33328
+ const [error, setError] = reactExports.useState(null);
33329
+ const [binaryContent, setBinaryContent] = reactExports.useState(null);
33330
+ const [fileSize, setFileSize] = reactExports.useState();
33331
+ const fileName = filePath.split("/").pop() || filePath;
33332
+ const fileTypeInfo = reactExports.useMemo(() => getFileType(fileName), [fileName]);
33333
+ const isBinary = reactExports.useMemo(() => isBinaryFile(fileName), [fileName]);
33334
+ const content2 = fileContents[filePath];
33335
+ reactExports.useEffect(() => {
33336
+ setError(null);
33337
+ setBinaryContent(null);
33338
+ setFileSize(void 0);
33339
+ }, [filePath]);
33340
+ reactExports.useEffect(() => {
33341
+ async function loadFile() {
33342
+ if (!currentThreadId) {
33343
+ setError("No thread selected");
33344
+ return;
33345
+ }
33346
+ if (content2 !== void 0 || binaryContent !== null) {
33347
+ return;
33348
+ }
33349
+ setIsLoading(true);
33350
+ setError(null);
33351
+ try {
33352
+ if (isBinary) {
33353
+ const result = await window.api.workspace.readBinaryFile(currentThreadId, filePath);
33354
+ if (result.success && result.content !== void 0) {
33355
+ setBinaryContent(result.content);
33356
+ setFileSize(result.size);
33357
+ } else {
33358
+ setError(result.error || "Failed to read file");
33359
+ }
33360
+ } else {
33361
+ const result = await window.api.workspace.readFile(currentThreadId, filePath);
33362
+ if (result.success && result.content !== void 0) {
33363
+ setFileContents(filePath, result.content);
33364
+ setFileSize(result.size);
33365
+ } else {
33366
+ setError(result.error || "Failed to read file");
33367
+ }
33368
+ }
33369
+ } catch (e) {
33370
+ setError(e instanceof Error ? e.message : "Failed to read file");
33371
+ } finally {
33372
+ setIsLoading(false);
33373
+ }
33374
+ }
33375
+ loadFile();
33376
+ }, [currentThreadId, filePath, content2, binaryContent, setFileContents, isBinary]);
33377
+ if (isLoading) {
33378
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 items-center justify-center text-muted-foreground", children: [
33379
+ /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "size-6 animate-spin mr-2" }),
33380
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Loading file..." })
33381
+ ] });
33382
+ }
33383
+ if (error) {
33384
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 flex-col items-center justify-center text-muted-foreground gap-3 p-8", children: [
33385
+ /* @__PURE__ */ jsxRuntimeExports.jsx(CircleAlert, { className: "size-10 text-status-critical" }),
33386
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "text-center", children: [
33387
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium text-foreground mb-1", children: "Failed to load file" }),
33388
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm", children: error })
33389
+ ] })
33390
+ ] });
33391
+ }
33392
+ if (content2 === void 0 && binaryContent === null) {
33393
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-1 items-center justify-center text-muted-foreground", children: [
33394
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FileCode, { className: "size-6 mr-2" }),
33395
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "No content" })
33396
+ ] });
33397
+ }
33398
+ if (fileTypeInfo.type === "image" && binaryContent) {
33399
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
33400
+ ImageViewer,
33401
+ {
33402
+ filePath,
33403
+ base64Content: binaryContent,
33404
+ mimeType: fileTypeInfo.mimeType || "image/png"
33405
+ }
33406
+ );
33407
+ }
33408
+ if (fileTypeInfo.type === "video" && binaryContent) {
33409
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
33410
+ MediaViewer,
33411
+ {
33412
+ filePath,
33413
+ base64Content: binaryContent,
33414
+ mimeType: fileTypeInfo.mimeType || "video/mp4",
33415
+ mediaType: "video"
33416
+ }
33417
+ );
33418
+ }
33419
+ if (fileTypeInfo.type === "audio" && binaryContent) {
33420
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
33421
+ MediaViewer,
33422
+ {
33423
+ filePath,
33424
+ base64Content: binaryContent,
33425
+ mimeType: fileTypeInfo.mimeType || "audio/mpeg",
33426
+ mediaType: "audio"
33427
+ }
33428
+ );
33429
+ }
33430
+ if (fileTypeInfo.type === "pdf" && binaryContent) {
33431
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(PDFViewer, { filePath, base64Content: binaryContent });
33432
+ }
33433
+ if (fileTypeInfo.type === "binary") {
33434
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(BinaryFileViewer, { filePath, size: fileSize });
33435
+ }
33436
+ if (content2 !== void 0) {
33437
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(CodeViewer, { filePath, content: content2 });
33438
+ }
33439
+ return null;
33440
+ }
32868
33441
  const CR = "\r".charCodeAt(0);
32869
33442
  const LF = "\n".charCodeAt(0);
32870
33443
  const NULL = "\0".charCodeAt(0);
@@ -73505,15 +74078,20 @@ function AnthropicIcon({ className }) {
73505
74078
  function OpenAIIcon({ className }) {
73506
74079
  return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z" }) });
73507
74080
  }
74081
+ function GoogleIcon({ className }) {
74082
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("svg", { className, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsxRuntimeExports.jsx("path", { d: "M20.616 10.835a14.147 14.147 0 01-4.45-3.001 14.111 14.111 0 01-3.678-6.452.503.503 0 00-.975 0 14.134 14.134 0 01-3.679 6.452 14.155 14.155 0 01-4.45 3.001c-.65.28-1.318.505-2.002.678a.502.502 0 000 .975c.684.172 1.35.397 2.002.677a14.147 14.147 0 014.45 3.001 14.112 14.112 0 013.679 6.453.502.502 0 00.975 0c.172-.685.397-1.351.677-2.003a14.145 14.145 0 013.001-4.45 14.113 14.113 0 016.453-3.678.503.503 0 000-.975 13.245 13.245 0 01-2.003-.678z" }) });
74083
+ }
73508
74084
  const PROVIDER_ICONS = {
73509
74085
  anthropic: AnthropicIcon,
73510
74086
  openai: OpenAIIcon,
74087
+ google: GoogleIcon,
73511
74088
  ollama: () => null
73512
74089
  // No icon for ollama yet
73513
74090
  };
73514
74091
  const FALLBACK_PROVIDERS = [
73515
74092
  { id: "anthropic", name: "Anthropic", hasApiKey: false },
73516
- { id: "openai", name: "OpenAI", hasApiKey: false }
74093
+ { id: "openai", name: "OpenAI", hasApiKey: false },
74094
+ { id: "google", name: "Google", hasApiKey: false }
73517
74095
  ];
73518
74096
  function ModelSwitcher() {
73519
74097
  const [open, setOpen] = reactExports.useState(false);
@@ -75565,7 +76143,7 @@ function App() {
75565
76143
  },
75566
76144
  children: [
75567
76145
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-name", children: "OPENWORK" }),
75568
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.1.1-rc.6" })
76146
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "app-badge-version", children: "0.1.2" })
75569
76147
  ]
75570
76148
  }
75571
76149
  ),
@@ -67,6 +67,7 @@
67
67
  --color-black: #000;
68
68
  --color-white: #fff;
69
69
  --spacing: .25rem;
70
+ --container-md: 28rem;
70
71
  --container-lg: 32rem;
71
72
  --container-3xl: 48rem;
72
73
  --text-xs: .75rem;
@@ -590,6 +591,11 @@
590
591
  height: calc(var(--spacing) * 12);
591
592
  }
592
593
 
594
+ .size-16 {
595
+ width: calc(var(--spacing) * 16);
596
+ height: calc(var(--spacing) * 16);
597
+ }
598
+
593
599
  .h-1 {
594
600
  height: calc(var(--spacing) * 1);
595
601
  }
@@ -634,6 +640,10 @@
634
640
  height: calc(var(--spacing) * 12);
635
641
  }
636
642
 
643
+ .h-24 {
644
+ height: calc(var(--spacing) * 24);
645
+ }
646
+
637
647
  .h-32 {
638
648
  height: calc(var(--spacing) * 32);
639
649
  }
@@ -650,6 +660,10 @@
650
660
  height: 200px;
651
661
  }
652
662
 
663
+ .h-auto {
664
+ height: auto;
665
+ }
666
+
653
667
  .h-full {
654
668
  height: 100%;
655
669
  }
@@ -678,6 +692,10 @@
678
692
  max-height: calc(var(--spacing) * 48);
679
693
  }
680
694
 
695
+ .max-h-\[70vh\] {
696
+ max-height: 70vh;
697
+ }
698
+
681
699
  .min-h-0 {
682
700
  min-height: calc(var(--spacing) * 0);
683
701
  }
@@ -686,6 +704,14 @@
686
704
  min-height: 240px;
687
705
  }
688
706
 
707
+ .min-h-\[600px\] {
708
+ min-height: 600px;
709
+ }
710
+
711
+ .min-h-full {
712
+ min-height: 100%;
713
+ }
714
+
689
715
  .w-2 {
690
716
  width: calc(var(--spacing) * 2);
691
717
  }
@@ -710,6 +736,14 @@
710
736
  width: calc(var(--spacing) * 16);
711
737
  }
712
738
 
739
+ .w-24 {
740
+ width: calc(var(--spacing) * 24);
741
+ }
742
+
743
+ .w-32 {
744
+ width: calc(var(--spacing) * 32);
745
+ }
746
+
713
747
  .w-64 {
714
748
  width: calc(var(--spacing) * 64);
715
749
  }
@@ -754,14 +788,26 @@
754
788
  max-width: 200px;
755
789
  }
756
790
 
791
+ .max-w-full {
792
+ max-width: 100%;
793
+ }
794
+
757
795
  .max-w-lg {
758
796
  max-width: var(--container-lg);
759
797
  }
760
798
 
799
+ .max-w-md {
800
+ max-width: var(--container-md);
801
+ }
802
+
761
803
  .min-w-0 {
762
804
  min-width: calc(var(--spacing) * 0);
763
805
  }
764
806
 
807
+ .min-w-\[3rem\] {
808
+ min-width: 3rem;
809
+ }
810
+
765
811
  .min-w-\[8rem\] {
766
812
  min-width: 8rem;
767
813
  }
@@ -893,6 +939,10 @@
893
939
  gap: calc(var(--spacing) * 4);
894
940
  }
895
941
 
942
+ .gap-6 {
943
+ gap: calc(var(--spacing) * 6);
944
+ }
945
+
896
946
  :where(.space-y-0\.5 > :not(:last-child)) {
897
947
  --tw-space-y-reverse: 0;
898
948
  margin-block-start: calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));
@@ -961,6 +1011,10 @@
961
1011
  border-radius: .25rem;
962
1012
  }
963
1013
 
1014
+ .rounded-2xl {
1015
+ border-radius: calc(var(--radius) + 4px);
1016
+ }
1017
+
964
1018
  .rounded-\[inherit\] {
965
1019
  border-radius: inherit;
966
1020
  }
@@ -969,6 +1023,10 @@
969
1023
  border-radius: 3.40282e38px;
970
1024
  }
971
1025
 
1026
+ .rounded-lg {
1027
+ border-radius: calc(var(--radius) + 1px);
1028
+ }
1029
+
972
1030
  .rounded-md {
973
1031
  border-radius: var(--radius);
974
1032
  }
@@ -1122,6 +1180,16 @@
1122
1180
  border-left-color: #0000;
1123
1181
  }
1124
1182
 
1183
+ .bg-accent\/10 {
1184
+ background-color: var(--accent);
1185
+ }
1186
+
1187
+ @supports (color: color-mix(in lab, red, red)) {
1188
+ .bg-accent\/10 {
1189
+ background-color: color-mix(in oklab, var(--accent) 10%, transparent);
1190
+ }
1191
+ }
1192
+
1125
1193
  .bg-amber-500\/5 {
1126
1194
  background-color: #f99c000d;
1127
1195
  }
@@ -1650,7 +1718,17 @@
1650
1718
  color: var(--color-green-400);
1651
1719
  }
1652
1720
 
1653
- .text-muted-foreground, .text-muted-foreground\/50 {
1721
+ .text-muted-foreground, .text-muted-foreground\/30 {
1722
+ color: var(--muted-foreground);
1723
+ }
1724
+
1725
+ @supports (color: color-mix(in lab, red, red)) {
1726
+ .text-muted-foreground\/30 {
1727
+ color: color-mix(in oklab, var(--muted-foreground) 30%, transparent);
1728
+ }
1729
+ }
1730
+
1731
+ .text-muted-foreground\/50 {
1654
1732
  color: var(--muted-foreground);
1655
1733
  }
1656
1734
 
@@ -1734,6 +1812,10 @@
1734
1812
  color: var(--color-yellow-600);
1735
1813
  }
1736
1814
 
1815
+ .capitalize {
1816
+ text-transform: capitalize;
1817
+ }
1818
+
1737
1819
  .uppercase {
1738
1820
  text-transform: uppercase;
1739
1821
  }
@@ -7,8 +7,8 @@
7
7
  http-equiv="Content-Security-Policy"
8
8
  content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
9
9
  />
10
- <script type="module" crossorigin src="./assets/index-BOB_WPKv.js"></script>
11
- <link rel="stylesheet" crossorigin href="./assets/index-CK8V1Wgb.css">
10
+ <script type="module" crossorigin src="./assets/index-B2jeuXtB.js"></script>
11
+ <link rel="stylesheet" crossorigin href="./assets/index-BGLhxKzd.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openwork",
3
- "version": "0.1.1-rc.6",
3
+ "version": "0.1.2",
4
4
  "description": "A tactical agent interface for deepagentsjs",
5
5
  "main": "./out/main/index.js",
6
6
  "files": [
@@ -53,6 +53,7 @@
53
53
  "@langchain/langgraph-checkpoint": "^1.0.0",
54
54
  "@langchain/langgraph-sdk": "^1.5.3",
55
55
  "@langchain/openai": "^1.2.2",
56
+ "@langchain/google-genai": "^2.1.10",
56
57
  "@radix-ui/react-context-menu": "^2.2.16",
57
58
  "@radix-ui/react-dialog": "^1.1.15",
58
59
  "@radix-ui/react-dropdown-menu": "^2.1.16",