koishi-plugin-memesluna 0.2.4 → 0.2.5

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 (3) hide show
  1. package/dist/index.js +1 -1
  2. package/lib/index.js +112 -594
  3. package/package.json +1 -2
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{icons as r}from"@koishijs/client";import{h as e}from"vue";const o="M1024 512C1024 229.230208 794.769792 0 512 0 229.230208 0 0 229.230208 0 512 0 794.769792 229.230208 1024 512 1024 629.410831 1024 740.826187 984.331046 830.768465 912.686662 841.557579 904.092491 843.33693 888.379234 834.742758 877.590121 826.148587 866.801009 810.43533 865.021658 799.646219 873.615827 718.470035 938.277495 618.001779 974.048781 512 974.048781 256.817504 974.048781 49.951219 767.182496 49.951219 512 49.951219 256.817504 256.817504 49.951219 512 49.951219 767.182496 49.951219 974.048781 256.817504 974.048781 512 974.048781 599.492834 949.714859 683.336764 904.470807 755.960693 897.177109 767.668243 900.755245 783.071797 912.462793 790.365493 924.170342 797.659191 939.573897 794.081058 946.867595 782.373508 997.013826 701.880796 1024 608.898379 1024 512ZM337.170731 499.512194C371.654852 499.512194 399.609756 471.557291 399.609756 437.073171 399.609756 402.58905 371.654852 374.634146 337.170731 374.634146 302.686611 374.634146 274.731708 402.58905 274.731708 437.073171 274.731708 471.557291 302.686611 499.512194 337.170731 499.512194ZM711.804879 499.512194C746.288998 499.512194 774.243902 471.557291 774.243902 437.073171 774.243902 402.58905 746.288998 374.634146 711.804879 374.634146 677.320757 374.634146 649.365854 402.58905 649.365854 437.073171 649.365854 471.557291 677.320757 499.512194 711.804879 499.512194ZM352.788105 768.770967C396.165222 803.472661 453.151987 824.195121 524.487806 824.195121 595.823622 824.195121 652.810387 803.472661 696.187505 768.770967 722.700531 747.560546 738.882517 725.984565 746.631548 710.486505 752.800254 698.149092 747.799529 683.146916 735.462114 676.978208 723.124702 670.809502 708.122526 675.810227 701.953818 688.147642 701.03616 689.982957 698.492224 694.172969 694.165854 699.941463 686.602473 710.025971 676.927317 720.210345 664.983226 729.765617 630.311565 757.502948 584.273939 774.243902 524.487806 774.243902 464.70167 774.243902 418.664045 757.502948 383.992384 729.765617 372.048292 720.210345 362.373137 710.025971 354.809756 699.941463 350.483386 694.172969 347.93945 689.982957 347.021792 688.147642 340.853084 675.810227 325.850908 670.809502 313.513495 676.978208 301.176081 683.146916 296.175356 698.149092 302.344062 710.486505 310.093092 725.984565 326.275078 747.560546 352.788105 768.770967Z",a={name:"MemesLunaIcon",render(){return e("svg",{class:"k-icon",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg"},[e("path",{fill:"currentColor",d:o})])}},s={name:"MemesLunaDashboard",render(){return e("iframe",{src:"/memesluna/",style:"width: 100%; height: calc(100vh - 120px); border: 0; background: transparent;"})}};r.register("MemesLunaEmoji",a);const i=n=>{n.page({name:"MemesLuna",path:"/memesluna/",icon:"MemesLunaEmoji",component:s,authority:3,order:500})};export{i as default};
1
+ import{icons as r}from"@koishijs/client";import{h as e}from"vue";const o="M1024 512C1024 229.230208 794.769792 0 512 0 229.230208 0 0 229.230208 0 512 0 794.769792 229.230208 1024 512 1024 629.410831 1024 740.826187 984.331046 830.768465 912.686662 841.557579 904.092491 843.33693 888.379234 834.742758 877.590121 826.148587 866.801009 810.43533 865.021658 799.646219 873.615827 718.470035 938.277495 618.001779 974.048781 512 974.048781 256.817504 974.048781 49.951219 767.182496 49.951219 512 49.951219 256.817504 256.817504 49.951219 512 49.951219 767.182496 49.951219 974.048781 256.817504 974.048781 512 974.048781 599.492834 949.714859 683.336764 904.470807 755.960693 897.177109 767.668243 900.755245 783.071797 912.462793 790.365493 924.170342 797.659191 939.573897 794.081058 946.867595 782.373508 997.013826 701.880796 1024 608.898379 1024 512ZM337.170731 499.512194C371.654852 499.512194 399.609756 471.557291 399.609756 437.073171 399.609756 402.58905 371.654852 374.634146 337.170731 374.634146 302.686611 374.634146 274.731708 402.58905 274.731708 437.073171 274.731708 471.557291 302.686611 499.512194 337.170731 499.512194ZM711.804879 499.512194C746.288998 499.512194 774.243902 471.557291 774.243902 437.073171 774.243902 402.58905 746.288998 374.634146 711.804879 374.634146 677.320757 374.634146 649.365854 402.58905 649.365854 437.073171 649.365854 471.557291 677.320757 499.512194 711.804879 499.512194ZM352.788105 768.770967C396.165222 803.472661 453.151987 824.195121 524.487806 824.195121 595.823622 824.195121 652.810387 803.472661 696.187505 768.770967 722.700531 747.560546 738.882517 725.984565 746.631548 710.486505 752.800254 698.149092 747.799529 683.146916 735.462114 676.978208 723.124702 670.809502 708.122526 675.810227 701.953818 688.147642 701.03616 689.982957 698.492224 694.172969 694.165854 699.941463 686.602473 710.025971 676.927317 720.210345 664.983226 729.765617 630.311565 757.502948 584.273939 774.243902 524.487806 774.243902 464.70167 774.243902 418.664045 757.502948 383.992384 729.765617 372.048292 720.210345 362.373137 710.025971 354.809756 699.941463 350.483386 694.172969 347.93945 689.982957 347.021792 688.147642 340.853084 675.810227 325.850908 670.809502 313.513495 676.978208 301.176081 683.146916 296.175356 698.149092 302.344062 710.486505 310.093092 725.984565 326.275078 747.560546 352.788105 768.770967Z",a={name:"MemesLunaIcon",render(){return e("svg",{class:"k-icon",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg"},[e("path",{fill:"currentColor",d:o})])}},s={name:"MemesLunaDashboard",render(){return e("iframe",{src:"/memesluna/",style:"width: 100%; height: calc(100vh - 120px); border: 0; background: transparent;"})}};r.register("MemesLunaEmoji",a);const c=n=>{n.page({name:"MemesLuna",path:"/memesluna/",icon:"MemesLunaEmoji",component:s,authority:3,order:500})};export{c as default};
package/lib/index.js CHANGED
@@ -4,6 +4,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
8
  var __export = (target, all) => {
8
9
  for (var name2 in all)
9
10
  __defProp(target, name2, { get: all[name2], enumerable: true });
@@ -72,6 +73,9 @@ var MemesLunaService = class extends import_koishi.Service {
72
73
  this._readyResolve();
73
74
  });
74
75
  }
76
+ static {
77
+ __name(this, "MemesLunaService");
78
+ }
75
79
  _readyPromise;
76
80
  _readyResolve;
77
81
  static inject = ["database"];
@@ -426,7 +430,7 @@ var MemesLunaService = class extends import_koishi.Service {
426
430
  return {
427
431
  id: row.id,
428
432
  name: row.name,
429
- group: row.group || "\u9ED8\u8BA4\u5206\u7EC4",
433
+ group: row.group || "默认分组",
430
434
  description: row.description || "",
431
435
  url: row.url,
432
436
  method: row.method || "redirect",
@@ -459,7 +463,7 @@ var MemesLunaService = class extends import_koishi.Service {
459
463
  await this.ctx.database.create("memesluna_endpoints", {
460
464
  id,
461
465
  name: input.name,
462
- group: input.group || "\u9ED8\u8BA4\u5206\u7EC4",
466
+ group: input.group || "默认分组",
463
467
  description: input.description || "",
464
468
  url: input.url,
465
469
  method: input.method || "redirect",
@@ -483,7 +487,7 @@ var MemesLunaService = class extends import_koishi.Service {
483
487
  const payload = {
484
488
  updated_at: /* @__PURE__ */ new Date()
485
489
  };
486
- if (input.group !== void 0) payload.group = input.group || "\u9ED8\u8BA4\u5206\u7EC4";
490
+ if (input.group !== void 0) payload.group = input.group || "默认分组";
487
491
  if (input.description !== void 0) payload.description = input.description || "";
488
492
  if (input.url !== void 0) payload.url = input.url;
489
493
  if (input.method !== void 0) payload.method = input.method;
@@ -526,12 +530,12 @@ var MemesLunaService = class extends import_koishi.Service {
526
530
  // src/config.ts
527
531
  var import_koishi2 = require("koishi");
528
532
  var Config = import_koishi2.Schema.object({
529
- backendPath: import_koishi2.Schema.string().default("/memesluna").description("\u540E\u7AEF\u670D\u52A1\u8DEF\u5F84\u524D\u7F00"),
530
- storagePath: import_koishi2.Schema.string().default("data/memesluna").description("\u672C\u5730\u5408\u96C6\u5B58\u50A8\u76EE\u5F55"),
531
- selfUrl: import_koishi2.Schema.string().default("").description("\u670D\u52A1\u516C\u5F00\u5730\u5740\uFF0C\u4E0D\u586B\u5219\u4F18\u5148\u4F7F\u7528 server.selfUrl"),
532
- injectVariables: import_koishi2.Schema.boolean().default(true).description("\u662F\u5426\u5411 ChatLuna \u6CE8\u5165 {{endpoint}} \u548C {{memesluna}} \u53D8\u91CF"),
533
- variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("\u53D8\u91CF\u5237\u65B0\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09"),
534
- injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(`\u4F60\u53EF\u4EE5\u4F7F\u7528\u8868\u60C5\u5305\u6765\u4E30\u5BCC\u4F60\u7684\u56DE\u590D\u3002\u8868\u60C5\u5305\u5217\u8868\u662F{endpoint}\uFF0C\u57FA\u7840URL\u662F{base_url}\uFF0C\u4F60\u8981\u628A\u57FA\u7840URL\u62FC\u63A5\u5230\u8DEF\u5F84\u524D\u9762,\u4E0D\u8981\u52A0\u6587\u4EF6\u540D,\u53EA\u52A0\u8DEF\u5F84,\u7528\u53D1\u9001\u56FE\u7247\u7684\u65B9\u5F0F\u53D1\u9001\u3002`).description("\u6CE8\u5165\u5230 ChatLuna {{memesluna}} \u53D8\u91CF\u7684\u63D0\u793A\u8BCD\u6A21\u677F\uFF0C\u652F\u6301 {endpoint} \u548C {base_url} \u5360\u4F4D\u7B26")
533
+ backendPath: import_koishi2.Schema.string().default("/memesluna").description("后端服务路径前缀"),
534
+ storagePath: import_koishi2.Schema.string().default("data/memesluna").description("本地合集存储目录"),
535
+ selfUrl: import_koishi2.Schema.string().default("").description("服务公开地址,不填则优先使用 server.selfUrl"),
536
+ injectVariables: import_koishi2.Schema.boolean().default(true).description("是否向 ChatLuna 注入 {{endpoint}} {{memesluna}} 变量"),
537
+ variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("变量刷新间隔(毫秒)"),
538
+ injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(`你可以使用表情包来丰富你的回复。表情包列表是{endpoint},基础URL是{base_url},你要把基础URL拼接到路径前面,不要加文件名,只加路径,用发送图片的方式发送。`).description("注入到 ChatLuna {{memesluna}} 变量的提示词模板,支持 {endpoint} {base_url} 占位符")
535
539
  });
536
540
  var name = "memesluna";
537
541
 
@@ -554,6 +558,7 @@ var IMAGE_URL_REGEXP = /\.(jpeg|jpg|gif|png|webp|bmp|svg)(\?.*)?$/i;
554
558
  function isReservedPath(name2) {
555
559
  return RESERVED_PATHS.has(name2) || name2.includes(".");
556
560
  }
561
+ __name(isReservedPath, "isReservedPath");
557
562
  function getValueByDotNotation(obj, dotPath) {
558
563
  if (!dotPath) return void 0;
559
564
  const parts = dotPath.split(".").filter(Boolean);
@@ -567,10 +572,12 @@ function getValueByDotNotation(obj, dotPath) {
567
572
  }
568
573
  return current;
569
574
  }
575
+ __name(getValueByDotNotation, "getValueByDotNotation");
570
576
  function normalizeContentType(contentType) {
571
577
  if (!contentType) return "";
572
578
  return contentType.toLowerCase().split(";")[0].trim();
573
579
  }
580
+ __name(normalizeContentType, "normalizeContentType");
574
581
  function guessMimeByExt(filePath) {
575
582
  const ext = import_path2.default.extname(filePath).toLowerCase();
576
583
  switch (ext) {
@@ -590,6 +597,7 @@ function guessMimeByExt(filePath) {
590
597
  return "image/jpeg";
591
598
  }
592
599
  }
600
+ __name(guessMimeByExt, "guessMimeByExt");
593
601
  async function handleProxyRequest(targetUrl, proxySettings = {}) {
594
602
  try {
595
603
  const response = await fetch(targetUrl, {
@@ -668,9 +676,11 @@ async function handleProxyRequest(targetUrl, proxySettings = {}) {
668
676
  };
669
677
  }
670
678
  }
679
+ __name(handleProxyRequest, "handleProxyRequest");
671
680
  function toAbsoluteBaseUrl(ctx, config) {
672
681
  return config.selfUrl || ctx.server?.selfUrl || "";
673
682
  }
683
+ __name(toAbsoluteBaseUrl, "toAbsoluteBaseUrl");
674
684
  async function applyDynamicForward(ctx, config, service, routeName, query) {
675
685
  const endpoint = await service.getEndpointByName(routeName);
676
686
  const isCollection = await service.collectionExists(routeName);
@@ -784,6 +794,7 @@ async function applyDynamicForward(ctx, config, service, routeName, query) {
784
794
  contentType: guessMimeByExt(resource.value)
785
795
  };
786
796
  }
797
+ __name(applyDynamicForward, "applyDynamicForward");
787
798
  function setKoaResponse(koa, result) {
788
799
  if (result.redirectTo) {
789
800
  koa.redirect(result.redirectTo);
@@ -800,6 +811,7 @@ function setKoaResponse(koa, result) {
800
811
  }
801
812
  koa.body = result.body;
802
813
  }
814
+ __name(setKoaResponse, "setKoaResponse");
803
815
  function getRequestBody(koa) {
804
816
  const body = koa?.request?.body;
805
817
  if (!body) return {};
@@ -815,13 +827,16 @@ function getRequestBody(koa) {
815
827
  }
816
828
  return {};
817
829
  }
830
+ __name(getRequestBody, "getRequestBody");
818
831
  function toTrimmedString(value) {
819
832
  return typeof value === "string" ? value.trim() : "";
820
833
  }
834
+ __name(toTrimmedString, "toTrimmedString");
821
835
  function toStringArray(value) {
822
836
  if (!Array.isArray(value)) return [];
823
837
  return value.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => item.length > 0);
824
838
  }
839
+ __name(toStringArray, "toStringArray");
825
840
  function parseJsonLike(value, fallback) {
826
841
  if (value === null || value === void 0) return fallback;
827
842
  if (typeof value === "string") {
@@ -838,9 +853,11 @@ function parseJsonLike(value, fallback) {
838
853
  }
839
854
  return fallback;
840
855
  }
856
+ __name(parseJsonLike, "parseJsonLike");
841
857
  function normalizeForwardMethod(value) {
842
858
  return toTrimmedString(value) === "proxy" ? "proxy" : "redirect";
843
859
  }
860
+ __name(normalizeForwardMethod, "normalizeForwardMethod");
844
861
  function normalizeUrlConstruction(value) {
845
862
  const normalized = toTrimmedString(value);
846
863
  if (normalized === "special_forward" || normalized === "special_pollinations" || normalized === "special_draw_redirect") {
@@ -848,6 +865,7 @@ function normalizeUrlConstruction(value) {
848
865
  }
849
866
  return "normal";
850
867
  }
868
+ __name(normalizeUrlConstruction, "normalizeUrlConstruction");
851
869
  async function buildAdminState(service) {
852
870
  const endpoints = await service.getEndpoints();
853
871
  const collectionNames = await service.getCollections();
@@ -858,13 +876,14 @@ async function buildAdminState(service) {
858
876
  collections: collections.filter(Boolean)
859
877
  };
860
878
  }
879
+ __name(buildAdminState, "buildAdminState");
861
880
  function buildHomepageHtml(basePath) {
862
881
  return `<!doctype html>
863
882
  <html lang="zh-CN">
864
883
  <head>
865
884
  <meta charset="utf-8" />
866
885
  <meta name="viewport" content="width=device-width, initial-scale=1" />
867
- <title>\u56FE\u5E8A\u8F6C\u53D1 - \u9996\u9875</title>
886
+ <title>图床转发 - 首页</title>
868
887
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
869
888
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
870
889
  <style>
@@ -1052,8 +1071,8 @@ function buildHomepageHtml(basePath) {
1052
1071
  animation: spin 1s linear infinite;
1053
1072
  }
1054
1073
 
1055
- display: none;
1056
- border-left: 4px solid #dc3545;
1074
+ .error-panel {
1075
+ display: none !important;
1057
1076
  }
1058
1077
 
1059
1078
  @keyframes spin {
@@ -1082,13 +1101,10 @@ function buildHomepageHtml(basePath) {
1082
1101
  <div class="collapse navbar-collapse" id="navbarNav">
1083
1102
  <ul class="navbar-nav me-auto">
1084
1103
  <li class="nav-item">
1085
- <a class="nav-link active" href="${basePath}/">\u9996\u9875</a>
1086
- </li>
1087
- <li class="nav-item">
1088
- <a class="nav-link" href="${basePath}/admin">\u7BA1\u7406</a>
1104
+ <a class="nav-link active" href="${basePath}/">首页</a>
1089
1105
  </li>
1090
1106
  <li class="nav-item">
1091
- <a class="nav-link" href="${basePath}/admin/endpoint">\u7AEF\u70B9</a>
1107
+ <a class="nav-link" href="${basePath}/admin">管理</a>
1092
1108
  </li>
1093
1109
  </ul>
1094
1110
  </div>
@@ -1097,17 +1113,17 @@ function buildHomepageHtml(basePath) {
1097
1113
 
1098
1114
  <div class="container page-container mt-3 pb-4">
1099
1115
  <div class="group-section">
1100
- <h3 class="group-title-home">\u{1F310} \u63A5\u53E3\u7AEF\u70B9</h3>
1116
+ <h3 class="group-title-home">🌐 接口端点</h3>
1101
1117
  <div class="cards-row" id="endpoint-cards"></div>
1102
1118
  </div>
1103
1119
 
1104
1120
  <div class="group-section">
1105
- <h3 class="group-title-home">\u{1F4F7} \u672C\u5730\u56FE\u7247\u5408\u96C6</h3>
1121
+ <h3 class="group-title-home">📷 本地图片合集</h3>
1106
1122
  <div class="cards-row" id="collection-cards"></div>
1107
1123
  </div>
1108
1124
 
1109
1125
  <div class="alert alert-danger error-panel" id="error-panel">
1110
- <strong>\u52A0\u8F7D\u5931\u8D25\uFF1A</strong><span id="error-text"></span>
1126
+ <strong>加载失败:</strong><span id="error-text"></span>
1111
1127
  </div>
1112
1128
  </div>
1113
1129
 
@@ -1119,13 +1135,14 @@ function buildHomepageHtml(basePath) {
1119
1135
  const panel = document.getElementById('error-panel')
1120
1136
  const text = document.getElementById('error-text')
1121
1137
  if (text) text.textContent = message
1138
+ if (panel) panel.classList.remove('error-panel')
1122
1139
  if (panel) panel.style.display = 'block'
1123
1140
  }
1124
1141
 
1125
1142
  function createLoader() {
1126
1143
  const loader = document.createElement('div')
1127
1144
  loader.className = 'media-loader'
1128
- loader.innerHTML = '<div class="loader-spinner"></div><span>\u52A0\u8F7D\u4E2D...</span>'
1145
+ loader.innerHTML = '<div class="loader-spinner"></div><span>加载中...</span>'
1129
1146
  return loader
1130
1147
  }
1131
1148
 
@@ -1183,9 +1200,9 @@ function buildHomepageHtml(basePath) {
1183
1200
  const target = String(item.url || '')
1184
1201
 
1185
1202
  info.innerHTML =
1186
- '<p class="api-hint">\u2699\uFE0F ' + method + ' \xB7 ' + mode + '</p>' +
1203
+ '<p class="api-hint">⚙️ ' + method + ' · ' + mode + '</p>' +
1187
1204
  '<p class="api-url">' + endpointPath + '</p>' +
1188
- '<p class="api-url">\u2192 ' + target + '</p>'
1205
+ '<p class="api-url">→ ' + target + '</p>'
1189
1206
 
1190
1207
  card.appendChild(info)
1191
1208
  return card
@@ -1211,7 +1228,7 @@ function buildHomepageHtml(basePath) {
1211
1228
  const totalCount = Number(item.totalCount || 0)
1212
1229
 
1213
1230
  info.innerHTML =
1214
- '<p class="api-hint">\u{1F4C1} \u672C\u5730 ' + localCount + ' \xB7 \u5916\u94FE ' + linkCount + ' \xB7 \u603B\u8BA1 ' + totalCount + '</p>' +
1231
+ '<p class="api-hint">📁 本地 ' + localCount + ' · 外链 ' + linkCount + ' · 总计 ' + totalCount + '</p>' +
1215
1232
  '<p class="api-url">' + route + '</p>'
1216
1233
 
1217
1234
  card.appendChild(info)
@@ -1234,7 +1251,7 @@ function buildHomepageHtml(basePath) {
1234
1251
  if (!endpoints.length) {
1235
1252
  const empty = document.createElement('div')
1236
1253
  empty.className = 'alert alert-secondary mb-0'
1237
- empty.textContent = '\u6682\u65E0 endpoint'
1254
+ empty.textContent = '暂无 endpoint'
1238
1255
  endpointWrap.appendChild(empty)
1239
1256
  } else {
1240
1257
  endpoints.forEach((item) => endpointWrap.appendChild(buildEndpointCard(item)))
@@ -1247,7 +1264,7 @@ function buildHomepageHtml(basePath) {
1247
1264
  if (!collections.length) {
1248
1265
  const empty = document.createElement('div')
1249
1266
  empty.className = 'alert alert-secondary mb-0'
1250
- empty.textContent = '\u6682\u65E0 collection'
1267
+ empty.textContent = '暂无 collection'
1251
1268
  collectionWrap.appendChild(empty)
1252
1269
  } else {
1253
1270
  collections.forEach((item) => collectionWrap.appendChild(buildCollectionCard(item)))
@@ -1264,13 +1281,14 @@ function buildHomepageHtml(basePath) {
1264
1281
  </body>
1265
1282
  </html>`;
1266
1283
  }
1284
+ __name(buildHomepageHtml, "buildHomepageHtml");
1267
1285
  function buildAdminHtml(basePath) {
1268
1286
  return `<!doctype html>
1269
1287
  <html lang="zh-CN">
1270
1288
  <head>
1271
1289
  <meta charset="utf-8" />
1272
1290
  <meta name="viewport" content="width=device-width, initial-scale=1" />
1273
- <title>\u56FE\u5E8A\u8F6C\u53D1 - \u7BA1\u7406</title>
1291
+ <title>图床转发 - 管理</title>
1274
1292
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
1275
1293
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
1276
1294
  <style>
@@ -1389,9 +1407,8 @@ function buildAdminHtml(basePath) {
1389
1407
  </button>
1390
1408
  <div class="collapse navbar-collapse" id="navbarNavAdmin">
1391
1409
  <ul class="navbar-nav me-auto">
1392
- <li class="nav-item"><a class="nav-link" href="${basePath}/">\u9996\u9875</a></li>
1393
- <li class="nav-item"><a class="nav-link active" href="${basePath}/admin">\u7BA1\u7406</a></li>
1394
- <li class="nav-item"><a class="nav-link" href="${basePath}/admin/endpoint">\u7AEF\u70B9</a></li>
1410
+ <li class="nav-item"><a class="nav-link" href="${basePath}/">首页</a></li>
1411
+ <li class="nav-item"><a class="nav-link active" href="${basePath}/admin">管理</a></li>
1395
1412
  </ul>
1396
1413
  </div>
1397
1414
  </div>
@@ -1401,26 +1418,26 @@ function buildAdminHtml(basePath) {
1401
1418
  <div class="row g-3">
1402
1419
  <div class="col-lg-3">
1403
1420
  <div class="sidebar-panel">
1404
- <h5 class="mb-3">\u5408\u96C6\u7BA1\u7406</h5>
1421
+ <h5 class="mb-3">合集管理</h5>
1405
1422
  <div class="input-group mb-3">
1406
- <input id="new-collection-name" class="form-control" placeholder="\u65B0\u5408\u96C6\u540D\u79F0" />
1407
- <button id="create-collection" class="btn btn-primary">\u521B\u5EFA</button>
1423
+ <input id="new-collection-name" class="form-control" placeholder="新合集名称" />
1424
+ <button id="create-collection" class="btn btn-primary">创建</button>
1408
1425
  </div>
1409
1426
  <div id="collection-list" class="list-group mb-3"></div>
1410
- <button id="delete-collection" class="btn btn-outline-danger w-100" disabled>\u5220\u9664\u5F53\u524D\u5408\u96C6</button>
1427
+ <button id="delete-collection" class="btn btn-outline-danger w-100" disabled>删除当前合集</button>
1411
1428
 
1412
1429
  <hr>
1413
- <h6 class="mb-2">\u5408\u96C6\u63CF\u8FF0</h6>
1430
+ <h6 class="mb-2">合集描述</h6>
1414
1431
  <div class="input-group mb-3">
1415
- <input id="collection-description" class="form-control" placeholder="\u4E3A\u5F53\u524D\u5408\u96C6\u6DFB\u52A0\u63CF\u8FF0" disabled />
1416
- <button id="save-description" class="btn btn-primary" disabled>\u4FDD\u5B58</button>
1432
+ <input id="collection-description" class="form-control" placeholder="为当前合集添加描述" disabled />
1433
+ <button id="save-description" class="btn btn-primary" disabled>保存</button>
1417
1434
  </div>
1418
1435
 
1419
1436
  <hr>
1420
- <h6 class="mb-2">\u5FEB\u6377\u4FE1\u606F</h6>
1437
+ <h6 class="mb-2">快捷信息</h6>
1421
1438
  <div class="small text-muted">
1422
- <div>\u7BA1\u7406\u94FE\u63A5\uFF1A<code>${basePath}/admin</code></div>
1423
- <div class="mt-1">\u968F\u673A\u8BBF\u95EE\uFF1A<code id="collection-random-url">-</code></div>
1439
+ <div>管理链接:<code>${basePath}/admin</code></div>
1440
+ <div class="mt-1">随机访问:<code id="collection-random-url">-</code></div>
1424
1441
  </div>
1425
1442
  </div>
1426
1443
  </div>
@@ -1428,74 +1445,38 @@ function buildAdminHtml(basePath) {
1428
1445
  <div class="col-lg-9">
1429
1446
  <div class="main-panel mb-3">
1430
1447
  <div class="d-flex justify-content-between align-items-center">
1431
- <h5 class="mb-0">\u5408\u96C6\u5185\u5BB9</h5>
1432
- <span id="selected-collection-badge" class="badge bg-primary">\u672A\u9009\u62E9</span>
1448
+ <h5 class="mb-0">合集内容</h5>
1449
+ <span id="selected-collection-badge" class="badge bg-primary">未选择</span>
1433
1450
  </div>
1434
1451
 
1435
1452
  <div class="row g-3 mt-1">
1436
1453
  <div class="col-md-6">
1437
1454
  <div class="sub-card h-100">
1438
- <div class="section-title mb-2">\u4E0A\u4F20\u672C\u5730\u56FE\u7247</div>
1455
+ <div class="section-title mb-2">上传本地图片</div>
1439
1456
  <input id="upload-files" type="file" class="form-control mb-2" multiple accept="image/*" />
1440
- <button id="upload-images" class="btn btn-primary w-100" disabled>\u4E0A\u4F20\u5230\u5F53\u524D\u5408\u96C6</button>
1441
- <div class="form-text">\u652F\u6301\u591A\u9009\u3002\u4E0A\u4F20\u540E\u81EA\u52A8\u5237\u65B0\u5217\u8868\u3002</div>
1457
+ <button id="upload-images" class="btn btn-primary w-100" disabled>上传到当前合集</button>
1458
+ <div class="form-text">支持多选。上传后自动刷新列表。</div>
1442
1459
  </div>
1443
1460
  </div>
1444
1461
  <div class="col-md-6">
1445
1462
  <div class="sub-card h-100">
1446
- <div class="section-title mb-2">\u6DFB\u52A0\u5916\u94FE</div>
1447
- <textarea id="links-input" class="form-control mb-2" rows="4" placeholder="\u6BCF\u884C\u4E00\u4E2A http/https \u94FE\u63A5"></textarea>
1448
- <button id="add-links" class="btn btn-primary w-100" disabled>\u6DFB\u52A0\u5916\u94FE\u5230\u5F53\u524D\u5408\u96C6</button>
1463
+ <div class="section-title mb-2">添加外链</div>
1464
+ <textarea id="links-input" class="form-control mb-2" rows="4" placeholder="每行一个 http/https 链接"></textarea>
1465
+ <button id="add-links" class="btn btn-primary w-100" disabled>添加外链到当前合集</button>
1449
1466
  </div>
1450
1467
  </div>
1451
1468
  </div>
1452
1469
 
1453
1470
  <div class="sub-card mt-3">
1454
- <div class="section-title mb-2">\u672C\u5730\u56FE\u7247</div>
1471
+ <div class="section-title mb-2">本地图片</div>
1455
1472
  <div id="images-grid" class="image-grid"></div>
1456
- <div id="images-empty" class="empty-tip mt-2">\u6682\u65E0\u672C\u5730\u56FE\u7247</div>
1473
+ <div id="images-empty" class="empty-tip mt-2">暂无本地图片</div>
1457
1474
  </div>
1458
1475
 
1459
1476
  <div class="sub-card mt-3">
1460
- <div class="section-title mb-2">\u5916\u94FE\u5217\u8868</div>
1477
+ <div class="section-title mb-2">外链列表</div>
1461
1478
  <div id="links-list" class="list-group"></div>
1462
- <div id="links-empty" class="empty-tip mt-2">\u6682\u65E0\u5916\u94FE</div>
1463
- </div>
1464
- </div>
1465
-
1466
- <div class="main-panel">
1467
- <h5 class="mb-3">API \u7AEF\u70B9\u7BA1\u7406</h5>
1468
-
1469
- <div class="row g-2 mb-3">
1470
- <div class="col-md-3"><input id="endpoint-name" class="form-control" placeholder="name" /></div>
1471
- <div class="col-md-3"><input id="endpoint-group" class="form-control" placeholder="group" /></div>
1472
- <div class="col-md-3"><select id="endpoint-method" class="form-select"><option value="redirect">redirect</option><option value="proxy">proxy</option></select></div>
1473
- <div class="col-md-3"><select id="endpoint-mode" class="form-select"><option value="normal">normal</option><option value="special_forward">special_forward</option><option value="special_pollinations">special_pollinations</option><option value="special_draw_redirect">special_draw_redirect</option></select></div>
1474
- <div class="col-md-8"><input id="endpoint-url" class="form-control" placeholder="target url" /></div>
1475
- <div class="col-md-4"><input id="endpoint-model" class="form-control" placeholder="modelName (optional)" /></div>
1476
- <div class="col-md-6"><textarea id="endpoint-description" class="form-control" rows="2" placeholder="description"></textarea></div>
1477
- <div class="col-md-3"><textarea id="endpoint-query" class="form-control code-url" rows="2" placeholder='queryParams JSON'></textarea></div>
1478
- <div class="col-md-3"><textarea id="endpoint-proxy" class="form-control code-url" rows="2" placeholder='proxySettings JSON'></textarea></div>
1479
- </div>
1480
-
1481
- <div class="d-flex gap-2 mb-3">
1482
- <button id="save-endpoint" class="btn btn-primary">\u521B\u5EFA / \u66F4\u65B0\u7AEF\u70B9</button>
1483
- <button id="reset-endpoint" class="btn btn-outline-secondary">\u6E05\u7A7A\u8868\u5355</button>
1484
- </div>
1485
-
1486
- <div class="table-responsive">
1487
- <table class="table table-sm align-middle">
1488
- <thead>
1489
- <tr>
1490
- <th>name</th>
1491
- <th>method</th>
1492
- <th>mode</th>
1493
- <th>url</th>
1494
- <th></th>
1495
- </tr>
1496
- </thead>
1497
- <tbody id="endpoint-table"></tbody>
1498
- </table>
1479
+ <div id="links-empty" class="empty-tip mt-2">暂无外链</div>
1499
1480
  </div>
1500
1481
  </div>
1501
1482
  </div>
@@ -1526,15 +1507,19 @@ function buildAdminHtml(basePath) {
1526
1507
  }
1527
1508
 
1528
1509
  async function request(url, options = {}) {
1510
+ console.log('Requesting:', url)
1529
1511
  const headers = Object.assign({}, options.headers || {})
1530
1512
  if (options.body && !headers['Content-Type']) {
1531
1513
  headers['Content-Type'] = 'application/json'
1532
1514
  }
1533
1515
  const res = await fetch(url, Object.assign({}, options, { headers }))
1516
+ console.log('Response status:', res.status)
1534
1517
  let data = null
1535
1518
  try {
1536
1519
  data = await res.json()
1537
- } catch {
1520
+ console.log('Response data:', data)
1521
+ } catch (e) {
1522
+ console.log('JSON parse error:', e)
1538
1523
  data = null
1539
1524
  }
1540
1525
  if (!res.ok) {
@@ -1554,7 +1539,6 @@ function buildAdminHtml(basePath) {
1554
1539
  }
1555
1540
 
1556
1541
  renderCollectionList()
1557
- renderEndpointTable()
1558
1542
  await refreshCollectionResources()
1559
1543
  syncSelectedCollectionUi()
1560
1544
  }
@@ -1576,7 +1560,7 @@ function buildAdminHtml(basePath) {
1576
1560
 
1577
1561
  function syncSelectedCollectionUi() {
1578
1562
  const selected = state.selectedCollection
1579
- byId('selected-collection-badge').textContent = selected || '\u672A\u9009\u62E9'
1563
+ byId('selected-collection-badge').textContent = selected || '未选择'
1580
1564
  byId('delete-collection').disabled = !selected
1581
1565
  byId('upload-images').disabled = !selected
1582
1566
  byId('add-links').disabled = !selected
@@ -1594,7 +1578,7 @@ function buildAdminHtml(basePath) {
1594
1578
  if (!state.collectionNames.length) {
1595
1579
  const empty = document.createElement('div')
1596
1580
  empty.className = 'text-muted small'
1597
- empty.textContent = '\u6682\u65E0\u5408\u96C6'
1581
+ empty.textContent = '暂无合集'
1598
1582
  list.appendChild(empty)
1599
1583
  return
1600
1584
  }
@@ -1647,7 +1631,7 @@ function buildAdminHtml(basePath) {
1647
1631
  const collections = state.collectionNames.filter((item) => item !== state.selectedCollection)
1648
1632
  const placeholder = document.createElement('option')
1649
1633
  placeholder.value = ''
1650
- placeholder.textContent = '\u79FB\u52A8\u5230...'
1634
+ placeholder.textContent = '移动到...'
1651
1635
  moveSelect.appendChild(placeholder)
1652
1636
  collections.forEach((target) => {
1653
1637
  const opt = document.createElement('option')
@@ -1658,7 +1642,7 @@ function buildAdminHtml(basePath) {
1658
1642
 
1659
1643
  const moveBtn = document.createElement('button')
1660
1644
  moveBtn.className = 'btn btn-sm btn-outline-primary'
1661
- moveBtn.textContent = '\u79FB\u52A8'
1645
+ moveBtn.textContent = '移动'
1662
1646
  moveBtn.disabled = collections.length === 0
1663
1647
  moveBtn.addEventListener('click', async () => {
1664
1648
  const targetCollection = moveSelect.value
@@ -1667,19 +1651,19 @@ function buildAdminHtml(basePath) {
1667
1651
  BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name) + '/move',
1668
1652
  { method: 'POST', body: JSON.stringify({ targetCollection }) }
1669
1653
  )
1670
- showAlert('\u56FE\u7247\u5DF2\u79FB\u52A8', 'success')
1654
+ showAlert('图片已移动', 'success')
1671
1655
  await refreshState()
1672
1656
  })
1673
1657
 
1674
1658
  const delBtn = document.createElement('button')
1675
1659
  delBtn.className = 'btn btn-sm btn-outline-danger'
1676
- delBtn.textContent = '\u5220\u9664'
1660
+ delBtn.textContent = '删除'
1677
1661
  delBtn.addEventListener('click', async () => {
1678
1662
  await request(
1679
1663
  BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name),
1680
1664
  { method: 'DELETE' }
1681
1665
  )
1682
- showAlert('\u56FE\u7247\u5DF2\u5220\u9664', 'success')
1666
+ showAlert('图片已删除', 'success')
1683
1667
  await refreshCollectionResources()
1684
1668
  })
1685
1669
 
@@ -1710,19 +1694,19 @@ function buildAdminHtml(basePath) {
1710
1694
 
1711
1695
  const open = document.createElement('a')
1712
1696
  open.className = 'btn btn-sm btn-outline-primary'
1713
- open.textContent = '\u67E5\u770B'
1697
+ open.textContent = '查看'
1714
1698
  open.href = link
1715
1699
  open.target = '_blank'
1716
1700
 
1717
1701
  const del = document.createElement('button')
1718
1702
  del.className = 'btn btn-sm btn-outline-danger'
1719
- del.textContent = '\u5220\u9664'
1703
+ del.textContent = '删除'
1720
1704
  del.addEventListener('click', async () => {
1721
1705
  await request(
1722
1706
  BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links',
1723
1707
  { method: 'DELETE', body: JSON.stringify({ link }) }
1724
1708
  )
1725
- showAlert('\u5916\u94FE\u5DF2\u5220\u9664', 'success')
1709
+ showAlert('外链已删除', 'success')
1726
1710
  await refreshCollectionResources()
1727
1711
  })
1728
1712
 
@@ -1734,75 +1718,11 @@ function buildAdminHtml(basePath) {
1734
1718
  })
1735
1719
  }
1736
1720
 
1737
- function fillEndpointForm(item) {
1738
- byId('endpoint-name').value = item.name || ''
1739
- byId('endpoint-group').value = item.group || ''
1740
- byId('endpoint-method').value = item.method || 'redirect'
1741
- byId('endpoint-mode').value = item.urlConstruction || 'normal'
1742
- byId('endpoint-url').value = item.url || ''
1743
- byId('endpoint-model').value = item.modelName || ''
1744
- byId('endpoint-description').value = item.description || ''
1745
- byId('endpoint-query').value = JSON.stringify(item.queryParams || [], null, 2)
1746
- byId('endpoint-proxy').value = JSON.stringify(item.proxySettings || {}, null, 2)
1747
- }
1748
-
1749
- function renderEndpointTable() {
1750
- const body = byId('endpoint-table')
1751
- body.textContent = ''
1752
- if (!state.endpoints.length) {
1753
- const tr = document.createElement('tr')
1754
- const td = document.createElement('td')
1755
- td.colSpan = 5
1756
- td.className = 'text-muted'
1757
- td.textContent = '\u6682\u65E0 endpoint'
1758
- tr.appendChild(td)
1759
- body.appendChild(tr)
1760
- return
1761
- }
1762
-
1763
- state.endpoints.forEach((item) => {
1764
- const tr = document.createElement('tr')
1765
- tr.innerHTML =
1766
- '<td class="code-url"></td>' +
1767
- '<td></td>' +
1768
- '<td class="code-url"></td>' +
1769
- '<td class="code-url"></td>' +
1770
- '<td></td>'
1771
-
1772
- tr.children[0].textContent = item.name || ''
1773
- tr.children[1].textContent = item.method || 'redirect'
1774
- tr.children[2].textContent = item.urlConstruction || 'normal'
1775
- tr.children[3].textContent = item.url || ''
1776
-
1777
- const actions = document.createElement('div')
1778
- actions.className = 'd-flex gap-1 justify-content-end'
1779
-
1780
- const edit = document.createElement('button')
1781
- edit.className = 'btn btn-sm btn-outline-primary'
1782
- edit.textContent = '\u7F16\u8F91'
1783
- edit.addEventListener('click', () => fillEndpointForm(item))
1784
-
1785
- const del = document.createElement('button')
1786
- del.className = 'btn btn-sm btn-outline-danger'
1787
- del.textContent = '\u5220\u9664'
1788
- del.addEventListener('click', async () => {
1789
- await request(BASE_PATH + '/api/admin/endpoints/' + encodeURIComponent(item.name || ''), { method: 'DELETE' })
1790
- showAlert('\u7AEF\u70B9\u5DF2\u5220\u9664', 'success')
1791
- await refreshState()
1792
- })
1793
-
1794
- actions.appendChild(edit)
1795
- actions.appendChild(del)
1796
- tr.children[4].appendChild(actions)
1797
- body.appendChild(tr)
1798
- })
1799
- }
1800
-
1801
1721
  function readFileAsDataURL(file) {
1802
1722
  return new Promise((resolve, reject) => {
1803
1723
  const reader = new FileReader()
1804
1724
  reader.onload = () => resolve(reader.result)
1805
- reader.onerror = () => reject(new Error('\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25'))
1725
+ reader.onerror = () => reject(new Error('读取文件失败'))
1806
1726
  reader.readAsDataURL(file)
1807
1727
  })
1808
1728
  }
@@ -1815,7 +1735,7 @@ function buildAdminHtml(basePath) {
1815
1735
  body: JSON.stringify({ name }),
1816
1736
  })
1817
1737
  byId('new-collection-name').value = ''
1818
- showAlert('\u5408\u96C6\u521B\u5EFA\u6210\u529F', 'success')
1738
+ showAlert('合集创建成功', 'success')
1819
1739
  await refreshState()
1820
1740
  })
1821
1741
 
@@ -1824,7 +1744,7 @@ function buildAdminHtml(basePath) {
1824
1744
  await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection), {
1825
1745
  method: 'DELETE',
1826
1746
  })
1827
- showAlert('\u5408\u96C6\u5DF2\u5220\u9664', 'success')
1747
+ showAlert('合集已删除', 'success')
1828
1748
  await refreshState()
1829
1749
  })
1830
1750
 
@@ -1835,7 +1755,7 @@ function buildAdminHtml(basePath) {
1835
1755
  method: 'PATCH',
1836
1756
  body: JSON.stringify({ description }),
1837
1757
  })
1838
- showAlert('\u63CF\u8FF0\u5DF2\u4FDD\u5B58', 'success')
1758
+ showAlert('描述已保存', 'success')
1839
1759
  await refreshState()
1840
1760
  })
1841
1761
 
@@ -1853,7 +1773,7 @@ function buildAdminHtml(basePath) {
1853
1773
  body: JSON.stringify({ images }),
1854
1774
  })
1855
1775
  byId('upload-files').value = ''
1856
- showAlert('\u56FE\u7247\u4E0A\u4F20\u6210\u529F', 'success')
1776
+ showAlert('图片上传成功', 'success')
1857
1777
  await refreshCollectionResources()
1858
1778
  await refreshState()
1859
1779
  })
@@ -1869,76 +1789,11 @@ function buildAdminHtml(basePath) {
1869
1789
  body: JSON.stringify({ links }),
1870
1790
  })
1871
1791
  byId('links-input').value = ''
1872
- showAlert('\u5916\u94FE\u6DFB\u52A0\u6210\u529F', 'success')
1792
+ showAlert('外链添加成功', 'success')
1873
1793
  await refreshCollectionResources()
1874
1794
  await refreshState()
1875
1795
  })
1876
1796
 
1877
- byId('save-endpoint').addEventListener('click', async () => {
1878
- const name = byId('endpoint-name').value.trim()
1879
- const url = byId('endpoint-url').value.trim()
1880
- if (!name || !url) {
1881
- showAlert('name \u548C url \u5FC5\u586B', 'warning')
1882
- return
1883
- }
1884
-
1885
- let queryParams = []
1886
- let proxySettings = {}
1887
- try {
1888
- queryParams = byId('endpoint-query').value.trim() ? JSON.parse(byId('endpoint-query').value) : []
1889
- } catch {
1890
- showAlert('queryParams JSON \u683C\u5F0F\u9519\u8BEF', 'warning')
1891
- return
1892
- }
1893
- try {
1894
- proxySettings = byId('endpoint-proxy').value.trim() ? JSON.parse(byId('endpoint-proxy').value) : {}
1895
- } catch {
1896
- showAlert('proxySettings JSON \u683C\u5F0F\u9519\u8BEF', 'warning')
1897
- return
1898
- }
1899
-
1900
- const payload = {
1901
- name,
1902
- group: byId('endpoint-group').value.trim(),
1903
- description: byId('endpoint-description').value.trim(),
1904
- url,
1905
- method: byId('endpoint-method').value,
1906
- urlConstruction: byId('endpoint-mode').value,
1907
- modelName: byId('endpoint-model').value.trim(),
1908
- queryParams,
1909
- proxySettings,
1910
- }
1911
-
1912
- const exists = state.endpoints.some((item) => item.name === name)
1913
- if (exists) {
1914
- await request(BASE_PATH + '/api/admin/endpoints/' + encodeURIComponent(name), {
1915
- method: 'PATCH',
1916
- body: JSON.stringify(payload),
1917
- })
1918
- showAlert('\u7AEF\u70B9\u5DF2\u66F4\u65B0', 'success')
1919
- } else {
1920
- await request(BASE_PATH + '/api/admin/endpoints', {
1921
- method: 'POST',
1922
- body: JSON.stringify(payload),
1923
- })
1924
- showAlert('\u7AEF\u70B9\u5DF2\u521B\u5EFA', 'success')
1925
- }
1926
-
1927
- await refreshState()
1928
- })
1929
-
1930
- byId('reset-endpoint').addEventListener('click', () => {
1931
- byId('endpoint-name').value = ''
1932
- byId('endpoint-group').value = ''
1933
- byId('endpoint-method').value = 'redirect'
1934
- byId('endpoint-mode').value = 'normal'
1935
- byId('endpoint-url').value = ''
1936
- byId('endpoint-model').value = ''
1937
- byId('endpoint-description').value = ''
1938
- byId('endpoint-query').value = ''
1939
- byId('endpoint-proxy').value = ''
1940
- })
1941
-
1942
1797
  byId('upload-files').addEventListener('change', () => {
1943
1798
  byId('upload-images').disabled = !state.selectedCollection
1944
1799
  })
@@ -1951,362 +1806,25 @@ function buildAdminHtml(basePath) {
1951
1806
  </body>
1952
1807
  </html>`;
1953
1808
  }
1954
- function buildAdminEndpointHtml(basePath) {
1955
- return `<!doctype html>
1956
- <html lang="zh-CN">
1957
- <head>
1958
- <meta charset="utf-8" />
1959
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1960
- <title>\u56FE\u5E8A\u8F6C\u53D1 - API \u7AEF\u70B9\u7BA1\u7406</title>
1961
- <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
1962
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
1963
- <style>
1964
- body {
1965
- min-height: 100vh;
1966
- margin: 0;
1967
- color: #1f2937;
1968
- background-color: #f4f6fb;
1969
- background-image: url('/project_bg/default_background.jpg');
1970
- background-size: cover;
1971
- background-position: center;
1972
- background-repeat: no-repeat;
1973
- background-attachment: fixed;
1974
- }
1975
-
1976
- body::before {
1977
- content: '';
1978
- position: fixed;
1979
- inset: 0;
1980
- background-color: rgba(255, 255, 255, 0.78);
1981
- z-index: -1;
1982
- }
1983
-
1984
- .acrylic-navbar {
1985
- background-color: rgba(248, 249, 250, 0.68);
1986
- -webkit-backdrop-filter: blur(12px) saturate(150%);
1987
- backdrop-filter: blur(12px) saturate(150%);
1988
- border-bottom: 1px solid rgba(0, 0, 0, 0.06);
1989
- }
1990
-
1991
- .layout-shell {
1992
- max-width: 1320px;
1993
- }
1994
-
1995
- .panel {
1996
- border-radius: 14px;
1997
- border: 1px solid rgba(255, 255, 255, 0.5);
1998
- background: rgba(255, 255, 255, 0.72);
1999
- -webkit-backdrop-filter: blur(10px) saturate(130%);
2000
- backdrop-filter: blur(10px) saturate(130%);
2001
- box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
2002
- padding: 1rem;
2003
- }
2004
-
2005
- .sidebar-panel {
2006
- position: sticky;
2007
- top: 1rem;
2008
- }
2009
-
2010
- .code-text {
2011
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
2012
- font-size: 0.8rem;
2013
- word-break: break-all;
2014
- }
2015
- </style>
2016
- </head>
2017
- <body>
2018
- <nav class="navbar navbar-expand-lg navbar-light acrylic-navbar">
2019
- <div class="container layout-shell">
2020
- <a class="navbar-brand" href="${basePath}/">MemesLuna</a>
2021
- <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavEndpoint">
2022
- <span class="navbar-toggler-icon"></span>
2023
- </button>
2024
- <div class="collapse navbar-collapse" id="navbarNavEndpoint">
2025
- <ul class="navbar-nav me-auto">
2026
- <li class="nav-item"><a class="nav-link" href="${basePath}/">\u9996\u9875</a></li>
2027
- <li class="nav-item"><a class="nav-link" href="${basePath}/admin">\u7BA1\u7406</a></li>
2028
- <li class="nav-item"><a class="nav-link active" href="${basePath}/admin/endpoint">\u7AEF\u70B9</a></li>
2029
- </ul>
2030
- </div>
2031
- </div>
2032
- </nav>
2033
-
2034
- <div class="container layout-shell mt-3 pb-4">
2035
- <div class="row g-3">
2036
- <div class="col-lg-3">
2037
- <div class="panel sidebar-panel">
2038
- <h5 class="mb-3">\u6DFB\u52A0 / \u7F16\u8F91\u7AEF\u70B9</h5>
2039
- <input id="endpoint-name" class="form-control mb-2" placeholder="\u7AEF\u70B9\u540D\u79F0" />
2040
- <input id="endpoint-description" class="form-control mb-2" placeholder="\u63CF\u8FF0" />
2041
- <input id="endpoint-url" class="form-control mb-2" placeholder="\u76EE\u6807 URL" />
2042
- <input id="endpoint-group" class="form-control mb-2" placeholder="\u5206\u7EC4" />
2043
- <select id="endpoint-method" class="form-select mb-2">
2044
- <option value="redirect">redirect</option>
2045
- <option value="proxy">proxy</option>
2046
- </select>
2047
- <select id="endpoint-mode" class="form-select mb-2">
2048
- <option value="normal">normal</option>
2049
- <option value="special_forward">special_forward</option>
2050
- <option value="special_pollinations">special_pollinations</option>
2051
- <option value="special_draw_redirect">special_draw_redirect</option>
2052
- </select>
2053
- <input id="endpoint-model" class="form-control mb-2" placeholder="modelName (optional)" />
2054
- <textarea id="endpoint-query" class="form-control code-text mb-2" rows="3" placeholder='queryParams JSON'></textarea>
2055
- <textarea id="endpoint-proxy" class="form-control code-text mb-2" rows="3" placeholder='proxySettings JSON'></textarea>
2056
-
2057
- <div class="d-grid gap-2">
2058
- <button id="save-endpoint" class="btn btn-primary">\u521B\u5EFA</button>
2059
- <button id="reset-endpoint" class="btn btn-outline-secondary">\u6E05\u7A7A</button>
2060
- </div>
2061
- </div>
2062
- </div>
2063
-
2064
- <div class="col-lg-9">
2065
- <div class="panel">
2066
- <h4 class="mb-3">API \u7AEF\u70B9\u7BA1\u7406</h4>
2067
- <p class="text-muted mb-3">\u901A\u8FC7 <code>${basePath}/\u7AEF\u70B9\u540D\u79F0</code> \u8BBF\u95EE\u3002</p>
2068
-
2069
- <div class="table-responsive">
2070
- <table class="table table-sm align-middle">
2071
- <thead>
2072
- <tr>
2073
- <th>\u540D\u79F0</th>
2074
- <th>\u63CF\u8FF0</th>
2075
- <th>\u6A21\u5F0F</th>
2076
- <th>\u76EE\u6807 URL</th>
2077
- <th></th>
2078
- </tr>
2079
- </thead>
2080
- <tbody id="endpoint-table"></tbody>
2081
- </table>
2082
- </div>
2083
- </div>
2084
- </div>
2085
- </div>
2086
-
2087
- <div id="endpoint-alert" class="alert mt-3 d-none"></div>
2088
- </div>
2089
-
2090
- <script>
2091
- const BASE_PATH = '${basePath}'
2092
- const endpointState = {
2093
- endpoints: [],
2094
- editingName: '',
2095
- }
2096
-
2097
- const byId = (id) => document.getElementById(id)
2098
-
2099
- function showAlert(message, type = 'info') {
2100
- const el = byId('endpoint-alert')
2101
- el.className = 'alert alert-' + type + ' mt-3'
2102
- el.textContent = message
2103
- el.classList.remove('d-none')
2104
- setTimeout(() => el.classList.add('d-none'), 2400)
2105
- }
2106
-
2107
- async function request(url, options = {}) {
2108
- const headers = Object.assign({}, options.headers || {})
2109
- if (options.body && !headers['Content-Type']) {
2110
- headers['Content-Type'] = 'application/json'
2111
- }
2112
- const res = await fetch(url, Object.assign({}, options, { headers }))
2113
- let data = null
2114
- try {
2115
- data = await res.json()
2116
- } catch {
2117
- data = null
2118
- }
2119
- if (!res.ok) {
2120
- throw new Error(data && data.error ? data.error : String(res.status) + ' ' + String(res.statusText))
2121
- }
2122
- return data
2123
- }
2124
-
2125
- function resetForm() {
2126
- endpointState.editingName = ''
2127
- byId('endpoint-name').value = ''
2128
- byId('endpoint-description').value = ''
2129
- byId('endpoint-url').value = ''
2130
- byId('endpoint-group').value = ''
2131
- byId('endpoint-method').value = 'redirect'
2132
- byId('endpoint-mode').value = 'normal'
2133
- byId('endpoint-model').value = ''
2134
- byId('endpoint-query').value = ''
2135
- byId('endpoint-proxy').value = ''
2136
- byId('save-endpoint').textContent = '\u521B\u5EFA'
2137
- byId('endpoint-name').disabled = false
2138
- }
2139
-
2140
- function fillForm(item) {
2141
- endpointState.editingName = item.name || ''
2142
- byId('endpoint-name').value = item.name || ''
2143
- byId('endpoint-description').value = item.description || ''
2144
- byId('endpoint-url').value = item.url || ''
2145
- byId('endpoint-group').value = item.group || ''
2146
- byId('endpoint-method').value = item.method || 'redirect'
2147
- byId('endpoint-mode').value = item.urlConstruction || 'normal'
2148
- byId('endpoint-model').value = item.modelName || ''
2149
- byId('endpoint-query').value = JSON.stringify(item.queryParams || [], null, 2)
2150
- byId('endpoint-proxy').value = JSON.stringify(item.proxySettings || {}, null, 2)
2151
- byId('save-endpoint').textContent = '\u66F4\u65B0'
2152
- byId('endpoint-name').disabled = true
2153
- }
2154
-
2155
- function renderTable() {
2156
- const body = byId('endpoint-table')
2157
- body.textContent = ''
2158
-
2159
- if (!endpointState.endpoints.length) {
2160
- const tr = document.createElement('tr')
2161
- const td = document.createElement('td')
2162
- td.colSpan = 5
2163
- td.className = 'text-muted'
2164
- td.textContent = '\u6682\u65E0\u7AEF\u70B9'
2165
- tr.appendChild(td)
2166
- body.appendChild(tr)
2167
- return
2168
- }
2169
-
2170
- endpointState.endpoints.forEach((item) => {
2171
- const tr = document.createElement('tr')
2172
- const visitUrl = BASE_PATH + '/' + encodeURIComponent(item.name || '')
2173
-
2174
- tr.innerHTML =
2175
- '<td class="code-text"></td>' +
2176
- '<td></td>' +
2177
- '<td class="code-text"></td>' +
2178
- '<td class="code-text"></td>' +
2179
- '<td></td>'
2180
-
2181
- const link = document.createElement('a')
2182
- link.href = visitUrl
2183
- link.target = '_blank'
2184
- link.className = 'text-decoration-none'
2185
- link.textContent = '/' + (item.name || '')
2186
- tr.children[0].appendChild(link)
2187
- tr.children[1].textContent = item.description || '-'
2188
- tr.children[2].textContent = (item.method || 'redirect') + ' \xB7 ' + (item.urlConstruction || 'normal')
2189
- tr.children[3].textContent = item.url || ''
2190
-
2191
- const actionWrap = document.createElement('div')
2192
- actionWrap.className = 'd-flex gap-1 justify-content-end'
2193
-
2194
- const editBtn = document.createElement('button')
2195
- editBtn.className = 'btn btn-sm btn-outline-primary'
2196
- editBtn.textContent = '\u7F16\u8F91'
2197
- editBtn.addEventListener('click', () => fillForm(item))
2198
-
2199
- const delBtn = document.createElement('button')
2200
- delBtn.className = 'btn btn-sm btn-outline-danger'
2201
- delBtn.textContent = '\u5220\u9664'
2202
- delBtn.addEventListener('click', async () => {
2203
- await request(BASE_PATH + '/api/admin/endpoints/' + encodeURIComponent(item.name || ''), {
2204
- method: 'DELETE',
2205
- })
2206
- showAlert('\u7AEF\u70B9\u5DF2\u5220\u9664', 'success')
2207
- await loadEndpoints()
2208
- if (endpointState.editingName === item.name) resetForm()
2209
- })
2210
-
2211
- actionWrap.appendChild(editBtn)
2212
- actionWrap.appendChild(delBtn)
2213
- tr.children[4].appendChild(actionWrap)
2214
- body.appendChild(tr)
2215
- })
2216
- }
2217
-
2218
- async function loadEndpoints() {
2219
- const data = await request(BASE_PATH + '/api/admin/endpoints')
2220
- endpointState.endpoints = Array.isArray(data.endpoints) ? data.endpoints : []
2221
- renderTable()
2222
- }
2223
-
2224
- byId('save-endpoint').addEventListener('click', async () => {
2225
- const name = byId('endpoint-name').value.trim()
2226
- const url = byId('endpoint-url').value.trim()
2227
- if (!name || !url) {
2228
- showAlert('name \u4E0E url \u5FC5\u586B', 'warning')
2229
- return
2230
- }
2231
-
2232
- let queryParams = []
2233
- let proxySettings = {}
2234
-
2235
- try {
2236
- queryParams = byId('endpoint-query').value.trim()
2237
- ? JSON.parse(byId('endpoint-query').value)
2238
- : []
2239
- } catch {
2240
- showAlert('queryParams JSON \u683C\u5F0F\u9519\u8BEF', 'warning')
2241
- return
2242
- }
2243
-
2244
- try {
2245
- proxySettings = byId('endpoint-proxy').value.trim()
2246
- ? JSON.parse(byId('endpoint-proxy').value)
2247
- : {}
2248
- } catch {
2249
- showAlert('proxySettings JSON \u683C\u5F0F\u9519\u8BEF', 'warning')
2250
- return
2251
- }
2252
-
2253
- const payload = {
2254
- name,
2255
- description: byId('endpoint-description').value.trim(),
2256
- url,
2257
- group: byId('endpoint-group').value.trim(),
2258
- method: byId('endpoint-method').value,
2259
- urlConstruction: byId('endpoint-mode').value,
2260
- modelName: byId('endpoint-model').value.trim(),
2261
- queryParams,
2262
- proxySettings,
2263
- }
2264
-
2265
- if (endpointState.editingName) {
2266
- await request(BASE_PATH + '/api/admin/endpoints/' + encodeURIComponent(endpointState.editingName), {
2267
- method: 'PATCH',
2268
- body: JSON.stringify(payload),
2269
- })
2270
- showAlert('\u7AEF\u70B9\u66F4\u65B0\u6210\u529F', 'success')
2271
- } else {
2272
- await request(BASE_PATH + '/api/admin/endpoints', {
2273
- method: 'POST',
2274
- body: JSON.stringify(payload),
2275
- })
2276
- showAlert('\u7AEF\u70B9\u521B\u5EFA\u6210\u529F', 'success')
2277
- }
2278
-
2279
- await loadEndpoints()
2280
- resetForm()
2281
- })
2282
-
2283
- byId('reset-endpoint').addEventListener('click', resetForm)
2284
-
2285
- loadEndpoints().catch((error) => {
2286
- showAlert(error instanceof Error ? error.message : String(error), 'danger')
2287
- })
2288
- </script>
2289
- <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
2290
- </body>
2291
- </html>`;
2292
- }
1809
+ __name(buildAdminHtml, "buildAdminHtml");
2293
1810
  async function updateMemesVariable(ctx, config, service) {
2294
1811
  const baseUrl = toAbsoluteBaseUrl(ctx, config);
2295
1812
  const inventory = await service.buildRouteInventory(config.backendPath);
2296
- ctx.chatluna.promptRenderer.setVariable("endpoint", inventory || "- \u6682\u65E0\u53EF\u7528\u8DEF\u7531");
2297
- const memeslunaText = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- \u6682\u65E0\u53EF\u7528\u8DEF\u7531").replace("{base_url}", baseUrl);
1813
+ ctx.chatluna.promptRenderer.setVariable("endpoint", inventory || "- 暂无可用路由");
1814
+ const memeslunaText = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- 暂无可用路由").replace("{base_url}", baseUrl);
2298
1815
  ctx.chatluna.promptRenderer.setVariable("memesluna", memeslunaText);
2299
1816
  }
1817
+ __name(updateMemesVariable, "updateMemesVariable");
2300
1818
  function applyConsole(ctx, config, service) {
2301
1819
  if (!ctx.console) return;
2302
1820
  const consoleService = ctx.console;
2303
1821
  const packageBase = import_path2.default.resolve(ctx.baseDir, "node_modules/koishi-plugin-memesluna");
2304
- const withReady = (handler) => {
1822
+ const withReady = /* @__PURE__ */ __name((handler) => {
2305
1823
  return async (...args) => {
2306
1824
  await service.ready;
2307
1825
  return await handler(...args);
2308
1826
  };
2309
- };
1827
+ }, "withReady");
2310
1828
  consoleService.addEntry({
2311
1829
  dev: import_path2.default.resolve(packageBase, "client/index.ts"),
2312
1830
  prod: import_path2.default.resolve(packageBase, "dist")
@@ -2397,6 +1915,7 @@ function applyConsole(ctx, config, service) {
2397
1915
  return `${toAbsoluteBaseUrl(ctx, config)}${config.backendPath}`;
2398
1916
  });
2399
1917
  }
1918
+ __name(applyConsole, "applyConsole");
2400
1919
  function applyServer(ctx, config, service) {
2401
1920
  if (!ctx.server) return;
2402
1921
  const basePath = config.backendPath;
@@ -2406,7 +1925,7 @@ function applyServer(ctx, config, service) {
2406
1925
  const collections = await service.getCollections();
2407
1926
  const collectionInfos = await Promise.all(collections.map((name2) => service.getCollectionInfo(name2)));
2408
1927
  const inventory = await service.buildRouteInventory(basePath);
2409
- const llmPrompt = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- \u6682\u65E0\u53EF\u7528\u8DEF\u7531").replace("{base_url}", baseUrl);
1928
+ const llmPrompt = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- 暂无可用路由").replace("{base_url}", baseUrl);
2410
1929
  koa.body = {
2411
1930
  llmPrompt,
2412
1931
  routeInventory: inventory,
@@ -2415,7 +1934,9 @@ function applyServer(ctx, config, service) {
2415
1934
  };
2416
1935
  });
2417
1936
  ctx.server.get(`${basePath}/api/admin/state`, async (koa) => {
2418
- koa.body = await buildAdminState(service);
1937
+ const state = await buildAdminState(service);
1938
+ console.log("Admin State:", JSON.stringify(state, null, 2));
1939
+ koa.body = state;
2419
1940
  });
2420
1941
  ctx.server.post(`${basePath}/api/admin/collections`, async (koa) => {
2421
1942
  const body = getRequestBody(koa);
@@ -2577,7 +2098,7 @@ function applyServer(ctx, config, service) {
2577
2098
  }
2578
2099
  const payload = {
2579
2100
  name: name2,
2580
- group: toTrimmedString(body.group) || "\u9ED8\u8BA4\u5206\u7EC4",
2101
+ group: toTrimmedString(body.group) || "默认分组",
2581
2102
  description: toTrimmedString(body.description),
2582
2103
  url,
2583
2104
  method: normalizeForwardMethod(body.method),
@@ -2598,7 +2119,7 @@ function applyServer(ctx, config, service) {
2598
2119
  const currentName = toTrimmedString(koa.params.name);
2599
2120
  const body = getRequestBody(koa);
2600
2121
  const payload = {};
2601
- if (body.group !== void 0) payload.group = toTrimmedString(body.group) || "\u9ED8\u8BA4\u5206\u7EC4";
2122
+ if (body.group !== void 0) payload.group = toTrimmedString(body.group) || "默认分组";
2602
2123
  if (body.description !== void 0) payload.description = toTrimmedString(body.description);
2603
2124
  if (body.url !== void 0) payload.url = toTrimmedString(body.url);
2604
2125
  if (body.method !== void 0) payload.method = normalizeForwardMethod(body.method);
@@ -2632,11 +2153,6 @@ function applyServer(ctx, config, service) {
2632
2153
  koa.set("Content-Type", "text/html; charset=utf-8");
2633
2154
  koa.body = buildAdminHtml(basePath);
2634
2155
  });
2635
- ctx.server.get(`${basePath}/admin/endpoint`, async (koa) => {
2636
- koa.status = 200;
2637
- koa.set("Content-Type", "text/html; charset=utf-8");
2638
- koa.body = buildAdminEndpointHtml(basePath);
2639
- });
2640
2156
  ctx.server.get(`${basePath}/api/collections/:name/resources`, async (koa) => {
2641
2157
  const collectionName = koa.params.name;
2642
2158
  const images = await service.getCollectionImages(collectionName);
@@ -2669,6 +2185,7 @@ function applyServer(ctx, config, service) {
2669
2185
  setKoaResponse(koa, result);
2670
2186
  });
2671
2187
  }
2188
+ __name(applyServer, "applyServer");
2672
2189
  function apply(ctx, config) {
2673
2190
  ctx.plugin(MemesLunaService, config);
2674
2191
  ctx.inject(["memesluna", "server"], async (ctx2) => {
@@ -2681,8 +2198,8 @@ function apply(ctx, config) {
2681
2198
  applyConsole(ctx2, config, service);
2682
2199
  });
2683
2200
  ctx.inject(["memesluna"], (ctx2) => {
2684
- const root = ctx2.command("memesluna", "MemesLuna \u547D\u4EE4");
2685
- root.subcommand(".list", "\u67E5\u770B\u5F53\u524D\u53EF\u7528\u8868\u60C5\u8DEF\u7531").action(async () => {
2201
+ const root = ctx2.command("memesluna", "MemesLuna 命令");
2202
+ root.subcommand(".list", "查看当前可用表情路由").action(async () => {
2686
2203
  const service = ctx2.memesluna;
2687
2204
  await service.ready;
2688
2205
  const [collectionNames, endpoints] = await Promise.all([
@@ -2693,14 +2210,14 @@ function apply(ctx, config) {
2693
2210
  for (const collectionName of collectionNames) {
2694
2211
  const info = await service.getCollectionInfo(collectionName);
2695
2212
  if (!info?.hasContent) continue;
2696
- lines.push(`${collectionName} ${collectionName}\u8868\u60C5\u5305`);
2213
+ lines.push(`${collectionName} ${collectionName}表情包`);
2697
2214
  }
2698
2215
  for (const endpoint of endpoints) {
2699
- const endpointLabel = endpoint.description || `${endpoint.name}\u7AEF\u70B9`;
2216
+ const endpointLabel = endpoint.description || `${endpoint.name}端点`;
2700
2217
  lines.push(`${endpoint.name} ${endpointLabel}`);
2701
2218
  }
2702
2219
  if (!lines.length) {
2703
- return "\u6682\u65E0\u53EF\u7528\u8868\u60C5\u8DEF\u7531";
2220
+ return "暂无可用表情路由";
2704
2221
  }
2705
2222
  return lines.join("\n");
2706
2223
  });
@@ -2709,9 +2226,9 @@ function apply(ctx, config) {
2709
2226
  ctx.inject(["memesluna", "chatluna", "server"], async (ctx2) => {
2710
2227
  const service = ctx2.memesluna;
2711
2228
  await service.ready;
2712
- const refresh = async () => {
2229
+ const refresh = /* @__PURE__ */ __name(async () => {
2713
2230
  await updateMemesVariable(ctx2, config, service);
2714
- };
2231
+ }, "refresh");
2715
2232
  await refresh();
2716
2233
  ctx2.setInterval(refresh, config.variableRefreshIntervalMs);
2717
2234
  ctx2.effect(() => () => {
@@ -2722,6 +2239,7 @@ function apply(ctx, config) {
2722
2239
  });
2723
2240
  }
2724
2241
  }
2242
+ __name(apply, "apply");
2725
2243
  var inject = ["database", "chatluna", "server"];
2726
2244
  // Annotate the CommonJS export names for ESM import in node:
2727
2245
  0 && (module.exports = {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-memesluna",
3
3
  "description": "Image Forward service for Koishi with ChatLuna integration",
4
- "version": "0.2.4",
4
+ "version": "0.2.5",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "types": "lib/index.d.ts",
@@ -82,4 +82,3 @@
82
82
  "icon": "mdi:emoticon-outline"
83
83
  }
84
84
  }
85
-