markpdfdown 0.1.8-beta.6 → 0.2.0

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.
Files changed (51) hide show
  1. package/dist/main/index.js +398 -14
  2. package/dist/preload/index.js +20 -1
  3. package/dist/renderer/assets/{index-yTU2skrN.css → index-B_JfRqwM.css} +54 -0
  4. package/dist/renderer/assets/{index-iEK8qT5k.js → index-wHB9i2kW.js} +623 -320
  5. package/dist/renderer/index.html +2 -2
  6. package/package.json +10 -3
  7. package/dist/app/app.js +0 -49
  8. package/dist/app/controllers/completionController.js +0 -19
  9. package/dist/app/controllers/modelController.js +0 -53
  10. package/dist/app/controllers/providerController.js +0 -120
  11. package/dist/app/dal/modelDal.js +0 -44
  12. package/dist/app/dal/providerDal.js +0 -78
  13. package/dist/app/db/index.js +0 -56
  14. package/dist/app/db/migration.js +0 -157
  15. package/dist/app/logic/llm/AnthropicClient.js +0 -219
  16. package/dist/app/logic/llm/AzureOpenAIClient.js +0 -239
  17. package/dist/app/logic/llm/GeminiClient.js +0 -212
  18. package/dist/app/logic/llm/LLMClient.js +0 -80
  19. package/dist/app/logic/llm/OpenAIClient.js +0 -235
  20. package/dist/app/logic/llm/example-advanced.js +0 -232
  21. package/dist/app/logic/llm/index.js +0 -14
  22. package/dist/app/logic/model.js +0 -27
  23. package/dist/app/middleware/logger.js +0 -23
  24. package/dist/app/routes/routes.js +0 -16
  25. package/dist/app/types/Provider.js +0 -1
  26. package/dist/server/controllers/FileController.js +0 -64
  27. package/dist/server/controllers/TaskController.js +0 -57
  28. package/dist/server/controllers/completionController.js +0 -64
  29. package/dist/server/controllers/modelController.js +0 -74
  30. package/dist/server/controllers/providerController.js +0 -120
  31. package/dist/server/dal/TaskDal.js +0 -67
  32. package/dist/server/dal/modelDal.js +0 -44
  33. package/dist/server/dal/providerDal.js +0 -83
  34. package/dist/server/db/index.js +0 -57
  35. package/dist/server/db/migration.js +0 -157
  36. package/dist/server/index.js +0 -49
  37. package/dist/server/logic/File.js +0 -34
  38. package/dist/server/logic/Task.js +0 -21
  39. package/dist/server/logic/llm/AnthropicClient.js +0 -220
  40. package/dist/server/logic/llm/AzureOpenAIClient.js +0 -239
  41. package/dist/server/logic/llm/GeminiClient.js +0 -213
  42. package/dist/server/logic/llm/LLMClient.js +0 -83
  43. package/dist/server/logic/llm/OllamaClient.js +0 -220
  44. package/dist/server/logic/llm/OpenAIClient.js +0 -235
  45. package/dist/server/logic/llm/example-advanced.js +0 -231
  46. package/dist/server/logic/llm/index.js +0 -15
  47. package/dist/server/logic/model.js +0 -59
  48. package/dist/server/middleware/logger.js +0 -23
  49. package/dist/server/routes/routes.js +0 -30
  50. package/dist/server/types/Provider.js +0 -1
  51. package/dist/server/types/Task.js +0 -1
@@ -12,6 +12,7 @@ import sharp from "sharp";
12
12
  import fs$1 from "fs/promises";
13
13
  import { v4 } from "uuid";
14
14
  import { createRequire } from "module";
15
+ import pkg$1 from "electron-updater";
15
16
  import __cjs_mod__ from "node:module";
16
17
  const __filename = import.meta.filename;
17
18
  const __dirname = import.meta.dirname;
@@ -248,6 +249,16 @@ var PageStatus = /* @__PURE__ */ ((PageStatus2) => {
248
249
  PageStatus2[PageStatus2["RETRYING"] = 3] = "RETRYING";
249
250
  return PageStatus2;
250
251
  })(PageStatus || {});
252
+ var UpdateStatus = /* @__PURE__ */ ((UpdateStatus2) => {
253
+ UpdateStatus2["IDLE"] = "idle";
254
+ UpdateStatus2["CHECKING"] = "checking";
255
+ UpdateStatus2["AVAILABLE"] = "available";
256
+ UpdateStatus2["NOT_AVAILABLE"] = "not_available";
257
+ UpdateStatus2["DOWNLOADING"] = "downloading";
258
+ UpdateStatus2["DOWNLOADED"] = "downloaded";
259
+ UpdateStatus2["ERROR"] = "error";
260
+ return UpdateStatus2;
261
+ })(UpdateStatus || {});
251
262
  var TaskEventType = /* @__PURE__ */ ((TaskEventType2) => {
252
263
  TaskEventType2["TASK_UPDATED"] = "task:updated";
253
264
  TaskEventType2["TASK_STATUS_CHANGED"] = "task:status_changed";
@@ -2439,6 +2450,151 @@ class WorkerOrchestrator {
2439
2450
  }
2440
2451
  }
2441
2452
  const workerOrchestrator = new WorkerOrchestrator();
2453
+ const providerPresets = [
2454
+ {
2455
+ name: "OpenAI",
2456
+ type: "openai-responses",
2457
+ apiBase: "https://api.openai.com/v1",
2458
+ modelListApi: "/models",
2459
+ modelNameField: "id",
2460
+ modelIdField: "id",
2461
+ defaultModels: []
2462
+ },
2463
+ {
2464
+ name: "Anthropic",
2465
+ type: "anthropic",
2466
+ apiBase: "https://api.anthropic.com/v1",
2467
+ modelListApi: "/models",
2468
+ modelNameField: "display_name",
2469
+ modelIdField: "id",
2470
+ capabilityField: "input_modalities",
2471
+ capabilityFilter: "image",
2472
+ defaultModels: []
2473
+ },
2474
+ {
2475
+ name: "Gemini",
2476
+ type: "gemini",
2477
+ apiBase: "https://generativelanguage.googleapis.com/v1beta",
2478
+ modelListApi: "/models",
2479
+ modelNameField: "displayName",
2480
+ modelIdField: "name",
2481
+ defaultModels: []
2482
+ },
2483
+ {
2484
+ name: "ZenMux",
2485
+ type: "anthropic",
2486
+ apiBase: "https://zenmux.ai/api/anthropic/v1",
2487
+ modelListApi: "/models",
2488
+ modelNameField: "display_name",
2489
+ modelIdField: "id",
2490
+ capabilityField: "input_modalities",
2491
+ capabilityFilter: "image",
2492
+ defaultModels: []
2493
+ },
2494
+ {
2495
+ name: "OpenRouter",
2496
+ type: "openai",
2497
+ apiBase: "https://openrouter.ai/api/v1",
2498
+ modelListApi: "/models",
2499
+ modelNameField: "name",
2500
+ modelIdField: "id",
2501
+ capabilityField: "architecture.input_modalities",
2502
+ capabilityFilter: "image",
2503
+ defaultModels: []
2504
+ },
2505
+ {
2506
+ name: "SiliconFlow",
2507
+ type: "openai",
2508
+ apiBase: "https://api.siliconflow.cn/v1",
2509
+ modelListApi: "/models",
2510
+ modelNameField: "id",
2511
+ modelIdField: "id",
2512
+ defaultModels: []
2513
+ },
2514
+ {
2515
+ name: "Ollama",
2516
+ type: "ollama",
2517
+ apiBase: "http://localhost:11434/api",
2518
+ modelListApi: "/tags",
2519
+ modelNameField: "name",
2520
+ modelIdField: "name",
2521
+ defaultModels: []
2522
+ }
2523
+ ];
2524
+ function findProviderPreset(type, name) {
2525
+ return providerPresets.find(
2526
+ (preset) => preset.type === type && preset.name === name
2527
+ );
2528
+ }
2529
+ function getProviderPresetKey(type, name) {
2530
+ return `${type}:${name}`;
2531
+ }
2532
+ class PresetProviderService {
2533
+ static instance;
2534
+ initialized = false;
2535
+ constructor() {
2536
+ }
2537
+ static getInstance() {
2538
+ if (!PresetProviderService.instance) {
2539
+ PresetProviderService.instance = new PresetProviderService();
2540
+ }
2541
+ return PresetProviderService.instance;
2542
+ }
2543
+ /**
2544
+ * 初始化预设供应商
2545
+ * 使用 type + name 去重,若不存在则插入,status = -1(禁用)
2546
+ */
2547
+ async initialize() {
2548
+ if (this.initialized) {
2549
+ console.log("[PresetProviderService] Already initialized, skipping...");
2550
+ return;
2551
+ }
2552
+ try {
2553
+ console.log("[PresetProviderService] Starting preset provider injection...");
2554
+ const existingProviders = await providerRepository.findAllIncludeDisabled();
2555
+ const existingKeys = new Set(
2556
+ existingProviders.map((p) => getProviderPresetKey(p.type, p.name))
2557
+ );
2558
+ let insertedCount = 0;
2559
+ for (const preset of providerPresets) {
2560
+ const key = getProviderPresetKey(preset.type, preset.name);
2561
+ if (!existingKeys.has(key)) {
2562
+ await providerRepository.create({
2563
+ name: preset.name,
2564
+ type: preset.type,
2565
+ api_key: "",
2566
+ base_url: preset.apiBase,
2567
+ suffix: "",
2568
+ status: -1
2569
+ // 默认禁用
2570
+ });
2571
+ console.log(
2572
+ `[PresetProviderService] Inserted preset provider: ${preset.name} (${preset.type})`
2573
+ );
2574
+ insertedCount++;
2575
+ } else {
2576
+ console.log(
2577
+ `[PresetProviderService] Preset provider already exists: ${preset.name} (${preset.type})`
2578
+ );
2579
+ }
2580
+ }
2581
+ console.log(
2582
+ `[PresetProviderService] Preset provider injection completed. Inserted: ${insertedCount}`
2583
+ );
2584
+ this.initialized = true;
2585
+ } catch (error) {
2586
+ console.error("[PresetProviderService] Failed to initialize preset providers:", error);
2587
+ throw error;
2588
+ }
2589
+ }
2590
+ /**
2591
+ * 重置初始化状态(主要用于测试)
2592
+ */
2593
+ reset() {
2594
+ this.initialized = false;
2595
+ }
2596
+ }
2597
+ const presetProviderService = PresetProviderService.getInstance();
2442
2598
  const IPC_CHANNELS = {
2443
2599
  // Provider channels
2444
2600
  PROVIDER: {
@@ -2447,7 +2603,9 @@ const IPC_CHANNELS = {
2447
2603
  CREATE: "provider:create",
2448
2604
  UPDATE: "provider:update",
2449
2605
  DELETE: "provider:delete",
2450
- UPDATE_STATUS: "provider:updateStatus"
2606
+ UPDATE_STATUS: "provider:updateStatus",
2607
+ GET_PRESETS: "provider:getPresets",
2608
+ FETCH_MODEL_LIST: "provider:fetchModelList"
2451
2609
  },
2452
2610
  // Model channels
2453
2611
  MODEL: {
@@ -2485,6 +2643,15 @@ const IPC_CHANNELS = {
2485
2643
  COMPLETION: {
2486
2644
  MARK_IMAGEDOWN: "completion:markImagedown",
2487
2645
  TEST_CONNECTION: "completion:testConnection"
2646
+ },
2647
+ // Event channels (for event bridge)
2648
+ EVENTS: {
2649
+ UPDATER_STATUS: "updater:status"
2650
+ },
2651
+ // Updater channels
2652
+ UPDATER: {
2653
+ CHECK_FOR_UPDATES: "updater:checkForUpdates",
2654
+ QUIT_AND_INSTALL: "updater:quitAndInstall"
2488
2655
  }
2489
2656
  };
2490
2657
  function registerProviderHandlers() {
@@ -2590,8 +2757,135 @@ function registerProviderHandlers() {
2590
2757
  }
2591
2758
  }
2592
2759
  );
2760
+ ipcMain.handle(
2761
+ IPC_CHANNELS.PROVIDER.GET_PRESETS,
2762
+ async () => {
2763
+ try {
2764
+ return { success: true, data: providerPresets };
2765
+ } catch (error) {
2766
+ console.error("[IPC] provider:getPresets error:", error);
2767
+ return { success: false, error: error.message };
2768
+ }
2769
+ }
2770
+ );
2771
+ ipcMain.handle(
2772
+ IPC_CHANNELS.PROVIDER.FETCH_MODEL_LIST,
2773
+ async (_, providerId) => {
2774
+ try {
2775
+ const provider = await providerRepository.findById(providerId);
2776
+ if (!provider) {
2777
+ return { success: false, error: "Provider not found" };
2778
+ }
2779
+ const preset = findProviderPreset(provider.type, provider.name);
2780
+ const baseUrl = provider.base_url || preset?.apiBase;
2781
+ if (!baseUrl) {
2782
+ return {
2783
+ success: false,
2784
+ error: "No API base URL configured for this provider"
2785
+ };
2786
+ }
2787
+ const modelListApi = preset?.modelListApi ?? "/models";
2788
+ const modelListUrl = `${baseUrl}${modelListApi}`;
2789
+ const headers = {
2790
+ "Content-Type": "application/json"
2791
+ };
2792
+ if (provider.api_key) {
2793
+ switch (provider.type) {
2794
+ case "anthropic":
2795
+ headers["x-api-key"] = provider.api_key;
2796
+ headers["anthropic-version"] = "2023-06-01";
2797
+ break;
2798
+ case "gemini":
2799
+ headers["x-goog-api-key"] = provider.api_key;
2800
+ break;
2801
+ default:
2802
+ headers["Authorization"] = `Bearer ${provider.api_key}`;
2803
+ break;
2804
+ }
2805
+ }
2806
+ const response = await fetch(modelListUrl, {
2807
+ method: "GET",
2808
+ headers
2809
+ });
2810
+ if (!response.ok) {
2811
+ const errorText = await response.text();
2812
+ console.error(
2813
+ `[IPC] provider:fetchModelList HTTP error: ${response.status}`,
2814
+ errorText
2815
+ );
2816
+ return {
2817
+ success: false,
2818
+ error: `Failed to fetch models: HTTP ${response.status}`
2819
+ };
2820
+ }
2821
+ const data = await response.json();
2822
+ const filterByCapability = (items) => {
2823
+ if (!preset?.capabilityField || !preset?.capabilityFilter) return items;
2824
+ return items.filter((item) => {
2825
+ const value = getNestedValue(item, preset.capabilityField);
2826
+ if (!Array.isArray(value)) return true;
2827
+ return value.includes(preset.capabilityFilter);
2828
+ });
2829
+ };
2830
+ let models = [];
2831
+ const modelIdField = preset?.modelIdField ?? "id";
2832
+ const modelNameField = preset?.modelNameField ?? "id";
2833
+ switch (provider.type) {
2834
+ case "openai":
2835
+ case "openai-responses":
2836
+ if (data.data && Array.isArray(data.data)) {
2837
+ models = filterByCapability(data.data).map((item) => ({
2838
+ id: item[modelIdField] || item.id,
2839
+ name: item[modelNameField] || item.id
2840
+ }));
2841
+ }
2842
+ break;
2843
+ case "anthropic":
2844
+ if (data.data && Array.isArray(data.data)) {
2845
+ models = filterByCapability(data.data).map((item) => ({
2846
+ id: item[modelIdField] || item.id,
2847
+ name: item[modelNameField] || item.id
2848
+ }));
2849
+ }
2850
+ break;
2851
+ case "gemini":
2852
+ if (data.models && Array.isArray(data.models)) {
2853
+ models = filterByCapability(data.models).map((item) => ({
2854
+ id: item[modelIdField]?.replace("models/", "") || item.name,
2855
+ name: item[modelNameField] || item.name
2856
+ }));
2857
+ }
2858
+ break;
2859
+ case "ollama":
2860
+ if (data.models && Array.isArray(data.models)) {
2861
+ models = filterByCapability(data.models).map((item) => ({
2862
+ id: item[modelIdField] || item.name,
2863
+ name: item[modelNameField] || item.name
2864
+ }));
2865
+ }
2866
+ break;
2867
+ default: {
2868
+ const modelArray = data.data || data.models || [];
2869
+ if (Array.isArray(modelArray)) {
2870
+ models = filterByCapability(modelArray).map((item) => ({
2871
+ id: getNestedValue(item, modelIdField) || item.id || item.name,
2872
+ name: getNestedValue(item, modelNameField) || item.id || item.name
2873
+ }));
2874
+ }
2875
+ }
2876
+ }
2877
+ return { success: true, data: models };
2878
+ } catch (error) {
2879
+ console.error("[IPC] provider:fetchModelList error:", error);
2880
+ return { success: false, error: error.message };
2881
+ }
2882
+ }
2883
+ );
2593
2884
  console.log("[IPC] Provider handlers registered");
2594
2885
  }
2886
+ function getNestedValue(obj, path2) {
2887
+ return path2.split(".").reduce((acc, part) => acc?.[part], obj);
2888
+ }
2595
2889
  const findAll$1 = async () => {
2596
2890
  return await prisma.model.findMany({
2597
2891
  orderBy: [{ createdAt: "desc" }]
@@ -3429,19 +3723,6 @@ function registerAppHandlers() {
3429
3723
  return getAppVersion();
3430
3724
  });
3431
3725
  }
3432
- function registerAllHandlers() {
3433
- registerProviderHandlers();
3434
- registerModelHandlers();
3435
- registerTaskHandlers();
3436
- registerTaskDetailHandlers();
3437
- registerFileHandlers();
3438
- registerCompletionHandlers();
3439
- registerAppHandlers();
3440
- console.log("[IPC] All handlers registered successfully");
3441
- }
3442
- function registerIpcHandlers() {
3443
- registerAllHandlers();
3444
- }
3445
3726
  class WindowManager {
3446
3727
  static instance;
3447
3728
  mainWindow = null;
@@ -3476,6 +3757,100 @@ class WindowManager {
3476
3757
  }
3477
3758
  }
3478
3759
  const windowManager = WindowManager.getInstance();
3760
+ const { autoUpdater } = pkg$1;
3761
+ class UpdateService {
3762
+ static instance;
3763
+ initialized = false;
3764
+ isChecking = false;
3765
+ constructor() {
3766
+ }
3767
+ static getInstance() {
3768
+ if (!UpdateService.instance) {
3769
+ UpdateService.instance = new UpdateService();
3770
+ }
3771
+ return UpdateService.instance;
3772
+ }
3773
+ ensureInitialized() {
3774
+ if (this.initialized) return;
3775
+ if (!app.isPackaged) return;
3776
+ this.initialized = true;
3777
+ autoUpdater.autoDownload = true;
3778
+ autoUpdater.allowPrerelease = false;
3779
+ autoUpdater.autoInstallOnAppQuit = true;
3780
+ this.registerListeners();
3781
+ }
3782
+ registerListeners() {
3783
+ autoUpdater.on("checking-for-update", () => {
3784
+ console.log("[UpdateService] Checking for updates...");
3785
+ this.sendStatus({ status: UpdateStatus.CHECKING });
3786
+ });
3787
+ autoUpdater.on("update-available", (info) => {
3788
+ console.log("[UpdateService] Update available:", info.version);
3789
+ this.sendStatus({ status: UpdateStatus.AVAILABLE, version: info.version });
3790
+ });
3791
+ autoUpdater.on("update-not-available", (info) => {
3792
+ console.log("[UpdateService] No update available. Current version:", info.version);
3793
+ this.sendStatus({ status: UpdateStatus.NOT_AVAILABLE, version: info.version });
3794
+ });
3795
+ autoUpdater.on("download-progress", (progress) => {
3796
+ console.log(`[UpdateService] Download progress: ${progress.percent.toFixed(1)}%`);
3797
+ this.sendStatus({ status: UpdateStatus.DOWNLOADING, progress: progress.percent });
3798
+ });
3799
+ autoUpdater.on("update-downloaded", (info) => {
3800
+ console.log("[UpdateService] Update downloaded:", info.version);
3801
+ this.sendStatus({ status: UpdateStatus.DOWNLOADED, version: info.version });
3802
+ });
3803
+ autoUpdater.on("error", (error) => {
3804
+ console.error("[UpdateService] Error:", error.message);
3805
+ this.sendStatus({ status: UpdateStatus.ERROR, error: error.message });
3806
+ });
3807
+ }
3808
+ sendStatus(data) {
3809
+ windowManager.sendToRenderer(IPC_CHANNELS.EVENTS.UPDATER_STATUS, data);
3810
+ }
3811
+ async checkForUpdates() {
3812
+ this.ensureInitialized();
3813
+ if (!this.initialized) return;
3814
+ if (this.isChecking) return;
3815
+ this.isChecking = true;
3816
+ try {
3817
+ await autoUpdater.checkForUpdates();
3818
+ } finally {
3819
+ this.isChecking = false;
3820
+ }
3821
+ }
3822
+ quitAndInstall() {
3823
+ this.ensureInitialized();
3824
+ autoUpdater.quitAndInstall();
3825
+ }
3826
+ }
3827
+ const updateService = UpdateService.getInstance();
3828
+ function registerUpdaterHandlers() {
3829
+ ipcMain.handle(IPC_CHANNELS.UPDATER.CHECK_FOR_UPDATES, async () => {
3830
+ if (!app.isPackaged) {
3831
+ console.log("[Updater] Skipping update check in development mode");
3832
+ return;
3833
+ }
3834
+ await updateService.checkForUpdates();
3835
+ });
3836
+ ipcMain.handle(IPC_CHANNELS.UPDATER.QUIT_AND_INSTALL, () => {
3837
+ updateService.quitAndInstall();
3838
+ });
3839
+ }
3840
+ function registerAllHandlers() {
3841
+ registerProviderHandlers();
3842
+ registerModelHandlers();
3843
+ registerTaskHandlers();
3844
+ registerTaskDetailHandlers();
3845
+ registerFileHandlers();
3846
+ registerCompletionHandlers();
3847
+ registerAppHandlers();
3848
+ registerUpdaterHandlers();
3849
+ console.log("[IPC] All handlers registered successfully");
3850
+ }
3851
+ function registerIpcHandlers() {
3852
+ registerAllHandlers();
3853
+ }
3479
3854
  class EventBridge {
3480
3855
  isInitialized = false;
3481
3856
  initialize() {
@@ -3709,6 +4084,10 @@ async function initializeBackgroundServices() {
3709
4084
  console.log("[Main] Initializing database in background...");
3710
4085
  await initDatabase();
3711
4086
  console.log(`[Main] Database initialized in ${Date.now() - startTime}ms`);
4087
+ console.log("[Main] Injecting preset providers...");
4088
+ const presetStartTime = Date.now();
4089
+ await presetProviderService.initialize();
4090
+ console.log(`[Main] Preset providers injected in ${Date.now() - presetStartTime}ms`);
3712
4091
  console.log("[Main] Starting task logic in background...");
3713
4092
  const taskStartTime = Date.now();
3714
4093
  await startTask();
@@ -3719,6 +4098,11 @@ async function initializeBackgroundServices() {
3719
4098
  if (mainWindow) {
3720
4099
  mainWindow.webContents.send("app:ready");
3721
4100
  }
4101
+ if (app.isPackaged) {
4102
+ updateService.checkForUpdates().catch(
4103
+ (err) => console.error("[Main] Auto-update check failed:", err)
4104
+ );
4105
+ }
3722
4106
  } catch (error) {
3723
4107
  console.error("[Main] Background services initialization error:", error);
3724
4108
  throw error;
@@ -8,7 +8,9 @@ electron.contextBridge.exposeInMainWorld("api", {
8
8
  create: (data) => electron.ipcRenderer.invoke("provider:create", data),
9
9
  update: (id, data) => electron.ipcRenderer.invoke("provider:update", id, data),
10
10
  delete: (id) => electron.ipcRenderer.invoke("provider:delete", id),
11
- updateStatus: (id, status) => electron.ipcRenderer.invoke("provider:updateStatus", id, status)
11
+ updateStatus: (id, status) => electron.ipcRenderer.invoke("provider:updateStatus", id, status),
12
+ getPresets: () => electron.ipcRenderer.invoke("provider:getPresets"),
13
+ fetchModelList: (providerId) => electron.ipcRenderer.invoke("provider:fetchModelList", providerId)
12
14
  },
13
15
  // ==================== Model APIs ====================
14
16
  model: {
@@ -56,6 +58,11 @@ electron.contextBridge.exposeInMainWorld("api", {
56
58
  maximize: () => electron.ipcRenderer.send("window:maximize"),
57
59
  close: () => electron.ipcRenderer.send("window:close")
58
60
  },
61
+ // ==================== Updater APIs ====================
62
+ updater: {
63
+ checkForUpdates: () => electron.ipcRenderer.invoke("updater:checkForUpdates"),
64
+ quitAndInstall: () => electron.ipcRenderer.invoke("updater:quitAndInstall")
65
+ },
59
66
  // ==================== Event APIs ====================
60
67
  events: {
61
68
  /**
@@ -81,6 +88,18 @@ electron.contextBridge.exposeInMainWorld("api", {
81
88
  return () => {
82
89
  electron.ipcRenderer.removeListener("taskDetail:event", handler);
83
90
  };
91
+ },
92
+ /**
93
+ * 监听更新状态事件
94
+ * @param callback 事件回调函数
95
+ * @returns 清理函数
96
+ */
97
+ onUpdaterStatus: (callback) => {
98
+ const handler = (_event, data) => callback(data);
99
+ electron.ipcRenderer.on("updater:status", handler);
100
+ return () => {
101
+ electron.ipcRenderer.removeListener("updater:status", handler);
102
+ };
84
103
  }
85
104
  },
86
105
  // ==================== Platform APIs ====================
@@ -95,6 +95,60 @@ body {
95
95
  overflow: hidden !important;
96
96
  }
97
97
 
98
+ /* Model Service Tabs 内容区域滚动 - 仅滚动时显示滚动条 */
99
+ .model-service-tabs .ant-tabs-content-holder {
100
+ overflow-y: auto;
101
+ scrollbar-gutter: stable;
102
+ }
103
+
104
+ .model-service-tabs .ant-tabs-content-holder::-webkit-scrollbar {
105
+ width: 6px;
106
+ }
107
+
108
+ .model-service-tabs .ant-tabs-content-holder::-webkit-scrollbar-thumb {
109
+ background-color: transparent;
110
+ border-radius: 3px;
111
+ }
112
+
113
+ .model-service-tabs .ant-tabs-content-holder:hover::-webkit-scrollbar-thumb,
114
+ .model-service-tabs .ant-tabs-content-holder:active::-webkit-scrollbar-thumb {
115
+ background-color: rgba(0, 0, 0, 0.2);
116
+ }
117
+
118
+ .model-service-tabs .ant-tabs-content-holder::-webkit-scrollbar-track {
119
+ background: transparent;
120
+ }
121
+
122
+ /* Provider 状态呼吸灯 */
123
+ .provider-status-light {
124
+ display: inline-block;
125
+ width: 8px;
126
+ height: 8px;
127
+ border-radius: 50%;
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ .provider-status-light--enabled {
132
+ background-color: #52c41a;
133
+ box-shadow: 0 0 6px rgba(82, 196, 26, 0.6);
134
+ animation: breathing-green 2s ease-in-out infinite;
135
+ }
136
+
137
+ .provider-status-light--disabled {
138
+ background-color: #bfbfbf;
139
+ }
140
+
141
+ @keyframes breathing-green {
142
+ 0%, 100% {
143
+ box-shadow: 0 0 4px rgba(82, 196, 26, 0.4);
144
+ opacity: 1;
145
+ }
146
+ 50% {
147
+ box-shadow: 0 0 10px rgba(82, 196, 26, 0.8);
148
+ opacity: 0.8;
149
+ }
150
+ }
151
+
98
152
  /* Markdown 预览面板滚动 */
99
153
  .ant-splitter-panel:last-child {
100
154
  overflow: auto !important;