aicq-openclaw-plugin 1.3.0 → 1.4.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.
- package/dist/index.js +1994 -369
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -105,7 +105,7 @@ var require_main = __commonJS({
|
|
|
105
105
|
var fs6 = __require("fs");
|
|
106
106
|
var path7 = __require("path");
|
|
107
107
|
var os2 = __require("os");
|
|
108
|
-
var
|
|
108
|
+
var crypto7 = __require("crypto");
|
|
109
109
|
var packageJson = require_package();
|
|
110
110
|
var version = packageJson.version;
|
|
111
111
|
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
@@ -324,7 +324,7 @@ var require_main = __commonJS({
|
|
|
324
324
|
const authTag = ciphertext.subarray(-16);
|
|
325
325
|
ciphertext = ciphertext.subarray(12, -16);
|
|
326
326
|
try {
|
|
327
|
-
const aesgcm =
|
|
327
|
+
const aesgcm = crypto7.createDecipheriv("aes-256-gcm", key, nonce);
|
|
328
328
|
aesgcm.setAuthTag(authTag);
|
|
329
329
|
return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
|
|
330
330
|
} catch (error) {
|
|
@@ -7120,22 +7120,22 @@ var require_nacl_fast = __commonJS({
|
|
|
7120
7120
|
randombytes = fn;
|
|
7121
7121
|
};
|
|
7122
7122
|
(function() {
|
|
7123
|
-
var
|
|
7124
|
-
if (
|
|
7123
|
+
var crypto7 = typeof self !== "undefined" ? self.crypto || self.msCrypto : null;
|
|
7124
|
+
if (crypto7 && crypto7.getRandomValues) {
|
|
7125
7125
|
var QUOTA = 65536;
|
|
7126
7126
|
nacl3.setPRNG(function(x, n) {
|
|
7127
7127
|
var i, v = new Uint8Array(n);
|
|
7128
7128
|
for (i = 0; i < n; i += QUOTA) {
|
|
7129
|
-
|
|
7129
|
+
crypto7.getRandomValues(v.subarray(i, i + Math.min(n - i, QUOTA)));
|
|
7130
7130
|
}
|
|
7131
7131
|
for (i = 0; i < n; i++) x[i] = v[i];
|
|
7132
7132
|
cleanup(v);
|
|
7133
7133
|
});
|
|
7134
7134
|
} else if (typeof __require !== "undefined") {
|
|
7135
|
-
|
|
7136
|
-
if (
|
|
7135
|
+
crypto7 = __require("crypto");
|
|
7136
|
+
if (crypto7 && crypto7.randomBytes) {
|
|
7137
7137
|
nacl3.setPRNG(function(x, n) {
|
|
7138
|
-
var i, v =
|
|
7138
|
+
var i, v = crypto7.randomBytes(n);
|
|
7139
7139
|
for (i = 0; i < n; i++) x[i] = v[i];
|
|
7140
7140
|
cleanup(v);
|
|
7141
7141
|
});
|
|
@@ -7529,11 +7529,11 @@ var require_password = __commonJS({
|
|
|
7529
7529
|
exports.deriveKeyFromPassword = deriveKeyFromPassword;
|
|
7530
7530
|
exports.encryptWithPassword = encryptWithPassword2;
|
|
7531
7531
|
exports.decryptWithPassword = decryptWithPassword2;
|
|
7532
|
-
var
|
|
7532
|
+
var crypto7 = __importStar(__require("crypto"));
|
|
7533
7533
|
var nacl_js_1 = require_nacl();
|
|
7534
7534
|
var cipher_js_1 = require_cipher();
|
|
7535
7535
|
function deriveKeyFromPassword(password, salt, iterations = 1e5, keyLength = 32) {
|
|
7536
|
-
return Uint8Array.from(
|
|
7536
|
+
return Uint8Array.from(crypto7.pbkdf2Sync(password, salt, iterations, keyLength, "sha512"));
|
|
7537
7537
|
}
|
|
7538
7538
|
function deriveKeyLegacy(password, salt, iterations = 1e5) {
|
|
7539
7539
|
const encoder = new TextEncoder();
|
|
@@ -7779,12 +7779,12 @@ import * as fs from "fs";
|
|
|
7779
7779
|
import * as path from "path";
|
|
7780
7780
|
|
|
7781
7781
|
// node_modules/uuid/dist/esm-node/rng.js
|
|
7782
|
-
import
|
|
7782
|
+
import crypto2 from "crypto";
|
|
7783
7783
|
var rnds8Pool = new Uint8Array(256);
|
|
7784
7784
|
var poolPtr = rnds8Pool.length;
|
|
7785
7785
|
function rng() {
|
|
7786
7786
|
if (poolPtr > rnds8Pool.length - 16) {
|
|
7787
|
-
|
|
7787
|
+
crypto2.randomFillSync(rnds8Pool);
|
|
7788
7788
|
poolPtr = 0;
|
|
7789
7789
|
}
|
|
7790
7790
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
@@ -7800,9 +7800,9 @@ function unsafeStringify(arr, offset = 0) {
|
|
|
7800
7800
|
}
|
|
7801
7801
|
|
|
7802
7802
|
// node_modules/uuid/dist/esm-node/native.js
|
|
7803
|
-
import
|
|
7803
|
+
import crypto3 from "crypto";
|
|
7804
7804
|
var native_default = {
|
|
7805
|
-
randomUUID:
|
|
7805
|
+
randomUUID: crypto3.randomUUID
|
|
7806
7806
|
};
|
|
7807
7807
|
|
|
7808
7808
|
// node_modules/uuid/dist/esm-node/v4.js
|
|
@@ -7863,7 +7863,7 @@ function generateAgentId() {
|
|
|
7863
7863
|
// dist/store.js
|
|
7864
7864
|
import * as fs2 from "fs";
|
|
7865
7865
|
import * as path2 from "path";
|
|
7866
|
-
import * as
|
|
7866
|
+
import * as crypto4 from "crypto";
|
|
7867
7867
|
function encodeBuffer(buf) {
|
|
7868
7868
|
return Buffer.from(buf).toString("base64");
|
|
7869
7869
|
}
|
|
@@ -7871,11 +7871,11 @@ function decodeBuffer(b64) {
|
|
|
7871
7871
|
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
7872
7872
|
}
|
|
7873
7873
|
function deriveEncryptionKey(nodeId, salt) {
|
|
7874
|
-
return Uint8Array.from(
|
|
7874
|
+
return Uint8Array.from(crypto4.pbkdf2Sync(nodeId, salt, 1e5, 32, "sha512"));
|
|
7875
7875
|
}
|
|
7876
7876
|
function aes256GcmEncrypt(plaintext, key) {
|
|
7877
|
-
const iv =
|
|
7878
|
-
const cipher =
|
|
7877
|
+
const iv = crypto4.randomBytes(12);
|
|
7878
|
+
const cipher = crypto4.createCipheriv("aes-256-gcm", key, iv);
|
|
7879
7879
|
const encrypted = Buffer.concat([
|
|
7880
7880
|
cipher.update(plaintext),
|
|
7881
7881
|
cipher.final()
|
|
@@ -7892,7 +7892,7 @@ function aes256GcmDecrypt(packedBase64, key) {
|
|
|
7892
7892
|
const iv = packed.subarray(0, 12);
|
|
7893
7893
|
const authTag = packed.subarray(packed.length - 16);
|
|
7894
7894
|
const ciphertext = packed.subarray(12, packed.length - 16);
|
|
7895
|
-
const decipher =
|
|
7895
|
+
const decipher = crypto4.createDecipheriv("aes-256-gcm", key, iv);
|
|
7896
7896
|
decipher.setAuthTag(authTag);
|
|
7897
7897
|
const decrypted = Buffer.concat([
|
|
7898
7898
|
decipher.update(ciphertext),
|
|
@@ -7916,7 +7916,7 @@ var PluginStore = class {
|
|
|
7916
7916
|
this.offlineMessages = [];
|
|
7917
7917
|
this.dataDir = "";
|
|
7918
7918
|
this.storePath = "";
|
|
7919
|
-
this.encryptionSalt =
|
|
7919
|
+
this.encryptionSalt = crypto4.randomBytes(16);
|
|
7920
7920
|
this.saveTimer = null;
|
|
7921
7921
|
this.saveDebounceMs = 1e3;
|
|
7922
7922
|
this.dirty = false;
|
|
@@ -8335,7 +8335,7 @@ var PluginStore = class {
|
|
|
8335
8335
|
// dist/services/identityService.js
|
|
8336
8336
|
var import_qrcode = __toESM(require_lib(), 1);
|
|
8337
8337
|
var import_crypto3 = __toESM(require_dist(), 1);
|
|
8338
|
-
import * as
|
|
8338
|
+
import * as crypto5 from "crypto";
|
|
8339
8339
|
var IdentityService = class {
|
|
8340
8340
|
constructor(store, logger) {
|
|
8341
8341
|
this.exportTimers = /* @__PURE__ */ new Map();
|
|
@@ -8390,7 +8390,7 @@ var IdentityService = class {
|
|
|
8390
8390
|
* @returns QR code data URL (string starting with "data:image/png;base64,")
|
|
8391
8391
|
*/
|
|
8392
8392
|
async exportPrivateKeyQR(password) {
|
|
8393
|
-
const exportToken =
|
|
8393
|
+
const exportToken = crypto5.randomBytes(32).toString("hex");
|
|
8394
8394
|
const exportPayload = {
|
|
8395
8395
|
a: this.store.agentId,
|
|
8396
8396
|
pk: (0, import_crypto3.encodeBase64)(this.store.identityKeys.publicKey),
|
|
@@ -8513,10 +8513,13 @@ var DEFAULT_CONFIG = {
|
|
|
8513
8513
|
maxReconnectDelay: 6e4,
|
|
8514
8514
|
reconnectBackoffFactor: 2,
|
|
8515
8515
|
heartbeatIntervalMs: 3e4,
|
|
8516
|
-
requestTimeoutMs: 3e4
|
|
8516
|
+
requestTimeoutMs: 3e4,
|
|
8517
|
+
initialRetryWindowMs: 6e4,
|
|
8518
|
+
hourlyCheckIntervalMs: 36e5
|
|
8517
8519
|
};
|
|
8518
8520
|
var ServerClient = class {
|
|
8519
8521
|
constructor(serverUrl, store, logger, config2) {
|
|
8522
|
+
this.authToken = "";
|
|
8520
8523
|
this.ws = null;
|
|
8521
8524
|
this.wsReconnectTimer = null;
|
|
8522
8525
|
this.heartbeatTimer = null;
|
|
@@ -8524,6 +8527,9 @@ var ServerClient = class {
|
|
|
8524
8527
|
this.connectionState = "offline";
|
|
8525
8528
|
this.reconnectDelay = DEFAULT_CONFIG.initialReconnectDelay;
|
|
8526
8529
|
this.reconnectAttempts = 0;
|
|
8530
|
+
this.connectStartTimestamp = 0;
|
|
8531
|
+
this.hourlyCheckMode = false;
|
|
8532
|
+
this.hourlyCheckTimer = null;
|
|
8527
8533
|
this.stateChangeCallbacks = [];
|
|
8528
8534
|
this.wsHandlers = /* @__PURE__ */ new Map();
|
|
8529
8535
|
this.serverUrl = serverUrl;
|
|
@@ -8531,6 +8537,37 @@ var ServerClient = class {
|
|
|
8531
8537
|
this.logger = logger;
|
|
8532
8538
|
this.config = { ...DEFAULT_CONFIG, ...config2 };
|
|
8533
8539
|
}
|
|
8540
|
+
/**
|
|
8541
|
+
* Check whether the initial retry window has elapsed.
|
|
8542
|
+
*/
|
|
8543
|
+
isInitialRetryWindowExpired() {
|
|
8544
|
+
if (this.connectStartTimestamp === 0)
|
|
8545
|
+
return false;
|
|
8546
|
+
return Date.now() - this.connectStartTimestamp >= this.config.initialRetryWindowMs;
|
|
8547
|
+
}
|
|
8548
|
+
/**
|
|
8549
|
+
* Set the JWT auth token for all subsequent requests.
|
|
8550
|
+
*/
|
|
8551
|
+
setAuthToken(token) {
|
|
8552
|
+
this.authToken = token;
|
|
8553
|
+
this.logger.info("[Server] Auth token set (" + token.substring(0, 12) + "...)");
|
|
8554
|
+
}
|
|
8555
|
+
/**
|
|
8556
|
+
* Get the current auth token.
|
|
8557
|
+
*/
|
|
8558
|
+
getAuthToken() {
|
|
8559
|
+
return this.authToken;
|
|
8560
|
+
}
|
|
8561
|
+
/**
|
|
8562
|
+
* Build common headers including Authorization when token is available.
|
|
8563
|
+
*/
|
|
8564
|
+
authHeaders() {
|
|
8565
|
+
const headers = { "Content-Type": "application/json" };
|
|
8566
|
+
if (this.authToken) {
|
|
8567
|
+
headers["Authorization"] = "Bearer " + this.authToken;
|
|
8568
|
+
}
|
|
8569
|
+
return headers;
|
|
8570
|
+
}
|
|
8534
8571
|
// ----------------------------------------------------------------
|
|
8535
8572
|
// Connection state management
|
|
8536
8573
|
// ----------------------------------------------------------------
|
|
@@ -8574,12 +8611,17 @@ var ServerClient = class {
|
|
|
8574
8611
|
// ----------------------------------------------------------------
|
|
8575
8612
|
/**
|
|
8576
8613
|
* Connect to the server via WebSocket and start heartbeat.
|
|
8614
|
+
* Strategy: retry aggressively for `initialRetryWindowMs` (default 1 minute),
|
|
8615
|
+
* then stop and switch to hourly checks.
|
|
8577
8616
|
*/
|
|
8578
8617
|
connectWebSocket() {
|
|
8579
8618
|
if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) {
|
|
8580
8619
|
this.logger.debug("[Server] WebSocket already connecting/connected");
|
|
8581
8620
|
return;
|
|
8582
8621
|
}
|
|
8622
|
+
if (this.connectStartTimestamp === 0 && !this.hourlyCheckMode) {
|
|
8623
|
+
this.connectStartTimestamp = Date.now();
|
|
8624
|
+
}
|
|
8583
8625
|
let wsUrl;
|
|
8584
8626
|
try {
|
|
8585
8627
|
const baseUrl = this.serverUrl.replace(/^http/, "ws");
|
|
@@ -8611,12 +8653,16 @@ var ServerClient = class {
|
|
|
8611
8653
|
this.wsConnected = true;
|
|
8612
8654
|
this.reconnectDelay = this.config.initialReconnectDelay;
|
|
8613
8655
|
this.reconnectAttempts = 0;
|
|
8656
|
+
this.connectStartTimestamp = 0;
|
|
8657
|
+
this.hourlyCheckMode = false;
|
|
8658
|
+
this.cancelHourlyCheck();
|
|
8614
8659
|
this.setConnectionState("online");
|
|
8615
8660
|
this.logger.info("[Server] WebSocket connected");
|
|
8616
8661
|
this.wsSend({
|
|
8617
8662
|
type: "online",
|
|
8618
8663
|
nodeId: this.store.agentId,
|
|
8619
|
-
publicKey: Buffer.from(this.store.identityKeys.publicKey).toString("base64")
|
|
8664
|
+
publicKey: Buffer.from(this.store.identityKeys.publicKey).toString("base64"),
|
|
8665
|
+
...this.authToken ? { token: this.authToken } : {}
|
|
8620
8666
|
});
|
|
8621
8667
|
this.startHeartbeat();
|
|
8622
8668
|
});
|
|
@@ -8642,7 +8688,7 @@ var ServerClient = class {
|
|
|
8642
8688
|
});
|
|
8643
8689
|
}
|
|
8644
8690
|
/**
|
|
8645
|
-
* Disconnect the WebSocket and stop reconnection.
|
|
8691
|
+
* Disconnect the WebSocket and stop all reconnection attempts.
|
|
8646
8692
|
*/
|
|
8647
8693
|
disconnectWebSocket() {
|
|
8648
8694
|
this.stopHeartbeat();
|
|
@@ -8652,6 +8698,7 @@ var ServerClient = class {
|
|
|
8652
8698
|
this.ws = null;
|
|
8653
8699
|
}
|
|
8654
8700
|
this.wsConnected = false;
|
|
8701
|
+
this.connectStartTimestamp = 0;
|
|
8655
8702
|
this.setConnectionState("offline");
|
|
8656
8703
|
}
|
|
8657
8704
|
/**
|
|
@@ -8681,15 +8728,30 @@ var ServerClient = class {
|
|
|
8681
8728
|
return false;
|
|
8682
8729
|
}
|
|
8683
8730
|
// ----------------------------------------------------------------
|
|
8684
|
-
//
|
|
8731
|
+
// Reconnection: try for 1 min, then hourly
|
|
8685
8732
|
// ----------------------------------------------------------------
|
|
8686
8733
|
/**
|
|
8687
|
-
* Schedule a reconnection attempt
|
|
8734
|
+
* Schedule a reconnection attempt.
|
|
8735
|
+
*
|
|
8736
|
+
* Phase 1 (initial retry window, default 1 minute):
|
|
8737
|
+
* Retries with exponential backoff (1s → 2s → 4s → ... → 60s max).
|
|
8738
|
+
* Once the window expires, stops retrying and switches to hourly checks.
|
|
8688
8739
|
*
|
|
8689
|
-
*
|
|
8690
|
-
*
|
|
8740
|
+
* Phase 2 (hourly check mode):
|
|
8741
|
+
* After the initial burst fails, sets up a timer that retries once
|
|
8742
|
+
* every hour. If a retry succeeds, hourly mode is cancelled and
|
|
8743
|
+
* normal operation resumes.
|
|
8691
8744
|
*/
|
|
8692
8745
|
scheduleReconnect() {
|
|
8746
|
+
if (this.hourlyCheckMode) {
|
|
8747
|
+
this.logger.info("[Server] Hourly check mode active \u2014 next attempt scheduled automatically");
|
|
8748
|
+
return;
|
|
8749
|
+
}
|
|
8750
|
+
if (this.isInitialRetryWindowExpired()) {
|
|
8751
|
+
this.logger.warn(`[Server] Initial retry window (${this.config.initialRetryWindowMs / 1e3}s) expired after ${this.reconnectAttempts} attempts. Switching to hourly check mode.`);
|
|
8752
|
+
this.enterHourlyCheckMode();
|
|
8753
|
+
return;
|
|
8754
|
+
}
|
|
8693
8755
|
if (this.wsReconnectTimer)
|
|
8694
8756
|
return;
|
|
8695
8757
|
const jitter = 0.75 + Math.random() * 0.5;
|
|
@@ -8704,6 +8766,36 @@ var ServerClient = class {
|
|
|
8704
8766
|
}, delay);
|
|
8705
8767
|
this.reconnectDelay = Math.min(this.reconnectDelay * this.config.reconnectBackoffFactor, this.config.maxReconnectDelay);
|
|
8706
8768
|
}
|
|
8769
|
+
/**
|
|
8770
|
+
* Enter hourly check mode: stop aggressive retries,
|
|
8771
|
+
* set up a single timer that fires once per hour.
|
|
8772
|
+
*/
|
|
8773
|
+
enterHourlyCheckMode() {
|
|
8774
|
+
this.hourlyCheckMode = true;
|
|
8775
|
+
this.reconnectDelay = this.config.initialReconnectDelay;
|
|
8776
|
+
this.reconnectAttempts = 0;
|
|
8777
|
+
if (this.wsReconnectTimer) {
|
|
8778
|
+
clearTimeout(this.wsReconnectTimer);
|
|
8779
|
+
this.wsReconnectTimer = null;
|
|
8780
|
+
}
|
|
8781
|
+
this.setConnectionState("offline");
|
|
8782
|
+
this.logger.info(`[Server] Entering hourly check mode \u2014 will retry every ${this.config.hourlyCheckIntervalMs / 6e4} minutes`);
|
|
8783
|
+
this.hourlyCheckTimer = setTimeout(() => {
|
|
8784
|
+
this.hourlyCheckTimer = null;
|
|
8785
|
+
this.logger.info("[Server] Hourly check: attempting to reconnect...");
|
|
8786
|
+
this.connectWebSocket();
|
|
8787
|
+
}, this.config.hourlyCheckIntervalMs);
|
|
8788
|
+
}
|
|
8789
|
+
/**
|
|
8790
|
+
* Cancel the hourly check timer.
|
|
8791
|
+
*/
|
|
8792
|
+
cancelHourlyCheck() {
|
|
8793
|
+
if (this.hourlyCheckTimer) {
|
|
8794
|
+
clearTimeout(this.hourlyCheckTimer);
|
|
8795
|
+
this.hourlyCheckTimer = null;
|
|
8796
|
+
}
|
|
8797
|
+
this.hourlyCheckMode = false;
|
|
8798
|
+
}
|
|
8707
8799
|
/**
|
|
8708
8800
|
* Cancel a pending reconnection.
|
|
8709
8801
|
*/
|
|
@@ -8712,20 +8804,27 @@ var ServerClient = class {
|
|
|
8712
8804
|
clearTimeout(this.wsReconnectTimer);
|
|
8713
8805
|
this.wsReconnectTimer = null;
|
|
8714
8806
|
}
|
|
8807
|
+
this.cancelHourlyCheck();
|
|
8715
8808
|
this.reconnectDelay = this.config.initialReconnectDelay;
|
|
8716
8809
|
this.reconnectAttempts = 0;
|
|
8810
|
+
this.connectStartTimestamp = 0;
|
|
8717
8811
|
}
|
|
8718
8812
|
// ----------------------------------------------------------------
|
|
8719
8813
|
// REST API methods
|
|
8720
8814
|
// ----------------------------------------------------------------
|
|
8721
8815
|
/**
|
|
8722
8816
|
* Register this node on the server.
|
|
8817
|
+
* Captures JWT token from response if returned.
|
|
8723
8818
|
*/
|
|
8724
8819
|
async registerNode(agentId, publicKey) {
|
|
8725
|
-
|
|
8820
|
+
const res = await this.fetchPost("/api/v1/node/register", {
|
|
8726
8821
|
id: agentId,
|
|
8727
8822
|
publicKey: Buffer.from(publicKey).toString("base64")
|
|
8728
8823
|
});
|
|
8824
|
+
if (res?.token) {
|
|
8825
|
+
this.setAuthToken(res.token);
|
|
8826
|
+
}
|
|
8827
|
+
return res?.ok ?? false;
|
|
8729
8828
|
}
|
|
8730
8829
|
/**
|
|
8731
8830
|
* Request a temporary 6-digit number for friend discovery.
|
|
@@ -8848,12 +8947,15 @@ var ServerClient = class {
|
|
|
8848
8947
|
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
|
|
8849
8948
|
const resp = await fetch(url, {
|
|
8850
8949
|
method: "POST",
|
|
8851
|
-
headers:
|
|
8950
|
+
headers: this.authHeaders(),
|
|
8852
8951
|
body: JSON.stringify(body),
|
|
8853
8952
|
signal: controller.signal
|
|
8854
8953
|
});
|
|
8855
8954
|
clearTimeout(timeout);
|
|
8856
8955
|
if (!resp.ok) {
|
|
8956
|
+
if (resp.status === 401 && this.authToken) {
|
|
8957
|
+
this.logger.warn(`[Server] 401 Unauthorized on ${path7} \u2014 token may be expired`);
|
|
8958
|
+
}
|
|
8857
8959
|
const text = await resp.text();
|
|
8858
8960
|
this.logger.error(`[Server] API error ${resp.status} on ${path7}: ${text}`);
|
|
8859
8961
|
return null;
|
|
@@ -8873,7 +8975,10 @@ var ServerClient = class {
|
|
|
8873
8975
|
try {
|
|
8874
8976
|
const controller = new AbortController();
|
|
8875
8977
|
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
|
|
8876
|
-
const resp = await fetch(url, {
|
|
8978
|
+
const resp = await fetch(url, {
|
|
8979
|
+
signal: controller.signal,
|
|
8980
|
+
headers: this.authToken ? { Authorization: "Bearer " + this.authToken } : {}
|
|
8981
|
+
});
|
|
8877
8982
|
clearTimeout(timeout);
|
|
8878
8983
|
if (!resp.ok) {
|
|
8879
8984
|
const text = await resp.text();
|
|
@@ -8897,7 +9002,7 @@ var ServerClient = class {
|
|
|
8897
9002
|
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
|
|
8898
9003
|
const resp = await fetch(url, {
|
|
8899
9004
|
method: "DELETE",
|
|
8900
|
-
headers:
|
|
9005
|
+
headers: this.authHeaders(),
|
|
8901
9006
|
body: body ? JSON.stringify(body) : void 0,
|
|
8902
9007
|
signal: controller.signal
|
|
8903
9008
|
});
|
|
@@ -8915,7 +9020,7 @@ var ServerClient = class {
|
|
|
8915
9020
|
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
|
|
8916
9021
|
const resp = await fetch(url, {
|
|
8917
9022
|
method: "POST",
|
|
8918
|
-
headers:
|
|
9023
|
+
headers: this.authHeaders(),
|
|
8919
9024
|
body: JSON.stringify(body),
|
|
8920
9025
|
signal: controller.signal
|
|
8921
9026
|
});
|
|
@@ -8955,7 +9060,7 @@ var ServerClient = class {
|
|
|
8955
9060
|
|
|
8956
9061
|
// dist/handshake/handshakeManager.js
|
|
8957
9062
|
var import_crypto4 = __toESM(require_dist(), 1);
|
|
8958
|
-
import * as
|
|
9063
|
+
import * as crypto6 from "crypto";
|
|
8959
9064
|
var HandshakeManager = class {
|
|
8960
9065
|
constructor(store, serverClient, config2, logger) {
|
|
8961
9066
|
this.pendingInitiates = /* @__PURE__ */ new Map();
|
|
@@ -9173,7 +9278,7 @@ var HandshakeManager = class {
|
|
|
9173
9278
|
combined.set(ee, 0);
|
|
9174
9279
|
combined.set(se, 32);
|
|
9175
9280
|
combined.set(es, 64);
|
|
9176
|
-
const nonce =
|
|
9281
|
+
const nonce = crypto6.randomBytes(16).toString("hex");
|
|
9177
9282
|
const newSessionKey = (0, import_crypto4.deriveSessionKey)(combined, "aicq-session-rotate-" + nonce);
|
|
9178
9283
|
const updatedSession = {
|
|
9179
9284
|
peerId,
|
|
@@ -10187,17 +10292,17 @@ var MessageSendingHook = class {
|
|
|
10187
10292
|
var CSS = `
|
|
10188
10293
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
10189
10294
|
:root {
|
|
10190
|
-
--bg: #
|
|
10191
|
-
--bg5: #
|
|
10192
|
-
--accent: #
|
|
10193
|
-
--ok: #
|
|
10194
|
-
--danger: #ef4444; --danger-bg: rgba(239,68,68,.
|
|
10195
|
-
--border: #
|
|
10295
|
+
--bg: #f5f7fa; --bg2: #ffffff; --bg3: #f0f2f5; --bg4: #e4e7ec;
|
|
10296
|
+
--bg5: #d1d5db; --text: #1f2937; --text2: #6b7280; --text3: #9ca3af;
|
|
10297
|
+
--accent: #4f46e5; --accent2: #6366f1; --accent-bg: rgba(79,70,229,.08);
|
|
10298
|
+
--ok: #10b981; --ok-bg: rgba(16,185,129,.08); --warn: #f59e0b; --warn-bg: rgba(245,158,11,.08);
|
|
10299
|
+
--danger: #ef4444; --danger-bg: rgba(239,68,68,.08); --info: #3b82f6; --info-bg: rgba(59,130,246,.08);
|
|
10300
|
+
--border: #e5e7eb; --radius: 8px; --radius-lg: 12px; --shadow: 0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
|
|
10196
10301
|
--sidebar-w: 240px; --header-h: 56px;
|
|
10197
10302
|
--transition: .2s cubic-bezier(.4,0,.2,1);
|
|
10198
10303
|
}
|
|
10199
10304
|
html, body { height: 100%; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.6; overflow: hidden; }
|
|
10200
|
-
a { color: var(--
|
|
10305
|
+
a { color: var(--accent); text-decoration: none; }
|
|
10201
10306
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
10202
10307
|
::-webkit-scrollbar-track { background: transparent; }
|
|
10203
10308
|
::-webkit-scrollbar-thumb { background: var(--bg4); border-radius: 3px; }
|
|
@@ -10209,7 +10314,7 @@ a { color: var(--info); text-decoration: none; }
|
|
|
10209
10314
|
/* Sidebar */
|
|
10210
10315
|
.sidebar {
|
|
10211
10316
|
width: var(--sidebar-w); min-width: var(--sidebar-w); height: 100vh;
|
|
10212
|
-
background:
|
|
10317
|
+
background: #ffffff; border-right: 1px solid var(--border);
|
|
10213
10318
|
display: flex; flex-direction: column; transition: width var(--transition), min-width var(--transition);
|
|
10214
10319
|
z-index: 20; overflow: hidden;
|
|
10215
10320
|
}
|
|
@@ -10224,7 +10329,7 @@ a { color: var(--info); text-decoration: none; }
|
|
|
10224
10329
|
border-bottom: 1px solid var(--border); min-height: var(--header-h);
|
|
10225
10330
|
}
|
|
10226
10331
|
.sidebar-logo {
|
|
10227
|
-
width: 32px; height: 32px; border-radius: 8px; background: linear-gradient(135deg, var(--accent), #
|
|
10332
|
+
width: 32px; height: 32px; border-radius: 8px; background: linear-gradient(135deg, var(--accent), #7c3aed);
|
|
10228
10333
|
display: grid; place-items: center; font-size: 13px; font-weight: 800; color: #fff; flex-shrink: 0;
|
|
10229
10334
|
}
|
|
10230
10335
|
.sidebar-header-text h1 { font-size: 14px; font-weight: 700; line-height: 1.2; }
|
|
@@ -10263,7 +10368,7 @@ a { color: var(--info); text-decoration: none; }
|
|
|
10263
10368
|
.main-header {
|
|
10264
10369
|
height: var(--header-h); min-height: var(--header-h);
|
|
10265
10370
|
display: flex; align-items: center; gap: 16px; padding: 0 24px;
|
|
10266
|
-
background:
|
|
10371
|
+
background: #ffffff; border-bottom: 1px solid var(--border);
|
|
10267
10372
|
}
|
|
10268
10373
|
.toggle-btn {
|
|
10269
10374
|
width: 32px; height: 32px; border-radius: 6px; background: var(--bg3);
|
|
@@ -10295,12 +10400,12 @@ a { color: var(--info); text-decoration: none; }
|
|
|
10295
10400
|
.btn-default:hover:not(:disabled) { background: var(--bg4); }
|
|
10296
10401
|
.btn-primary { background: var(--accent); color: #fff; }
|
|
10297
10402
|
.btn-primary:hover:not(:disabled) { background: var(--accent2); }
|
|
10298
|
-
.btn-danger { background: var(--danger-bg); color: #
|
|
10299
|
-
.btn-danger:hover:not(:disabled) { background: rgba(239,68,68,.
|
|
10300
|
-
.btn-ok { background: var(--ok-bg); color: #
|
|
10301
|
-
.btn-ok:hover:not(:disabled) { background: rgba(
|
|
10302
|
-
.btn-warn { background: var(--warn-bg); color: #
|
|
10303
|
-
.btn-warn:hover:not(:disabled) { background: rgba(
|
|
10403
|
+
.btn-danger { background: var(--danger-bg); color: #dc2626; border: 1px solid rgba(239,68,68,.15); }
|
|
10404
|
+
.btn-danger:hover:not(:disabled) { background: rgba(239,68,68,.15); }
|
|
10405
|
+
.btn-ok { background: var(--ok-bg); color: #059669; border: 1px solid rgba(16,185,129,.15); }
|
|
10406
|
+
.btn-ok:hover:not(:disabled) { background: rgba(16,185,129,.15); }
|
|
10407
|
+
.btn-warn { background: var(--warn-bg); color: #d97706; border: 1px solid rgba(245,158,11,.15); }
|
|
10408
|
+
.btn-warn:hover:not(:disabled) { background: rgba(245,158,11,.15); }
|
|
10304
10409
|
.btn-ghost { background: transparent; color: var(--text2); }
|
|
10305
10410
|
.btn-ghost:hover:not(:disabled) { background: var(--bg3); color: var(--text); }
|
|
10306
10411
|
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
|
@@ -10373,17 +10478,18 @@ tbody tr:hover { background: var(--bg3); }
|
|
|
10373
10478
|
.provider-card .prov-desc { font-size: 12px; color: var(--text3); margin-bottom: 10px; }
|
|
10374
10479
|
.provider-card .prov-model { font-size: 11px; color: var(--text2); background: var(--bg3); padding: 3px 8px; border-radius: 4px; display: inline-block; }
|
|
10375
10480
|
.provider-card .prov-actions { margin-top: 12px; display: flex; gap: 6px; }
|
|
10481
|
+
.provider-card.custom-provider { border-color: var(--accent); border-style: dashed; }
|
|
10376
10482
|
|
|
10377
10483
|
/* Modal */
|
|
10378
10484
|
.modal-overlay {
|
|
10379
|
-
position: fixed; inset: 0; background: rgba(0,0,0,.
|
|
10485
|
+
position: fixed; inset: 0; background: rgba(0,0,0,.3); display: flex;
|
|
10380
10486
|
align-items: center; justify-content: center; z-index: 100;
|
|
10381
10487
|
animation: fadeIn .15s ease-out;
|
|
10382
10488
|
}
|
|
10383
10489
|
.modal-overlay.hidden { display: none; }
|
|
10384
10490
|
.modal {
|
|
10385
10491
|
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
10386
|
-
padding: 28px; width: 90%; max-width: 520px; box-shadow: 0
|
|
10492
|
+
padding: 28px; width: 90%; max-width: 520px; box-shadow: 0 10px 25px rgba(0,0,0,.1), 0 6px 10px rgba(0,0,0,.06);
|
|
10387
10493
|
max-height: 85vh; overflow-y: auto; animation: modalIn .2s ease-out;
|
|
10388
10494
|
}
|
|
10389
10495
|
@keyframes modalIn { from { transform: scale(.95); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
@@ -10419,15 +10525,15 @@ tbody tr:hover { background: var(--bg3); }
|
|
|
10419
10525
|
/* Toast */
|
|
10420
10526
|
.toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
|
|
10421
10527
|
.toast {
|
|
10422
|
-
padding: 12px 20px; border-radius: var(--radius); color:
|
|
10423
|
-
animation: slideIn .2s ease-out; box-shadow:
|
|
10528
|
+
padding: 12px 20px; border-radius: var(--radius); color: var(--text); font-size: 13px;
|
|
10529
|
+
animation: slideIn .2s ease-out; box-shadow: 0 4px 12px rgba(0,0,0,.08); display: flex; align-items: center; gap: 8px;
|
|
10424
10530
|
max-width: 400px;
|
|
10425
10531
|
}
|
|
10426
10532
|
.toast.hidden { display: none; }
|
|
10427
|
-
.toast-ok { background: #
|
|
10428
|
-
.toast-err { background: #
|
|
10429
|
-
.toast-info { background: #
|
|
10430
|
-
.toast-warn { background: #
|
|
10533
|
+
.toast-ok { background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; }
|
|
10534
|
+
.toast-err { background: #fef2f2; border: 1px solid #fecaca; color: #991b1b; }
|
|
10535
|
+
.toast-info { background: #eff6ff; border: 1px solid #bfdbfe; color: #1e3a5f; }
|
|
10536
|
+
.toast-warn { background: #fffbeb; border: 1px solid #fde68a; color: #92400e; }
|
|
10431
10537
|
@keyframes slideIn { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
10432
10538
|
|
|
10433
10539
|
/* Actions cell */
|
|
@@ -10459,8 +10565,9 @@ tbody tr:hover { background: var(--bg3); }
|
|
|
10459
10565
|
|
|
10460
10566
|
/* Offline banner */
|
|
10461
10567
|
.offline-banner {
|
|
10462
|
-
background:
|
|
10463
|
-
color: #
|
|
10568
|
+
background: #fef2f2;
|
|
10569
|
+
color: #991b1b;
|
|
10570
|
+
border-bottom: 1px solid #fecaca;
|
|
10464
10571
|
padding: 10px 24px;
|
|
10465
10572
|
font-size: 13px;
|
|
10466
10573
|
display: flex;
|
|
@@ -10488,6 +10595,380 @@ tbody tr:hover { background: var(--bg3); }
|
|
|
10488
10595
|
}
|
|
10489
10596
|
`;
|
|
10490
10597
|
var JS = `
|
|
10598
|
+
// \u2500\u2500 i18n \u2500\u2500
|
|
10599
|
+
const _lang = 'zh'; // Default Chinese; set to 'en' to switch to English
|
|
10600
|
+
const _T = {
|
|
10601
|
+
// Sidebar
|
|
10602
|
+
nav_overview: { zh: '\u6982\u89C8', en: 'Overview' },
|
|
10603
|
+
nav_management: { zh: '\u7BA1\u7406', en: 'Management' },
|
|
10604
|
+
nav_system: { zh: '\u7CFB\u7EDF', en: 'System' },
|
|
10605
|
+
nav_dashboard: { zh: '\u4EEA\u8868\u76D8', en: 'Dashboard' },
|
|
10606
|
+
nav_agents: { zh: '\u667A\u80FD\u4F53', en: 'Agents' },
|
|
10607
|
+
nav_friends: { zh: '\u597D\u53CB', en: 'Friends' },
|
|
10608
|
+
nav_models: { zh: '\u6A21\u578B', en: 'Models' },
|
|
10609
|
+
nav_settings: { zh: '\u8BBE\u7F6E', en: 'Settings' },
|
|
10610
|
+
collapse_sidebar: { zh: '\u6536\u8D77\u4FA7\u680F', en: 'Collapse sidebar' },
|
|
10611
|
+
management_console: { zh: '\u7BA1\u7406\u63A7\u5236\u53F0', en: 'Management Console' },
|
|
10612
|
+
// Header
|
|
10613
|
+
connecting: { zh: '\u8FDE\u63A5\u4E2D...', en: 'Connecting...' },
|
|
10614
|
+
connected: { zh: '\u5DF2\u8FDE\u63A5', en: 'Connected' },
|
|
10615
|
+
disconnected: { zh: '\u5DF2\u65AD\u5F00', en: 'Disconnected' },
|
|
10616
|
+
refresh: { zh: '\u5237\u65B0', en: 'Refresh' },
|
|
10617
|
+
// Dashboard
|
|
10618
|
+
loading_dashboard: { zh: '\u6B63\u5728\u52A0\u8F7D\u4EEA\u8868\u76D8...', en: 'Loading dashboard...' },
|
|
10619
|
+
failed_connect: { zh: '\u65E0\u6CD5\u8FDE\u63A5\u5230 AICQ \u63D2\u4EF6', en: 'Failed to connect to AICQ plugin' },
|
|
10620
|
+
server_status: { zh: '\u670D\u52A1\u5668\u72B6\u6001', en: 'Server Status' },
|
|
10621
|
+
total_friends: { zh: '\u597D\u53CB\u603B\u6570', en: 'Total Friends' },
|
|
10622
|
+
active_sessions: { zh: '\u6D3B\u8DC3\u4F1A\u8BDD', en: 'Active Sessions' },
|
|
10623
|
+
encrypted_sessions: { zh: '\u52A0\u5BC6\u4F1A\u8BDD', en: 'Encrypted sessions' },
|
|
10624
|
+
agent_id: { zh: '\u667A\u80FD\u4F53 ID', en: 'Agent ID' },
|
|
10625
|
+
fingerprint: { zh: '\u6307\u7EB9', en: 'Fingerprint' },
|
|
10626
|
+
recent_friends: { zh: '\u6700\u8FD1\u597D\u53CB', en: 'Recent Friends' },
|
|
10627
|
+
view_all: { zh: '\u67E5\u770B\u5168\u90E8 \u2192', en: 'View All \u2192' },
|
|
10628
|
+
identity_info: { zh: '\u8EAB\u4EFD\u4FE1\u606F', en: 'Identity Info' },
|
|
10629
|
+
server_url: { zh: '\u670D\u52A1\u5668\u5730\u5740', en: 'Server URL' },
|
|
10630
|
+
connection: { zh: '\u8FDE\u63A5', en: 'Connection' },
|
|
10631
|
+
online: { zh: '\u5728\u7EBF', en: 'Online' },
|
|
10632
|
+
offline: { zh: '\u79BB\u7EBF', en: 'Offline' },
|
|
10633
|
+
plugin_version: { zh: '\u63D2\u4EF6\u7248\u672C', en: 'Plugin Version' },
|
|
10634
|
+
mgmt_ui_access: { zh: '\u7BA1\u7406\u754C\u9762\u8BBF\u95EE', en: 'Management UI Access' },
|
|
10635
|
+
current_url: { zh: '\u5F53\u524D\u5730\u5740', en: 'Current URL' },
|
|
10636
|
+
local_access: { zh: '\u672C\u5730\u8BBF\u95EE', en: 'Local Access' },
|
|
10637
|
+
open: { zh: '\u6253\u5F00', en: 'Open' },
|
|
10638
|
+
gateway_path: { zh: '\u7F51\u5173\u8DEF\u5F84', en: 'Gateway Path' },
|
|
10639
|
+
no_friends_yet: { zh: '\u6682\u65E0\u597D\u53CB', en: 'No friends yet' },
|
|
10640
|
+
// Agents
|
|
10641
|
+
loading_agents: { zh: '\u6B63\u5728\u52A0\u8F7D\u667A\u80FD\u4F53...', en: 'Loading agents...' },
|
|
10642
|
+
active: { zh: '\u542F\u7528', en: 'active' },
|
|
10643
|
+
disabled: { zh: '\u7981\u7528', en: 'disabled' },
|
|
10644
|
+
default_model: { zh: '\u9ED8\u8BA4', en: 'default' },
|
|
10645
|
+
no_agents_configured: { zh: '\u672A\u914D\u7F6E\u667A\u80FD\u4F53', en: 'No agents configured' },
|
|
10646
|
+
add_agents_hint: { zh: '\u8BF7\u5728 openclaw.json \u6216 stableclaw.json \u914D\u7F6E\u6587\u4EF6\u4E2D\u6DFB\u52A0\u667A\u80FD\u4F53', en: 'Add agents to your openclaw.json or stableclaw.json config file' },
|
|
10647
|
+
search_agents: { zh: '\u641C\u7D22\u667A\u80FD\u4F53...', en: 'Search agents...' },
|
|
10648
|
+
add_agent: { zh: '\u6DFB\u52A0\u667A\u80FD\u4F53', en: 'Add Agent' },
|
|
10649
|
+
agent_list_from: { zh: '\u667A\u80FD\u4F53\u5217\u8868\u6765\u81EA', en: 'Agent list from' },
|
|
10650
|
+
total_label: { zh: '\u603B\u8BA1', en: 'Total' },
|
|
10651
|
+
agents_configured: { zh: '\u4E2A\u667A\u80FD\u4F53\u5DF2\u914D\u7F6E', en: 'agents configured' },
|
|
10652
|
+
status: { zh: '\u72B6\u6001', en: 'Status' },
|
|
10653
|
+
agent: { zh: '\u667A\u80FD\u4F53', en: 'Agent' },
|
|
10654
|
+
model: { zh: '\u6A21\u578B', en: 'Model' },
|
|
10655
|
+
provider: { zh: '\u63D0\u4F9B\u5546', en: 'Provider' },
|
|
10656
|
+
system_prompt: { zh: '\u7CFB\u7EDF\u63D0\u793A\u8BCD', en: 'System Prompt' },
|
|
10657
|
+
actions: { zh: '\u64CD\u4F5C', en: 'Actions' },
|
|
10658
|
+
confirm_delete_agent: { zh: '\u786E\u5B9A\u8981\u5220\u9664\u8FD9\u4E2A\u667A\u80FD\u4F53\u5417\uFF1F', en: 'Are you sure you want to delete this agent?' },
|
|
10659
|
+
agent_deleted: { zh: '\u667A\u80FD\u4F53\u5DF2\u5220\u9664', en: 'Agent deleted' },
|
|
10660
|
+
delete_failed: { zh: '\u5220\u9664\u5931\u8D25', en: 'Delete failed' },
|
|
10661
|
+
default_model_badge: { zh: '\u9ED8\u8BA4', en: 'Default' },
|
|
10662
|
+
provider_model: { zh: '\u6765\u81EA\u6A21\u578B\u63D0\u4F9B\u5546', en: 'From model provider' },
|
|
10663
|
+
default_model_label: { zh: '\u9ED8\u8BA4\u6A21\u578B', en: 'Default Model' },
|
|
10664
|
+
set_as_default: { zh: '\u8BBE\u4E3A\u9ED8\u8BA4', en: 'Set as default' },
|
|
10665
|
+
models_under_provider: { zh: '\u4E2A\u6A21\u578B', en: 'models' },
|
|
10666
|
+
model_id_label: { zh: '\u6A21\u578BID', en: 'Model ID' },
|
|
10667
|
+
model_name_label: { zh: '\u6A21\u578B\u540D\u79F0', en: 'Model Name' },
|
|
10668
|
+
add_new_agent: { zh: '\u2795 \u6DFB\u52A0\u65B0\u667A\u80FD\u4F53', en: '\u2795 Add New Agent' },
|
|
10669
|
+
edit_agent: { zh: '\u270F\uFE0F \u7F16\u8F91\u667A\u80FD\u4F53', en: '\u270F\uFE0F Edit Agent' },
|
|
10670
|
+
agent_name_required: { zh: '\u8BF7\u8F93\u5165\u667A\u80FD\u4F53\u540D\u79F0', en: 'Agent name is required' },
|
|
10671
|
+
agent_updated: { zh: '\u667A\u80FD\u4F53\u5DF2\u66F4\u65B0', en: 'Agent updated' },
|
|
10672
|
+
agent_added: { zh: '\u667A\u80FD\u4F53\u5DF2\u6DFB\u52A0', en: 'Agent added' },
|
|
10673
|
+
no_data: { zh: '\u6682\u65E0\u6570\u636E', en: 'No data' },
|
|
10674
|
+
// Friends
|
|
10675
|
+
loading_friends: { zh: '\u6B63\u5728\u52A0\u8F7D\u597D\u53CB...', en: 'Loading friends...' },
|
|
10676
|
+
friends: { zh: '\u597D\u53CB', en: 'Friends' },
|
|
10677
|
+
requests: { zh: '\u8BF7\u6C42', en: 'Requests' },
|
|
10678
|
+
sessions: { zh: '\u4F1A\u8BDD', en: 'Sessions' },
|
|
10679
|
+
search_friends: { zh: '\u641C\u7D22\u597D\u53CB...', en: 'Search friends...' },
|
|
10680
|
+
all: { zh: '\u5168\u90E8', en: 'All' },
|
|
10681
|
+
ai: { zh: 'AI', en: 'AI' },
|
|
10682
|
+
human: { zh: '\u4EBA\u7C7B', en: 'Human' },
|
|
10683
|
+
add_friend: { zh: '\u6DFB\u52A0\u597D\u53CB', en: 'Add Friend' },
|
|
10684
|
+
type: { zh: '\u7C7B\u578B', en: 'Type' },
|
|
10685
|
+
friend_label: { zh: '\u597D\u53CB', en: 'Friend' },
|
|
10686
|
+
permissions: { zh: '\u6743\u9650', en: 'Permissions' },
|
|
10687
|
+
last_message: { zh: '\u6700\u540E\u6D88\u606F', en: 'Last Message' },
|
|
10688
|
+
add_friend_hint: { zh: '\u4F7F\u75286\u4F4D\u4E34\u65F6\u53F7\u7801\u6216\u8282\u70B9ID\u6DFB\u52A0\u597D\u53CB', en: 'Add a friend using their 6-digit temp number or node ID' },
|
|
10689
|
+
unavailable_offline: { zh: '\u79BB\u7EBF\u65F6\u4E0D\u53EF\u7528', en: 'Unavailable while offline' },
|
|
10690
|
+
request_id: { zh: '\u8BF7\u6C42 ID', en: 'Request ID' },
|
|
10691
|
+
from: { zh: '\u6765\u81EA', en: 'From' },
|
|
10692
|
+
time: { zh: '\u65F6\u95F4', en: 'Time' },
|
|
10693
|
+
no_pending_requests: { zh: '\u6682\u65E0\u5F85\u5904\u7406\u8BF7\u6C42', en: 'No pending requests' },
|
|
10694
|
+
accept: { zh: '\u63A5\u53D7', en: 'Accept' },
|
|
10695
|
+
reject: { zh: '\u62D2\u7EDD', en: 'Reject' },
|
|
10696
|
+
peer_id: { zh: '\u5BF9\u7AEF ID', en: 'Peer ID' },
|
|
10697
|
+
established: { zh: '\u5EFA\u7ACB\u65F6\u95F4', en: 'Established' },
|
|
10698
|
+
messages: { zh: '\u6761\u6D88\u606F', en: 'messages' },
|
|
10699
|
+
no_active_sessions: { zh: '\u6682\u65E0\u6D3B\u8DC3\u4F1A\u8BDD', en: 'No active sessions' },
|
|
10700
|
+
enter_temp_or_id: { zh: '\u8BF7\u8F93\u5165\u4E34\u65F6\u53F7\u7801\u6216\u8282\u70B9ID', en: 'Enter a temp number or node ID' },
|
|
10701
|
+
sending_request: { zh: '\u6B63\u5728\u53D1\u9001\u597D\u53CB\u8BF7\u6C42...', en: 'Sending friend request...' },
|
|
10702
|
+
friend_request_sent: { zh: '\u597D\u53CB\u8BF7\u6C42\u5DF2\u53D1\u9001\uFF01', en: 'Friend request sent!' },
|
|
10703
|
+
failed_add_friend: { zh: '\u6DFB\u52A0\u597D\u53CB\u5931\u8D25', en: 'Failed to add friend' },
|
|
10704
|
+
remove_friend_confirm: { zh: '\u786E\u5B9A\u79FB\u9664\u597D\u53CB ', en: 'Remove friend ' },
|
|
10705
|
+
friend_removed: { zh: '\u597D\u53CB\u5DF2\u79FB\u9664', en: 'Friend removed' },
|
|
10706
|
+
edit_permissions: { zh: '\u7F16\u8F91\u6743\u9650', en: 'Edit Permissions' },
|
|
10707
|
+
friend_permissions: { zh: '\u597D\u53CB\u6743\u9650', en: 'Friend Permissions' },
|
|
10708
|
+
chat_perm: { zh: '\u{1F4AC} \u804A\u5929', en: '\u{1F4AC} Chat' },
|
|
10709
|
+
exec_perm: { zh: '\u{1F527} \u6267\u884C', en: '\u{1F527} Exec' },
|
|
10710
|
+
chat_perm_hint: { zh: '\uFF08\u53D1\u9001/\u63A5\u6536\u6D88\u606F\uFF09', en: '(send/receive messages)' },
|
|
10711
|
+
exec_perm_hint: { zh: '\uFF08\u6267\u884C\u5DE5\u5177/\u547D\u4EE4\uFF09', en: '(execute tools/commands)' },
|
|
10712
|
+
save_permissions: { zh: '\u4FDD\u5B58\u6743\u9650', en: 'Save Permissions' },
|
|
10713
|
+
permissions_updated: { zh: '\u6743\u9650\u5DF2\u66F4\u65B0', en: 'Permissions updated' },
|
|
10714
|
+
request_accepted: { zh: '\u8BF7\u6C42\u5DF2\u63A5\u53D7', en: 'Request accepted' },
|
|
10715
|
+
request_rejected: { zh: '\u8BF7\u6C42\u5DF2\u62D2\u7EDD', en: 'Request rejected' },
|
|
10716
|
+
// Models
|
|
10717
|
+
loading_models: { zh: '\u6B63\u5728\u52A0\u8F7D\u6A21\u578B\u914D\u7F6E...', en: 'Loading model configuration...' },
|
|
10718
|
+
configured: { zh: '\u5DF2\u914D\u7F6E', en: 'Configured' },
|
|
10719
|
+
providers_with_keys: { zh: '\u5DF2\u914D\u7F6EAPI\u5BC6\u94A5\u7684\u63D0\u4F9B\u5546', en: 'Providers with API keys' },
|
|
10720
|
+
configure: { zh: '\u914D\u7F6E', en: 'Configure' },
|
|
10721
|
+
not_set: { zh: '\u672A\u8BBE\u7F6E', en: 'Not set' },
|
|
10722
|
+
key_set: { zh: '\u25CF \u5DF2\u8BBE\u7F6E\u5BC6\u94A5', en: '\u25CF Key set' },
|
|
10723
|
+
active_model_configs: { zh: '\u5F53\u524D\u6A21\u578B\u914D\u7F6E', en: 'Active Model Configurations' },
|
|
10724
|
+
default_model_global: { zh: '\u5168\u5C40\u9ED8\u8BA4\u6A21\u578B', en: 'Global Default Model' },
|
|
10725
|
+
model_count: { zh: '\u6A21\u578B\u6570\u91CF', en: 'Model Count' },
|
|
10726
|
+
multi_model: { zh: '\u591A\u6A21\u578B', en: 'Multi-model' },
|
|
10727
|
+
base_url: { zh: '\u57FA\u7840\u5730\u5740', en: 'Base URL' },
|
|
10728
|
+
configure_providers_desc: { zh: '\u4E3A\u667A\u80FD\u4F53\u914D\u7F6ELLM\u63D0\u4F9B\u5546\u3002\u70B9\u51FB\u63D0\u4F9B\u5546\u5361\u7247\u8BBE\u7F6E\u6216\u66F4\u65B0API\u5BC6\u94A5\u3001\u6A21\u578B\u548C\u57FA\u7840\u5730\u5740\u3002\u66F4\u6539\u5C06\u76F4\u63A5\u4FDD\u5B58\u5230\u914D\u7F6E\u6587\u4EF6\u3002', en: 'Configure LLM providers for your agents. Click a provider card to set or update the API key, model, and base URL. Changes are saved directly to your config file.' },
|
|
10729
|
+
current_key: { zh: '\u5F53\u524D\uFF1A', en: 'Current: ' },
|
|
10730
|
+
no_api_key: { zh: '\u672A\u914D\u7F6EAPI\u5BC6\u94A5', en: 'No API key configured' },
|
|
10731
|
+
provider_not_found: { zh: '\u672A\u627E\u5230\u63D0\u4F9B\u5546', en: 'Provider not found' },
|
|
10732
|
+
enter_api_key: { zh: '\u8F93\u5165API\u5BC6\u94A5', en: 'Enter API key' },
|
|
10733
|
+
enter_model_id: { zh: '\u8F93\u5165\u6A21\u578BID', en: 'Model ID' },
|
|
10734
|
+
default_url: { zh: '\u9ED8\u8BA4\u5730\u5740', en: 'Default URL' },
|
|
10735
|
+
enter_api_key_or_model: { zh: '\u8BF7\u81F3\u5C11\u8F93\u5165API\u5BC6\u94A5\u6216\u6A21\u578BID', en: 'Enter at least an API key or model ID' },
|
|
10736
|
+
saving_config: { zh: '\u6B63\u5728\u4FDD\u5B58\u914D\u7F6E...', en: 'Saving configuration...' },
|
|
10737
|
+
config_saved: { zh: '\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF01', en: 'Configuration saved!' },
|
|
10738
|
+
failed_save: { zh: '\u4FDD\u5B58\u5931\u8D25', en: 'Failed to save' },
|
|
10739
|
+
confirm_delete_provider: { zh: '\u786E\u5B9A\u5220\u9664\u63D0\u4F9B\u5546 "', en: 'Delete configuration for provider "' },
|
|
10740
|
+
provider_deleted: { zh: '\u63D0\u4F9B\u5546\u914D\u7F6E\u5DF2\u5220\u9664', en: 'Provider configuration deleted' },
|
|
10741
|
+
// Settings
|
|
10742
|
+
loading_settings: { zh: '\u6B63\u5728\u52A0\u8F7D\u8BBE\u7F6E...', en: 'Loading settings...' },
|
|
10743
|
+
tab_connection: { zh: '\u{1F50C} \u8FDE\u63A5', en: '\u{1F50C} Connection' },
|
|
10744
|
+
tab_friends: { zh: '\u{1F465} \u597D\u53CB', en: '\u{1F465} Friends' },
|
|
10745
|
+
tab_security: { zh: '\u{1F512} \u5B89\u5168', en: '\u{1F512} Security' },
|
|
10746
|
+
tab_advanced: { zh: '\u2699\uFE0F \u9AD8\u7EA7', en: '\u2699\uFE0F Advanced' },
|
|
10747
|
+
tab_json: { zh: '\u{1F4DD} JSON\u7F16\u8F91', en: '\u{1F4DD} JSON Editor' },
|
|
10748
|
+
conn_desc: { zh: '\u914D\u7F6E\u670D\u52A1\u5668\u8FDE\u63A5\u548CWebSocket\u8BBE\u7F6E\u3002\u66F4\u6539\u9700\u8981\u91CD\u542F\u63D2\u4EF6\u624D\u80FD\u5B8C\u5168\u751F\u6548\u3002', en: 'Configure server connection and WebSocket settings. Changes require a plugin restart to take full effect.' },
|
|
10749
|
+
server_connection: { zh: '\u{1F310} \u670D\u52A1\u5668\u8FDE\u63A5', en: '\u{1F310} Server Connection' },
|
|
10750
|
+
server_url_label: { zh: '\u670D\u52A1\u5668\u5730\u5740', en: 'Server URL' },
|
|
10751
|
+
server_url_hint: { zh: 'AICQ\u4E2D\u7EE7\u670D\u52A1\u5668\u7684HTTPS\u5730\u5740\u3002WebSocket\u8DEF\u5F84 /ws \u4F1A\u81EA\u52A8\u8FFD\u52A0\u3002', en: 'The HTTPS URL of the AICQ relay server. WebSocket path /ws is auto-appended.' },
|
|
10752
|
+
conn_timeout: { zh: '\u8FDE\u63A5\u8D85\u65F6\uFF08\u79D2\uFF09', en: 'Connection Timeout (seconds)' },
|
|
10753
|
+
conn_timeout_hint: { zh: 'HTTP\u8BF7\u6C42\u8D85\u65F6\u65F6\u95F4\uFF085-120\u79D2\uFF09\u3002\u9ED8\u8BA4\uFF1A30\u79D2\u3002', en: 'HTTP request timeout (5\u2013120s). Default: 30s.' },
|
|
10754
|
+
ws_auto_reconnect: { zh: 'WebSocket\u81EA\u52A8\u91CD\u8FDE', en: 'WS Auto-Reconnect' },
|
|
10755
|
+
auto_reconnect_label: { zh: '\u65AD\u5F00\u65F6\u81EA\u52A8\u91CD\u8FDE', en: 'Auto-reconnect when disconnected' },
|
|
10756
|
+
auto_reconnect_hint: { zh: '\u65AD\u5F00\u8FDE\u63A5\u540E\u81EA\u52A8\u91CD\u65B0\u8FDE\u63A5WebSocket\u3002', en: 'Automatically reconnect WebSocket on disconnection.' },
|
|
10757
|
+
ws_reconnect_interval: { zh: '\u91CD\u8FDE\u95F4\u9694\uFF08\u79D2\uFF09', en: 'WS Reconnect Interval (seconds)' },
|
|
10758
|
+
ws_reconnect_hint: { zh: '\u91CD\u8FDE\u5C1D\u8BD5\u4E4B\u95F4\u7684\u95F4\u9694\uFF085-600\u79D2\uFF09\u3002\u9ED8\u8BA4\uFF1A60\u79D2\u3002', en: 'Interval between reconnection attempts (5\u2013600s). Default: 60s.' },
|
|
10759
|
+
test: { zh: '\u6D4B\u8BD5', en: 'Test' },
|
|
10760
|
+
testing: { zh: '\u6D4B\u8BD5\u4E2D...', en: 'Testing...' },
|
|
10761
|
+
enter_server_url: { zh: '\u8BF7\u5148\u8F93\u5165\u670D\u52A1\u5668\u5730\u5740', en: 'Enter a server URL first' },
|
|
10762
|
+
conn_ok: { zh: '\u8FDE\u63A5\u6210\u529F', en: 'Connected successfully' },
|
|
10763
|
+
conn_ok_latency: { zh: '\u8FDE\u63A5\u6210\u529F\uFF01\u5EF6\u8FDF\uFF1A', en: 'Connection OK! Latency: ' },
|
|
10764
|
+
conn_failed: { zh: '\u8FDE\u63A5\u5931\u8D25', en: 'Connection failed' },
|
|
10765
|
+
config_file: { zh: '\u{1F4C1} \u914D\u7F6E\u6587\u4EF6', en: '\u{1F4C1} Config File' },
|
|
10766
|
+
source: { zh: '\u6765\u6E90', en: 'Source' },
|
|
10767
|
+
mgmt_ui: { zh: '\u7BA1\u7406\u754C\u9762', en: 'Management UI' },
|
|
10768
|
+
uptime: { zh: '\u8FD0\u884C\u65F6\u95F4', en: 'Uptime' },
|
|
10769
|
+
not_found: { zh: '\u672A\u627E\u5230', en: 'Not found' },
|
|
10770
|
+
friends_tab_desc: { zh: '\u914D\u7F6E\u597D\u53CB\u7BA1\u7406\u3001\u6743\u9650\u548C\u4E34\u65F6\u53F7\u7801\u8BBE\u7F6E\u3002', en: 'Configure friend management, permissions, and temporary number settings.' },
|
|
10771
|
+
of_max: { zh: ' / \u6700\u5927 ', en: ' of ' },
|
|
10772
|
+
max_friends: { zh: '\u6700\u5927\u597D\u53CB\u6570', en: 'Max Friends' },
|
|
10773
|
+
auto_accept: { zh: '\u81EA\u52A8\u63A5\u53D7\u597D\u53CB', en: 'Auto-Accept Friends' },
|
|
10774
|
+
auto_accept_label: { zh: '\u81EA\u52A8\u63A5\u53D7\u8BF7\u6C42', en: 'Automatically accept requests' },
|
|
10775
|
+
auto_accept_hint: { zh: '\u542F\u7528\u540E\uFF0C\u4F20\u5165\u7684\u597D\u53CB\u8BF7\u6C42\u5C06\u81EA\u52A8\u63A5\u53D7\u3002', en: 'When enabled, incoming friend requests are accepted without review.' },
|
|
10776
|
+
default_perms: { zh: '\u65B0\u597D\u53CB\u9ED8\u8BA4\u6743\u9650', en: 'Default Permissions for New Friends' },
|
|
10777
|
+
default_perms_hint: { zh: '\u81EA\u52A8\u63A5\u53D7\u65B0\u597D\u53CB\u8BF7\u6C42\u65F6\u5E94\u7528\u7684\u9ED8\u8BA4\u6743\u9650\u3002', en: 'Default permissions applied when auto-accepting new friend requests.' },
|
|
10778
|
+
temp_numbers: { zh: '\u{1F522} \u4E34\u65F6\u53F7\u7801', en: '\u{1F522} Temporary Numbers' },
|
|
10779
|
+
temp_expiry: { zh: '\u4E34\u65F6\u53F7\u7801\u6709\u6548\u671F\uFF08\u79D2\uFF09', en: 'Temp Number Expiry (seconds)' },
|
|
10780
|
+
temp_expiry_hint: { zh: '\u4E34\u65F6\u597D\u53CB\u53F7\u7801\u7684\u6709\u6548\u65F6\u95F4\uFF0860-3600\u79D2\uFF09\u3002\u9ED8\u8BA4\uFF1A5\u5206\u949F\u3002', en: 'How long a temporary friend number remains valid (60\u20133600s). Default: 5 minutes.' },
|
|
10781
|
+
sec_desc: { zh: '\u914D\u7F6E\u52A0\u5BC6\u3001P2P\u548C\u8EAB\u4EFD\u5B89\u5168\u8BBE\u7F6E\u3002', en: 'Configure encryption, P2P, and identity security settings.' },
|
|
10782
|
+
agent_identity: { zh: '\u{1F916} \u667A\u80FD\u4F53\u8EAB\u4EFD', en: '\u{1F916} Agent Identity' },
|
|
10783
|
+
public_key_fp: { zh: '\u516C\u94A5\u6307\u7EB9', en: 'Public Key Fingerprint' },
|
|
10784
|
+
reset_identity: { zh: '\u{1F5D1}\uFE0F \u91CD\u7F6E\u8EAB\u4EFD', en: '\u{1F5D1}\uFE0F Reset Identity' },
|
|
10785
|
+
reset_identity_warn: { zh: '\u26A0\uFE0F \u8FD9\u5C06\u6C38\u4E45\u5220\u9664\u6240\u6709\u597D\u53CB\u3001\u4F1A\u8BDD\u548C\u5BC6\u94A5', en: '\u26A0\uFE0F This deletes all friends, sessions, and keys permanently' },
|
|
10786
|
+
p2p_encryption: { zh: '\u{1F512} P2P\u4E0E\u52A0\u5BC6', en: '\u{1F512} P2P & Encryption' },
|
|
10787
|
+
enable_p2p: { zh: '\u542F\u7528P2P\u8FDE\u63A5', en: 'Enable P2P Connections' },
|
|
10788
|
+
allow_p2p: { zh: '\u5141\u8BB8\u76F4\u63A5P2P\u6D88\u606F', en: 'Allow direct P2P messaging' },
|
|
10789
|
+
enable_p2p_hint: { zh: '\u53CC\u65B9\u90FD\u5728\u7EBF\u65F6\u542F\u7528\u70B9\u5BF9\u70B9\u52A0\u5BC6\u8FDE\u63A5\u3002', en: 'Enable peer-to-peer encrypted connections when both parties are online.' },
|
|
10790
|
+
hs_timeout: { zh: '\u63E1\u624B\u8D85\u65F6\uFF08\u79D2\uFF09', en: 'Handshake Timeout (seconds)' },
|
|
10791
|
+
hs_timeout_hint: { zh: 'Noise-XK\u63E1\u624B\u8D85\u65F6\u65F6\u95F4\uFF0810-300\u79D2\uFF09\u3002\u9ED8\u8BA4\uFF1A60\u79D2\u3002', en: 'Noise-XK handshake timeout (10\u2013300s). Default: 60s.' },
|
|
10792
|
+
adv_desc: { zh: '\u6587\u4EF6\u4F20\u8F93\u3001\u65E5\u5FD7\u548C\u914D\u7F6E\u7BA1\u7406\u7684\u9AD8\u7EA7\u8BBE\u7F6E\u3002', en: 'Advanced settings for file transfer, logging, and configuration management.' },
|
|
10793
|
+
file_transfer: { zh: '\u{1F4CE} \u6587\u4EF6\u4F20\u8F93', en: '\u{1F4CE} File Transfer' },
|
|
10794
|
+
enable_ft: { zh: '\u542F\u7528\u6587\u4EF6\u4F20\u8F93', en: 'Enable File Transfer' },
|
|
10795
|
+
allow_ft: { zh: '\u5141\u8BB8\u6587\u4EF6\u4F20\u8F93', en: 'Allow file transfers' },
|
|
10796
|
+
enable_ft_hint: { zh: '\u542F\u7528\u597D\u53CB\u95F4\u7684\u52A0\u5BC6\u6587\u4EF6\u4F20\u8F93\u3002', en: 'Enable encrypted file transfer between friends.' },
|
|
10797
|
+
max_file_size: { zh: '\u6700\u5927\u6587\u4EF6\u5927\u5C0F', en: 'Max File Size' },
|
|
10798
|
+
max_file_size_hint: { zh: '\u52A0\u5BC6\u4F20\u8F93\u7684\u6700\u5927\u6587\u4EF6\u5927\u5C0F\u3002\u5F53\u524D\uFF1A', en: 'Maximum file size for encrypted transfers. Current: ' },
|
|
10799
|
+
logging: { zh: '\u{1F4CB} \u65E5\u5FD7', en: '\u{1F4CB} Logging' },
|
|
10800
|
+
log_level: { zh: '\u65E5\u5FD7\u7EA7\u522B', en: 'Log Level' },
|
|
10801
|
+
log_debug: { zh: '\u{1F41B} \u8C03\u8BD5 \u2014 \u8BE6\u7EC6\u8F93\u51FA', en: '\u{1F41B} Debug \u2014 Verbose output for troubleshooting' },
|
|
10802
|
+
log_info: { zh: '\u2139\uFE0F \u4FE1\u606F \u2014 \u4E00\u822C\u4FE1\u606F\uFF08\u9ED8\u8BA4\uFF09', en: '\u2139\uFE0F Info \u2014 General information (default)' },
|
|
10803
|
+
log_warn: { zh: '\u26A0\uFE0F \u8B66\u544A \u2014 \u8B66\u544A\u548C\u91CD\u8981\u4E8B\u4EF6', en: '\u26A0\uFE0F Warn \u2014 Warnings and important events' },
|
|
10804
|
+
log_error: { zh: '\u274C \u9519\u8BEF \u2014 \u4EC5\u9519\u8BEF', en: '\u274C Error \u2014 Errors only' },
|
|
10805
|
+
log_none: { zh: '\u{1F507} \u65E0 \u2014 \u7981\u7528\u6240\u6709\u65E5\u5FD7', en: '\u{1F507} None \u2014 Disable all logging' },
|
|
10806
|
+
log_level_hint: { zh: '\u63A7\u5236\u63D2\u4EF6\u65E5\u5FD7\u8F93\u51FA\u7684\u8BE6\u7EC6\u7A0B\u5EA6\u3002', en: 'Controls the verbosity of plugin log output.' },
|
|
10807
|
+
import_export: { zh: '\u{1F4E6} \u5BFC\u5165/\u5BFC\u51FA\u8BBE\u7F6E', en: '\u{1F4E6} Import / Export Settings' },
|
|
10808
|
+
export_settings: { zh: '\u{1F4E5} \u5BFC\u51FA\u8BBE\u7F6E', en: '\u{1F4E5} Export Settings' },
|
|
10809
|
+
import_settings: { zh: '\u{1F4E4} \u5BFC\u5165\u8BBE\u7F6E', en: '\u{1F4E4} Import Settings' },
|
|
10810
|
+
import_export_hint: { zh: '\u5C06\u5F53\u524DAICQ\u63D2\u4EF6\u8BBE\u7F6E\u5BFC\u51FA\u4E3AJSON\u3002\u5BFC\u5165\u53EF\u4ECE\u5907\u4EFD\u6062\u590D\u8BBE\u7F6E\u3002', en: 'Export current AICQ plugin settings as JSON. Import to restore settings from a backup.' },
|
|
10811
|
+
save: { zh: '\u{1F4BE} \u4FDD\u5B58', en: '\u{1F4BE} Save' },
|
|
10812
|
+
saving: { zh: '\u4FDD\u5B58\u4E2D...', en: 'Saving...' },
|
|
10813
|
+
saved: { zh: '\u2713 \u5DF2\u4FDD\u5B58', en: '\u2713 Saved' },
|
|
10814
|
+
settings_saved: { zh: '\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF1A', en: 'Settings saved: ' },
|
|
10815
|
+
all_saved: { zh: '\u6240\u6709\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF01', en: 'All settings saved!' },
|
|
10816
|
+
delete_everything: { zh: '\u{1F5D1}\uFE0F \u5220\u9664\u6240\u6709\u6570\u636E', en: '\u{1F5D1}\uFE0F Delete Everything' },
|
|
10817
|
+
confirm_delete: { zh: '\u{1F5D1}\uFE0F \u786E\u8BA4\u5220\u9664', en: '\u{1F5D1}\uFE0F Confirm Delete' },
|
|
10818
|
+
resetting: { zh: '\u91CD\u7F6E\u4E2D...', en: 'Resetting...' },
|
|
10819
|
+
reset_success: { zh: '\u8EAB\u4EFD\u91CD\u7F6E\u6210\u529F\uFF0C\u8BF7\u91CD\u542F\u63D2\u4EF6\u3002', en: 'Identity reset successfully. Please restart the plugin.' },
|
|
10820
|
+
reset_failed: { zh: '\u91CD\u7F6E\u5931\u8D25', en: 'Reset failed' },
|
|
10821
|
+
exported_success: { zh: '\u8BBE\u7F6E\u5BFC\u51FA\u6210\u529F', en: 'Settings exported successfully' },
|
|
10822
|
+
paste_json: { zh: '\u8BF7\u5148\u7C98\u8D34JSON\u8BBE\u7F6E', en: 'Paste JSON settings first' },
|
|
10823
|
+
importing: { zh: '\u5BFC\u5165\u4E2D...', en: 'Importing...' },
|
|
10824
|
+
imported_success: { zh: '\u8BBE\u7F6E\u5BFC\u5165\u6210\u529F\uFF01', en: 'Settings imported successfully!' },
|
|
10825
|
+
import_failed: { zh: '\u5BFC\u5165\u5931\u8D25', en: 'Import failed' },
|
|
10826
|
+
loading_config: { zh: '\u6B63\u5728\u52A0\u8F7D\u914D\u7F6E...', en: 'Loading config...' },
|
|
10827
|
+
json_editor_desc: { zh: '\u76F4\u63A5\u7F16\u8F91\u539F\u59CBJSON\u914D\u7F6E\u3002\u6CE8\u610F\u8BED\u6CD5\u2014\u2014\u65E0\u6548\u7684JSON\u5C06\u88AB\u62D2\u7EDD\u3002', en: 'Edit the raw JSON configuration directly. Be careful with syntax \u2014 invalid JSON will be rejected.' },
|
|
10828
|
+
json_editor: { zh: '\u{1F4DD} JSON\u914D\u7F6E\u7F16\u8F91\u5668', en: '\u{1F4DD} Config JSON Editor' },
|
|
10829
|
+
raw_json: { zh: '\u539F\u59CBJSON\u914D\u7F6E', en: 'Raw JSON Configuration' },
|
|
10830
|
+
raw_json_hint: { zh: '\u76F4\u63A5\u7F16\u8F91\u914D\u7F6EJSON\u3002\u4F7F\u7528\u683C\u5F0F\u5316\u6309\u94AE\u7F8E\u5316\u3002', en: 'Directly edit the configuration JSON. Use the Format button to prettify.' },
|
|
10831
|
+
format: { zh: '\u{1F4D0} \u683C\u5F0F\u5316', en: '\u{1F4D0} Format' },
|
|
10832
|
+
copy: { zh: '\u{1F4CB} \u590D\u5236', en: '\u{1F4CB} Copy' },
|
|
10833
|
+
revert: { zh: '\u21A9\uFE0F \u8FD8\u539F', en: '\u21A9\uFE0F Revert' },
|
|
10834
|
+
save_config: { zh: '\u{1F4BE} \u4FDD\u5B58\u914D\u7F6E', en: '\u{1F4BE} Save Config' },
|
|
10835
|
+
json_formatted: { zh: 'JSON\u5DF2\u683C\u5F0F\u5316', en: 'JSON formatted' },
|
|
10836
|
+
valid_json: { zh: '\u2713 \u6709\u6548JSON', en: '\u2713 Valid JSON' },
|
|
10837
|
+
invalid_json: { zh: '\u2717 \u65E0\u6548JSON\uFF1A', en: '\u2717 Invalid JSON: ' },
|
|
10838
|
+
no_content: { zh: '\u6CA1\u6709\u53EF\u4FDD\u5B58\u7684\u5185\u5BB9', en: 'No content to save' },
|
|
10839
|
+
config_saved: { zh: '\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF01', en: 'Config saved successfully!' },
|
|
10840
|
+
testing_conn_to: { zh: '\u6B63\u5728\u6D4B\u8BD5\u5230 ', en: 'Testing connection to ' },
|
|
10841
|
+
config_file_label: { zh: '\u{1F4C4} \u914D\u7F6E\u6587\u4EF6', en: '\u{1F4C4} Config File' },
|
|
10842
|
+
// Modals
|
|
10843
|
+
add_friend_title: { zh: '\u2795 \u6DFB\u52A0\u597D\u53CB', en: '\u2795 Add Friend' },
|
|
10844
|
+
temp_or_node: { zh: '\u4E34\u65F6\u53F7\u7801\u6216\u8282\u70B9ID', en: 'Temp Number or Node ID' },
|
|
10845
|
+
temp_or_node_ph: { zh: '6\u4F4D\u53F7\u7801\u6216\u8282\u70B9ID', en: '6-digit number or node ID' },
|
|
10846
|
+
temp_or_node_hint: { zh: '\u8F93\u5165\u597D\u53CB\u76846\u4F4D\u4E34\u65F6\u53F7\u7801\u6216\u5B8C\u6574\u8282\u70B9ID\u3002', en: 'Enter the 6-digit temporary number or the full node ID of your friend.' },
|
|
10847
|
+
cancel: { zh: '\u53D6\u6D88', en: 'Cancel' },
|
|
10848
|
+
send_request: { zh: '\u53D1\u9001\u8BF7\u6C42', en: 'Send Request' },
|
|
10849
|
+
close: { zh: '\u5173\u95ED', en: 'Close' },
|
|
10850
|
+
save_agent: { zh: '\u{1F4BE} \u4FDD\u5B58\u667A\u80FD\u4F53', en: '\u{1F4BE} Save Agent' },
|
|
10851
|
+
save_configuration: { zh: '\u{1F4BE} \u4FDD\u5B58\u914D\u7F6E', en: '\u{1F4BE} Save Configuration' },
|
|
10852
|
+
reset_identity_title: { zh: '\u{1F5D1}\uFE0F \u91CD\u7F6E\u667A\u80FD\u4F53\u8EAB\u4EFD', en: '\u{1F5D1}\uFE0F Reset Agent Identity' },
|
|
10853
|
+
reset_warning_title: { zh: '\u26A0\uFE0F \u8B66\u544A\uFF1A\u8FD9\u662F\u4E00\u4E2A\u7834\u574F\u6027\u64CD\u4F5C\uFF01', en: '\u26A0\uFE0F WARNING: This is a destructive operation!' },
|
|
10854
|
+
reset_warning_desc: { zh: '\u8FD9\u5C06\u6C38\u4E45\u5220\u9664\uFF1A', en: 'This will permanently delete:' },
|
|
10855
|
+
reset_keypair: { zh: '\u2022 \u4F60\u7684Ed25519\u5BC6\u94A5\u5BF9\u548C\u667A\u80FD\u4F53ID', en: '\u2022 Your Ed25519 key pair and agent ID' },
|
|
10856
|
+
reset_friends: { zh: '\u2022 \u6240\u6709\u597D\u53CB\u8FDE\u63A5\u548C\u4F1A\u8BDD', en: '\u2022 All friend connections and sessions' },
|
|
10857
|
+
reset_requests: { zh: '\u2022 \u6240\u6709\u5F85\u5904\u7406\u7684\u597D\u53CB\u8BF7\u6C42', en: '\u2022 All pending friend requests' },
|
|
10858
|
+
reset_temp: { zh: '\u2022 \u6240\u6709\u4E34\u65F6\u53F7\u7801', en: '\u2022 All temporary numbers' },
|
|
10859
|
+
reset_restart_hint: { zh: '\u91CD\u7F6E\u540E\uFF0C\u5FC5\u987B\u91CD\u542F\u63D2\u4EF6\u4EE5\u751F\u6210\u65B0\u8EAB\u4EFD\u3002', en: 'After reset, you must restart the plugin to generate a new identity.' },
|
|
10860
|
+
type_reset: { zh: '\u8F93\u5165 RESET \u4EE5\u786E\u8BA4', en: 'Type RESET to confirm' },
|
|
10861
|
+
import_title: { zh: '\u{1F4E4} \u5BFC\u5165\u8BBE\u7F6E', en: '\u{1F4E4} Import Settings' },
|
|
10862
|
+
paste_json_label: { zh: '\u7C98\u8D34JSON\u8BBE\u7F6E', en: 'Paste JSON Settings' },
|
|
10863
|
+
paste_json_ph: { zh: '{"serverUrl": "https://...", ...}', en: '{"serverUrl": "https://...", ...}' },
|
|
10864
|
+
paste_json_hint: { zh: '\u7C98\u8D34\u4ECE\u53E6\u4E00\u4E2AAICQ\u5B9E\u4F8B\u5BFC\u51FA\u7684JSON\u8BBE\u7F6E\u3002\u8BBE\u7F6E\u5C06\u4E0E\u73B0\u6709\u503C\u5408\u5E76\u3002', en: 'Paste the JSON settings exported from another AICQ instance. Settings will be merged with existing values.' },
|
|
10865
|
+
agent_name: { zh: '\u667A\u80FD\u4F53\u540D\u79F0 *', en: 'Agent Name *' },
|
|
10866
|
+
agent_id_label: { zh: '\u667A\u80FD\u4F53ID', en: 'Agent ID' },
|
|
10867
|
+
agent_id_hint: { zh: '\u552F\u4E00\u6807\u8BC6\u7B26\u3002\u7559\u7A7A\u81EA\u52A8\u751F\u6210\u3002', en: 'Unique identifier. Leave empty for auto-generation.' },
|
|
10868
|
+
model_label: { zh: '\u6A21\u578B', en: 'Model' },
|
|
10869
|
+
provider_label: { zh: '\u63D0\u4F9B\u5546', en: 'Provider' },
|
|
10870
|
+
system_prompt_label: { zh: '\u7CFB\u7EDF\u63D0\u793A\u8BCD', en: 'System Prompt' },
|
|
10871
|
+
temperature: { zh: '\u6E29\u5EA6', en: 'Temperature' },
|
|
10872
|
+
max_tokens: { zh: '\u6700\u5927Token\u6570', en: 'Max Tokens' },
|
|
10873
|
+
top_p: { zh: 'Top P', en: 'Top P' },
|
|
10874
|
+
tools: { zh: '\u5DE5\u5177', en: 'Tools' },
|
|
10875
|
+
enabled: { zh: '\u542F\u7528', en: 'Enabled' },
|
|
10876
|
+
// Utilities
|
|
10877
|
+
copied_clipboard: { zh: '\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F', en: 'Copied to clipboard' },
|
|
10878
|
+
copy_failed: { zh: '\u590D\u5236\u5931\u8D25', en: 'Copy failed' },
|
|
10879
|
+
just_now: { zh: '\u521A\u521A', en: 'just now' },
|
|
10880
|
+
min_ago: { zh: '\u5206\u949F\u524D', en: ' min ago' },
|
|
10881
|
+
h_ago: { zh: '\u5C0F\u65F6\u524D', en: 'h ago' },
|
|
10882
|
+
d_ago: { zh: '\u5929\u524D', en: 'd ago' },
|
|
10883
|
+
none: { zh: '\u65E0', en: 'none' },
|
|
10884
|
+
failed: { zh: '\u5931\u8D25', en: 'Failed' },
|
|
10885
|
+
// Offline
|
|
10886
|
+
offline_msg: { zh: '\u60A8\u5F53\u524D\u5904\u4E8E\u79BB\u7EBF\u72B6\u6001\u3002\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u53D7\u9650\u3002\u6570\u636E\u4ECE\u672C\u5730\u7F13\u5B58\u52A0\u8F7D\u3002', en: 'You are offline. Some features may be limited. Data is loaded from local cache.' },
|
|
10887
|
+
// Custom Providers
|
|
10888
|
+
add_custom_provider: { zh: '\u2795 \u6DFB\u52A0\u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546', en: '\u2795 Add Custom Provider' },
|
|
10889
|
+
custom_provider: { zh: '\u{1F9E9} \u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546', en: '\u{1F9E9} Custom Provider' },
|
|
10890
|
+
custom_provider_desc: { zh: '\u624B\u52A8\u6DFB\u52A0\u517C\u5BB9 OpenAI API \u683C\u5F0F\u7684\u81EA\u5B9A\u4E49\u6A21\u578B\u63D0\u4F9B\u5546', en: 'Manually add custom model providers compatible with OpenAI API format' },
|
|
10891
|
+
provider_name: { zh: '\u63D0\u4F9B\u5546\u540D\u79F0', en: 'Provider Name' },
|
|
10892
|
+
provider_name_placeholder: { zh: '\u4F8B\u5982\uFF1AMy Custom API', en: 'e.g., My Custom API' },
|
|
10893
|
+
provider_name_required: { zh: '\u8BF7\u8F93\u5165\u63D0\u4F9B\u5546\u540D\u79F0', en: 'Provider name is required' },
|
|
10894
|
+
custom_provider_added: { zh: '\u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546\u5DF2\u6DFB\u52A0', en: 'Custom provider added' },
|
|
10895
|
+
custom_provider_updated: { zh: '\u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546\u5DF2\u66F4\u65B0', en: 'Custom provider updated' },
|
|
10896
|
+
custom_provider_deleted: { zh: '\u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546\u5DF2\u5220\u9664', en: 'Custom provider deleted' },
|
|
10897
|
+
confirm_delete_custom: { zh: '\u786E\u5B9A\u8981\u5220\u9664\u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546 "{name}" \u5417\uFF1F\u6B64\u64CD\u4F5C\u4E0D\u53EF\u6062\u590D\u3002', en: 'Are you sure you want to delete custom provider "{name}"? This cannot be undone.' },
|
|
10898
|
+
edit_custom_provider: { zh: '\u7F16\u8F91\u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546', en: 'Edit Custom Provider' },
|
|
10899
|
+
save_custom: { zh: '\u{1F4BE} \u4FDD\u5B58', en: '\u{1F4BE} Save' },
|
|
10900
|
+
description_label: { zh: '\u63CF\u8FF0', en: 'Description' },
|
|
10901
|
+
description_placeholder: { zh: '\u53EF\u9009\uFF1A\u63CF\u8FF0\u6B64\u63D0\u4F9B\u5546\u7684\u7528\u9014', en: 'Optional: describe what this provider is for' },
|
|
10902
|
+
api_key_label: { zh: 'API \u5BC6\u94A5', en: 'API Key' },
|
|
10903
|
+
model_id_label2: { zh: '\u6A21\u578B ID', en: 'Model ID' },
|
|
10904
|
+
base_url_label: { zh: '\u57FA\u7840\u5730\u5740', en: 'Base URL' },
|
|
10905
|
+
// OpenClaw Config
|
|
10906
|
+
nav_openclaw: { zh: 'OpenClaw', en: 'OpenClaw' },
|
|
10907
|
+
openclaw_config: { zh: 'OpenClaw \u914D\u7F6E', en: 'OpenClaw Configuration' },
|
|
10908
|
+
loading_openclaw: { zh: '\u6B63\u5728\u52A0\u8F7D\u914D\u7F6E...', en: 'Loading configuration...' },
|
|
10909
|
+
openclaw_desc: { zh: '\u7BA1\u7406 OpenClaw \u7684\u667A\u80FD\u4F53\u914D\u7F6E\u3001\u8DEF\u7531\u7ED1\u5B9A\u548C\u9891\u9053\u8BBE\u7F6E\u3002\u66F4\u6539\u5C06\u76F4\u63A5\u4FDD\u5B58\u5230 openclaw.json \u914D\u7F6E\u6587\u4EF6\u3002', en: 'Manage OpenClaw agent configuration, routing bindings, and channel settings. Changes are saved directly to the openclaw.json config file.' },
|
|
10910
|
+
// Agent Defaults
|
|
10911
|
+
agent_defaults: { zh: '\u{1F916} \u667A\u80FD\u4F53\u9ED8\u8BA4\u8BBE\u7F6E', en: '\u{1F916} Agent Defaults' },
|
|
10912
|
+
agent_defaults_desc: { zh: '\u914D\u7F6E\u6240\u6709\u667A\u80FD\u4F53\u7684\u9ED8\u8BA4\u53C2\u6570\uFF0C\u5305\u62EC\u6A21\u578B\u9009\u62E9\u3001\u5E76\u53D1\u6570\u3001\u538B\u7F29\u6A21\u5F0F\u7B49', en: 'Configure default parameters for all agents, including model selection, concurrency, compaction mode, etc.' },
|
|
10913
|
+
compaction_mode: { zh: '\u538B\u7F29\u6A21\u5F0F', en: 'Compaction Mode' },
|
|
10914
|
+
max_concurrent: { zh: '\u6700\u5927\u5E76\u53D1\u6570', en: 'Max Concurrent' },
|
|
10915
|
+
primary_model: { zh: '\u4E3B\u6A21\u578B', en: 'Primary Model' },
|
|
10916
|
+
fallback_models: { zh: '\u5907\u7528\u6A21\u578B', en: 'Fallback Models' },
|
|
10917
|
+
image_model: { zh: '\u56FE\u50CF\u6A21\u578B', en: 'Image Model' },
|
|
10918
|
+
subagent_max_concurrent: { zh: '\u5B50\u667A\u80FD\u4F53\u6700\u5927\u5E76\u53D1', en: 'Subagent Max Concurrent' },
|
|
10919
|
+
thinking_default: { zh: '\u9ED8\u8BA4\u601D\u8003\u6A21\u5F0F', en: 'Thinking Default' },
|
|
10920
|
+
workspace: { zh: '\u5DE5\u4F5C\u7A7A\u95F4', en: 'Workspace' },
|
|
10921
|
+
models_registry: { zh: '\u6A21\u578B\u6CE8\u518C\u8868', en: 'Models Registry' },
|
|
10922
|
+
// Agent List
|
|
10923
|
+
oc_agent_list: { zh: '\u{1F4CB} \u667A\u80FD\u4F53\u5217\u8868', en: '\u{1F4CB} Agent List' },
|
|
10924
|
+
oc_agent_list_desc: { zh: "\u914D\u7F6E\u5404\u4E2A\u667A\u80FD\u4F53\u7684\u8EAB\u4EFD\u3001\u6A21\u578B\u548C\u5DE5\u5177\u6743\u9650\u3002\u6BCF\u4E2A\u667A\u80FD\u4F53\u53EF\u4EE5\u5206\u914D\u4E0D\u540C\u7684\u6A21\u578B\u548C\u5DE5\u5177\u914D\u7F6E\u3002", en: "Configure each agent's identity, model, and tool permissions. Each agent can be assigned different models and tool configurations." },
|
|
10925
|
+
add_agent_btn: { zh: '\u2795 \u6DFB\u52A0\u667A\u80FD\u4F53', en: '\u2795 Add Agent' },
|
|
10926
|
+
agent_identity_name: { zh: '\u8EAB\u4EFD\u540D\u79F0', en: 'Identity Name' },
|
|
10927
|
+
agent_model_primary: { zh: '\u4E3B\u6A21\u578B', en: 'Primary Model' },
|
|
10928
|
+
agent_tools_profile: { zh: '\u5DE5\u5177\u914D\u7F6E', en: 'Tools Profile' },
|
|
10929
|
+
agent_workspace: { zh: '\u5DE5\u4F5C\u7A7A\u95F4', en: 'Workspace' },
|
|
10930
|
+
no_agents_in_list: { zh: '\u6682\u65E0\u667A\u80FD\u4F53\uFF0C\u70B9\u51FB\u4E0A\u65B9\u6309\u94AE\u6DFB\u52A0', en: 'No agents configured. Click the button above to add one.' },
|
|
10931
|
+
// Bindings
|
|
10932
|
+
bindings_title: { zh: '\u{1F517} \u8DEF\u7531\u7ED1\u5B9A', en: '\u{1F517} Routing Bindings' },
|
|
10933
|
+
bindings_desc: { zh: '\u914D\u7F6E\u6D88\u606F\u8DEF\u7531\u89C4\u5219\uFF0C\u5C06\u9891\u9053\u6216\u8D26\u53F7\u7ED1\u5B9A\u5230\u6307\u5B9A\u7684\u667A\u80FD\u4F53\u3002\u652F\u6301\u6309\u9891\u9053\u7C7B\u578B\u6216\u8D26\u53F7ID\u5339\u914D\u3002', en: 'Configure message routing rules to bind channels or accounts to specific agents. Supports matching by channel type or account ID.' },
|
|
10934
|
+
add_binding: { zh: '\u2795 \u6DFB\u52A0\u7ED1\u5B9A', en: '\u2795 Add Binding' },
|
|
10935
|
+
binding_agent_id: { zh: '\u667A\u80FD\u4F53ID', en: 'Agent ID' },
|
|
10936
|
+
binding_channel: { zh: '\u9891\u9053', en: 'Channel' },
|
|
10937
|
+
binding_account_id: { zh: '\u8D26\u53F7ID', en: 'Account ID' },
|
|
10938
|
+
binding_type: { zh: '\u7C7B\u578B', en: 'Type' },
|
|
10939
|
+
no_bindings: { zh: '\u6682\u65E0\u8DEF\u7531\u7ED1\u5B9A', en: 'No routing bindings configured' },
|
|
10940
|
+
// Channels
|
|
10941
|
+
channels_title: { zh: '\u{1F4E1} \u9891\u9053\u914D\u7F6E', en: '\u{1F4E1} Channel Configuration' },
|
|
10942
|
+
channels_desc: { zh: '\u914D\u7F6E\u5916\u90E8\u901A\u4FE1\u9891\u9053\uFF08\u5982 Telegram\uFF09\u3002\u6BCF\u4E2A\u9891\u9053\u53EF\u4EE5\u6709\u591A\u4E2A\u8D26\u53F7\uFF0C\u652F\u6301\u4E0D\u540C\u7684\u6D88\u606F\u7B56\u7565\u548C\u6743\u9650\u3002', en: 'Configure external communication channels (e.g., Telegram). Each channel can have multiple accounts with different messaging policies and permissions.' },
|
|
10943
|
+
channel_enabled: { zh: '\u5DF2\u542F\u7528', en: 'Enabled' },
|
|
10944
|
+
channel_disabled: { zh: '\u5DF2\u7981\u7528', en: 'Disabled' },
|
|
10945
|
+
group_policy: { zh: '\u7FA4\u7EC4\u7B56\u7565', en: 'Group Policy' },
|
|
10946
|
+
accounts: { zh: '\u8D26\u53F7', en: 'Accounts' },
|
|
10947
|
+
add_account: { zh: '\u2795 \u6DFB\u52A0\u8D26\u53F7', en: '\u2795 Add Account' },
|
|
10948
|
+
bot_token: { zh: 'Bot Token', en: 'Bot Token' },
|
|
10949
|
+
dm_policy: { zh: '\u79C1\u4FE1\u7B56\u7565', en: 'DM Policy' },
|
|
10950
|
+
allow_from: { zh: '\u5141\u8BB8\u6765\u6E90', en: 'Allow From' },
|
|
10951
|
+
add_allow_from: { zh: '\u6DFB\u52A0\u5141\u8BB8\u6765\u6E90', en: 'Add Allow From' },
|
|
10952
|
+
no_channels_configured: { zh: '\u6682\u65E0\u9891\u9053\u914D\u7F6E', en: 'No channels configured' },
|
|
10953
|
+
// OpenClaw Actions
|
|
10954
|
+
save_openclaw_config: { zh: '\u{1F4BE} \u4FDD\u5B58 OpenClaw \u914D\u7F6E', en: '\u{1F4BE} Save OpenClaw Config' },
|
|
10955
|
+
openclaw_config_saved: { zh: 'OpenClaw \u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF01', en: 'OpenClaw configuration saved!' },
|
|
10956
|
+
confirm_delete_binding: { zh: '\u786E\u5B9A\u5220\u9664\u6B64\u8DEF\u7531\u7ED1\u5B9A\uFF1F', en: 'Delete this routing binding?' },
|
|
10957
|
+
binding_deleted: { zh: '\u8DEF\u7531\u7ED1\u5B9A\u5DF2\u5220\u9664', en: 'Routing binding deleted' },
|
|
10958
|
+
confirm_delete_oc_agent: { zh: '\u786E\u5B9A\u5220\u9664\u667A\u80FD\u4F53 "{name}" \u5417\uFF1F', en: 'Are you sure you want to delete agent "{name}"?' },
|
|
10959
|
+
confirm_delete_account: { zh: '\u786E\u5B9A\u5220\u9664\u6B64\u8D26\u53F7\uFF1F', en: 'Are you sure you want to delete this account?' },
|
|
10960
|
+
// Misc
|
|
10961
|
+
add_item: { zh: '\u2795 \u6DFB\u52A0', en: '\u2795 Add' },
|
|
10962
|
+
remove_item: { zh: '\u{1F5D1}\uFE0F \u5220\u9664', en: '\u{1F5D1}\uFE0F Delete' },
|
|
10963
|
+
edit_item: { zh: '\u270F\uFE0F \u7F16\u8F91', en: '\u270F\uFE0F Edit' },
|
|
10964
|
+
tab_defaults: { zh: '\u{1F916} \u9ED8\u8BA4\u8BBE\u7F6E', en: '\u{1F916} Defaults' },
|
|
10965
|
+
tab_agents: { zh: '\u{1F4CB} \u667A\u80FD\u4F53', en: '\u{1F4CB} Agents' },
|
|
10966
|
+
tab_bindings: { zh: '\u{1F517} \u7ED1\u5B9A', en: '\u{1F517} Bindings' },
|
|
10967
|
+
tab_channels: { zh: '\u{1F4E1} \u9891\u9053', en: '\u{1F4E1} Channels' },
|
|
10968
|
+
};
|
|
10969
|
+
function t(key) { return (_T[key] && _T[key][_lang]) || key; }
|
|
10970
|
+
function translateStatic() { document.querySelectorAll('[data-i18n]').forEach(el => { const k = el.getAttribute('data-i18n'); if (k && _T[k]) { el.textContent = _T[k][_lang] || el.textContent; } }); document.querySelectorAll('[data-i18n-ph]').forEach(el => { const k = el.getAttribute('data-i18n-ph'); if (k && _T[k]) { el.placeholder = _T[k][_lang] || el.placeholder; } }); }
|
|
10971
|
+
|
|
10491
10972
|
// \u2500\u2500 Globals \u2500\u2500
|
|
10492
10973
|
const API = '/api';
|
|
10493
10974
|
let currentPage = 'dashboard';
|
|
@@ -10513,7 +10994,7 @@ function showOfflineBanner() {
|
|
|
10513
10994
|
if (offlineBannerEl) return;
|
|
10514
10995
|
offlineBannerEl = document.createElement('div');
|
|
10515
10996
|
offlineBannerEl.className = 'offline-banner';
|
|
10516
|
-
offlineBannerEl.innerHTML = '<span class="offline-icon">\u{1F50C}</span><span>
|
|
10997
|
+
offlineBannerEl.innerHTML = '<span class="offline-icon">\u{1F50C}</span><span>' + t('offline_msg') + '</span>';
|
|
10517
10998
|
const mainContent = document.querySelector('.main');
|
|
10518
10999
|
if (mainContent) {
|
|
10519
11000
|
mainContent.insertBefore(offlineBannerEl, mainContent.firstChild);
|
|
@@ -10571,13 +11052,13 @@ function escHtml(s) { if (s == null) return ''; const d = document.createElement
|
|
|
10571
11052
|
function timeAgo(iso) {
|
|
10572
11053
|
if (!iso) return '\u2014';
|
|
10573
11054
|
const diff = Date.now() - new Date(iso).getTime();
|
|
10574
|
-
if (diff < 0) return '
|
|
11055
|
+
if (diff < 0) return t('just_now');
|
|
10575
11056
|
const m = Math.floor(diff / 60000), h = Math.floor(m / 60), d = Math.floor(h / 24);
|
|
10576
|
-
if (m < 1) return '
|
|
11057
|
+
if (m < 1) return t('just_now'); if (m < 60) return m + t('min_ago'); if (h < 24) return h + t('h_ago'); if (d < 30) return d + t('d_ago');
|
|
10577
11058
|
return new Date(iso).toLocaleDateString();
|
|
10578
11059
|
}
|
|
10579
11060
|
function maskKey(s) { if (!s || s.length < 12) return s || ''; return s.substring(0, 6) + '\u2022\u2022\u2022\u2022\u2022\u2022' + s.slice(-4); }
|
|
10580
|
-
function copyText(text) { navigator.clipboard.writeText(text).then(() => toast('
|
|
11061
|
+
function copyText(text) { navigator.clipboard.writeText(text).then(() => toast(t('copied_clipboard'), 'ok')).catch(() => toast(t('copy_failed'), 'err')); }
|
|
10581
11062
|
|
|
10582
11063
|
// \u2500\u2500 Modal \u2500\u2500
|
|
10583
11064
|
function showModal(id) { show(id); }
|
|
@@ -10607,6 +11088,7 @@ function loadPage(page) {
|
|
|
10607
11088
|
case 'friends': loadFriends(); break;
|
|
10608
11089
|
case 'models': loadModels(); break;
|
|
10609
11090
|
case 'settings': loadSettings(); break;
|
|
11091
|
+
case 'openclaw': loadOpenClawConfig(); break;
|
|
10610
11092
|
}
|
|
10611
11093
|
}
|
|
10612
11094
|
|
|
@@ -10615,15 +11097,15 @@ function loadPage(page) {
|
|
|
10615
11097
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
10616
11098
|
async function loadDashboard() {
|
|
10617
11099
|
const el = $('#dashboard-content');
|
|
10618
|
-
html(el, '<div class="loading-mask"><div class="spinner"></div>
|
|
11100
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_dashboard') + '</div>');
|
|
10619
11101
|
const results = await Promise.allSettled([api('/status'), api('/friends'), api('/identity'), api('/mgmt-url')]);
|
|
10620
11102
|
const status = results[0].status === 'fulfilled' ? results[0].value : { error: results[0].reason?.message || 'Failed' };
|
|
10621
11103
|
const friends = results[1].status === 'fulfilled' ? results[1].value : { friends: [], error: true };
|
|
10622
11104
|
const identity = results[2].status === 'fulfilled' ? results[2].value : { agentId: '\u2014', publicKeyFingerprint: '\u2014', serverUrl: '\u2014', connected: false };
|
|
10623
11105
|
const mgmtUrl = results[3].status === 'fulfilled' ? results[3].value : { mgmtUrl: window.location.origin };
|
|
10624
|
-
if (status.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>
|
|
11106
|
+
if (status.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + t('failed_connect') + '</p></div>'); return; }
|
|
10625
11107
|
const connCls = status.connected ? 'dot-ok' : 'dot-err';
|
|
10626
|
-
const connText = status.connected ? '
|
|
11108
|
+
const connText = status.connected ? t('connected') : t('disconnected');
|
|
10627
11109
|
const friendList = friends.friends || [];
|
|
10628
11110
|
const aiFriends = friendList.filter(f => f.friendType === 'ai').length;
|
|
10629
11111
|
const humanFriends = friendList.filter(f => f.friendType !== 'ai').length;
|
|
@@ -10633,7 +11115,7 @@ async function loadDashboard() {
|
|
|
10633
11115
|
<div class="stats-grid">
|
|
10634
11116
|
<div class="stat-card">
|
|
10635
11117
|
<div class="stat-icon" style="background:var(--accent-bg)">\u{1F4E1}</div>
|
|
10636
|
-
<div class="stat-label"
|
|
11118
|
+
<div class="stat-label">\${t('server_status')}</div>
|
|
10637
11119
|
<div class="stat-value" style="font-size:16px;display:flex;align-items:center;gap:8px">
|
|
10638
11120
|
<span class="dot \${connCls}"></span> \${connText}
|
|
10639
11121
|
</div>
|
|
@@ -10641,42 +11123,42 @@ async function loadDashboard() {
|
|
|
10641
11123
|
</div>
|
|
10642
11124
|
<div class="stat-card">
|
|
10643
11125
|
<div class="stat-icon" style="background:var(--ok-bg)">\u{1F465}</div>
|
|
10644
|
-
<div class="stat-label"
|
|
11126
|
+
<div class="stat-label">\${t('total_friends')}</div>
|
|
10645
11127
|
<div class="stat-value">\${friendList.length}</div>
|
|
10646
11128
|
<div class="stat-sub">\${aiFriends} AI \xB7 \${humanFriends} Human</div>
|
|
10647
11129
|
</div>
|
|
10648
11130
|
<div class="stat-card">
|
|
10649
11131
|
<div class="stat-icon" style="background:var(--info-bg)">\u{1F517}</div>
|
|
10650
|
-
<div class="stat-label"
|
|
11132
|
+
<div class="stat-label">\${t('active_sessions')}</div>
|
|
10651
11133
|
<div class="stat-value">\${status.sessionCount || 0}</div>
|
|
10652
|
-
<div class="stat-sub"
|
|
11134
|
+
<div class="stat-sub">\${t('encrypted_sessions')}</div>
|
|
10653
11135
|
</div>
|
|
10654
11136
|
<div class="stat-card">
|
|
10655
11137
|
<div class="stat-icon" style="background:var(--warn-bg)">\u{1F511}</div>
|
|
10656
|
-
<div class="stat-label"
|
|
11138
|
+
<div class="stat-label">\${t('agent_id')}</div>
|
|
10657
11139
|
<div class="stat-value mono" style="font-size:13px">\${escHtml(status.agentId)}</div>
|
|
10658
|
-
<div class="stat-sub"
|
|
11140
|
+
<div class="stat-sub">\${t('fingerprint')}: \${escHtml(status.fingerprint)}</div>
|
|
10659
11141
|
</div>
|
|
10660
11142
|
</div>
|
|
10661
11143
|
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
|
|
10662
11144
|
<div class="card">
|
|
10663
|
-
<div class="card-header"><div class="card-title">\u{1F4CB}
|
|
11145
|
+
<div class="card-header"><div class="card-title">\u{1F4CB} \${t('recent_friends')}</div><button class="btn btn-sm btn-ghost" onclick="navigate('friends')">\${t('view_all')}</button></div>
|
|
10664
11146
|
\${renderMiniFriendList(friendList.slice(0, 5))}
|
|
10665
11147
|
</div>
|
|
10666
11148
|
<div class="card">
|
|
10667
|
-
<div class="card-header"><div class="card-title">\u{1F916}
|
|
10668
|
-
<div class="detail-row"><div class="detail-key"
|
|
10669
|
-
<div class="detail-row"><div class="detail-key"
|
|
10670
|
-
<div class="detail-row"><div class="detail-key"
|
|
10671
|
-
<div class="detail-row"><div class="detail-key"
|
|
10672
|
-
<div class="detail-row"><div class="detail-key"
|
|
11149
|
+
<div class="card-header"><div class="card-title">\u{1F916} \${t('identity_info')}</div></div>
|
|
11150
|
+
<div class="detail-row"><div class="detail-key">\${t('agent_id')}</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${identity.agentId}')">\${escHtml(identity.agentId)} \u{1F4CB}</div></div>
|
|
11151
|
+
<div class="detail-row"><div class="detail-key">\${t('fingerprint')}</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint)}</div></div>
|
|
11152
|
+
<div class="detail-row"><div class="detail-key">\${t('server_url')}</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${identity.serverUrl}')">\${escHtml(identity.serverUrl)} \u{1F4CB}</div></div>
|
|
11153
|
+
<div class="detail-row"><div class="detail-key">\${t('connection')}</div><div class="detail-val"><span class="badge badge-\${identity.connected ? 'ok' : 'danger'}">\${identity.connected ? t('online') : t('offline')}</span></div></div>
|
|
11154
|
+
<div class="detail-row"><div class="detail-key">\${t('plugin_version')}</div><div class="detail-val"><span class="badge badge-accent">v1.2.0</span></div></div>
|
|
10673
11155
|
</div>
|
|
10674
11156
|
</div>
|
|
10675
11157
|
<div class="card" style="margin-top:0">
|
|
10676
|
-
<div class="card-header"><div class="card-title">\u{1F5A5}\uFE0F
|
|
10677
|
-
<div class="detail-row"><div class="detail-key"
|
|
10678
|
-
<div class="detail-row"><div class="detail-key"
|
|
10679
|
-
<div class="detail-row"><div class="detail-key"
|
|
11158
|
+
<div class="card-header"><div class="card-title">\u{1F5A5}\uFE0F \${t('mgmt_ui_access')}</div></div>
|
|
11159
|
+
<div class="detail-row"><div class="detail-key">\${t('current_url')}</div><div class="detail-val"><a href="\${escHtml(mgmtLink)}" target="_blank" style="color:var(--info);text-decoration:underline">\${escHtml(mgmtLink)}</a></div></div>
|
|
11160
|
+
<div class="detail-row"><div class="detail-key">\${t('local_access')}</div><div class="detail-val"><a href="http://127.0.0.1:6109" target="_blank" style="color:var(--info);text-decoration:underline">http://127.0.0.1:6109</a> <button class="btn btn-sm btn-primary" onclick="window.open('http://127.0.0.1:6109','_blank')" style="margin-left:8px">\u{1F517} \${t('open')}</button></div></div>
|
|
11161
|
+
<div class="detail-row"><div class="detail-key">\${t('gateway_path')}</div><div class="detail-val mono">/plugins/aicq-chat/</div></div>
|
|
10680
11162
|
</div>
|
|
10681
11163
|
\\\`);
|
|
10682
11164
|
|
|
@@ -10686,7 +11168,7 @@ async function loadDashboard() {
|
|
|
10686
11168
|
}
|
|
10687
11169
|
|
|
10688
11170
|
function renderMiniFriendList(friends) {
|
|
10689
|
-
if (!friends.length) return '<div class="empty"><p>
|
|
11171
|
+
if (!friends.length) return '<div class="empty"><p>' + t('no_friends_yet') + '</p></div>';
|
|
10690
11172
|
let html = '';
|
|
10691
11173
|
friends.forEach(f => {
|
|
10692
11174
|
html += '<div class="detail-row"><div class="detail-key"><span class="badge badge-' + (f.friendType === 'ai' ? 'info' : 'ghost') + '">' + escHtml(f.friendType || '?') + '</span></div><div class="detail-val mono truncate" style="font-size:12px">' + escHtml(f.id) + '</div></div>';
|
|
@@ -10699,7 +11181,7 @@ function renderMiniFriendList(friends) {
|
|
|
10699
11181
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
10700
11182
|
async function loadAgents() {
|
|
10701
11183
|
const el = $('#agents-content');
|
|
10702
|
-
html(el, '<div class="loading-mask"><div class="spinner"></div>
|
|
11184
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_agents') + '</div>');
|
|
10703
11185
|
const data = await api('/agents');
|
|
10704
11186
|
if (data.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(data.error) + '</p></div>'); return; }
|
|
10705
11187
|
|
|
@@ -10709,13 +11191,16 @@ async function loadAgents() {
|
|
|
10709
11191
|
|
|
10710
11192
|
let rows = '';
|
|
10711
11193
|
agents.forEach((a, i) => {
|
|
10712
|
-
const
|
|
10713
|
-
const
|
|
10714
|
-
const
|
|
11194
|
+
const isProviderModel = a._source === 'provider-model';
|
|
11195
|
+
const modelBadge = a.model ? '<span class="badge badge-accent">' + escHtml(a.model) + '</span>' : '<span class="badge badge-ghost">' + t('default_model') + '</span>';
|
|
11196
|
+
const providerName = isProviderModel ? escHtml(capitalizeProvider(a.provider || '')) : escHtml(a.provider || '');
|
|
11197
|
+
const providerBadge = providerName ? '<span class="tag">' + providerName + (a.isDefault ? ' \u2B50' : '') + '</span>' : '';
|
|
11198
|
+
const statusBadge = a.enabled !== false ? '<span class="badge badge-ok">' + t('active') + '</span>' : '<span class="badge badge-warn">' + t('disabled') + '</span>';
|
|
11199
|
+
const defaultBadge = a.isDefault ? ' <span class="badge badge-warn" style="font-size:10px">' + t('default_model_badge') + '</span>' : '';
|
|
10715
11200
|
|
|
10716
11201
|
rows += \\\`<tr>
|
|
10717
|
-
<td>\${statusBadge}</td>
|
|
10718
|
-
<td><div style="font-weight:600">\${escHtml(a.name ||
|
|
11202
|
+
<td>\${statusBadge}\${defaultBadge}</td>
|
|
11203
|
+
<td><div style="font-weight:600">\${escHtml(a.name || 'Agent ' + (i + 1))}</div><div class="mono" style="font-size:11px;color:var(--text3)">\${isProviderModel ? escHtml(a._configPath || '') : escHtml(a.id || '\u2014')}</div></td>
|
|
10719
11204
|
<td>\${modelBadge}</td>
|
|
10720
11205
|
<td>\${providerBadge}</td>
|
|
10721
11206
|
<td>\${escHtml(a.systemPrompt ? a.systemPrompt.substring(0, 60) + '...' : '\u2014')}</td>
|
|
@@ -10731,23 +11216,23 @@ async function loadAgents() {
|
|
|
10731
11216
|
|
|
10732
11217
|
if (!agents.length) {
|
|
10733
11218
|
html(el, \\\`
|
|
10734
|
-
<p class="section-desc"
|
|
10735
|
-
<div class="empty"><div class="icon">\u{1F916}</div><p
|
|
11219
|
+
<p class="section-desc">\${t('agent_list_from')} <strong>\${escHtml(configSource)}</strong></p>
|
|
11220
|
+
<div class="empty"><div class="icon">\u{1F916}</div><p>\${t('no_agents_configured')}</p><p class="sub">\${t('add_agents_hint')}</p></div>
|
|
10736
11221
|
\\\`);
|
|
10737
11222
|
return;
|
|
10738
11223
|
}
|
|
10739
11224
|
|
|
10740
11225
|
html(el, \\\`
|
|
10741
11226
|
<div class="toolbar">
|
|
10742
|
-
<div class="search-box"><input type="text" placeholder="
|
|
10743
|
-
<button class="btn btn-sm btn-primary" onclick="showAddAgentModal()"
|
|
10744
|
-
<button class="btn btn-sm btn-default" onclick="loadAgents()">\u{1F504}
|
|
11227
|
+
<div class="search-box"><input type="text" placeholder="\${t('search_agents')}" id="agent-search" oninput="filterAgentTable()"></div>
|
|
11228
|
+
<button class="btn btn-sm btn-primary" onclick="showAddAgentModal()">\${t('add_agent')}</button>
|
|
11229
|
+
<button class="btn btn-sm btn-default" onclick="loadAgents()">\u{1F504} \${t('refresh')}</button>
|
|
10745
11230
|
</div>
|
|
10746
|
-
<p class="section-desc"
|
|
11231
|
+
<p class="section-desc">\${t('agent_list_from')} <strong style="color:var(--accent2)">\${escHtml(configSource)}</strong>. \${t('total_label')}: <strong>\${agents.length}</strong> \${t('agents_configured')}.</p>
|
|
10747
11232
|
<div class="card" style="padding:0;overflow:hidden">
|
|
10748
11233
|
<div style="overflow-x:auto">
|
|
10749
11234
|
<table>
|
|
10750
|
-
<thead><tr><th style="width:
|
|
11235
|
+
<thead><tr><th style="width:80px">\${t('status')}</th><th>\${t('agent')}</th><th>\${t('model')}</th><th>\${t('provider')}</th><th>\${t('system_prompt')}</th><th style="width:90px">\${t('actions')}</th></tr></thead>
|
|
10751
11236
|
<tbody id="agent-table-body">\${rows}</tbody>
|
|
10752
11237
|
</table>
|
|
10753
11238
|
</div>
|
|
@@ -10755,6 +11240,17 @@ async function loadAgents() {
|
|
|
10755
11240
|
\\\`);
|
|
10756
11241
|
}
|
|
10757
11242
|
|
|
11243
|
+
function capitalizeProvider(id) {
|
|
11244
|
+
const names = {
|
|
11245
|
+
modelscope: 'ModelScope', zhipu: 'Zhipu AI', qwen: 'Qwen', doubao: 'Doubao',
|
|
11246
|
+
moonshot: 'Moonshot', minimax: 'MiniMax', stepfun: 'StepFun', baidu: 'Baidu',
|
|
11247
|
+
spark: 'Spark', deepseek: 'DeepSeek', openai: 'OpenAI', anthropic: 'Anthropic',
|
|
11248
|
+
google: 'Google AI', groq: 'Groq', ollama: 'Ollama', openrouter: 'OpenRouter',
|
|
11249
|
+
mistral: 'Mistral AI', together: 'Together AI', fireworks: 'Fireworks AI',
|
|
11250
|
+
};
|
|
11251
|
+
return names[id] || (id ? id.charAt(0).toUpperCase() + id.slice(1) : '\u2014');
|
|
11252
|
+
}
|
|
11253
|
+
|
|
10758
11254
|
function filterAgentTable() {
|
|
10759
11255
|
const q = ($('#agent-search')?.value || '').toLowerCase();
|
|
10760
11256
|
$$('#agent-table-body tr').forEach(tr => {
|
|
@@ -10773,23 +11269,34 @@ function viewAgent(index) {
|
|
|
10773
11269
|
details += '<div class="detail-row"><div class="detail-key">' + escHtml(k) + '</div><div class="detail-val mono" style="font-size:12px;cursor:pointer" onclick="copyText(decodeURIComponent(\\'' + encodeURIComponent(String(v)) + '\\'))">' + display + ' \u{1F4CB}</div></div>';
|
|
10774
11270
|
}
|
|
10775
11271
|
}
|
|
10776
|
-
html('#view-agent-body', details || '<div class="empty"><p>
|
|
10777
|
-
$('#view-agent-title').textContent = a.name || a.id || '
|
|
11272
|
+
html('#view-agent-body', details || '<div class="empty"><p>' + t('no_data') + '</p></div>');
|
|
11273
|
+
$('#view-agent-title').textContent = a.name || a.id || t('agent');
|
|
10778
11274
|
showModal('modal-view-agent');
|
|
10779
11275
|
}
|
|
10780
11276
|
|
|
10781
11277
|
async function deleteAgent(index) {
|
|
10782
|
-
|
|
10783
|
-
const
|
|
10784
|
-
if (
|
|
10785
|
-
|
|
11278
|
+
const agents = window._lastAgentsData?.agents || [];
|
|
11279
|
+
const a = agents[index];
|
|
11280
|
+
if (!a) return;
|
|
11281
|
+
if (!confirm(t('confirm_delete_agent'))) return;
|
|
11282
|
+
let identifier;
|
|
11283
|
+
if (a._source === 'provider-model') {
|
|
11284
|
+
identifier = 'provider:' + (a._providerId || '') + ':' + (a._modelIndex || 0);
|
|
11285
|
+
} else {
|
|
11286
|
+
identifier = index;
|
|
11287
|
+
}
|
|
11288
|
+
const r = await api('/agents/' + encodeURIComponent(identifier), { method: 'DELETE' });
|
|
11289
|
+
if (r.success) { toast(t('agent_deleted'), 'ok'); loadAgents(); }
|
|
11290
|
+
else { toast(r.message || r.error || t('delete_failed'), 'err'); }
|
|
10786
11291
|
}
|
|
10787
11292
|
|
|
10788
11293
|
let _editAgentIndex = null;
|
|
11294
|
+
let _editAgentIsProviderModel = false;
|
|
10789
11295
|
|
|
10790
11296
|
function showAddAgentModal() {
|
|
10791
11297
|
_editAgentIndex = null;
|
|
10792
|
-
|
|
11298
|
+
_editAgentIsProviderModel = false;
|
|
11299
|
+
$('#agent-form-title').textContent = t('add_new_agent');
|
|
10793
11300
|
$('#agent-form-name').value = '';
|
|
10794
11301
|
$('#agent-form-id').value = '';
|
|
10795
11302
|
$('#agent-form-model').value = '';
|
|
@@ -10809,9 +11316,10 @@ function showEditAgentModal(index) {
|
|
|
10809
11316
|
const a = agents[index];
|
|
10810
11317
|
if (!a) return;
|
|
10811
11318
|
_editAgentIndex = index;
|
|
10812
|
-
|
|
11319
|
+
_editAgentIsProviderModel = a._source === 'provider-model';
|
|
11320
|
+
$('#agent-form-title').textContent = t('edit_agent');
|
|
10813
11321
|
$('#agent-form-name').value = a.name || '';
|
|
10814
|
-
$('#agent-form-id').value = a.id || '';
|
|
11322
|
+
$('#agent-form-id').value = a._source === 'provider-model' ? (a._configPath || '') : (a.id || '');
|
|
10815
11323
|
$('#agent-form-model').value = a.model || '';
|
|
10816
11324
|
$('#agent-form-provider').value = a.provider || '';
|
|
10817
11325
|
$('#agent-form-prompt').value = a.systemPrompt || '';
|
|
@@ -10820,6 +11328,13 @@ function showEditAgentModal(index) {
|
|
|
10820
11328
|
$('#agent-form-max-tokens').value = a.maxTokens ?? 4096;
|
|
10821
11329
|
$('#agent-form-top-p').value = a.topP ?? 1;
|
|
10822
11330
|
$('#agent-form-tools').value = Array.isArray(a.tools) ? a.tools.join(', ') : (a.tools || '');
|
|
11331
|
+
if (a._source === 'provider-model') {
|
|
11332
|
+
$('#agent-form-id').readOnly = true;
|
|
11333
|
+
$('#agent-form-id').title = a._configPath || '';
|
|
11334
|
+
} else {
|
|
11335
|
+
$('#agent-form-id').readOnly = false;
|
|
11336
|
+
$('#agent-form-id').title = '';
|
|
11337
|
+
}
|
|
10823
11338
|
showModal('modal-add-agent');
|
|
10824
11339
|
}
|
|
10825
11340
|
|
|
@@ -10842,23 +11357,29 @@ async function saveAgent() {
|
|
|
10842
11357
|
tools: toolsRaw ? toolsRaw.split(',').map(t => t.trim()).filter(Boolean) : [],
|
|
10843
11358
|
};
|
|
10844
11359
|
|
|
10845
|
-
if (!agent.name) { toast('
|
|
11360
|
+
if (!agent.name) { toast(t('agent_name_required'), 'warn'); return; }
|
|
10846
11361
|
|
|
10847
11362
|
let r;
|
|
10848
11363
|
if (_editAgentIndex !== null) {
|
|
10849
|
-
|
|
10850
|
-
|
|
11364
|
+
let identifier;
|
|
11365
|
+
if (_editAgentIsProviderModel) {
|
|
11366
|
+
const a = (window._lastAgentsData?.agents || [])[_editAgentIndex];
|
|
11367
|
+
identifier = 'provider:' + (a?._providerId || '') + ':' + (a?._modelIndex || 0);
|
|
11368
|
+
} else {
|
|
11369
|
+
identifier = _editAgentIndex;
|
|
11370
|
+
}
|
|
11371
|
+
r = await api('/agents/' + encodeURIComponent(identifier), { method: 'PUT', body: JSON.stringify({ agent }) });
|
|
10851
11372
|
} else {
|
|
10852
11373
|
// Add new
|
|
10853
11374
|
r = await api('/agents', { method: 'POST', body: JSON.stringify({ agent }) });
|
|
10854
11375
|
}
|
|
10855
11376
|
|
|
10856
11377
|
if (r.success) {
|
|
10857
|
-
toast(_editAgentIndex !== null ? '
|
|
11378
|
+
toast(_editAgentIndex !== null ? t('agent_updated') : t('agent_added'), 'ok');
|
|
10858
11379
|
hideModal('modal-add-agent');
|
|
10859
11380
|
loadAgents();
|
|
10860
11381
|
} else {
|
|
10861
|
-
toast(r.message || r.error || '
|
|
11382
|
+
toast(r.message || r.error || t('failed'), 'err');
|
|
10862
11383
|
}
|
|
10863
11384
|
}
|
|
10864
11385
|
|
|
@@ -10869,7 +11390,7 @@ let friendsFilter = 'all';
|
|
|
10869
11390
|
|
|
10870
11391
|
async function loadFriends() {
|
|
10871
11392
|
const el = $('#friends-content');
|
|
10872
|
-
html(el, '<div class="loading-mask"><div class="spinner"></div>
|
|
11393
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_friends') + '</div>');
|
|
10873
11394
|
const results = await Promise.allSettled([api('/friends'), api('/friends/requests'), api('/sessions')]);
|
|
10874
11395
|
const friends = results[0].status === 'fulfilled' ? results[0].value : { friends: [] };
|
|
10875
11396
|
const requests = results[1].status === 'fulfilled' ? results[1].value : { requests: [] };
|
|
@@ -10888,9 +11409,9 @@ async function loadFriends() {
|
|
|
10888
11409
|
const sessCount = (sessions.sessions || []).length;
|
|
10889
11410
|
|
|
10890
11411
|
html('#friends-tabs', \\\`
|
|
10891
|
-
<button class="filter-btn \${friendsSubTab==='friends'?'active':''}" onclick="friendsSubTab='friends';loadFriends()">\u{1F465}
|
|
10892
|
-
<button class="filter-btn \${friendsSubTab==='requests'?'active':''}" onclick="friendsSubTab='requests';loadFriends()">\u{1F4E8}
|
|
10893
|
-
<button class="filter-btn \${friendsSubTab==='sessions'?'active':''}" onclick="friendsSubTab='sessions';loadFriends()">\u{1F517}
|
|
11412
|
+
<button class="filter-btn \${friendsSubTab==='friends'?'active':''}" onclick="friendsSubTab='friends';loadFriends()">\u{1F465} \${t('friends')} (<span id="fc">\${friendCount}</span>)</button>
|
|
11413
|
+
<button class="filter-btn \${friendsSubTab==='requests'?'active':''}" onclick="friendsSubTab='requests';loadFriends()">\u{1F4E8} \${t('requests')} (<span id="rc">\${reqCount}</span>)</button>
|
|
11414
|
+
<button class="filter-btn \${friendsSubTab==='sessions'?'active':''}" onclick="friendsSubTab='sessions';loadFriends()">\u{1F517} \${t('sessions')} (<span id="sc">\${sessCount}</span>)</button>
|
|
10894
11415
|
\\\`);
|
|
10895
11416
|
|
|
10896
11417
|
window._friendsData = friends;
|
|
@@ -10925,24 +11446,24 @@ function renderFriendsList(friends) {
|
|
|
10925
11446
|
|
|
10926
11447
|
html(el, \\\`
|
|
10927
11448
|
<div class="toolbar">
|
|
10928
|
-
<div class="search-box"><input type="text" placeholder="
|
|
11449
|
+
<div class="search-box"><input type="text" placeholder="\${t('search_friends')}" id="friend-search" oninput="filterFriendTable()"></div>
|
|
10929
11450
|
<div class="filter-group">
|
|
10930
|
-
<button class="filter-btn \${friendsFilter==='all'?'active':''}" onclick="friendsFilter='all';filterFriendTable()"
|
|
10931
|
-
<button class="filter-btn \${friendsFilter==='ai'?'active':''}" onclick="friendsFilter='ai';filterFriendTable()"
|
|
10932
|
-
<button class="filter-btn \${friendsFilter==='human'?'active':''}" onclick="friendsFilter='human';filterFriendTable()"
|
|
11451
|
+
<button class="filter-btn \${friendsFilter==='all'?'active':''}" onclick="friendsFilter='all';filterFriendTable()">\${t('all')}</button>
|
|
11452
|
+
<button class="filter-btn \${friendsFilter==='ai'?'active':''}" onclick="friendsFilter='ai';filterFriendTable()">\${t('ai')}</button>
|
|
11453
|
+
<button class="filter-btn \${friendsFilter==='human'?'active':''}" onclick="friendsFilter='human';filterFriendTable()">\${t('human')}</button>
|
|
10933
11454
|
</div>
|
|
10934
11455
|
<span style="flex:1"></span>
|
|
10935
|
-
<button class="btn btn-sm btn-primary" onclick="showAddFriendModal()" \${isOffline ? 'disabled title="
|
|
11456
|
+
<button class="btn btn-sm btn-primary" onclick="showAddFriendModal()" \${isOffline ? 'disabled title="' + t('unavailable_offline') + '"' : ''}>\${t('add_friend')}</button>
|
|
10936
11457
|
<button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504}</button>
|
|
10937
11458
|
</div>
|
|
10938
11459
|
<div class="card" style="padding:0;overflow:hidden">
|
|
10939
11460
|
<div style="overflow-x:auto;max-height:calc(100vh - 280px);overflow-y:auto">
|
|
10940
11461
|
<table>
|
|
10941
|
-
<thead><tr><th style="width:60px"
|
|
11462
|
+
<thead><tr><th style="width:60px">\${t('type')}</th><th>\${t('friend_label')}</th><th>\${t('permissions')}</th><th>\${t('fingerprint')}</th><th>\${t('last_message')}</th><th style="width:80px">\${t('actions')}</th></tr></thead>
|
|
10942
11463
|
<tbody id="friend-table-body">\${rows}</tbody>
|
|
10943
11464
|
</table>
|
|
10944
11465
|
</div>
|
|
10945
|
-
\${!friends.length ? '<div class="empty"><div class="icon">\u{1F465}</div><p>
|
|
11466
|
+
\${!friends.length ? '<div class="empty"><div class="icon">\u{1F465}</div><p>' + t('no_friends_yet') + '</p><p class="sub">' + t('add_friend_hint') + '</p></div>' : ''}
|
|
10946
11467
|
</div>
|
|
10947
11468
|
\\\`);
|
|
10948
11469
|
}
|
|
@@ -10967,18 +11488,18 @@ function renderRequestsList(requests) {
|
|
|
10967
11488
|
<td><span class="badge badge-\${stCls}">\${escHtml(r.status)}</span></td>
|
|
10968
11489
|
<td>\${timeAgo(r.createdAt)}</td>
|
|
10969
11490
|
<td>
|
|
10970
|
-
\${r.status === 'pending' ? '<div class="actions-cell"><button class="btn btn-sm btn-ok" onclick="acceptFriendReq(\\'' + escHtml(r.id) + '\\')">\u2713
|
|
11491
|
+
\${r.status === 'pending' ? '<div class="actions-cell"><button class="btn btn-sm btn-ok" onclick="acceptFriendReq(\\'' + escHtml(r.id) + '\\')">\u2713 \${t('accept')}</button><button class="btn btn-sm btn-danger" onclick="rejectFriendReq(\\'' + escHtml(r.id) + '\\')">\u2717 \${t('reject')}</button></div>' : '\u2014'}
|
|
10971
11492
|
</td>
|
|
10972
11493
|
</tr>\\\`;
|
|
10973
11494
|
});
|
|
10974
11495
|
html(el, \\\`
|
|
10975
|
-
<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504}
|
|
11496
|
+
<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} \${t('refresh')}</button></div>
|
|
10976
11497
|
<div class="card" style="padding:0;overflow:hidden">
|
|
10977
11498
|
<div style="overflow-x:auto"><table>
|
|
10978
|
-
<thead><tr><th
|
|
11499
|
+
<thead><tr><th>\${t('request_id')}</th><th>\${t('from')}</th><th>\${t('status')}</th><th>\${t('time')}</th><th style="width:160px">\${t('actions')}</th></tr></thead>
|
|
10979
11500
|
<tbody>\${rows}</tbody>
|
|
10980
11501
|
</table></div>
|
|
10981
|
-
\${!requests.length ? '<div class="empty"><div class="icon">\u{1F4E8}</div><p>
|
|
11502
|
+
\${!requests.length ? '<div class="empty"><div class="icon">\u{1F4E8}</div><p>' + t('no_pending_requests') + '</p></div>' : ''}
|
|
10982
11503
|
</div>
|
|
10983
11504
|
\\\`);
|
|
10984
11505
|
}
|
|
@@ -10990,17 +11511,17 @@ function renderSessionsList(sessions) {
|
|
|
10990
11511
|
rows += \\\`<tr>
|
|
10991
11512
|
<td class="mono" style="font-size:12px;cursor:pointer" onclick="copyText('\${escHtml(s.peerId)}')">\${escHtml(s.peerId)} \u{1F4CB}</td>
|
|
10992
11513
|
<td>\${timeAgo(s.createdAt)}</td>
|
|
10993
|
-
<td><span class="badge badge-info">\${s.messageCount} messages</span></td>
|
|
11514
|
+
<td><span class="badge badge-info">\${s.messageCount} \${t('messages')}</span></td>
|
|
10994
11515
|
</tr>\\\`;
|
|
10995
11516
|
});
|
|
10996
11517
|
html(el, \\\`
|
|
10997
|
-
<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504}
|
|
11518
|
+
<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} \${t('refresh')}</button></div>
|
|
10998
11519
|
<div class="card" style="padding:0;overflow:hidden">
|
|
10999
11520
|
<div style="overflow-x:auto"><table>
|
|
11000
|
-
<thead><tr><th
|
|
11521
|
+
<thead><tr><th>\${t('peer_id')}</th><th>\${t('established')}</th><th>\${t('messages')}</th></tr></thead>
|
|
11001
11522
|
<tbody>\${rows}</tbody>
|
|
11002
11523
|
</table></div>
|
|
11003
|
-
\${!sessions.length ? '<div class="empty"><div class="icon">\u{1F517}</div><p>
|
|
11524
|
+
\${!sessions.length ? '<div class="empty"><div class="icon">\u{1F517}</div><p>' + t('no_active_sessions') + '</p></div>' : ''}
|
|
11004
11525
|
</div>
|
|
11005
11526
|
\\\`);
|
|
11006
11527
|
}
|
|
@@ -11008,18 +11529,18 @@ function renderSessionsList(sessions) {
|
|
|
11008
11529
|
function showAddFriendModal() { $('#add-friend-target').value = ''; showModal('modal-add-friend'); setTimeout(() => $('#add-friend-target')?.focus(), 100); }
|
|
11009
11530
|
async function addFriend() {
|
|
11010
11531
|
const target = $('#add-friend-target').value.trim();
|
|
11011
|
-
if (!target) { toast('
|
|
11532
|
+
if (!target) { toast(t('enter_temp_or_id'), 'warn'); return; }
|
|
11012
11533
|
hideModal('modal-add-friend');
|
|
11013
|
-
toast('
|
|
11534
|
+
toast(t('sending_request'), 'info');
|
|
11014
11535
|
const r = await api('/friends', { method: 'POST', body: JSON.stringify({ target }) });
|
|
11015
|
-
if (r.success) { toast(r.message || '
|
|
11016
|
-
else { toast(r.message || r.error || '
|
|
11536
|
+
if (r.success) { toast(r.message || t('friend_request_sent'), 'ok'); loadFriends(); }
|
|
11537
|
+
else { toast(r.message || r.error || t('failed_add_friend'), 'err'); }
|
|
11017
11538
|
}
|
|
11018
11539
|
async function removeFriend(id) {
|
|
11019
|
-
if (!confirm('
|
|
11540
|
+
if (!confirm(t('remove_friend_confirm') + id + '?')) return;
|
|
11020
11541
|
const r = await api('/friends/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
11021
|
-
if (r.success) { toast('
|
|
11022
|
-
else { toast(r.message || r.error || '
|
|
11542
|
+
if (r.success) { toast(t('friend_removed'), 'ok'); loadFriends(); }
|
|
11543
|
+
else { toast(r.message || r.error || t('failed'), 'err'); }
|
|
11023
11544
|
}
|
|
11024
11545
|
|
|
11025
11546
|
let _editFriendId = null;
|
|
@@ -11034,16 +11555,16 @@ async function saveFriendPerms() {
|
|
|
11034
11555
|
if ($('#perm-chat').checked) perms.push('chat');
|
|
11035
11556
|
if ($('#perm-exec').checked) perms.push('exec');
|
|
11036
11557
|
const r = await api('/friends/' + encodeURIComponent(_editFriendId) + '/permissions', { method: 'PUT', body: JSON.stringify({ permissions: perms }) });
|
|
11037
|
-
if (r.success) { toast('
|
|
11038
|
-
else { toast(r.message || r.error || '
|
|
11558
|
+
if (r.success) { toast(t('permissions_updated'), 'ok'); hideModal('modal-permissions'); loadFriends(); }
|
|
11559
|
+
else { toast(r.message || r.error || t('failed'), 'err'); }
|
|
11039
11560
|
}
|
|
11040
11561
|
async function acceptFriendReq(id) {
|
|
11041
11562
|
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/accept', { method: 'POST', body: JSON.stringify({ permissions: ['chat'] }) });
|
|
11042
|
-
if (r.success) { toast('
|
|
11563
|
+
if (r.success) { toast(t('request_accepted'), 'ok'); loadFriends(); } else { toast(r.message || r.error || t('failed'), 'err'); }
|
|
11043
11564
|
}
|
|
11044
11565
|
async function rejectFriendReq(id) {
|
|
11045
11566
|
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/reject', { method: 'POST', body: JSON.stringify({}) });
|
|
11046
|
-
if (r.success) { toast('
|
|
11567
|
+
if (r.success) { toast(t('request_rejected'), 'ok'); loadFriends(); } else { toast(r.message || r.error || t('failed'), 'err'); }
|
|
11047
11568
|
}
|
|
11048
11569
|
|
|
11049
11570
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
@@ -11053,36 +11574,81 @@ let _modelProviders = null;
|
|
|
11053
11574
|
|
|
11054
11575
|
async function loadModels() {
|
|
11055
11576
|
const el = $('#models-content');
|
|
11056
|
-
html(el, '<div class="loading-mask"><div class="spinner"></div>
|
|
11577
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_models') + '</div>');
|
|
11057
11578
|
const data = await api('/models');
|
|
11058
11579
|
if (data.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(data.error) + '</p></div>'); return; }
|
|
11059
11580
|
_modelProviders = data;
|
|
11060
11581
|
renderModels(data);
|
|
11061
11582
|
}
|
|
11062
11583
|
|
|
11584
|
+
function getProviderIcon(id) {
|
|
11585
|
+
const icons = {
|
|
11586
|
+
openai: '\u{1F7E2}', anthropic: '\u{1F7E0}', google: '\u{1F535}', ollama: '\u{1F7E3}', deepseek: '\u{1F537}',
|
|
11587
|
+
groq: '\u26A1', openrouter: '\u{1F310}', mistral: '\u{1F300}', together: '\u{1F52E}', fireworks: '\u{1F386}',
|
|
11588
|
+
modelscope: '\u{1F3D7}\uFE0F', zhipu: '\u{1F9E0}', qwen: '\u2601\uFE0F', doubao: '\u{1FAD8}', moonshot: '\u{1F319}',
|
|
11589
|
+
minimax: '\u{1F537}', stepfun: '\u{1F4C8}', baidu: '\u{1F50D}', spark: '\u2728',
|
|
11590
|
+
};
|
|
11591
|
+
if (id && id.startsWith('custom-')) return '\u{1F9E9}';
|
|
11592
|
+
return icons[id] || '\u26AA';
|
|
11593
|
+
}
|
|
11594
|
+
|
|
11063
11595
|
function renderModels(data) {
|
|
11064
11596
|
const el = $('#models-content');
|
|
11065
11597
|
const providers = data.providers || [];
|
|
11066
11598
|
const configured = providers.filter(p => p.configured).length;
|
|
11599
|
+
const defaultModel = data.defaultModel || '';
|
|
11600
|
+
|
|
11601
|
+
// Default model banner
|
|
11602
|
+
let defaultBanner = '';
|
|
11603
|
+
if (defaultModel) {
|
|
11604
|
+
defaultBanner = \\\`
|
|
11605
|
+
<div class="card" style="border-color:var(--warn);background:var(--warn-bg)">
|
|
11606
|
+
<div class="card-header">
|
|
11607
|
+
<div class="card-title" style="color:#d97706">\u2B50 \${t('default_model_global')}</div>
|
|
11608
|
+
</div>
|
|
11609
|
+
<div style="display:flex;align-items:center;gap:12px">
|
|
11610
|
+
<span class="badge badge-warn" style="font-size:13px;padding:4px 14px">\${escHtml(defaultModel)}</span>
|
|
11611
|
+
</div>
|
|
11612
|
+
</div>\\\`;
|
|
11613
|
+
}
|
|
11067
11614
|
|
|
11068
11615
|
let cards = '';
|
|
11069
11616
|
providers.forEach(p => {
|
|
11070
|
-
const icon = p.id
|
|
11617
|
+
const icon = getProviderIcon(p.id);
|
|
11071
11618
|
const statusBadge = p.configured
|
|
11072
|
-
? '<span class="badge badge-ok"
|
|
11073
|
-
: '<span class="badge badge-ghost">
|
|
11074
|
-
|
|
11619
|
+
? '<span class="badge badge-ok">' + t('key_set') + '</span>'
|
|
11620
|
+
: '<span class="badge badge-ghost">' + t('not_set') + '</span>';
|
|
11621
|
+
|
|
11622
|
+
// Show multi-model info
|
|
11623
|
+
let modelInfo = '';
|
|
11624
|
+
if (p.configured && p.modelCount > 0) {
|
|
11625
|
+
modelInfo = '<span class="prov-model">' + t('multi_model') + ': ' + p.modelCount + ' ' + t('models_under_provider') + '</span>';
|
|
11626
|
+
const shownModels = (p.models || []).slice(0, 3);
|
|
11627
|
+
shownModels.forEach(m => {
|
|
11628
|
+
const isDef = defaultModel === (m.id || '');
|
|
11629
|
+
modelInfo += '<span class="prov-model" style="margin-left:4px">' + escHtml(m.name || m.id || '') + (isDef ? ' \u2B50' : '') + '</span>';
|
|
11630
|
+
});
|
|
11631
|
+
if (p.modelCount > 3) {
|
|
11632
|
+
modelInfo += '<span class="prov-model" style="color:var(--text3);margin-left:4px">+' + (p.modelCount - 3) + ' more</span>';
|
|
11633
|
+
}
|
|
11634
|
+
} else if (p.modelId) {
|
|
11635
|
+
modelInfo = '<span class="prov-model">' + escHtml(p.modelId) + '</span>';
|
|
11636
|
+
}
|
|
11075
11637
|
|
|
11076
11638
|
cards += \\\`
|
|
11077
|
-
<div class="provider-card" onclick="showModelConfigModal('
|
|
11639
|
+
<div class="provider-card \${p.isCustom ? 'custom-provider' : ''}" onclick="\${p.isCustom ? "showEditCustomProviderModal('" + escHtml(p.id) + "')" : "showModelConfigModal('" + escHtml(p.id) + "')"}">
|
|
11078
11640
|
<div class="prov-head">
|
|
11079
|
-
<div class="prov-name">\${icon} \${escHtml(p.name)}</div>
|
|
11641
|
+
<div class="prov-name">\${icon} \${escHtml(p.name)}\${p.isCustom ? ' <span class="tag" style="background:var(--accent-bg);color:var(--accent2)">\u{1F9E9} Custom</span>' : ''}</div>
|
|
11080
11642
|
\${statusBadge}
|
|
11081
11643
|
</div>
|
|
11082
|
-
<div class="prov-desc">\${escHtml(p.description)}</div>
|
|
11083
|
-
\${
|
|
11644
|
+
<div class="prov-desc">\${escHtml(p.description || '')}</div>
|
|
11645
|
+
\${modelInfo}
|
|
11084
11646
|
<div class="prov-actions">
|
|
11085
|
-
|
|
11647
|
+
\${p.isCustom
|
|
11648
|
+
? '<button class="btn btn-sm btn-ok" onclick="event.stopPropagation();showEditCustomProviderModal(\\'' + escHtml(p.id) + '\\')">\u270F\uFE0F ' + t('configure') + '</button>' +
|
|
11649
|
+
'<button class="btn btn-sm btn-danger" onclick="event.stopPropagation();deleteCustomProvider(\\'' + escHtml(p.id) + '\\',\\'' + escHtml(p.name) + '\\')">\u{1F5D1}\uFE0F</button>'
|
|
11650
|
+
: '<button class="btn btn-sm btn-primary" onclick="event.stopPropagation();showModelConfigModal(\\'' + escHtml(p.id) + '\\')">' + t('configure') + '</button>'
|
|
11651
|
+
}
|
|
11086
11652
|
</div>
|
|
11087
11653
|
</div>\\\`;
|
|
11088
11654
|
});
|
|
@@ -11091,14 +11657,16 @@ function renderModels(data) {
|
|
|
11091
11657
|
if (data.currentModels && data.currentModels.length) {
|
|
11092
11658
|
let rows = '';
|
|
11093
11659
|
data.currentModels.forEach(m => {
|
|
11660
|
+
const defaultTag = m.isDefault ? ' <span class="badge badge-warn" style="font-size:10px">\u2B50 ' + t('default_model_badge') + '</span>' : '';
|
|
11094
11661
|
rows += \\\`<tr>
|
|
11095
11662
|
<td style="font-weight:500">\${escHtml(m.provider)}</td>
|
|
11096
|
-
<td class="mono">\${escHtml(m.modelId)}</td>
|
|
11097
|
-
<td
|
|
11098
|
-
<td class="
|
|
11663
|
+
<td class="mono">\${escHtml(m.modelId)}\${defaultTag}</td>
|
|
11664
|
+
<td>\${escHtml(m.modelName || '')}</td>
|
|
11665
|
+
<td><span class="badge badge-ok">' + t('key_set') + '</span></td>
|
|
11666
|
+
<td class="mono" style="font-size:11px">\${escHtml(m.baseUrl || t('default_model'))}</td>
|
|
11099
11667
|
<td>
|
|
11100
11668
|
<div class="actions-cell">
|
|
11101
|
-
<button class="btn btn-sm btn-ghost" onclick="showModelConfigModal('\${escHtml(m.providerId)}')"
|
|
11669
|
+
<button class="btn btn-sm btn-ghost" onclick="showModelConfigModal('\${escHtml(m.providerId)}')">\${t('configure')}</button>
|
|
11102
11670
|
<button class="btn btn-sm btn-danger" onclick="deleteModelProvider('\${escHtml(m.providerId)}')" title="Delete">\u{1F5D1}\uFE0F</button>
|
|
11103
11671
|
</div>
|
|
11104
11672
|
</td>
|
|
@@ -11106,61 +11674,174 @@ function renderModels(data) {
|
|
|
11106
11674
|
});
|
|
11107
11675
|
activeModelsSection = \\\`
|
|
11108
11676
|
<div class="card" style="margin-top:20px">
|
|
11109
|
-
<div class="card-header"><div class="card-title">\u{1F4CA}
|
|
11677
|
+
<div class="card-header"><div class="card-title">\u{1F4CA} \${t('active_model_configs')}</div></div>
|
|
11110
11678
|
<div style="overflow-x:auto"><table>
|
|
11111
|
-
<thead><tr><th
|
|
11679
|
+
<thead><tr><th>\${t('provider')}</th><th>\${t('model')}</th><th>\${t('model_name_label')}</th><th>API Key</th><th>\${t('base_url')}</th><th>\${t('actions')}</th></tr></thead>
|
|
11112
11680
|
<tbody>\${rows}</tbody>
|
|
11113
11681
|
</table></div>
|
|
11114
11682
|
</div>\\\`;
|
|
11115
11683
|
}
|
|
11116
11684
|
|
|
11117
11685
|
html(el, \\\`
|
|
11686
|
+
\${defaultBanner}
|
|
11118
11687
|
<div class="stats-grid" style="margin-bottom:24px">
|
|
11119
11688
|
<div class="stat-card">
|
|
11120
11689
|
<div class="stat-icon" style="background:var(--accent-bg)">\u{1F9E0}</div>
|
|
11121
|
-
<div class="stat-label"
|
|
11690
|
+
<div class="stat-label">\${t('configured')}</div>
|
|
11122
11691
|
<div class="stat-value">\${configured} / \${providers.length}</div>
|
|
11123
|
-
<div class="stat-sub"
|
|
11692
|
+
<div class="stat-sub">\${t('providers_with_keys')}</div>
|
|
11124
11693
|
</div>
|
|
11694
|
+
\${defaultModel ? '<div class="stat-card"><div class="stat-icon" style="background:var(--warn-bg)">\u2B50</div><div class="stat-label">' + t('default_model_label') + '</div><div class="stat-value mono" style="font-size:13px">' + escHtml(defaultModel) + '</div></div>' : ''}
|
|
11695
|
+
</div>
|
|
11696
|
+
<p class="section-desc">\${t('configure_providers_desc')}</p>
|
|
11697
|
+
<div style="margin-bottom:16px;text-align:right">
|
|
11698
|
+
<button class="btn btn-primary" onclick="showAddCustomProviderModal()" style="font-size:13px">
|
|
11699
|
+
\${t('add_custom_provider')}
|
|
11700
|
+
</button>
|
|
11125
11701
|
</div>
|
|
11126
|
-
<p class="section-desc">Configure LLM providers for your agents. Click a provider card to set or update the API key, model, and base URL. Changes are saved directly to your config file.</p>
|
|
11127
11702
|
<div class="provider-grid">\${cards}</div>
|
|
11128
11703
|
\${activeModelsSection}
|
|
11129
11704
|
\\\`);
|
|
11130
11705
|
}
|
|
11131
11706
|
|
|
11132
11707
|
let _editProviderId = null;
|
|
11708
|
+
|
|
11709
|
+
// --- Custom Provider Functions ---
|
|
11710
|
+
var _editCustomId = null;
|
|
11711
|
+
|
|
11712
|
+
function showAddCustomProviderModal() {
|
|
11713
|
+
_editCustomId = null;
|
|
11714
|
+
document.getElementById('custom-provider-title').textContent = t('custom_provider');
|
|
11715
|
+
document.getElementById('custom-name').value = '';
|
|
11716
|
+
document.getElementById('custom-api-key').value = '';
|
|
11717
|
+
document.getElementById('custom-model-id').value = '';
|
|
11718
|
+
document.getElementById('custom-base-url').value = '';
|
|
11719
|
+
document.getElementById('custom-description').value = '';
|
|
11720
|
+
showModal('modal-custom-provider');
|
|
11721
|
+
}
|
|
11722
|
+
|
|
11723
|
+
function showEditCustomProviderModal(id) {
|
|
11724
|
+
if (!_modelProviders) return;
|
|
11725
|
+
const p = _modelProviders.providers.find(pr => pr.id === id);
|
|
11726
|
+
if (!p || !p.isCustom) return;
|
|
11727
|
+
_editCustomId = id;
|
|
11728
|
+
document.getElementById('custom-provider-title').textContent = t('edit_custom_provider');
|
|
11729
|
+
document.getElementById('custom-name').value = p.name || '';
|
|
11730
|
+
document.getElementById('custom-api-key').value = '';
|
|
11731
|
+
document.getElementById('custom-model-id').value = p.modelId || p.modelHint || '';
|
|
11732
|
+
document.getElementById('custom-base-url').value = p.baseUrl || '';
|
|
11733
|
+
document.getElementById('custom-description').value = p.description || '';
|
|
11734
|
+
showModal('modal-custom-provider');
|
|
11735
|
+
}
|
|
11736
|
+
|
|
11737
|
+
async function saveCustomProvider() {
|
|
11738
|
+
var name = document.getElementById('custom-name').value.trim();
|
|
11739
|
+
var apiKey = document.getElementById('custom-api-key').value.trim();
|
|
11740
|
+
var modelId = document.getElementById('custom-model-id').value.trim();
|
|
11741
|
+
var baseUrl = document.getElementById('custom-base-url').value.trim();
|
|
11742
|
+
var description = document.getElementById('custom-description').value.trim();
|
|
11743
|
+
|
|
11744
|
+
if (!name) { toast(t('provider_name_required'), 'err'); return; }
|
|
11745
|
+
|
|
11746
|
+
var url, method;
|
|
11747
|
+
if (_editCustomId) {
|
|
11748
|
+
url = '/api/models/custom/' + encodeURIComponent(_editCustomId);
|
|
11749
|
+
method = 'PUT';
|
|
11750
|
+
} else {
|
|
11751
|
+
url = '/api/models/custom';
|
|
11752
|
+
method = 'POST';
|
|
11753
|
+
}
|
|
11754
|
+
|
|
11755
|
+
var body = { name: name };
|
|
11756
|
+
if (apiKey) body.apiKey = apiKey;
|
|
11757
|
+
if (modelId) body.modelId = modelId;
|
|
11758
|
+
if (baseUrl) body.baseUrl = baseUrl;
|
|
11759
|
+
if (description) body.description = description;
|
|
11760
|
+
|
|
11761
|
+
try {
|
|
11762
|
+
var resp = await fetch(url, { method: method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
11763
|
+
var data = await resp.json();
|
|
11764
|
+
if (data.success) {
|
|
11765
|
+
hideModal('modal-custom-provider');
|
|
11766
|
+
toast(_editCustomId ? t('custom_provider_updated') : t('custom_provider_added'), 'ok');
|
|
11767
|
+
loadModels();
|
|
11768
|
+
} else {
|
|
11769
|
+
toast(data.message || 'Failed', 'err');
|
|
11770
|
+
}
|
|
11771
|
+
} catch (e) {
|
|
11772
|
+
toast('Request failed: ' + e.message, 'err');
|
|
11773
|
+
}
|
|
11774
|
+
}
|
|
11775
|
+
|
|
11776
|
+
function deleteCustomProvider(id, name) {
|
|
11777
|
+
if (!confirm(t('confirm_delete_custom').replace('{name}', name || id))) return;
|
|
11778
|
+
fetch('/api/models/custom/' + encodeURIComponent(id), { method: 'DELETE' })
|
|
11779
|
+
.then(function(r) { return r.json(); })
|
|
11780
|
+
.then(function(data) {
|
|
11781
|
+
if (data.success) {
|
|
11782
|
+
toast(t('custom_provider_deleted'), 'ok');
|
|
11783
|
+
loadModels();
|
|
11784
|
+
} else {
|
|
11785
|
+
toast(data.message || 'Failed to delete', 'err');
|
|
11786
|
+
}
|
|
11787
|
+
})
|
|
11788
|
+
.catch(function(e) { toast('Delete failed: ' + e.message, 'err'); });
|
|
11789
|
+
}
|
|
11790
|
+
|
|
11133
11791
|
function showModelConfigModal(id) {
|
|
11134
11792
|
const p = (_modelProviders?.providers || []).find(x => x.id === id);
|
|
11135
|
-
if (!p) { toast('
|
|
11793
|
+
if (!p) { toast(t('provider_not_found'), 'err'); return; }
|
|
11136
11794
|
_editProviderId = id;
|
|
11137
11795
|
$('#model-name').textContent = p.name;
|
|
11138
|
-
$('#model-icon').textContent = p.id
|
|
11796
|
+
$('#model-icon').textContent = getProviderIcon(p.id);
|
|
11139
11797
|
$('#model-api-key').value = '';
|
|
11140
|
-
$('#model-api-key').placeholder = p.apiKeyHint || '
|
|
11798
|
+
$('#model-api-key').placeholder = p.apiKeyHint || t('enter_api_key');
|
|
11141
11799
|
$('#model-model-id').value = p.modelId || '';
|
|
11142
|
-
$('#model-model-id').placeholder = p.modelHint || '
|
|
11800
|
+
$('#model-model-id').placeholder = p.modelHint || t('enter_model_id');
|
|
11143
11801
|
$('#model-base-url').value = p.baseUrl || '';
|
|
11144
|
-
$('#model-base-url').placeholder = p.baseUrlHint || '
|
|
11145
|
-
$('#model-current-key').textContent = p.apiKeyHasValue ? '
|
|
11802
|
+
$('#model-base-url').placeholder = p.baseUrlHint || t('default_url');
|
|
11803
|
+
$('#model-current-key').textContent = p.apiKeyHasValue ? t('current_key') + p.apiKey : t('no_api_key');
|
|
11804
|
+
// Show multi-model list if available
|
|
11805
|
+
const modelsListEl = document.getElementById('model-multi-list');
|
|
11806
|
+
if (modelsListEl) {
|
|
11807
|
+
if (p.models && p.models.length > 0) {
|
|
11808
|
+
const defaultModel = _modelProviders?.defaultModel || '';
|
|
11809
|
+
let html = '<div style="font-size:12px;color:var(--text2);margin-bottom:8px;font-weight:600">' + t('model_count') + ': ' + p.models.length + '</div>';
|
|
11810
|
+
html += '<div style="max-height:160px;overflow-y:auto">';
|
|
11811
|
+
p.models.forEach(m => {
|
|
11812
|
+
const isDef = defaultModel === (m.id || '');
|
|
11813
|
+
html += '<div style="display:flex;align-items:center;gap:8px;padding:4px 0;font-size:12px;border-bottom:1px solid var(--border)">' +
|
|
11814
|
+
'<span class="mono" style="flex:1">' + escHtml(m.name || m.id || '') + '</span>' +
|
|
11815
|
+
'<span class="mono" style="color:var(--text3);font-size:11px">' + escHtml(m.id || '') + '</span>' +
|
|
11816
|
+
(isDef ? '<span class="badge badge-warn" style="font-size:10px">\u2B50</span>' : '') +
|
|
11817
|
+
'</div>';
|
|
11818
|
+
});
|
|
11819
|
+
html += '</div>';
|
|
11820
|
+
modelsListEl.innerHTML = html;
|
|
11821
|
+
modelsListEl.style.display = '';
|
|
11822
|
+
} else {
|
|
11823
|
+
modelsListEl.innerHTML = '';
|
|
11824
|
+
modelsListEl.style.display = 'none';
|
|
11825
|
+
}
|
|
11826
|
+
}
|
|
11146
11827
|
showModal('modal-model-config');
|
|
11147
11828
|
}
|
|
11148
11829
|
async function saveModelConfig() {
|
|
11149
11830
|
const apiKey = $('#model-api-key').value.trim();
|
|
11150
11831
|
const modelId = $('#model-model-id').value.trim();
|
|
11151
11832
|
const baseUrl = $('#model-base-url').value.trim();
|
|
11152
|
-
if (!apiKey && !modelId) { toast('
|
|
11833
|
+
if (!apiKey && !modelId) { toast(t('enter_api_key_or_model'), 'warn'); return; }
|
|
11153
11834
|
hideModal('modal-model-config');
|
|
11154
|
-
toast('
|
|
11835
|
+
toast(t('saving_config'), 'info');
|
|
11155
11836
|
const r = await api('/models/' + encodeURIComponent(_editProviderId), { method: 'PUT', body: JSON.stringify({ apiKey, modelId, baseUrl }) });
|
|
11156
|
-
if (r.success) { toast(r.message || '
|
|
11157
|
-
else { toast(r.message || r.error || '
|
|
11837
|
+
if (r.success) { toast(r.message || t('config_saved'), 'ok'); loadModels(); }
|
|
11838
|
+
else { toast(r.message || r.error || t('failed_save'), 'err'); }
|
|
11158
11839
|
}
|
|
11159
11840
|
async function deleteModelProvider(providerId) {
|
|
11160
|
-
if (!confirm('
|
|
11841
|
+
if (!confirm(t('confirm_delete_provider') + providerId + '"?')) return;
|
|
11161
11842
|
const r = await api('/models/' + encodeURIComponent(providerId), { method: 'DELETE' });
|
|
11162
|
-
if (r.success) { toast('
|
|
11163
|
-
else { toast(r.message || r.error || '
|
|
11843
|
+
if (r.success) { toast(t('provider_deleted'), 'ok'); loadModels(); }
|
|
11844
|
+
else { toast(r.message || r.error || t('delete_failed'), 'err'); }
|
|
11164
11845
|
}
|
|
11165
11846
|
|
|
11166
11847
|
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
@@ -11191,7 +11872,7 @@ function formatUptime(seconds) {
|
|
|
11191
11872
|
|
|
11192
11873
|
async function loadSettings() {
|
|
11193
11874
|
const el = $('#settings-content');
|
|
11194
|
-
html(el, '<div class="loading-mask"><div class="spinner"></div>
|
|
11875
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_settings') + '</div>');
|
|
11195
11876
|
|
|
11196
11877
|
const settings = await api('/settings');
|
|
11197
11878
|
if (settings.error) {
|
|
@@ -11203,11 +11884,11 @@ async function loadSettings() {
|
|
|
11203
11884
|
|
|
11204
11885
|
// Render settings tabs nav
|
|
11205
11886
|
html('#settings-tabs', \\\`
|
|
11206
|
-
<button class="filter-btn \${_settingsTab==='connection'?'active':''}" onclick="_settingsTab='connection';renderSettingsTab()"
|
|
11207
|
-
<button class="filter-btn \${_settingsTab==='friends'?'active':''}" onclick="_settingsTab='friends';renderSettingsTab()"
|
|
11208
|
-
<button class="filter-btn \${_settingsTab==='security'?'active':''}" onclick="_settingsTab='security';renderSettingsTab()"
|
|
11209
|
-
<button class="filter-btn \${_settingsTab==='advanced'?'active':''}" onclick="_settingsTab='advanced';renderSettingsTab()"
|
|
11210
|
-
<button class="filter-btn \${_settingsTab==='json'?'active':''}" onclick="_settingsTab='json';renderSettingsTab()"
|
|
11887
|
+
<button class="filter-btn \${_settingsTab==='connection'?'active':''}" onclick="_settingsTab='connection';renderSettingsTab()">\${t('tab_connection')}</button>
|
|
11888
|
+
<button class="filter-btn \${_settingsTab==='friends'?'active':''}" onclick="_settingsTab='friends';renderSettingsTab()">\${t('tab_friends')}</button>
|
|
11889
|
+
<button class="filter-btn \${_settingsTab==='security'?'active':''}" onclick="_settingsTab='security';renderSettingsTab()">\${t('tab_security')}</button>
|
|
11890
|
+
<button class="filter-btn \${_settingsTab==='advanced'?'active':''}" onclick="_settingsTab='advanced';renderSettingsTab()">\${t('tab_advanced')}</button>
|
|
11891
|
+
<button class="filter-btn \${_settingsTab==='json'?'active':''}" onclick="_settingsTab='json';renderSettingsTab()">\${t('tab_json')}</button>
|
|
11211
11892
|
\\\`);
|
|
11212
11893
|
|
|
11213
11894
|
renderSettingsTab();
|
|
@@ -11216,11 +11897,11 @@ async function loadSettings() {
|
|
|
11216
11897
|
function renderSettingsTab() {
|
|
11217
11898
|
// Update tab buttons
|
|
11218
11899
|
html('#settings-tabs', \\\`
|
|
11219
|
-
<button class="filter-btn \${_settingsTab==='connection'?'active':''}" onclick="_settingsTab='connection';renderSettingsTab()"
|
|
11220
|
-
<button class="filter-btn \${_settingsTab==='friends'?'active':''}" onclick="_settingsTab='friends';renderSettingsTab()"
|
|
11221
|
-
<button class="filter-btn \${_settingsTab==='security'?'active':''}" onclick="_settingsTab='security';renderSettingsTab()"
|
|
11222
|
-
<button class="filter-btn \${_settingsTab==='advanced'?'active':''}" onclick="_settingsTab='advanced';renderSettingsTab()"
|
|
11223
|
-
<button class="filter-btn \${_settingsTab==='json'?'active':''}" onclick="_settingsTab='json';renderSettingsTab()"
|
|
11900
|
+
<button class="filter-btn \${_settingsTab==='connection'?'active':''}" onclick="_settingsTab='connection';renderSettingsTab()">\${t('tab_connection')}</button>
|
|
11901
|
+
<button class="filter-btn \${_settingsTab==='friends'?'active':''}" onclick="_settingsTab='friends';renderSettingsTab()">\${t('tab_friends')}</button>
|
|
11902
|
+
<button class="filter-btn \${_settingsTab==='security'?'active':''}" onclick="_settingsTab='security';renderSettingsTab()">\${t('tab_security')}</button>
|
|
11903
|
+
<button class="filter-btn \${_settingsTab==='advanced'?'active':''}" onclick="_settingsTab='advanced';renderSettingsTab()">\${t('tab_advanced')}</button>
|
|
11904
|
+
<button class="filter-btn \${_settingsTab==='json'?'active':''}" onclick="_settingsTab='json';renderSettingsTab()">\${t('tab_json')}</button>
|
|
11224
11905
|
\\\`);
|
|
11225
11906
|
|
|
11226
11907
|
switch (_settingsTab) {
|
|
@@ -11233,7 +11914,7 @@ function renderSettingsTab() {
|
|
|
11233
11914
|
}
|
|
11234
11915
|
|
|
11235
11916
|
function sectionSaveBtn(section, id) {
|
|
11236
|
-
return \\\`<button class="btn btn-primary btn-sm" id="btn-save-\${id}" onclick="saveSettingsSection('\${section}', '\${id}')"
|
|
11917
|
+
return \\\`<button class="btn btn-primary btn-sm" id="btn-save-\${id}" onclick="saveSettingsSection('\${section}', '\${id}')">\${t('save')}</button>
|
|
11237
11918
|
<span id="status-\${id}" style="font-size:12px;color:var(--text3);margin-left:8px"></span>\\\`;
|
|
11238
11919
|
}
|
|
11239
11920
|
|
|
@@ -11243,51 +11924,51 @@ function renderSettingsConnection() {
|
|
|
11243
11924
|
const el = $('#settings-content');
|
|
11244
11925
|
|
|
11245
11926
|
html(el, \\\`
|
|
11246
|
-
<p class="section-desc"
|
|
11927
|
+
<p class="section-desc">\${t('conn_desc')}</p>
|
|
11247
11928
|
|
|
11248
11929
|
<div class="card">
|
|
11249
11930
|
<div class="card-header">
|
|
11250
|
-
<div class="card-title"
|
|
11251
|
-
<span class="badge badge-\${s.connected ? 'ok' : 'danger'}">\${s.connected ? '\u25CF
|
|
11931
|
+
<div class="card-title">\${t('server_connection')}</div>
|
|
11932
|
+
<span class="badge badge-\${s.connected ? 'ok' : 'danger'}">\${s.connected ? '\u25CF ' + t('connected') : '\u25CB ' + t('disconnected')}</span>
|
|
11252
11933
|
</div>
|
|
11253
11934
|
<div class="form-group">
|
|
11254
|
-
<label
|
|
11935
|
+
<label>\${t('server_url_label')}</label>
|
|
11255
11936
|
<div style="display:flex;gap:8px;align-items:start">
|
|
11256
11937
|
<div style="flex:1">
|
|
11257
11938
|
<div class="input-prefix">
|
|
11258
11939
|
<span class="prefix">\u{1F310}</span>
|
|
11259
11940
|
<input type="url" id="set-server-url" value="\${escHtml(s.serverUrl || '')}" placeholder="https://aicq.online">
|
|
11260
11941
|
</div>
|
|
11261
|
-
<div class="hint"
|
|
11942
|
+
<div class="hint">\${t('server_url_hint')}</div>
|
|
11262
11943
|
</div>
|
|
11263
|
-
<button class="btn btn-ok btn-sm" id="btn-test-conn" onclick="testConnection()" style="white-space:nowrap;margin-top:1px">\u{1F50D}
|
|
11944
|
+
<button class="btn btn-ok btn-sm" id="btn-test-conn" onclick="testConnection()" style="white-space:nowrap;margin-top:1px">\u{1F50D} \${t('test')}</button>
|
|
11264
11945
|
</div>
|
|
11265
11946
|
<div id="conn-test-result" style="margin-top:8px"></div>
|
|
11266
11947
|
</div>
|
|
11267
11948
|
|
|
11268
11949
|
<div class="form-row">
|
|
11269
11950
|
<div class="form-group">
|
|
11270
|
-
<label
|
|
11951
|
+
<label>\${t('conn_timeout')}</label>
|
|
11271
11952
|
<input type="number" id="set-connection-timeout" value="\${s.connectionTimeout || 30}" min="5" max="120" placeholder="30">
|
|
11272
|
-
<div class="hint"
|
|
11953
|
+
<div class="hint">\${t('conn_timeout_hint')}</div>
|
|
11273
11954
|
</div>
|
|
11274
11955
|
<div class="form-group">
|
|
11275
|
-
<label
|
|
11956
|
+
<label>\${t('ws_auto_reconnect')}</label>
|
|
11276
11957
|
<div style="display:flex;align-items:center;gap:10px;margin-top:6px">
|
|
11277
11958
|
<label class="toggle-label">
|
|
11278
11959
|
<input type="checkbox" id="set-ws-auto-reconnect" \${s.wsAutoReconnect ? 'checked' : ''}>
|
|
11279
11960
|
<span class="toggle-slider"></span>
|
|
11280
|
-
<span
|
|
11961
|
+
<span>\${t('auto_reconnect_label')}</span>
|
|
11281
11962
|
</label>
|
|
11282
11963
|
</div>
|
|
11283
|
-
<div class="hint"
|
|
11964
|
+
<div class="hint">\${t('auto_reconnect_hint')}</div>
|
|
11284
11965
|
</div>
|
|
11285
11966
|
</div>
|
|
11286
11967
|
|
|
11287
11968
|
<div class="form-group">
|
|
11288
|
-
<label
|
|
11969
|
+
<label>\${t('ws_reconnect_interval')}</label>
|
|
11289
11970
|
<input type="number" id="set-ws-reconnect-interval" value="\${s.wsReconnectInterval || 60}" min="5" max="600" placeholder="60">
|
|
11290
|
-
<div class="hint"
|
|
11971
|
+
<div class="hint">\${t('ws_reconnect_hint')}</div>
|
|
11291
11972
|
</div>
|
|
11292
11973
|
|
|
11293
11974
|
<div style="display:flex;justify-content:flex-end;padding-top:8px;border-top:1px solid var(--border);margin-top:8px">
|
|
@@ -11296,11 +11977,11 @@ function renderSettingsConnection() {
|
|
|
11296
11977
|
</div>
|
|
11297
11978
|
|
|
11298
11979
|
<div class="card">
|
|
11299
|
-
<div class="card-header"><div class="card-title"
|
|
11300
|
-
<div class="detail-row"><div class="detail-key"
|
|
11301
|
-
<div class="detail-row"><div class="detail-key"
|
|
11302
|
-
<div class="detail-row"><div class="detail-key"
|
|
11303
|
-
<div class="detail-row"><div class="detail-key"
|
|
11980
|
+
<div class="card-header"><div class="card-title">\${t('config_file')}</div></div>
|
|
11981
|
+
<div class="detail-row"><div class="detail-key">\${t('source')}</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(s.configPath || '')}')">\${escHtml(s.configPath || t('not_found'))} \u{1F4CB}</div></div>
|
|
11982
|
+
<div class="detail-row"><div class="detail-key">\${t('plugin_version')}</div><div class="detail-val">1.1.1</div></div>
|
|
11983
|
+
<div class="detail-row"><div class="detail-key">\${t('mgmt_ui')}</div><div class="detail-val" id="mgmt-url-display" style="cursor:pointer" onclick="copyText(document.getElementById('mgmt-url-display')?.textContent || '')"></div></div>
|
|
11984
|
+
<div class="detail-row"><div class="detail-key">\${t('uptime')}</div><div class="detail-val">\${formatUptime(s.uptimeSeconds)}</div></div>
|
|
11304
11985
|
</div>
|
|
11305
11986
|
\\\`);
|
|
11306
11987
|
}
|
|
@@ -11310,37 +11991,37 @@ async function testConnection() {
|
|
|
11310
11991
|
const resultEl = $('#conn-test-result');
|
|
11311
11992
|
const url = $('#set-server-url')?.value?.trim() || _settingsData.serverUrl;
|
|
11312
11993
|
|
|
11313
|
-
if (!url) { toast('
|
|
11994
|
+
if (!url) { toast(t('enter_server_url'), 'warn'); return; }
|
|
11314
11995
|
|
|
11315
|
-
if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner" style="width:14px;height:14px;border-width:2px;margin:0"></span>
|
|
11316
|
-
if (resultEl) html(resultEl, '<div style="font-size:12px;color:var(--text3);display:flex;align-items:center;gap:6px"><span class="spinner" style="width:12px;height:12px;border-width:2px"></span>
|
|
11996
|
+
if (btn) { btn.disabled = true; btn.innerHTML = '<span class="spinner" style="width:14px;height:14px;border-width:2px;margin:0"></span> ' + t('testing'); }
|
|
11997
|
+
if (resultEl) html(resultEl, '<div style="font-size:12px;color:var(--text3);display:flex;align-items:center;gap:6px"><span class="spinner" style="width:12px;height:12px;border-width:2px"></span> ' + t('testing_conn_to') + escHtml(url) + '...</div>');
|
|
11317
11998
|
|
|
11318
11999
|
const r = await api('/settings/test-connection', {
|
|
11319
12000
|
method: 'POST',
|
|
11320
12001
|
body: JSON.stringify({ serverUrl: url, timeout: 10000 }),
|
|
11321
12002
|
});
|
|
11322
12003
|
|
|
11323
|
-
if (btn) { btn.disabled = false; btn.innerHTML = '\u{1F50D}
|
|
12004
|
+
if (btn) { btn.disabled = false; btn.innerHTML = '\u{1F50D} ' + t('test'); }
|
|
11324
12005
|
|
|
11325
12006
|
if (r.success) {
|
|
11326
12007
|
const latencyBadge = r.latency < 200 ? '<span class="badge badge-ok">' + r.latency + 'ms</span>' : r.latency < 1000 ? '<span class="badge badge-warn">' + r.latency + 'ms</span>' : '<span class="badge badge-danger">' + r.latency + 'ms</span>';
|
|
11327
12008
|
if (resultEl) html(resultEl, \\\`
|
|
11328
12009
|
<div style="display:flex;align-items:center;gap:10px;font-size:12px;color:var(--ok)">
|
|
11329
|
-
<span class="dot dot-ok"></span>
|
|
12010
|
+
<span class="dot dot-ok"></span> \${t('conn_ok')} \${latencyBadge}
|
|
11330
12011
|
\${r.serverInfo?.version ? '<span class="tag">v' + escHtml(r.serverInfo.version) + '</span>' : ''}
|
|
11331
12012
|
</div>
|
|
11332
12013
|
\\\`);
|
|
11333
|
-
toast('
|
|
12014
|
+
toast(t('conn_ok_latency') + r.latency + 'ms', 'ok');
|
|
11334
12015
|
} else {
|
|
11335
12016
|
const cls = r.status === 'timeout' ? 'warn' : 'danger';
|
|
11336
12017
|
const icon = r.status === 'timeout' ? '\u23F1\uFE0F' : '\u274C';
|
|
11337
12018
|
if (resultEl) html(resultEl, \\\`
|
|
11338
12019
|
<div style="font-size:12px;color:var(--\${cls});display:flex;align-items:center;gap:8px">
|
|
11339
|
-
\${icon} \${escHtml(r.message || '
|
|
12020
|
+
\${icon} \${escHtml(r.message || t('conn_failed'))}
|
|
11340
12021
|
<span class="badge badge-ghost">\${r.latency}ms</span>
|
|
11341
12022
|
</div>
|
|
11342
12023
|
\\\`);
|
|
11343
|
-
toast(r.message || '
|
|
12024
|
+
toast(r.message || t('conn_failed'), 'err');
|
|
11344
12025
|
}
|
|
11345
12026
|
}
|
|
11346
12027
|
|
|
@@ -11350,58 +12031,58 @@ function renderSettingsFriends() {
|
|
|
11350
12031
|
const el = $('#settings-content');
|
|
11351
12032
|
|
|
11352
12033
|
html(el, \\\`
|
|
11353
|
-
<p class="section-desc"
|
|
12034
|
+
<p class="section-desc">\${t('friends_tab_desc')}</p>
|
|
11354
12035
|
|
|
11355
12036
|
<div class="stats-grid" style="margin-bottom:20px">
|
|
11356
12037
|
<div class="stat-card">
|
|
11357
12038
|
<div class="stat-icon" style="background:var(--ok-bg)">\u{1F465}</div>
|
|
11358
|
-
<div class="stat-label"
|
|
12039
|
+
<div class="stat-label">\${t('friends')}</div>
|
|
11359
12040
|
<div class="stat-value">\${s.friendCount || 0}</div>
|
|
11360
|
-
<div class="stat-sub"
|
|
12041
|
+
<div class="stat-sub">\${t('of_max')}\${s.maxFriends || 200}</div>
|
|
11361
12042
|
</div>
|
|
11362
12043
|
<div class="stat-card">
|
|
11363
12044
|
<div class="stat-icon" style="background:var(--info-bg)">\u{1F517}</div>
|
|
11364
|
-
<div class="stat-label"
|
|
12045
|
+
<div class="stat-label">\${t('sessions')}</div>
|
|
11365
12046
|
<div class="stat-value">\${s.sessionCount || 0}</div>
|
|
11366
|
-
<div class="stat-sub"
|
|
12047
|
+
<div class="stat-sub">\${t('encrypted_sessions')}</div>
|
|
11367
12048
|
</div>
|
|
11368
12049
|
</div>
|
|
11369
12050
|
|
|
11370
12051
|
<div class="card">
|
|
11371
|
-
<div class="card-header"><div class="card-title">\u{1F465}
|
|
12052
|
+
<div class="card-header"><div class="card-title">\u{1F465} \${t('max_friends')} & \${t('permissions')}</div></div>
|
|
11372
12053
|
<div class="form-row">
|
|
11373
12054
|
<div class="form-group">
|
|
11374
|
-
<label
|
|
12055
|
+
<label>\${t('max_friends')}</label>
|
|
11375
12056
|
<input type="number" id="set-max-friends" value="\${s.maxFriends || 200}" min="1" max="10000" placeholder="200">
|
|
11376
|
-
<div class="hint"
|
|
12057
|
+
<div class="hint">\${t('max_friends')} (1\u201310,000)</div>
|
|
11377
12058
|
</div>
|
|
11378
12059
|
<div class="form-group">
|
|
11379
|
-
<label
|
|
12060
|
+
<label>\${t('auto_accept')}</label>
|
|
11380
12061
|
<div style="display:flex;align-items:center;gap:10px;margin-top:6px">
|
|
11381
12062
|
<label class="toggle-label">
|
|
11382
12063
|
<input type="checkbox" id="set-auto-accept" \${s.autoAcceptFriends ? 'checked' : ''}>
|
|
11383
12064
|
<span class="toggle-slider"></span>
|
|
11384
|
-
<span
|
|
12065
|
+
<span>\${t('auto_accept_label')}</span>
|
|
11385
12066
|
</label>
|
|
11386
12067
|
</div>
|
|
11387
|
-
<div class="hint"
|
|
12068
|
+
<div class="hint">\${t('auto_accept_hint')}</div>
|
|
11388
12069
|
</div>
|
|
11389
12070
|
</div>
|
|
11390
12071
|
<div class="form-group">
|
|
11391
|
-
<label
|
|
12072
|
+
<label>\${t('default_perms')}</label>
|
|
11392
12073
|
<div style="display:flex;gap:16px;margin-top:6px;flex-wrap:wrap">
|
|
11393
12074
|
<label class="toggle-label">
|
|
11394
12075
|
<input type="checkbox" id="set-perm-chat" \${(s.defaultPermissions || []).includes('chat') ? 'checked' : ''}>
|
|
11395
12076
|
<span class="toggle-slider"></span>
|
|
11396
|
-
<span
|
|
12077
|
+
<span>\${t('chat_perm')}</span>
|
|
11397
12078
|
</label>
|
|
11398
12079
|
<label class="toggle-label">
|
|
11399
12080
|
<input type="checkbox" id="set-perm-exec" \${(s.defaultPermissions || []).includes('exec') ? 'checked' : ''}>
|
|
11400
12081
|
<span class="toggle-slider"></span>
|
|
11401
|
-
<span
|
|
12082
|
+
<span>\${t('exec_perm')}</span>
|
|
11402
12083
|
</label>
|
|
11403
12084
|
</div>
|
|
11404
|
-
<div class="hint"
|
|
12085
|
+
<div class="hint">\${t('default_perms_hint')}</div>
|
|
11405
12086
|
</div>
|
|
11406
12087
|
<div style="display:flex;justify-content:flex-end;padding-top:8px;border-top:1px solid var(--border);margin-top:8px">
|
|
11407
12088
|
\${sectionSaveBtn('friends', 'friends')}
|
|
@@ -11409,11 +12090,11 @@ function renderSettingsFriends() {
|
|
|
11409
12090
|
</div>
|
|
11410
12091
|
|
|
11411
12092
|
<div class="card">
|
|
11412
|
-
<div class="card-header"><div class="card-title"
|
|
12093
|
+
<div class="card-header"><div class="card-title">\${t('temp_numbers')}</div></div>
|
|
11413
12094
|
<div class="form-group">
|
|
11414
|
-
<label
|
|
12095
|
+
<label>\${t('temp_expiry')}</label>
|
|
11415
12096
|
<input type="number" id="set-temp-expiry" value="\${s.tempNumberExpiry || 300}" min="60" max="3600" placeholder="300">
|
|
11416
|
-
<div class="hint"
|
|
12097
|
+
<div class="hint">\${t('temp_expiry_hint')}</div>
|
|
11417
12098
|
</div>
|
|
11418
12099
|
<div style="display:flex;justify-content:flex-end;padding-top:8px;border-top:1px solid var(--border);margin-top:8px">
|
|
11419
12100
|
\${sectionSaveBtn('temp', 'temp')}
|
|
@@ -11428,36 +12109,36 @@ function renderSettingsSecurity() {
|
|
|
11428
12109
|
const el = $('#settings-content');
|
|
11429
12110
|
|
|
11430
12111
|
html(el, \\\`
|
|
11431
|
-
<p class="section-desc"
|
|
12112
|
+
<p class="section-desc">\${t('sec_desc')}</p>
|
|
11432
12113
|
|
|
11433
12114
|
<div class="card">
|
|
11434
|
-
<div class="card-header"><div class="card-title"
|
|
11435
|
-
<div class="detail-row"><div class="detail-key"
|
|
11436
|
-
<div class="detail-row"><div class="detail-key"
|
|
12115
|
+
<div class="card-header"><div class="card-title">\${t('agent_identity')}</div></div>
|
|
12116
|
+
<div class="detail-row"><div class="detail-key">\${t('agent_id')}</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(s.agentId)}')">\${escHtml(s.agentId)} \u{1F4CB}</div></div>
|
|
12117
|
+
<div class="detail-row"><div class="detail-key">\${t('public_key_fp')}</div><div class="detail-val mono">\${escHtml(s.publicKeyFingerprint || '\u2014')}</div></div>
|
|
11437
12118
|
<div style="padding-top:12px;display:flex;gap:8px">
|
|
11438
|
-
<button class="btn btn-danger btn-sm" onclick="showResetIdentityModal()"
|
|
11439
|
-
<span style="font-size:12px;color:var(--text3);display:flex;align-items:center"
|
|
12119
|
+
<button class="btn btn-danger btn-sm" onclick="showResetIdentityModal()">\${t('reset_identity')}</button>
|
|
12120
|
+
<span style="font-size:12px;color:var(--text3);display:flex;align-items:center">\${t('reset_identity_warn')}</span>
|
|
11440
12121
|
</div>
|
|
11441
12122
|
</div>
|
|
11442
12123
|
|
|
11443
12124
|
<div class="card">
|
|
11444
|
-
<div class="card-header"><div class="card-title"
|
|
12125
|
+
<div class="card-header"><div class="card-title">\${t('p2p_encryption')}</div></div>
|
|
11445
12126
|
<div class="form-row">
|
|
11446
12127
|
<div class="form-group">
|
|
11447
|
-
<label
|
|
12128
|
+
<label>\${t('enable_p2p')}</label>
|
|
11448
12129
|
<div style="display:flex;align-items:center;gap:10px;margin-top:6px">
|
|
11449
12130
|
<label class="toggle-label">
|
|
11450
12131
|
<input type="checkbox" id="set-enable-p2p" \${s.enableP2P ? 'checked' : ''}>
|
|
11451
12132
|
<span class="toggle-slider"></span>
|
|
11452
|
-
<span
|
|
12133
|
+
<span>\${t('allow_p2p')}</span>
|
|
11453
12134
|
</label>
|
|
11454
12135
|
</div>
|
|
11455
|
-
<div class="hint"
|
|
12136
|
+
<div class="hint">\${t('enable_p2p_hint')}</div>
|
|
11456
12137
|
</div>
|
|
11457
12138
|
<div class="form-group">
|
|
11458
|
-
<label
|
|
12139
|
+
<label>\${t('hs_timeout')}</label>
|
|
11459
12140
|
<input type="number" id="set-handshake-timeout" value="\${s.handshakeTimeout || 60}" min="10" max="300" placeholder="60">
|
|
11460
|
-
<div class="hint"
|
|
12141
|
+
<div class="hint">\${t('hs_timeout_hint')}</div>
|
|
11461
12142
|
</div>
|
|
11462
12143
|
</div>
|
|
11463
12144
|
<div style="display:flex;justify-content:flex-end;padding-top:8px;border-top:1px solid var(--border);margin-top:8px">
|
|
@@ -11473,24 +12154,24 @@ function renderSettingsAdvanced() {
|
|
|
11473
12154
|
const el = $('#settings-content');
|
|
11474
12155
|
|
|
11475
12156
|
html(el, \\\`
|
|
11476
|
-
<p class="section-desc"
|
|
12157
|
+
<p class="section-desc">\${t('adv_desc')}</p>
|
|
11477
12158
|
|
|
11478
12159
|
<div class="card">
|
|
11479
|
-
<div class="card-header"><div class="card-title"
|
|
12160
|
+
<div class="card-header"><div class="card-title">\${t('file_transfer')}</div></div>
|
|
11480
12161
|
<div class="form-row">
|
|
11481
12162
|
<div class="form-group">
|
|
11482
|
-
<label
|
|
12163
|
+
<label>\${t('enable_ft')}</label>
|
|
11483
12164
|
<div style="display:flex;align-items:center;gap:10px;margin-top:6px">
|
|
11484
12165
|
<label class="toggle-label">
|
|
11485
12166
|
<input type="checkbox" id="set-enable-ft" \${s.enableFileTransfer ? 'checked' : ''}>
|
|
11486
12167
|
<span class="toggle-slider"></span>
|
|
11487
|
-
<span
|
|
12168
|
+
<span>\${t('allow_ft')}</span>
|
|
11488
12169
|
</label>
|
|
11489
12170
|
</div>
|
|
11490
|
-
<div class="hint"
|
|
12171
|
+
<div class="hint">\${t('enable_ft_hint')}</div>
|
|
11491
12172
|
</div>
|
|
11492
12173
|
<div class="form-group">
|
|
11493
|
-
<label
|
|
12174
|
+
<label>\${t('max_file_size')}</label>
|
|
11494
12175
|
<select id="set-max-file-size">
|
|
11495
12176
|
<option value="10485760" \${s.maxFileSize <= 10485760 ? 'selected' : ''}>10 MB</option>
|
|
11496
12177
|
<option value="52428800" \${s.maxFileSize > 10485760 && s.maxFileSize <= 52428800 ? 'selected' : ''}>50 MB</option>
|
|
@@ -11498,7 +12179,7 @@ function renderSettingsAdvanced() {
|
|
|
11498
12179
|
<option value="524288000" \${s.maxFileSize > 104857600 && s.maxFileSize <= 524288000 ? 'selected' : ''}>500 MB</option>
|
|
11499
12180
|
<option value="1073741824" \${s.maxFileSize > 524288000 ? 'selected' : ''}>1 GB</option>
|
|
11500
12181
|
</select>
|
|
11501
|
-
<div class="hint"
|
|
12182
|
+
<div class="hint">\${t('max_file_size_hint')}\${formatBytes(s.maxFileSize)}.</div>
|
|
11502
12183
|
</div>
|
|
11503
12184
|
</div>
|
|
11504
12185
|
<div style="display:flex;justify-content:flex-end;padding-top:8px;border-top:1px solid var(--border);margin-top:8px">
|
|
@@ -11507,17 +12188,17 @@ function renderSettingsAdvanced() {
|
|
|
11507
12188
|
</div>
|
|
11508
12189
|
|
|
11509
12190
|
<div class="card">
|
|
11510
|
-
<div class="card-header"><div class="card-title"
|
|
12191
|
+
<div class="card-header"><div class="card-title">\${t('logging')}</div></div>
|
|
11511
12192
|
<div class="form-group">
|
|
11512
|
-
<label
|
|
12193
|
+
<label>\${t('log_level')}</label>
|
|
11513
12194
|
<select id="set-log-level" style="max-width:300px">
|
|
11514
|
-
<option value="debug" \${s.logLevel === 'debug' ? 'selected' : ''}
|
|
11515
|
-
<option value="info" \${s.logLevel === 'info' ? 'selected' : ''}
|
|
11516
|
-
<option value="warn" \${s.logLevel === 'warn' ? 'selected' : ''}
|
|
11517
|
-
<option value="error" \${s.logLevel === 'error' ? 'selected' : ''}
|
|
11518
|
-
<option value="none" \${s.logLevel === 'none' ? 'selected' : ''}
|
|
12195
|
+
<option value="debug" \${s.logLevel === 'debug' ? 'selected' : ''}>\${t('log_debug')}</option>
|
|
12196
|
+
<option value="info" \${s.logLevel === 'info' ? 'selected' : ''}>\${t('log_info')}</option>
|
|
12197
|
+
<option value="warn" \${s.logLevel === 'warn' ? 'selected' : ''}>\${t('log_warn')}</option>
|
|
12198
|
+
<option value="error" \${s.logLevel === 'error' ? 'selected' : ''}>\${t('log_error')}</option>
|
|
12199
|
+
<option value="none" \${s.logLevel === 'none' ? 'selected' : ''}>\${t('log_none')}</option>
|
|
11519
12200
|
</select>
|
|
11520
|
-
<div class="hint"
|
|
12201
|
+
<div class="hint">\${t('log_level_hint')}</div>
|
|
11521
12202
|
</div>
|
|
11522
12203
|
<div style="display:flex;justify-content:flex-end;padding-top:8px;border-top:1px solid var(--border);margin-top:8px">
|
|
11523
12204
|
\${sectionSaveBtn('logging', 'log')}
|
|
@@ -11525,12 +12206,12 @@ function renderSettingsAdvanced() {
|
|
|
11525
12206
|
</div>
|
|
11526
12207
|
|
|
11527
12208
|
<div class="card">
|
|
11528
|
-
<div class="card-header"><div class="card-title"
|
|
12209
|
+
<div class="card-header"><div class="card-title">\${t('import_export')}</div></div>
|
|
11529
12210
|
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
|
11530
|
-
<button class="btn btn-default btn-sm" onclick="exportSettings()"
|
|
11531
|
-
<button class="btn btn-ok btn-sm" onclick="showImportSettingsModal()"
|
|
12211
|
+
<button class="btn btn-default btn-sm" onclick="exportSettings()">\${t('export_settings')}</button>
|
|
12212
|
+
<button class="btn btn-ok btn-sm" onclick="showImportSettingsModal()">\${t('import_settings')}</button>
|
|
11532
12213
|
</div>
|
|
11533
|
-
<div class="hint" style="margin-top:10px"
|
|
12214
|
+
<div class="hint" style="margin-top:10px">\${t('import_export_hint')}</div>
|
|
11534
12215
|
</div>
|
|
11535
12216
|
\\\`);
|
|
11536
12217
|
}
|
|
@@ -11539,7 +12220,7 @@ function renderSettingsAdvanced() {
|
|
|
11539
12220
|
async function saveSettingsSection(section, id) {
|
|
11540
12221
|
const btn = $('#btn-save-' + id);
|
|
11541
12222
|
const statusEl = $('#status-' + id);
|
|
11542
|
-
if (btn) { btn.disabled = true; btn.textContent = '
|
|
12223
|
+
if (btn) { btn.disabled = true; btn.textContent = t('saving'); }
|
|
11543
12224
|
if (statusEl) { statusEl.textContent = ''; statusEl.style.color = 'var(--text3)'; }
|
|
11544
12225
|
|
|
11545
12226
|
let data = {};
|
|
@@ -11587,17 +12268,17 @@ async function saveSettingsSection(section, id) {
|
|
|
11587
12268
|
body: JSON.stringify({ section, data }),
|
|
11588
12269
|
});
|
|
11589
12270
|
|
|
11590
|
-
if (btn) { btn.disabled = false; btn.textContent = '
|
|
12271
|
+
if (btn) { btn.disabled = false; btn.textContent = t('save'); }
|
|
11591
12272
|
|
|
11592
12273
|
if (r.success) {
|
|
11593
|
-
toast('
|
|
11594
|
-
if (statusEl) { statusEl.textContent = '
|
|
12274
|
+
toast(t('settings_saved') + section, 'ok');
|
|
12275
|
+
if (statusEl) { statusEl.textContent = t('saved'); statusEl.style.color = 'var(--ok)'; }
|
|
11595
12276
|
// Refresh settings data
|
|
11596
12277
|
const fresh = await api('/settings');
|
|
11597
12278
|
if (fresh && !fresh.error) { _settingsData = fresh; }
|
|
11598
12279
|
} else {
|
|
11599
|
-
toast(r.message || r.error || '
|
|
11600
|
-
if (statusEl) { statusEl.textContent = '\u2717 ' + (r.message || '
|
|
12280
|
+
toast(r.message || r.error || t('failed_save'), 'err');
|
|
12281
|
+
if (statusEl) { statusEl.textContent = '\u2717 ' + (r.message || t('failed')); statusEl.style.color = 'var(--danger)'; }
|
|
11601
12282
|
}
|
|
11602
12283
|
}
|
|
11603
12284
|
|
|
@@ -11615,15 +12296,15 @@ async function saveSettings() {
|
|
|
11615
12296
|
const r = await api('/settings', { method: 'PUT', body: JSON.stringify(allData) });
|
|
11616
12297
|
_settingsSaving = false;
|
|
11617
12298
|
|
|
11618
|
-
if (r.success) { toast('
|
|
11619
|
-
else { toast(r.message || r.error || '
|
|
12299
|
+
if (r.success) { toast(t('all_saved'), 'ok'); setTimeout(() => loadSettings(), 800); }
|
|
12300
|
+
else { toast(r.message || r.error || t('failed_save'), 'err'); }
|
|
11620
12301
|
}
|
|
11621
12302
|
|
|
11622
12303
|
// \u2500\u2500 Reset Identity \u2500\u2500
|
|
11623
12304
|
function showResetIdentityModal() {
|
|
11624
12305
|
$('#reset-confirm-input').value = '';
|
|
11625
12306
|
$('#reset-confirm-btn').disabled = true;
|
|
11626
|
-
$('#reset-confirm-btn').textContent = '
|
|
12307
|
+
$('#reset-confirm-btn').textContent = t('delete_everything');
|
|
11627
12308
|
showModal('modal-reset-identity');
|
|
11628
12309
|
setTimeout(() => $('#reset-confirm-input')?.focus(), 100);
|
|
11629
12310
|
}
|
|
@@ -11631,27 +12312,27 @@ function showResetIdentityModal() {
|
|
|
11631
12312
|
function checkResetConfirm() {
|
|
11632
12313
|
const v = $('#reset-confirm-input')?.value?.trim();
|
|
11633
12314
|
const btn = $('#reset-confirm-btn');
|
|
11634
|
-
if (btn) { btn.disabled = (v !== 'RESET'); btn.textContent = v === 'RESET' ? '
|
|
12315
|
+
if (btn) { btn.disabled = (v !== 'RESET'); btn.textContent = v === 'RESET' ? t('confirm_delete') : t('delete_everything'); }
|
|
11635
12316
|
}
|
|
11636
12317
|
|
|
11637
12318
|
async function executeResetIdentity() {
|
|
11638
12319
|
const btn = $('#reset-confirm-btn');
|
|
11639
|
-
if (btn) { btn.disabled = true; btn.textContent = '
|
|
12320
|
+
if (btn) { btn.disabled = true; btn.textContent = t('resetting'); }
|
|
11640
12321
|
|
|
11641
12322
|
const r = await api('/settings/reset-identity', {
|
|
11642
12323
|
method: 'POST',
|
|
11643
12324
|
body: JSON.stringify({ confirm: true }),
|
|
11644
12325
|
});
|
|
11645
12326
|
|
|
11646
|
-
if (btn) { btn.disabled = false; btn.textContent = '
|
|
12327
|
+
if (btn) { btn.disabled = false; btn.textContent = t('delete_everything'); }
|
|
11647
12328
|
|
|
11648
12329
|
if (r.success) {
|
|
11649
|
-
toast('
|
|
12330
|
+
toast(t('reset_success'), 'ok');
|
|
11650
12331
|
hideModal('modal-reset-identity');
|
|
11651
12332
|
// Reload settings to reflect cleared state
|
|
11652
12333
|
setTimeout(() => loadSettings(), 1000);
|
|
11653
12334
|
} else {
|
|
11654
|
-
toast(r.message || r.error || '
|
|
12335
|
+
toast(r.message || r.error || t('reset_failed'), 'err');
|
|
11655
12336
|
}
|
|
11656
12337
|
}
|
|
11657
12338
|
|
|
@@ -11668,7 +12349,7 @@ async function exportSettings() {
|
|
|
11668
12349
|
a.download = 'aicq-settings-' + new Date().toISOString().slice(0, 10) + '.json';
|
|
11669
12350
|
a.click();
|
|
11670
12351
|
URL.revokeObjectURL(url);
|
|
11671
|
-
toast('
|
|
12352
|
+
toast(t('exported_success'), 'ok');
|
|
11672
12353
|
}
|
|
11673
12354
|
|
|
11674
12355
|
function showImportSettingsModal() {
|
|
@@ -11679,27 +12360,27 @@ function showImportSettingsModal() {
|
|
|
11679
12360
|
|
|
11680
12361
|
async function executeImportSettings() {
|
|
11681
12362
|
const raw = $('#import-json-input')?.value?.trim();
|
|
11682
|
-
if (!raw) { toast('
|
|
12363
|
+
if (!raw) { toast(t('paste_json'), 'warn'); return; }
|
|
11683
12364
|
|
|
11684
12365
|
let settings;
|
|
11685
|
-
try { settings = JSON.parse(raw); } catch (e) { toast('
|
|
12366
|
+
try { settings = JSON.parse(raw); } catch (e) { toast(t('invalid_json') + e.message, 'err'); return; }
|
|
11686
12367
|
|
|
11687
12368
|
const btn = $('#import-confirm-btn');
|
|
11688
|
-
if (btn) { btn.disabled = true; btn.textContent = '
|
|
12369
|
+
if (btn) { btn.disabled = true; btn.textContent = t('importing'); }
|
|
11689
12370
|
|
|
11690
12371
|
const r = await api('/settings/import', {
|
|
11691
12372
|
method: 'POST',
|
|
11692
12373
|
body: JSON.stringify({ settings, merge: true }),
|
|
11693
12374
|
});
|
|
11694
12375
|
|
|
11695
|
-
if (btn) { btn.disabled = false; btn.textContent = '
|
|
12376
|
+
if (btn) { btn.disabled = false; btn.textContent = t('import_settings'); }
|
|
11696
12377
|
|
|
11697
12378
|
if (r.success) {
|
|
11698
|
-
toast('
|
|
12379
|
+
toast(t('imported_success'), 'ok');
|
|
11699
12380
|
hideModal('modal-import-settings');
|
|
11700
12381
|
setTimeout(() => loadSettings(), 800);
|
|
11701
12382
|
} else {
|
|
11702
|
-
toast(r.message || r.error || '
|
|
12383
|
+
toast(r.message || r.error || t('import_failed'), 'err');
|
|
11703
12384
|
}
|
|
11704
12385
|
}
|
|
11705
12386
|
|
|
@@ -11710,7 +12391,7 @@ let _jsonEditorConfigFile = '';
|
|
|
11710
12391
|
|
|
11711
12392
|
async function renderSettingsJsonEditor() {
|
|
11712
12393
|
const el = $('#settings-content');
|
|
11713
|
-
html(el, '<div class="loading-mask"><div class="spinner"></div>
|
|
12394
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_config') + '</div>');
|
|
11714
12395
|
|
|
11715
12396
|
const queryParams = _jsonEditorConfigFile ? '?file=' + encodeURIComponent(_jsonEditorConfigFile) : '';
|
|
11716
12397
|
const data = await api('/config-file/raw' + queryParams);
|
|
@@ -11742,7 +12423,7 @@ async function renderSettingsJsonEditor() {
|
|
|
11742
12423
|
|
|
11743
12424
|
<div class="card">
|
|
11744
12425
|
<div class="card-header">
|
|
11745
|
-
<div class="card-title"
|
|
12426
|
+
<div class="card-title">\${t('json_editor')}</div>
|
|
11746
12427
|
<div style="display:flex;gap:8px;align-items:center">
|
|
11747
12428
|
<span class="mono" style="font-size:11px;color:var(--text3)">\${escHtml(data.filePath)}</span>
|
|
11748
12429
|
<button class="btn btn-sm btn-default" onclick="renderSettingsJsonEditor()">\u{1F504} Reload</button>
|
|
@@ -11750,19 +12431,19 @@ async function renderSettingsJsonEditor() {
|
|
|
11750
12431
|
</div>
|
|
11751
12432
|
\${fileSelectorHtml}
|
|
11752
12433
|
<div class="form-group">
|
|
11753
|
-
<label
|
|
12434
|
+
<label>\${t('raw_json')}</label>
|
|
11754
12435
|
<textarea id="json-editor" style="min-height:400px;font-family:'SF Mono','Fira Code','Cascadia Code',monospace;font-size:12px;line-height:1.5;tab-size:2;background:var(--bg)" spellcheck="false">\${escHtml(data.content)}</textarea>
|
|
11755
|
-
<div class="hint"
|
|
12436
|
+
<div class="hint">\${t('raw_json_hint')}</div>
|
|
11756
12437
|
</div>
|
|
11757
12438
|
<div id="json-editor-status" style="margin-bottom:12px;font-size:12px"></div>
|
|
11758
12439
|
<div class="form-actions" style="justify-content:space-between">
|
|
11759
12440
|
<div style="display:flex;gap:8px">
|
|
11760
|
-
<button class="btn btn-sm btn-default" onclick="formatJsonEditor()"
|
|
11761
|
-
<button class="btn btn-sm btn-default" onclick="copyText($('#json-editor')?.value || '')"
|
|
12441
|
+
<button class="btn btn-sm btn-default" onclick="formatJsonEditor()">\${t('format')}</button>
|
|
12442
|
+
<button class="btn btn-sm btn-default" onclick="copyText($('#json-editor')?.value || '')">\${t('copy')}</button>
|
|
11762
12443
|
</div>
|
|
11763
12444
|
<div style="display:flex;gap:8px">
|
|
11764
|
-
<button class="btn btn-sm btn-default" onclick="renderSettingsJsonEditor()"
|
|
11765
|
-
<button class="btn btn-sm btn-primary" id="btn-save-json" onclick="saveJsonConfig()"
|
|
12445
|
+
<button class="btn btn-sm btn-default" onclick="renderSettingsJsonEditor()">\${t('revert')}</button>
|
|
12446
|
+
<button class="btn btn-sm btn-primary" id="btn-save-json" onclick="saveJsonConfig()">\${t('save_config')}</button>
|
|
11766
12447
|
</div>
|
|
11767
12448
|
</div>
|
|
11768
12449
|
</div>
|
|
@@ -11775,10 +12456,10 @@ function formatJsonEditor() {
|
|
|
11775
12456
|
try {
|
|
11776
12457
|
const obj = JSON.parse(ta.value);
|
|
11777
12458
|
ta.value = JSON.stringify(obj, null, 2);
|
|
11778
|
-
toast('
|
|
11779
|
-
$('#json-editor-status').innerHTML = '<span style="color:var(--ok)"
|
|
12459
|
+
toast(t('json_formatted'), 'ok');
|
|
12460
|
+
$('#json-editor-status').innerHTML = '<span style="color:var(--ok)">' + t('valid_json') + '</span>';
|
|
11780
12461
|
} catch (e) {
|
|
11781
|
-
toast('
|
|
12462
|
+
toast(t('invalid_json') + e.message, 'err');
|
|
11782
12463
|
$('#json-editor-status').innerHTML = '<span style="color:var(--danger)">\u2717 ' + escHtml(e.message) + '</span>';
|
|
11783
12464
|
}
|
|
11784
12465
|
}
|
|
@@ -11787,29 +12468,439 @@ async function saveJsonConfig() {
|
|
|
11787
12468
|
const btn = $('#btn-save-json');
|
|
11788
12469
|
const statusEl = $('#json-editor-status');
|
|
11789
12470
|
const raw = $('#json-editor')?.value;
|
|
11790
|
-
if (!raw) { toast('
|
|
12471
|
+
if (!raw) { toast(t('no_content'), 'warn'); return; }
|
|
11791
12472
|
|
|
11792
12473
|
// Validate first
|
|
11793
12474
|
try { JSON.parse(raw); } catch (e) {
|
|
11794
|
-
toast('
|
|
12475
|
+
toast(t('invalid_json') + e.message, 'err');
|
|
11795
12476
|
if (statusEl) statusEl.innerHTML = '<span style="color:var(--danger)">\u2717 ' + escHtml(e.message) + '</span>';
|
|
11796
12477
|
return;
|
|
11797
12478
|
}
|
|
11798
12479
|
|
|
11799
|
-
if (btn) { btn.disabled = true; btn.textContent = '
|
|
11800
|
-
if (statusEl) statusEl.innerHTML = '<span style="color:var(--text3)"><span class="spinner" style="width:12px;height:12px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px"></span>
|
|
12480
|
+
if (btn) { btn.disabled = true; btn.textContent = t('saving'); }
|
|
12481
|
+
if (statusEl) statusEl.innerHTML = '<span style="color:var(--text3)"><span class="spinner" style="width:12px;height:12px;border-width:2px;display:inline-block;vertical-align:middle;margin-right:6px"></span> ' + t('saving') + '</span>';
|
|
11801
12482
|
|
|
11802
12483
|
const queryParams = _jsonEditorConfigFile ? '?file=' + encodeURIComponent(_jsonEditorConfigFile) : '';
|
|
11803
12484
|
const r = await api('/config-file/raw' + queryParams, { method: 'PUT', body: JSON.stringify({ content: raw }) });
|
|
11804
12485
|
|
|
11805
|
-
if (btn) { btn.disabled = false; btn.textContent = '
|
|
12486
|
+
if (btn) { btn.disabled = false; btn.textContent = t('save_config'); }
|
|
11806
12487
|
|
|
11807
12488
|
if (r.success) {
|
|
11808
|
-
toast('
|
|
12489
|
+
toast(t('config_saved'), 'ok');
|
|
11809
12490
|
if (statusEl) statusEl.innerHTML = '<span style="color:var(--ok)">\u2713 Saved at ' + new Date().toLocaleTimeString() + '</span>';
|
|
11810
12491
|
} else {
|
|
11811
|
-
toast(r.message || '
|
|
11812
|
-
if (statusEl) statusEl.innerHTML = '<span style="color:var(--danger)">\u2717 ' + escHtml(r.message || '
|
|
12492
|
+
toast(r.message || t('failed_save'), 'err');
|
|
12493
|
+
if (statusEl) statusEl.innerHTML = '<span style="color:var(--danger)">\u2717 ' + escHtml(r.message || t('failed')) + '</span>';
|
|
12494
|
+
}
|
|
12495
|
+
}
|
|
12496
|
+
|
|
12497
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
12498
|
+
// PAGE: OpenClaw Config
|
|
12499
|
+
// \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
|
|
12500
|
+
let _ocConfig = null;
|
|
12501
|
+
let _ocTab = 'defaults';
|
|
12502
|
+
|
|
12503
|
+
async function loadOpenClawConfig() {
|
|
12504
|
+
const el = $('#openclaw-content');
|
|
12505
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>' + t('loading_openclaw') + '</div>');
|
|
12506
|
+
try {
|
|
12507
|
+
const data = await api('/openclaw-config');
|
|
12508
|
+
if (data.error) { html(el, '<div class="card"><p style="color:var(--danger)">' + escHtml(data.error) + '</p></div>'); return; }
|
|
12509
|
+
_ocConfig = data;
|
|
12510
|
+
_ocTab = 'defaults';
|
|
12511
|
+
renderOpenClawConfig(data);
|
|
12512
|
+
} catch (e) {
|
|
12513
|
+
html(el, '<div class="card"><p style="color:var(--danger)">' + escHtml(e.message || 'Failed to load') + '</p></div>');
|
|
12514
|
+
}
|
|
12515
|
+
}
|
|
12516
|
+
|
|
12517
|
+
function switchOpenClawTab(tab) {
|
|
12518
|
+
_ocTab = tab;
|
|
12519
|
+
$$('.oc-tab').forEach(b => b.classList.toggle('active', b.dataset.ocTab === tab));
|
|
12520
|
+
$$('.oc-panel').forEach(p => { p.style.display = p.id === 'oc-panel-' + tab ? '' : 'none'; });
|
|
12521
|
+
}
|
|
12522
|
+
|
|
12523
|
+
function renderOpenClawConfig(data) {
|
|
12524
|
+
const el = $('#openclaw-content');
|
|
12525
|
+
const tabBtns = [
|
|
12526
|
+
{ id: 'defaults', label: t('tab_defaults') },
|
|
12527
|
+
{ id: 'agents', label: t('tab_agents') },
|
|
12528
|
+
{ id: 'bindings', label: t('tab_bindings') },
|
|
12529
|
+
{ id: 'channels', label: t('tab_channels') },
|
|
12530
|
+
];
|
|
12531
|
+
let tabsHtml = '<div style="display:flex;gap:6px;margin-bottom:20px;flex-wrap:wrap">';
|
|
12532
|
+
for (const tb of tabBtns) {
|
|
12533
|
+
tabsHtml += '<button class="btn btn-sm filter-btn oc-tab' + (tb.id === _ocTab ? ' active' : '') + '" data-oc-tab="' + tb.id + '" onclick="switchOpenClawTab(\\'' + tb.id + '\\')">' + tb.label + '</button>';
|
|
12534
|
+
}
|
|
12535
|
+
tabsHtml += '</div>';
|
|
12536
|
+
|
|
12537
|
+
let panelsHtml = '<div id="oc-panel-defaults" class="oc-panel"' + (_ocTab !== 'defaults' ? ' style="display:none"' : '') + '>' + renderOcDefaults(data.agents) + '</div>';
|
|
12538
|
+
panelsHtml += '<div id="oc-panel-agents" class="oc-panel"' + (_ocTab !== 'agents' ? ' style="display:none"' : '') + '>' + renderOcAgentList(data.agents) + '</div>';
|
|
12539
|
+
panelsHtml += '<div id="oc-panel-bindings" class="oc-panel"' + (_ocTab !== 'bindings' ? ' style="display:none"' : '') + '>' + renderOcBindings(data.bindings) + '</div>';
|
|
12540
|
+
panelsHtml += '<div id="oc-panel-channels" class="oc-panel"' + (_ocTab !== 'channels' ? ' style="display:none"' : '') + '>' + renderOcChannels(data.channels) + '</div>';
|
|
12541
|
+
|
|
12542
|
+
html(el, '<div class="section-desc">' + t('openclaw_desc') + '</div>' +
|
|
12543
|
+
'<div class="card"><div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;flex-wrap:wrap;gap:8px"><div class="card-title">' + t('openclaw_config') + '</div>' +
|
|
12544
|
+
'<button class="btn btn-primary btn-sm" onclick="saveOpenClawConfig()">' + t('save_openclaw_config') + '</button></div>' +
|
|
12545
|
+
tabsHtml + panelsHtml + '</div>');
|
|
12546
|
+
}
|
|
12547
|
+
|
|
12548
|
+
function renderOcDefaults(agents) {
|
|
12549
|
+
const d = agents?.defaults || {};
|
|
12550
|
+
const model = d.model || {};
|
|
12551
|
+
const imgModel = d.imageModel || {};
|
|
12552
|
+
const subagents = d.subagents || {};
|
|
12553
|
+
const modelsRegistry = d.models || {};
|
|
12554
|
+
|
|
12555
|
+
let fallbacksHtml = '';
|
|
12556
|
+
const fallbacks = model.fallbacks || [];
|
|
12557
|
+
for (let i = 0; i < fallbacks.length; i++) {
|
|
12558
|
+
fallbacksHtml += '<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px"><input type="text" class="oc-fallback-item" value="' + escHtml(fallbacks[i]) + '" style="flex:1" placeholder="model-id"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button></div>';
|
|
12559
|
+
}
|
|
12560
|
+
fallbacksHtml += '<button class="btn btn-sm btn-default" onclick="addOcListItem(this,\\'oc-fallback-item\\')">\u2795 ' + t('add_item') + '</button>';
|
|
12561
|
+
|
|
12562
|
+
let imgFallbacksHtml = '';
|
|
12563
|
+
const imgFallbacks = imgModel.fallbacks || [];
|
|
12564
|
+
for (let i = 0; i < imgFallbacks.length; i++) {
|
|
12565
|
+
imgFallbacksHtml += '<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px"><input type="text" class="oc-img-fallback-item" value="' + escHtml(imgFallbacks[i]) + '" style="flex:1" placeholder="model-id"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button></div>';
|
|
12566
|
+
}
|
|
12567
|
+
imgFallbacksHtml += '<button class="btn btn-sm btn-default" onclick="addOcListItem(this,\\'oc-img-fallback-item\\')">\u2795 ' + t('add_item') + '</button>';
|
|
12568
|
+
|
|
12569
|
+
let modelsRegHtml = '';
|
|
12570
|
+
const modelKeys = Object.keys(modelsRegistry);
|
|
12571
|
+
for (let i = 0; i < modelKeys.length; i++) {
|
|
12572
|
+
const k = modelKeys[i];
|
|
12573
|
+
modelsRegHtml += '<div style="display:flex;gap:6px;align-items:center;margin-bottom:6px"><input type="text" class="oc-model-reg-key" value="' + escHtml(k) + '" style="width:45%" placeholder="model-id"><span style="color:var(--text3)">\u2192</span><input type="text" class="oc-model-reg-val" value="" style="flex:1" placeholder="{}" disabled><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button></div>';
|
|
12574
|
+
}
|
|
12575
|
+
modelsRegHtml += '<button class="btn btn-sm btn-default" onclick="addOcModelRegItem(this)">\u2795 ' + t('add_item') + '</button>';
|
|
12576
|
+
|
|
12577
|
+
return '<div class="card" style="margin-bottom:16px"><div class="card-title">' + t('agent_defaults') + '</div><div class="card-desc">' + t('agent_defaults_desc') + '</div>' +
|
|
12578
|
+
'<div class="form-group"><label>' + t('compaction_mode') + '</label><input type="text" id="oc-compaction-mode" value="' + escHtml((d.compaction?.mode) || '') + '" placeholder="safeguard"></div>' +
|
|
12579
|
+
'<div class="form-row">' +
|
|
12580
|
+
'<div class="form-group"><label>' + t('primary_model') + '</label><input type="text" id="oc-primary-model" value="' + escHtml(model.primary || '') + '" placeholder="zerotoken/glm-4-flash"></div>' +
|
|
12581
|
+
'<div class="form-group"><label>' + t('max_concurrent') + '</label><input type="number" id="oc-max-concurrent" value="' + (d.maxConcurrent || '') + '" min="1" max="32"></div>' +
|
|
12582
|
+
'</div>' +
|
|
12583
|
+
'<div class="form-group"><label>' + t('fallback_models') + '</label><div id="oc-fallbacks-list">' + fallbacksHtml + '</div></div>' +
|
|
12584
|
+
'<div class="form-row">' +
|
|
12585
|
+
'<div class="form-group"><label>' + t('image_model') + ' (Primary)</label><input type="text" id="oc-img-model-primary" value="' + escHtml(imgModel.primary || '') + '" placeholder="modelscope/ZhipuAI/GLM-5"></div>' +
|
|
12586
|
+
'<div class="form-group"><label>' + t('subagent_max_concurrent') + '</label><input type="number" id="oc-subagent-max" value="' + (subagents.maxConcurrent || '') + '" min="1" max="32"></div>' +
|
|
12587
|
+
'</div>' +
|
|
12588
|
+
'<div class="form-group"><label>' + t('image_model') + ' (Fallbacks)</label><div id="oc-img-fallbacks-list">' + imgFallbacksHtml + '</div></div>' +
|
|
12589
|
+
'<div class="form-row">' +
|
|
12590
|
+
'<div class="form-group"><label>' + t('thinking_default') + '</label><select id="oc-thinking-default"><option value="off"' + (d.thinkingDefault === 'off' || !d.thinkingDefault ? ' selected' : '') + '>off</option><option value="low"' + (d.thinkingDefault === 'low' ? ' selected' : '') + '>low</option><option value="medium"' + (d.thinkingDefault === 'medium' ? ' selected' : '') + '>medium</option><option value="high"' + (d.thinkingDefault === 'high' ? ' selected' : '') + '>high</option></select></div>' +
|
|
12591
|
+
'<div class="form-group"><label>' + t('workspace') + '</label><input type="text" id="oc-workspace" value="' + escHtml(d.workspace || '') + '" placeholder="~/.openclaw/workspace"></div>' +
|
|
12592
|
+
'</div>' +
|
|
12593
|
+
'<div class="form-group"><label>' + t('models_registry') + '</label><div id="oc-models-reg-list">' + modelsRegHtml + '</div></div>' +
|
|
12594
|
+
'</div>';
|
|
12595
|
+
}
|
|
12596
|
+
|
|
12597
|
+
function renderOcAgentList(agents) {
|
|
12598
|
+
const list = agents?.list || [];
|
|
12599
|
+
let tableHtml = '';
|
|
12600
|
+
if (list.length === 0) {
|
|
12601
|
+
tableHtml = '<div class="empty"><div class="icon">\u{1F916}</div><p>' + t('no_agents_in_list') + '</p></div>';
|
|
12602
|
+
} else {
|
|
12603
|
+
tableHtml = '<table><thead><tr><th>ID</th><th>' + t('agent_identity_name') + '</th><th>' + t('agent_model_primary') + '</th><th>' + t('agent_tools_profile') + '</th><th>' + t('actions') + '</th></tr></thead><tbody>';
|
|
12604
|
+
for (let i = 0; i < list.length; i++) {
|
|
12605
|
+
const a = list[i];
|
|
12606
|
+
tableHtml += '<tr><td class="mono">' + escHtml(a.id || '') + '</td><td>' + escHtml(a.identity?.name || '') + '</td><td class="mono" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + escHtml(a.model?.primary || '') + '</td><td><span class="badge badge-ghost">' + escHtml(a.tools?.profile || '') + '</span></td>' +
|
|
12607
|
+
'<td class="actions-cell"><button class="btn btn-sm btn-default" onclick="editOcAgent(' + i + ')">\u270F\uFE0F</button><button class="btn btn-sm btn-danger" onclick="deleteOcAgent(' + i + ')">\u{1F5D1}\uFE0F</button></td></tr>';
|
|
12608
|
+
}
|
|
12609
|
+
tableHtml += '</tbody></table>';
|
|
12610
|
+
}
|
|
12611
|
+
return '<div class="card"><div class="card-header"><div><div class="card-title">' + t('oc_agent_list') + '</div><div class="card-desc">' + t('oc_agent_list_desc') + '</div></div>' +
|
|
12612
|
+
'<button class="btn btn-sm btn-primary" onclick="addOcAgent()">' + t('add_agent_btn') + '</button></div>' +
|
|
12613
|
+
'<div id="oc-agents-table">' + tableHtml + '</div></div>';
|
|
12614
|
+
}
|
|
12615
|
+
|
|
12616
|
+
function renderOcBindings(bindings) {
|
|
12617
|
+
bindings = bindings || [];
|
|
12618
|
+
let tableHtml = '';
|
|
12619
|
+
if (bindings.length === 0) {
|
|
12620
|
+
tableHtml = '<div class="empty"><div class="icon">\u{1F517}</div><p>' + t('no_bindings') + '</p></div>';
|
|
12621
|
+
} else {
|
|
12622
|
+
tableHtml = '<table><thead><tr><th>' + t('binding_agent_id') + '</th><th>' + t('binding_channel') + '</th><th>' + t('binding_account_id') + '</th><th>' + t('binding_type') + '</th><th>' + t('actions') + '</th></tr></thead><tbody>';
|
|
12623
|
+
for (let i = 0; i < bindings.length; i++) {
|
|
12624
|
+
const b = bindings[i];
|
|
12625
|
+
tableHtml += '<tr><td class="mono">' + escHtml(b.agentId || '') + '</td><td class="mono">' + escHtml(b.match?.channel || '') + '</td><td class="mono">' + escHtml(b.match?.accountId || '-') + '</td><td><span class="badge badge-ghost">' + escHtml(b.type || 'route') + '</span></td>' +
|
|
12626
|
+
'<td class="actions-cell"><button class="btn btn-sm btn-danger" onclick="deleteOcBinding(' + i + ')">\u{1F5D1}\uFE0F</button></td></tr>';
|
|
12627
|
+
}
|
|
12628
|
+
tableHtml += '</tbody></table>';
|
|
12629
|
+
}
|
|
12630
|
+
return '<div class="card"><div class="card-header"><div><div class="card-title">' + t('bindings_title') + '</div><div class="card-desc">' + t('bindings_desc') + '</div></div>' +
|
|
12631
|
+
'<button class="btn btn-sm btn-primary" onclick="addOcBinding()">' + t('add_binding') + '</button></div>' +
|
|
12632
|
+
'<div id="oc-bindings-table">' + tableHtml + '</div></div>';
|
|
12633
|
+
}
|
|
12634
|
+
|
|
12635
|
+
function renderOcChannels(channels) {
|
|
12636
|
+
channels = channels || {};
|
|
12637
|
+
const channelKeys = Object.keys(channels);
|
|
12638
|
+
let html2 = '';
|
|
12639
|
+
if (channelKeys.length === 0) {
|
|
12640
|
+
html2 = '<div class="empty"><div class="icon">\u{1F4E1}</div><p>' + t('no_channels_configured') + '</p></div>';
|
|
12641
|
+
} else {
|
|
12642
|
+
for (const ck of channelKeys) {
|
|
12643
|
+
const ch = channels[ck];
|
|
12644
|
+
const accs = ch.accounts || {};
|
|
12645
|
+
const accKeys = Object.keys(accs);
|
|
12646
|
+
const isEnabled = ch.enabled !== false;
|
|
12647
|
+
html2 += '<div class="card" style="margin-bottom:16px">';
|
|
12648
|
+
html2 += '<div class="card-header"><div class="card-title">\u{1F4E1} ' + escHtml(ck) + '</div>' +
|
|
12649
|
+
'<div style="display:flex;gap:8px;align-items:center"><span class="badge ' + (isEnabled ? 'badge-ok' : 'badge-danger') + '">' + (isEnabled ? t('channel_enabled') : t('channel_disabled')) + '</span>' +
|
|
12650
|
+
'<label class="toggle-label"><input type="checkbox" class="oc-ch-enabled" data-channel="' + escHtml(ck) + '"' + (isEnabled ? ' checked' : '') + '><span class="toggle-slider"></span></label></div></div>';
|
|
12651
|
+
|
|
12652
|
+
html2 += '<div class="form-row">';
|
|
12653
|
+
html2 += '<div class="form-group"><label>' + t('group_policy') + '</label><input type="text" class="oc-ch-policy" data-channel="' + escHtml(ck) + '" value="' + escHtml(ch.groupPolicy || '') + '" placeholder="open"></div>';
|
|
12654
|
+
html2 += '</div>';
|
|
12655
|
+
|
|
12656
|
+
html2 += '<div class="form-group"><label>' + t('accounts') + ' (' + accKeys.length + ')</label>';
|
|
12657
|
+
for (const ak of accKeys) {
|
|
12658
|
+
const acc = accs[ak];
|
|
12659
|
+
const allowFrom = acc.allowFrom || [];
|
|
12660
|
+
html2 += '<div class="card" style="margin-bottom:10px;padding:14px;background:var(--bg3)">';
|
|
12661
|
+
html2 += '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px"><strong class="mono">' + escHtml(ak) + '</strong><div class="actions-cell"><button class="btn btn-sm btn-default" onclick="editOcAccount(\\'' + escHtml(ck) + '\\',\\'' + escHtml(ak) + '\\')">\u270F\uFE0F</button><button class="btn btn-sm btn-danger" onclick="deleteOcAccount(\\'' + escHtml(ck) + '\\',\\'' + escHtml(ak) + '\\')">\u{1F5D1}\uFE0F</button></div></div>';
|
|
12662
|
+
html2 += '<div class="form-group"><label>Bot Token</label><input type="password" class="oc-acc-token" data-channel="' + escHtml(ck) + '" data-account="' + escHtml(ak) + '" value="' + escHtml(acc.botToken || '') + '" placeholder="****"></div>';
|
|
12663
|
+
html2 += '<div class="form-group"><label>' + t('dm_policy') + '</label><select class="oc-acc-dm-policy" data-channel="' + escHtml(ck) + '" data-account="' + escHtml(ak) + '"><option value="allowlist"' + (acc.dmPolicy === 'allowlist' ? ' selected' : '') + '>allowlist</option><option value="open"' + (acc.dmPolicy === 'open' ? ' selected' : '') + '>open</option><option value="block"' + (acc.dmPolicy === 'block' ? ' selected' : '') + '>block</option></select></div>';
|
|
12664
|
+
html2 += '<div class="form-group"><label>' + t('allow_from') + '</label>';
|
|
12665
|
+
for (const af of allowFrom) {
|
|
12666
|
+
html2 += '<div style="display:flex;gap:6px;align-items:center;margin-bottom:4px"><input type="text" class="oc-allow-from" data-channel="' + escHtml(ck) + '" data-account="' + escHtml(ak) + '" value="' + escHtml(af) + '" style="flex:1"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button></div>';
|
|
12667
|
+
}
|
|
12668
|
+
html2 += '<button class="btn btn-sm btn-default" data-channel="' + escHtml(ck) + '" data-account="' + escHtml(ak) + '" onclick="addOcAllowFrom(this)">\u2795 ' + t('add_allow_from') + '</button>';
|
|
12669
|
+
html2 += '</div></div>';
|
|
12670
|
+
}
|
|
12671
|
+
html2 += '<button class="btn btn-sm btn-default" onclick="addOcAccount(\\'' + escHtml(ck) + '\\')">\u2795 ' + t('add_account') + '</button>';
|
|
12672
|
+
html2 += '</div></div>';
|
|
12673
|
+
}
|
|
12674
|
+
}
|
|
12675
|
+
return '<div class="card"><div class="card-header"><div><div class="card-title">' + t('channels_title') + '</div><div class="card-desc">' + t('channels_desc') + '</div></div></div>' +
|
|
12676
|
+
'<div id="oc-channels-content">' + html2 + '</div></div>';
|
|
12677
|
+
}
|
|
12678
|
+
|
|
12679
|
+
// \u2500\u2500 OpenClaw dynamic list helpers \u2500\u2500
|
|
12680
|
+
function addOcListItem(btn, inputClass) {
|
|
12681
|
+
const container = btn.parentElement;
|
|
12682
|
+
const div = document.createElement('div');
|
|
12683
|
+
div.style.cssText = 'display:flex;gap:6px;align-items:center;margin-bottom:6px';
|
|
12684
|
+
div.innerHTML = '<input type="text" class="' + inputClass + '" value="" style="flex:1" placeholder="model-id"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button>';
|
|
12685
|
+
container.insertBefore(div, btn);
|
|
12686
|
+
div.querySelector('input').focus();
|
|
12687
|
+
}
|
|
12688
|
+
|
|
12689
|
+
function addOcModelRegItem(btn) {
|
|
12690
|
+
const container = btn.parentElement;
|
|
12691
|
+
const div = document.createElement('div');
|
|
12692
|
+
div.style.cssText = 'display:flex;gap:6px;align-items:center;margin-bottom:6px';
|
|
12693
|
+
div.innerHTML = '<input type="text" class="oc-model-reg-key" value="" style="width:45%" placeholder="model-id"><span style="color:var(--text3)">\u2192</span><input type="text" class="oc-model-reg-val" value="" style="flex:1" placeholder="{}" disabled><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button>';
|
|
12694
|
+
container.insertBefore(div, btn);
|
|
12695
|
+
div.querySelector('.oc-model-reg-key').focus();
|
|
12696
|
+
}
|
|
12697
|
+
|
|
12698
|
+
function addOcAllowFrom(btn) {
|
|
12699
|
+
const container = btn.parentElement;
|
|
12700
|
+
const div = document.createElement('div');
|
|
12701
|
+
div.style.cssText = 'display:flex;gap:6px;align-items:center;margin-bottom:4px';
|
|
12702
|
+
div.innerHTML = '<input type="text" class="oc-allow-from" data-channel="' + (btn.dataset.channel || '') + '" data-account="' + (btn.dataset.account || '') + '" value="" style="flex:1"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">\u2715</button>';
|
|
12703
|
+
container.insertBefore(div, btn);
|
|
12704
|
+
div.querySelector('input').focus();
|
|
12705
|
+
}
|
|
12706
|
+
|
|
12707
|
+
// \u2500\u2500 OpenClaw CRUD operations \u2500\u2500
|
|
12708
|
+
function addOcAgent() {
|
|
12709
|
+
if (!_ocConfig) return;
|
|
12710
|
+
if (!_ocConfig.agents) _ocConfig.agents = { defaults: _ocConfig.agents?.defaults || {} };
|
|
12711
|
+
if (!Array.isArray(_ocConfig.agents.list)) _ocConfig.agents.list = [];
|
|
12712
|
+
_ocConfig.agents.list.push({ id: 'new-agent-' + Date.now(), identity: { name: '' }, model: { primary: '' }, tools: { profile: 'full' }, workspace: '' });
|
|
12713
|
+
renderOpenClawConfig(_ocConfig);
|
|
12714
|
+
switchOpenClawTab('agents');
|
|
12715
|
+
toast(t('agent_added'), 'ok');
|
|
12716
|
+
}
|
|
12717
|
+
|
|
12718
|
+
function editOcAgent(idx) {
|
|
12719
|
+
if (!_ocConfig?.agents?.list?.[idx]) return;
|
|
12720
|
+
const a = _ocConfig.agents.list[idx];
|
|
12721
|
+
const newId = prompt('Agent ID:', a.id);
|
|
12722
|
+
if (newId === null) return;
|
|
12723
|
+
const newName = prompt('Identity Name:', a.identity?.name || '');
|
|
12724
|
+
if (newName === null) return;
|
|
12725
|
+
const newModel = prompt('Primary Model:', a.model?.primary || '');
|
|
12726
|
+
if (newModel === null) return;
|
|
12727
|
+
const newTools = prompt('Tools Profile:', a.tools?.profile || '');
|
|
12728
|
+
if (newTools === null) return;
|
|
12729
|
+
const newWorkspace = prompt('Workspace:', a.workspace || '');
|
|
12730
|
+
if (newWorkspace === null) return;
|
|
12731
|
+
a.id = newId;
|
|
12732
|
+
a.identity = { name: newName };
|
|
12733
|
+
a.model = { primary: newModel };
|
|
12734
|
+
a.tools = { profile: newTools };
|
|
12735
|
+
a.workspace = newWorkspace;
|
|
12736
|
+
renderOpenClawConfig(_ocConfig);
|
|
12737
|
+
switchOpenClawTab('agents');
|
|
12738
|
+
}
|
|
12739
|
+
|
|
12740
|
+
function deleteOcAgent(idx) {
|
|
12741
|
+
if (!_ocConfig?.agents?.list) return;
|
|
12742
|
+
const a = _ocConfig.agents.list[idx];
|
|
12743
|
+
const name = a?.id || a?.identity?.name || '';
|
|
12744
|
+
if (!confirm(t('confirm_delete_oc_agent').replace('{name}', name))) return;
|
|
12745
|
+
_ocConfig.agents.list.splice(idx, 1);
|
|
12746
|
+
renderOpenClawConfig(_ocConfig);
|
|
12747
|
+
switchOpenClawTab('agents');
|
|
12748
|
+
toast(t('agent_deleted'), 'ok');
|
|
12749
|
+
}
|
|
12750
|
+
|
|
12751
|
+
function addOcBinding() {
|
|
12752
|
+
if (!_ocConfig) return;
|
|
12753
|
+
if (!Array.isArray(_ocConfig.bindings)) _ocConfig.bindings = [];
|
|
12754
|
+
const agentId = prompt('Agent ID:');
|
|
12755
|
+
if (!agentId) return;
|
|
12756
|
+
const channel = prompt('Channel:', '');
|
|
12757
|
+
const accountId = prompt('Account ID (optional):', '');
|
|
12758
|
+
const type = prompt('Type (route/direct):', 'route');
|
|
12759
|
+
const binding = { agentId };
|
|
12760
|
+
if (channel) binding.match = { channel };
|
|
12761
|
+
if (accountId) { if (!binding.match) binding.match = {}; binding.match.accountId = accountId; }
|
|
12762
|
+
if (type) binding.type = type;
|
|
12763
|
+
_ocConfig.bindings.push(binding);
|
|
12764
|
+
renderOpenClawConfig(_ocConfig);
|
|
12765
|
+
switchOpenClawTab('bindings');
|
|
12766
|
+
toast(t('openclaw_config_saved'), 'ok');
|
|
12767
|
+
}
|
|
12768
|
+
|
|
12769
|
+
function deleteOcBinding(idx) {
|
|
12770
|
+
if (!_ocConfig?.bindings) return;
|
|
12771
|
+
if (!confirm(t('confirm_delete_binding'))) return;
|
|
12772
|
+
_ocConfig.bindings.splice(idx, 1);
|
|
12773
|
+
renderOpenClawConfig(_ocConfig);
|
|
12774
|
+
switchOpenClawTab('bindings');
|
|
12775
|
+
toast(t('binding_deleted'), 'ok');
|
|
12776
|
+
}
|
|
12777
|
+
|
|
12778
|
+
function addOcAccount(channelKey) {
|
|
12779
|
+
if (!_ocConfig?.channels) return;
|
|
12780
|
+
const ch = _ocConfig.channels[channelKey];
|
|
12781
|
+
if (!ch) return;
|
|
12782
|
+
if (!ch.accounts) ch.accounts = {};
|
|
12783
|
+
const accId = prompt('Account ID:');
|
|
12784
|
+
if (!accId) return;
|
|
12785
|
+
ch.accounts[accId] = { botToken: '', dmPolicy: 'allowlist', allowFrom: [] };
|
|
12786
|
+
renderOpenClawConfig(_ocConfig);
|
|
12787
|
+
switchOpenClawTab('channels');
|
|
12788
|
+
toast(t('openclaw_config_saved'), 'ok');
|
|
12789
|
+
}
|
|
12790
|
+
|
|
12791
|
+
function editOcAccount(channelKey, accountId) {
|
|
12792
|
+
if (!_ocConfig?.channels?.[channelKey]?.accounts?.[accountId]) return;
|
|
12793
|
+
const acc = _ocConfig.channels[channelKey].accounts[accountId];
|
|
12794
|
+
const token = prompt('Bot Token:', acc.botToken || '');
|
|
12795
|
+
if (token === null) return;
|
|
12796
|
+
acc.botToken = token;
|
|
12797
|
+
renderOpenClawConfig(_ocConfig);
|
|
12798
|
+
switchOpenClawTab('channels');
|
|
12799
|
+
}
|
|
12800
|
+
|
|
12801
|
+
function deleteOcAccount(channelKey, accountId) {
|
|
12802
|
+
if (!_ocConfig?.channels?.[channelKey]?.accounts) return;
|
|
12803
|
+
if (!confirm(t('confirm_delete_account'))) return;
|
|
12804
|
+
delete _ocConfig.channels[channelKey].accounts[accountId];
|
|
12805
|
+
renderOpenClawConfig(_ocConfig);
|
|
12806
|
+
switchOpenClawTab('channels');
|
|
12807
|
+
toast(t('openclaw_config_saved'), 'ok');
|
|
12808
|
+
}
|
|
12809
|
+
|
|
12810
|
+
async function saveOpenClawConfig() {
|
|
12811
|
+
if (!_ocConfig) return;
|
|
12812
|
+
// Collect defaults
|
|
12813
|
+
const defaults = _ocConfig.agents?.defaults || {};
|
|
12814
|
+
const model = defaults.model || {};
|
|
12815
|
+
const imgModel = defaults.imageModel || {};
|
|
12816
|
+
const subagents = defaults.subagents || {};
|
|
12817
|
+
const modelsRegistry = defaults.models || {};
|
|
12818
|
+
|
|
12819
|
+
// Collect fallbacks
|
|
12820
|
+
const fallbackItems = $$('.oc-fallback-item');
|
|
12821
|
+
const fallbacks = [];
|
|
12822
|
+
for (const el of fallbackItems) { if (el.value.trim()) fallbacks.push(el.value.trim()); }
|
|
12823
|
+
|
|
12824
|
+
const imgFallbackItems = $$('.oc-img-fallback-item');
|
|
12825
|
+
const imgFallbacks = [];
|
|
12826
|
+
for (const el of imgFallbackItems) { if (el.value.trim()) imgFallbacks.push(el.value.trim()); }
|
|
12827
|
+
|
|
12828
|
+
const modelRegKeys = $$('.oc-model-reg-key');
|
|
12829
|
+
const newModelsReg = {};
|
|
12830
|
+
for (const el of modelRegKeys) { if (el.value.trim()) newModelsReg[el.value.trim()] = {}; }
|
|
12831
|
+
|
|
12832
|
+
const newDefaults = {
|
|
12833
|
+
compaction: { mode: ($('#oc-compaction-mode')?.value || defaults.compaction?.mode || 'safeguard') },
|
|
12834
|
+
model: { primary: $('#oc-primary-model')?.value || model.primary || '', fallbacks },
|
|
12835
|
+
imageModel: { primary: $('#oc-img-model-primary')?.value || imgModel.primary || '', fallbacks: imgFallbacks },
|
|
12836
|
+
maxConcurrent: parseInt($('#oc-max-concurrent')?.value) || defaults.maxConcurrent || 4,
|
|
12837
|
+
subagents: { maxConcurrent: parseInt($('#oc-subagent-max')?.value) || subagents.maxConcurrent || 8 },
|
|
12838
|
+
thinkingDefault: $('#oc-thinking-default')?.value || defaults.thinkingDefault || 'off',
|
|
12839
|
+
workspace: $('#oc-workspace')?.value || defaults.workspace || '',
|
|
12840
|
+
models: newModelsReg,
|
|
12841
|
+
};
|
|
12842
|
+
|
|
12843
|
+
// Collect channels state
|
|
12844
|
+
const channels = _ocConfig.channels || {};
|
|
12845
|
+
const chEnabledEls = $$('.oc-ch-enabled');
|
|
12846
|
+
for (const el of chEnabledEls) {
|
|
12847
|
+
const ck = el.dataset.channel;
|
|
12848
|
+
if (channels[ck]) channels[ck].enabled = el.checked;
|
|
12849
|
+
}
|
|
12850
|
+
const chPolicyEls = $$('.oc-ch-policy');
|
|
12851
|
+
for (const el of chPolicyEls) {
|
|
12852
|
+
const ck = el.dataset.channel;
|
|
12853
|
+
if (channels[ck]) channels[ck].groupPolicy = el.value;
|
|
12854
|
+
}
|
|
12855
|
+
// Collect account data
|
|
12856
|
+
const tokenEls = $$('.oc-acc-token');
|
|
12857
|
+
for (const el of tokenEls) {
|
|
12858
|
+
const ck = el.dataset.channel;
|
|
12859
|
+
const ak = el.dataset.account;
|
|
12860
|
+
if (channels[ck]?.accounts?.[ak]) channels[ck].accounts[ak].botToken = el.value;
|
|
12861
|
+
}
|
|
12862
|
+
const dmPolicyEls = $$('.oc-acc-dm-policy');
|
|
12863
|
+
for (const el of dmPolicyEls) {
|
|
12864
|
+
const ck = el.dataset.channel;
|
|
12865
|
+
const ak = el.dataset.account;
|
|
12866
|
+
if (channels[ck]?.accounts?.[ak]) channels[ck].accounts[ak].dmPolicy = el.value;
|
|
12867
|
+
}
|
|
12868
|
+
const allowFromEls = $$('.oc-allow-from');
|
|
12869
|
+
for (const el of allowFromEls) {
|
|
12870
|
+
const ck = el.dataset.channel;
|
|
12871
|
+
const ak = el.dataset.account;
|
|
12872
|
+
if (channels[ck]?.accounts?.[ak]) {
|
|
12873
|
+
if (!channels[ck].accounts[ak].allowFrom) channels[ck].accounts[ak].allowFrom = [];
|
|
12874
|
+
channels[ck].accounts[ak].allowFrom.push(el.value.trim());
|
|
12875
|
+
}
|
|
12876
|
+
}
|
|
12877
|
+
// Deduplicate allowFrom per account
|
|
12878
|
+
for (const ck of Object.keys(channels)) {
|
|
12879
|
+
const ch = channels[ck];
|
|
12880
|
+
if (!ch.accounts) continue;
|
|
12881
|
+
for (const ak of Object.keys(ch.accounts)) {
|
|
12882
|
+
const af = ch.accounts[ak].allowFrom;
|
|
12883
|
+
if (Array.isArray(af)) ch.accounts[ak].allowFrom = [...new Set(af)].filter(x => x);
|
|
12884
|
+
}
|
|
12885
|
+
}
|
|
12886
|
+
|
|
12887
|
+
const payload = {
|
|
12888
|
+
agents: { defaults: newDefaults, list: _ocConfig.agents?.list || [] },
|
|
12889
|
+
bindings: _ocConfig.bindings || [],
|
|
12890
|
+
channels,
|
|
12891
|
+
};
|
|
12892
|
+
|
|
12893
|
+
try {
|
|
12894
|
+
const r = await api('/openclaw-config', { method: 'PUT', body: JSON.stringify(payload) });
|
|
12895
|
+
if (r.success) {
|
|
12896
|
+
toast(t('openclaw_config_saved'), 'ok');
|
|
12897
|
+
_ocConfig = await api('/openclaw-config');
|
|
12898
|
+
renderOpenClawConfig(_ocConfig);
|
|
12899
|
+
} else {
|
|
12900
|
+
toast(r.message || t('failed_save'), 'err');
|
|
12901
|
+
}
|
|
12902
|
+
} catch (e) {
|
|
12903
|
+
toast(e.message || t('failed_save'), 'err');
|
|
11813
12904
|
}
|
|
11814
12905
|
}
|
|
11815
12906
|
|
|
@@ -11833,7 +12924,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
11833
12924
|
const dot = $('#header-dot');
|
|
11834
12925
|
if (dot) { dot.className = 'dot ' + (s.connected ? 'dot-ok' : 'dot-err'); }
|
|
11835
12926
|
const txt = $('#header-status');
|
|
11836
|
-
if (txt) txt.textContent = s.connected ? '
|
|
12927
|
+
if (txt) txt.textContent = s.connected ? t('connected') : t('disconnected');
|
|
11837
12928
|
// Auto-remove offline banner when server reconnects
|
|
11838
12929
|
if (s.connected) hideOfflineBanner();
|
|
11839
12930
|
}
|
|
@@ -11872,6 +12963,10 @@ var HTML = `<!DOCTYPE html>
|
|
|
11872
12963
|
<div class="nav-group-title">System</div>
|
|
11873
12964
|
<div class="nav-item" data-page="settings"><span class="nav-icon">\u2699\uFE0F</span><span class="nav-label">Settings</span></div>
|
|
11874
12965
|
</div>
|
|
12966
|
+
<div class="nav-group">
|
|
12967
|
+
<div class="nav-group-title">OpenClaw</div>
|
|
12968
|
+
<div class="nav-item" data-page="openclaw"><span class="nav-icon">\u2699\uFE0F</span><span class="nav-label">OpenClaw</span></div>
|
|
12969
|
+
</div>
|
|
11875
12970
|
</nav>
|
|
11876
12971
|
<div class="sidebar-footer" onclick="toggleSidebar()">
|
|
11877
12972
|
<span>\u25C0</span><span class="sidebar-footer-text">Collapse sidebar</span>
|
|
@@ -11914,6 +13009,9 @@ var HTML = `<!DOCTYPE html>
|
|
|
11914
13009
|
<div id="settings-content"></div>
|
|
11915
13010
|
</div>
|
|
11916
13011
|
|
|
13012
|
+
<!-- OpenClaw Config -->
|
|
13013
|
+
<div class="page" id="page-openclaw"><div id="openclaw-content"></div></div>
|
|
13014
|
+
|
|
11917
13015
|
</div>
|
|
11918
13016
|
</main>
|
|
11919
13017
|
</div>
|
|
@@ -11972,6 +13070,7 @@ var HTML = `<!DOCTYPE html>
|
|
|
11972
13070
|
<div class="input-prefix"><span class="prefix">\u{1F310}</span><input id="model-base-url" type="text" placeholder="https://..."></div>
|
|
11973
13071
|
<div class="hint">Custom endpoint URL. Only needed for proxies or self-hosted models.</div>
|
|
11974
13072
|
</div>
|
|
13073
|
+
<div id="model-multi-list" style="display:none;margin-top:12px"></div>
|
|
11975
13074
|
<div class="form-actions">
|
|
11976
13075
|
<button class="btn btn-default" onclick="hideModal('modal-model-config')">Cancel</button>
|
|
11977
13076
|
<button class="btn btn-primary" onclick="saveModelConfig()">\u{1F4BE} Save Configuration</button>
|
|
@@ -11979,6 +13078,39 @@ var HTML = `<!DOCTYPE html>
|
|
|
11979
13078
|
</div>
|
|
11980
13079
|
</div>
|
|
11981
13080
|
|
|
13081
|
+
<!-- Modal: Custom Provider -->
|
|
13082
|
+
<div class="modal-overlay hidden" id="modal-custom-provider" onclick="if(event.target===this)hideModal('modal-custom-provider')">
|
|
13083
|
+
<div class="modal" style="max-width:520px">
|
|
13084
|
+
<div class="modal-header"><h3>\u{1F9E9} <span id="custom-provider-title">Custom Provider / \u81EA\u5B9A\u4E49\u63D0\u4F9B\u5546</span></h3><button class="modal-close" onclick="hideModal('modal-custom-provider')">\u2715</button></div>
|
|
13085
|
+
<p style="font-size:12px;color:var(--text3);margin-bottom:16px">Manually add custom model providers compatible with OpenAI API format. / \u624B\u52A8\u6DFB\u52A0\u517C\u5BB9 OpenAI API \u683C\u5F0F\u7684\u81EA\u5B9A\u4E49\u6A21\u578B\u63D0\u4F9B\u5546</p>
|
|
13086
|
+
<div class="form-group">
|
|
13087
|
+
<label>\u{1F4E6} Provider Name / \u63D0\u4F9B\u5546\u540D\u79F0 *</label>
|
|
13088
|
+
<input id="custom-name" type="text" placeholder="e.g., My Custom API / \u4F8B\u5982\uFF1AMy Custom API">
|
|
13089
|
+
</div>
|
|
13090
|
+
<div class="form-group">
|
|
13091
|
+
<label>\u{1F511} API Key</label>
|
|
13092
|
+
<input id="custom-api-key" type="password" placeholder="sk-...">
|
|
13093
|
+
<div class="hint">Leave blank to keep the existing key when editing.</div>
|
|
13094
|
+
</div>
|
|
13095
|
+
<div class="form-group">
|
|
13096
|
+
<label>\u{1F916} Model ID</label>
|
|
13097
|
+
<input id="custom-model-id" type="text" placeholder="gpt-4o">
|
|
13098
|
+
</div>
|
|
13099
|
+
<div class="form-group">
|
|
13100
|
+
<label>\u{1F310} Base URL</label>
|
|
13101
|
+
<input id="custom-base-url" type="text" placeholder="https://api.example.com/v1">
|
|
13102
|
+
</div>
|
|
13103
|
+
<div class="form-group">
|
|
13104
|
+
<label>\u{1F4DD} Description / \u63CF\u8FF0</label>
|
|
13105
|
+
<textarea id="custom-description" rows="2" placeholder="Optional: describe what this provider is for / \u53EF\u9009\uFF1A\u63CF\u8FF0\u6B64\u63D0\u4F9B\u5546\u7684\u7528\u9014"></textarea>
|
|
13106
|
+
</div>
|
|
13107
|
+
<div class="form-actions">
|
|
13108
|
+
<button class="btn btn-default" onclick="hideModal('modal-custom-provider')">Cancel / \u53D6\u6D88</button>
|
|
13109
|
+
<button class="btn btn-primary" onclick="saveCustomProvider()">\u{1F4BE} Save / \u4FDD\u5B58</button>
|
|
13110
|
+
</div>
|
|
13111
|
+
</div>
|
|
13112
|
+
</div>
|
|
13113
|
+
|
|
11982
13114
|
<!-- Modal: View Agent -->
|
|
11983
13115
|
<div class="modal-overlay hidden" id="modal-view-agent" onclick="if(event.target===this)hideModal('modal-view-agent')">
|
|
11984
13116
|
<div class="modal">
|
|
@@ -11994,7 +13126,7 @@ var HTML = `<!DOCTYPE html>
|
|
|
11994
13126
|
<div class="modal-header"><h3>\u{1F5D1}\uFE0F Reset Agent Identity</h3><button class="modal-close" onclick="hideModal('modal-reset-identity')">\u2715</button></div>
|
|
11995
13127
|
<div style="margin-bottom:16px">
|
|
11996
13128
|
<div class="card" style="border-color:var(--danger);background:var(--danger-bg)">
|
|
11997
|
-
<p style="font-size:13px;color:#
|
|
13129
|
+
<p style="font-size:13px;color:#991b1b;line-height:1.6">
|
|
11998
13130
|
<strong>\u26A0\uFE0F WARNING: This is a destructive operation!</strong><br><br>
|
|
11999
13131
|
This will permanently delete:<br>
|
|
12000
13132
|
\u2022 Your Ed25519 key pair and agent ID<br>
|
|
@@ -12121,29 +13253,59 @@ var MODEL_PROVIDERS = [
|
|
|
12121
13253
|
{ id: "openrouter", name: "OpenRouter", description: "Unified API for 200+ open-source and commercial models", apiKeyHint: "sk-or-...", modelHint: "openai/gpt-4o", baseUrlHint: "https://openrouter.ai/api/v1", configKey: "openrouter" },
|
|
12122
13254
|
{ id: "mistral", name: "Mistral AI", description: "Mistral Large, Medium, Small, Codestral", apiKeyHint: "(your key)", modelHint: "mistral-large-latest", baseUrlHint: "https://api.mistral.ai/v1", configKey: "mistral" },
|
|
12123
13255
|
{ id: "together", name: "Together AI", description: "Open-source models with fast inference", apiKeyHint: "...", modelHint: "meta-llama/Llama-3-70b-chat-hf", baseUrlHint: "https://api.together.xyz/v1", configKey: "together" },
|
|
12124
|
-
{ id: "fireworks", name: "Fireworks AI", description: "Fast open-source model serving", apiKeyHint: "...", modelHint: "accounts/fireworks/models/llama-v3-70b-instruct", baseUrlHint: "https://api.fireworks.ai/inference/v1", configKey: "fireworks" }
|
|
13256
|
+
{ id: "fireworks", name: "Fireworks AI", description: "Fast open-source model serving", apiKeyHint: "...", modelHint: "accounts/fireworks/models/llama-v3-70b-instruct", baseUrlHint: "https://api.fireworks.ai/inference/v1", configKey: "fireworks" },
|
|
13257
|
+
// Chinese / local providers
|
|
13258
|
+
{ id: "modelscope", name: "ModelScope (\u9B54\u642D)", description: "ZhipuAI GLM-5, Kimi-K2.5, MiniMax, Step models", apiKeyHint: "ms-...", modelHint: "Qwen/Qwen3-235B-A22B", baseUrlHint: "https://api-inference.modelscope.cn/v1", configKey: "modelscope" },
|
|
13259
|
+
{ id: "zhipu", name: "Zhipu AI (\u667A\u8C31)", description: "GLM-5, GLM-4, CogView, CogVideoX", apiKeyHint: "...", modelHint: "glm-5", baseUrlHint: "https://open.bigmodel.cn/api/paas/v4", configKey: "zhipu" },
|
|
13260
|
+
{ id: "qwen", name: "Alibaba Qwen (\u901A\u4E49\u5343\u95EE)", description: "Qwen3-235B, Qwen3-32B, Qwen-VL", apiKeyHint: "sk-...", modelHint: "qwen3-235b-a22b", baseUrlHint: "https://dashscope.aliyuncs.com/compatible-mode/v1", configKey: "qwen" },
|
|
13261
|
+
{ id: "doubao", name: "ByteDance Doubao (\u8C46\u5305)", description: "Doubao Pro, Doubao Lite \u2014 ByteDance LLMs", apiKeyHint: "...", modelHint: "doubao-pro-32k", baseUrlHint: "https://ark.cn-beijing.volces.com/api/v3", configKey: "doubao" },
|
|
13262
|
+
{ id: "moonshot", name: "Moonshot/Kimi (\u6708\u4E4B\u6697\u9762)", description: "Kimi-K2.5, Moonshot-v1 \u2014 strong Chinese reasoning", apiKeyHint: "sk-...", modelHint: "moonshot-v1-128k", baseUrlHint: "https://api.moonshot.cn/v1", configKey: "moonshot" },
|
|
13263
|
+
{ id: "minimax", name: "MiniMax", description: "MiniMax-M2.5, abab \u2014 multimodal AI", apiKeyHint: "...", modelHint: "MiniMax-M2.5", baseUrlHint: "https://api.minimax.chat/v1", configKey: "minimax" },
|
|
13264
|
+
{ id: "stepfun", name: "StepFun (\u9636\u8DC3\u661F\u8FB0)", description: "Step-3.5, Step-2 \u2014 reasoning models", apiKeyHint: "...", modelHint: "step-3.5-flash", baseUrlHint: "https://api.stepfun.com/v1", configKey: "stepfun" },
|
|
13265
|
+
{ id: "baidu", name: "Baidu Wenxin (\u767E\u5EA6\u6587\u5FC3)", description: "ERNIE 4.0, ERNIE 3.5 \u2014 Baidu LLMs", apiKeyHint: "...", modelHint: "ernie-4.0-8k", baseUrlHint: "https://aip.baidubce.com/rpc/2.0/ai_custom", configKey: "baidu" },
|
|
13266
|
+
{ id: "spark", name: "iFlytek Spark (\u8BAF\u98DE\u661F\u706B)", description: "Spark 4.0 Ultra, Spark 3.5 \u2014 iFlytek LLMs", apiKeyHint: "...", modelHint: "spark-4.0-ultra", baseUrlHint: "https://spark-api-open.xf-yun.com/v1", configKey: "spark" }
|
|
12125
13267
|
];
|
|
12126
13268
|
function findConfigPath() {
|
|
12127
|
-
|
|
12128
|
-
|
|
13269
|
+
function searchUpward(fileName) {
|
|
13270
|
+
let dir = process.cwd();
|
|
13271
|
+
for (let i = 0; i < 10; i++) {
|
|
13272
|
+
const candidate = path5.join(dir, fileName);
|
|
13273
|
+
try {
|
|
13274
|
+
if (fs5.existsSync(candidate))
|
|
13275
|
+
return candidate;
|
|
13276
|
+
} catch {
|
|
13277
|
+
}
|
|
13278
|
+
const parent = path5.dirname(dir);
|
|
13279
|
+
if (parent === dir)
|
|
13280
|
+
break;
|
|
13281
|
+
dir = parent;
|
|
13282
|
+
}
|
|
13283
|
+
return null;
|
|
13284
|
+
}
|
|
13285
|
+
const fromCwd = searchUpward("openclaw.json");
|
|
13286
|
+
if (fromCwd)
|
|
13287
|
+
return fromCwd;
|
|
13288
|
+
const openclawHomePaths = [
|
|
12129
13289
|
path5.join(os.homedir(), ".config", "openclaw", "openclaw.json"),
|
|
12130
13290
|
path5.join(os.homedir(), ".openclaw", "openclaw.json"),
|
|
12131
13291
|
path5.join(os.homedir(), "openclaw.json")
|
|
12132
13292
|
];
|
|
12133
|
-
for (const p of
|
|
13293
|
+
for (const p of openclawHomePaths) {
|
|
12134
13294
|
try {
|
|
12135
13295
|
if (fs5.existsSync(p))
|
|
12136
13296
|
return p;
|
|
12137
13297
|
} catch {
|
|
12138
13298
|
}
|
|
12139
13299
|
}
|
|
12140
|
-
const
|
|
12141
|
-
|
|
13300
|
+
const fromCwdStable = searchUpward("stableclaw.json");
|
|
13301
|
+
if (fromCwdStable)
|
|
13302
|
+
return fromCwdStable;
|
|
13303
|
+
const stableclawHomePaths = [
|
|
12142
13304
|
path5.join(os.homedir(), ".config", "stableclaw", "stableclaw.json"),
|
|
12143
13305
|
path5.join(os.homedir(), ".stableclaw", "stableclaw.json"),
|
|
12144
13306
|
path5.join(os.homedir(), "stableclaw.json")
|
|
12145
13307
|
];
|
|
12146
|
-
for (const p of
|
|
13308
|
+
for (const p of stableclawHomePaths) {
|
|
12147
13309
|
try {
|
|
12148
13310
|
if (fs5.existsSync(p))
|
|
12149
13311
|
return p;
|
|
@@ -12177,9 +13339,30 @@ function writeConfig(config2) {
|
|
|
12177
13339
|
}
|
|
12178
13340
|
function extractAgentsFromConfig(config2) {
|
|
12179
13341
|
const agents = [];
|
|
12180
|
-
const
|
|
12181
|
-
if (Array.isArray(
|
|
12182
|
-
|
|
13342
|
+
const agentsObj = config2.agents;
|
|
13343
|
+
if (typeof agentsObj === "object" && agentsObj !== null && !Array.isArray(agentsObj)) {
|
|
13344
|
+
const agentsRecord = agentsObj;
|
|
13345
|
+
if (typeof agentsRecord.defaults === "object" && agentsRecord.defaults !== null) {
|
|
13346
|
+
agents.push({
|
|
13347
|
+
_source: "agents-defaults",
|
|
13348
|
+
name: "(Defaults)",
|
|
13349
|
+
...agentsRecord.defaults
|
|
13350
|
+
});
|
|
13351
|
+
}
|
|
13352
|
+
const listArr = agentsRecord.list;
|
|
13353
|
+
if (Array.isArray(listArr)) {
|
|
13354
|
+
for (const a of listArr) {
|
|
13355
|
+
if (typeof a === "object" && a !== null) {
|
|
13356
|
+
agents.push({
|
|
13357
|
+
_source: "agents-list",
|
|
13358
|
+
...a
|
|
13359
|
+
});
|
|
13360
|
+
}
|
|
13361
|
+
}
|
|
13362
|
+
}
|
|
13363
|
+
}
|
|
13364
|
+
if (agents.length === 0 && Array.isArray(config2.agents)) {
|
|
13365
|
+
for (const a of config2.agents) {
|
|
12183
13366
|
if (typeof a === "object" && a !== null) {
|
|
12184
13367
|
agents.push(a);
|
|
12185
13368
|
}
|
|
@@ -12187,7 +13370,56 @@ function extractAgentsFromConfig(config2) {
|
|
|
12187
13370
|
}
|
|
12188
13371
|
const agentVal = config2.agent;
|
|
12189
13372
|
if (typeof agentVal === "object" && agentVal !== null && !Array.isArray(agentVal)) {
|
|
12190
|
-
|
|
13373
|
+
const agentRecord = agentVal;
|
|
13374
|
+
if (agentRecord.model) {
|
|
13375
|
+
const existingPrimary = agents.find((a) => a.default === true || a.isDefault === true);
|
|
13376
|
+
if (!existingPrimary) {
|
|
13377
|
+
agents.unshift({
|
|
13378
|
+
_source: "agent-primary",
|
|
13379
|
+
name: "Primary Agent",
|
|
13380
|
+
default: true,
|
|
13381
|
+
...agentRecord
|
|
13382
|
+
});
|
|
13383
|
+
}
|
|
13384
|
+
} else if (agents.length === 0) {
|
|
13385
|
+
agents.unshift({
|
|
13386
|
+
_source: "agent-single",
|
|
13387
|
+
...agentRecord
|
|
13388
|
+
});
|
|
13389
|
+
}
|
|
13390
|
+
}
|
|
13391
|
+
const modelsSection = config2.models;
|
|
13392
|
+
const defaultModel = modelsSection?.defaultModel || "";
|
|
13393
|
+
if (modelsSection && typeof modelsSection === "object") {
|
|
13394
|
+
const providersSection = modelsSection.providers;
|
|
13395
|
+
if (providersSection && typeof providersSection === "object") {
|
|
13396
|
+
for (const [providerId, providerConfig] of Object.entries(providersSection)) {
|
|
13397
|
+
if (typeof providerConfig !== "object" || providerConfig === null)
|
|
13398
|
+
continue;
|
|
13399
|
+
const pc = providerConfig;
|
|
13400
|
+
const modelsArr = pc.models;
|
|
13401
|
+
if (Array.isArray(modelsArr)) {
|
|
13402
|
+
for (let mi = 0; mi < modelsArr.length; mi++) {
|
|
13403
|
+
const m = modelsArr[mi];
|
|
13404
|
+
if (typeof m === "object" && m !== null) {
|
|
13405
|
+
const mObj = m;
|
|
13406
|
+
const agentEntry = {
|
|
13407
|
+
_source: "provider-model",
|
|
13408
|
+
_providerId: providerId,
|
|
13409
|
+
_modelIndex: mi,
|
|
13410
|
+
_configPath: "models.providers." + providerId + ".models." + mi,
|
|
13411
|
+
name: mObj.name || mObj.id || providerId + "/" + mi,
|
|
13412
|
+
model: mObj.id || "",
|
|
13413
|
+
provider: providerId,
|
|
13414
|
+
enabled: true,
|
|
13415
|
+
isDefault: defaultModel === mObj.id
|
|
13416
|
+
};
|
|
13417
|
+
agents.push(agentEntry);
|
|
13418
|
+
}
|
|
13419
|
+
}
|
|
13420
|
+
}
|
|
13421
|
+
}
|
|
13422
|
+
}
|
|
12191
13423
|
}
|
|
12192
13424
|
if (agents.length === 0) {
|
|
12193
13425
|
for (const [key, val] of Object.entries(config2)) {
|
|
@@ -12208,34 +13440,174 @@ function getModelProviders(config2) {
|
|
|
12208
13440
|
if (model?.providers)
|
|
12209
13441
|
providersSection = model.providers;
|
|
12210
13442
|
}
|
|
13443
|
+
if (!providersSection || typeof providersSection !== "object") {
|
|
13444
|
+
const models = config2.models;
|
|
13445
|
+
if (models?.providers)
|
|
13446
|
+
providersSection = models.providers;
|
|
13447
|
+
}
|
|
13448
|
+
const modelsSection = config2.models;
|
|
13449
|
+
let defaultModel = modelsSection?.defaultModel || "";
|
|
13450
|
+
const agentObj = config2.agent;
|
|
13451
|
+
const agentModel = agentObj?.model;
|
|
13452
|
+
if (typeof agentModel === "string" && !defaultModel) {
|
|
13453
|
+
defaultModel = agentModel;
|
|
13454
|
+
} else if (typeof agentModel === "object" && agentModel) {
|
|
13455
|
+
if (!defaultModel)
|
|
13456
|
+
defaultModel = agentModel.primary || "";
|
|
13457
|
+
}
|
|
13458
|
+
const authProfiles = {};
|
|
13459
|
+
const authObj = config2.auth;
|
|
13460
|
+
if (authObj && typeof authObj === "object") {
|
|
13461
|
+
const profiles = authObj.profiles;
|
|
13462
|
+
if (profiles && typeof profiles === "object") {
|
|
13463
|
+
for (const [profileKey, profileVal] of Object.entries(profiles)) {
|
|
13464
|
+
if (typeof profileVal !== "object" || profileVal === null)
|
|
13465
|
+
continue;
|
|
13466
|
+
const pv = profileVal;
|
|
13467
|
+
authProfiles[profileKey] = {
|
|
13468
|
+
provider: pv.provider || "",
|
|
13469
|
+
mode: pv.mode || ""
|
|
13470
|
+
};
|
|
13471
|
+
}
|
|
13472
|
+
}
|
|
13473
|
+
}
|
|
13474
|
+
const envVars = {};
|
|
13475
|
+
const envObj = config2.env;
|
|
13476
|
+
if (envObj && typeof envObj === "object") {
|
|
13477
|
+
const vars = envObj.vars;
|
|
13478
|
+
if (vars && typeof vars === "object") {
|
|
13479
|
+
for (const [key, val] of Object.entries(vars)) {
|
|
13480
|
+
if (typeof val === "string") {
|
|
13481
|
+
envVars[key] = val;
|
|
13482
|
+
}
|
|
13483
|
+
}
|
|
13484
|
+
}
|
|
13485
|
+
for (const [key, val] of Object.entries(envObj)) {
|
|
13486
|
+
if (key === "vars" || key === "shellEnv")
|
|
13487
|
+
continue;
|
|
13488
|
+
if (typeof val === "string" && (key.includes("API_KEY") || key.includes("_KEY") || key.toLowerCase().includes("apikey"))) {
|
|
13489
|
+
envVars[key] = val;
|
|
13490
|
+
}
|
|
13491
|
+
}
|
|
13492
|
+
}
|
|
12211
13493
|
const providers = MODEL_PROVIDERS.map((p) => {
|
|
12212
13494
|
const pc = providersSection?.[p.configKey] ?? config2[p.configKey];
|
|
12213
|
-
|
|
12214
|
-
|
|
13495
|
+
let apiKey = pc?.apiKey || "";
|
|
13496
|
+
if (!apiKey) {
|
|
13497
|
+
for (const [profileKey, profile] of Object.entries(authProfiles)) {
|
|
13498
|
+
if (profile.provider === p.configKey && profile.mode === "api_key") {
|
|
13499
|
+
apiKey = "__auth_profile__";
|
|
13500
|
+
}
|
|
13501
|
+
}
|
|
13502
|
+
}
|
|
13503
|
+
if (apiKey === "" || apiKey === "__auth_profile__") {
|
|
13504
|
+
for (const [envKey, envVal] of Object.entries(envVars)) {
|
|
13505
|
+
if (envKey.toUpperCase().includes(p.configKey.toUpperCase()) || p.configKey === "openai" && envKey.toUpperCase().includes("OPENAI") || p.configKey === "anthropic" && envKey.toUpperCase().includes("ANTHROPIC") || p.configKey === "google" && envKey.toUpperCase().includes("GOOGLE") || p.configKey === "groq" && envKey.toUpperCase().includes("GROQ") || p.configKey === "deepseek" && envKey.toUpperCase().includes("DEEPSEEK") || p.configKey === "openrouter" && envKey.toUpperCase().includes("OPENROUTER") || p.configKey === "zhipu" && envKey.toUpperCase().includes("ZHIPU") || p.configKey === "qwen" && envKey.toUpperCase().includes("QWEN") || p.configKey === "moonshot" && envKey.toUpperCase().includes("MOONSHOT") || p.configKey === "doubao" && envKey.toUpperCase().includes("DOUBAO") || p.configKey === "stepfun" && envKey.toUpperCase().includes("STEPFUN")) {
|
|
13506
|
+
apiKey = envVal;
|
|
13507
|
+
break;
|
|
13508
|
+
}
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
let singleModelId = pc?.model || pc?.defaultModel || "";
|
|
13512
|
+
if (!singleModelId && defaultModel && defaultModel.startsWith(p.configKey + "/")) {
|
|
13513
|
+
singleModelId = defaultModel.split("/").slice(1).join("/");
|
|
13514
|
+
}
|
|
12215
13515
|
const baseUrl = pc?.baseUrl || pc?.baseURL || "";
|
|
13516
|
+
let modelsList = [];
|
|
13517
|
+
const modelsArr = pc?.models;
|
|
13518
|
+
if (Array.isArray(modelsArr)) {
|
|
13519
|
+
modelsList = modelsArr.filter((m) => typeof m === "object" && m !== null).map((m) => m);
|
|
13520
|
+
}
|
|
13521
|
+
let modelId = singleModelId;
|
|
13522
|
+
if (modelsList.length > 0 && !modelId) {
|
|
13523
|
+
modelId = defaultModel || modelsList[0]?.id || "";
|
|
13524
|
+
}
|
|
12216
13525
|
return {
|
|
12217
13526
|
...p,
|
|
12218
13527
|
configured: Boolean(apiKey || p.id === "ollama" && baseUrl),
|
|
12219
13528
|
apiKey: apiKey ? apiKey.substring(0, 6) + "\u2022\u2022\u2022\u2022\u2022\u2022" + apiKey.slice(-4) : "",
|
|
12220
13529
|
apiKeyHasValue: Boolean(apiKey),
|
|
12221
13530
|
modelId,
|
|
12222
|
-
baseUrl
|
|
13531
|
+
baseUrl,
|
|
13532
|
+
models: modelsList,
|
|
13533
|
+
modelCount: modelsList.length,
|
|
13534
|
+
isCustom: false
|
|
12223
13535
|
};
|
|
12224
13536
|
});
|
|
13537
|
+
const customProvidersSection = providersSection?.custom;
|
|
13538
|
+
const customProviders = [];
|
|
13539
|
+
if (Array.isArray(customProvidersSection)) {
|
|
13540
|
+
for (let i = 0; i < customProvidersSection.length; i++) {
|
|
13541
|
+
const cp = customProvidersSection[i];
|
|
13542
|
+
if (typeof cp !== "object" || cp === null)
|
|
13543
|
+
continue;
|
|
13544
|
+
const cpObj = cp;
|
|
13545
|
+
const cApiKey = cpObj.apiKey || "";
|
|
13546
|
+
const cModelId = cpObj.modelId || cpObj.model || "";
|
|
13547
|
+
const cBaseUrl = cpObj.baseUrl || cpObj.baseURL || "";
|
|
13548
|
+
const cId = cpObj.id || "custom-" + i;
|
|
13549
|
+
let cModelsList = [];
|
|
13550
|
+
const cModelsArr = cpObj.models;
|
|
13551
|
+
if (Array.isArray(cModelsArr)) {
|
|
13552
|
+
cModelsList = cModelsArr.filter((m) => typeof m === "object" && m !== null).map((m) => m);
|
|
13553
|
+
}
|
|
13554
|
+
providers.push({
|
|
13555
|
+
id: cId,
|
|
13556
|
+
name: cpObj.name || cId,
|
|
13557
|
+
description: cpObj.description || "Custom provider",
|
|
13558
|
+
configured: Boolean(cApiKey || cBaseUrl),
|
|
13559
|
+
apiKey: cApiKey ? cApiKey.substring(0, 6) + "\u2022\u2022\u2022\u2022\u2022\u2022" + cApiKey.slice(-4) : "",
|
|
13560
|
+
apiKeyHasValue: Boolean(cApiKey),
|
|
13561
|
+
apiKeyHint: "sk-...",
|
|
13562
|
+
modelId: cModelsList.length > 0 ? defaultModel || cModelsList[0]?.id || "" : cModelId,
|
|
13563
|
+
modelHint: cModelId || "model-name",
|
|
13564
|
+
baseUrl: cBaseUrl,
|
|
13565
|
+
baseUrlHint: cBaseUrl || "https://...",
|
|
13566
|
+
models: cModelsList,
|
|
13567
|
+
modelCount: cModelsList.length,
|
|
13568
|
+
isCustom: true
|
|
13569
|
+
});
|
|
13570
|
+
}
|
|
13571
|
+
}
|
|
12225
13572
|
const currentModels = [];
|
|
12226
|
-
for (const p of
|
|
12227
|
-
|
|
12228
|
-
|
|
13573
|
+
for (const p of providers) {
|
|
13574
|
+
if (!p.apiKeyHasValue)
|
|
13575
|
+
continue;
|
|
13576
|
+
const pId = p.id;
|
|
13577
|
+
const pName = p.name;
|
|
13578
|
+
const isCustom = p.isCustom;
|
|
13579
|
+
const pModels = p.models;
|
|
13580
|
+
const pBaseUrl = p.baseUrl;
|
|
13581
|
+
const pModelHint = p.modelHint || "";
|
|
13582
|
+
if (pModels && pModels.length > 0) {
|
|
13583
|
+
for (const m of pModels) {
|
|
13584
|
+
if (typeof m === "object" && m !== null) {
|
|
13585
|
+
const mObj = m;
|
|
13586
|
+
currentModels.push({
|
|
13587
|
+
provider: pName,
|
|
13588
|
+
providerId: pId,
|
|
13589
|
+
modelId: mObj.id || "",
|
|
13590
|
+
modelName: mObj.name || "",
|
|
13591
|
+
hasApiKey: true,
|
|
13592
|
+
baseUrl: pBaseUrl,
|
|
13593
|
+
isDefault: defaultModel === mObj.id,
|
|
13594
|
+
isCustom
|
|
13595
|
+
});
|
|
13596
|
+
}
|
|
13597
|
+
}
|
|
13598
|
+
} else {
|
|
12229
13599
|
currentModels.push({
|
|
12230
|
-
provider:
|
|
12231
|
-
providerId:
|
|
12232
|
-
modelId:
|
|
13600
|
+
provider: pName,
|
|
13601
|
+
providerId: pId,
|
|
13602
|
+
modelId: p.modelId || pModelHint,
|
|
12233
13603
|
hasApiKey: true,
|
|
12234
|
-
baseUrl:
|
|
13604
|
+
baseUrl: pBaseUrl,
|
|
13605
|
+
isDefault: false,
|
|
13606
|
+
isCustom
|
|
12235
13607
|
});
|
|
12236
13608
|
}
|
|
12237
13609
|
}
|
|
12238
|
-
return { providers, currentModels };
|
|
13610
|
+
return { providers, currentModels, defaultModel, customProviders: customProvidersSection || [] };
|
|
12239
13611
|
}
|
|
12240
13612
|
function parseApiPath(reqUrl) {
|
|
12241
13613
|
if (!reqUrl)
|
|
@@ -12354,14 +13726,33 @@ function createManagementHandler(ctx) {
|
|
|
12354
13726
|
});
|
|
12355
13727
|
}
|
|
12356
13728
|
if (apiPath.startsWith("/agents/") && method === "DELETE") {
|
|
12357
|
-
const
|
|
12358
|
-
const idx = parseInt(idxStr, 10);
|
|
12359
|
-
if (isNaN(idx) || idx < 0)
|
|
12360
|
-
return json(res, { success: false, message: "Invalid agent index" }, 400);
|
|
13729
|
+
const identifier = decodeURIComponent(apiPath.slice("/agents/".length));
|
|
12361
13730
|
const result = readConfig();
|
|
12362
13731
|
if (!result)
|
|
12363
13732
|
return json(res, { success: false, message: "No config file found" }, 400);
|
|
12364
13733
|
const config2 = result.config;
|
|
13734
|
+
const providerModelMatch = identifier.match(/^provider:([^:]+):(\d+)$/);
|
|
13735
|
+
if (providerModelMatch) {
|
|
13736
|
+
const providerId = providerModelMatch[1];
|
|
13737
|
+
const modelIdx = parseInt(providerModelMatch[2], 10);
|
|
13738
|
+
const modelsSection = config2.models;
|
|
13739
|
+
const providersObj = modelsSection?.providers;
|
|
13740
|
+
const provConfig = providersObj?.[providerId];
|
|
13741
|
+
const modelsArr = provConfig?.models;
|
|
13742
|
+
if (!modelsArr || modelIdx >= modelsArr.length) {
|
|
13743
|
+
return json(res, { success: false, message: "Provider model index out of range" }, 400);
|
|
13744
|
+
}
|
|
13745
|
+
modelsArr.splice(modelIdx, 1);
|
|
13746
|
+
const written2 = writeConfig(config2);
|
|
13747
|
+
if (written2) {
|
|
13748
|
+
logger.info("[API] Provider model deleted: " + providerId + ".models[" + modelIdx + "]");
|
|
13749
|
+
return json(res, { success: true, message: "Model removed from " + providerId });
|
|
13750
|
+
}
|
|
13751
|
+
return json(res, { success: false, message: "Failed to write config" }, 500);
|
|
13752
|
+
}
|
|
13753
|
+
const idx = parseInt(identifier, 10);
|
|
13754
|
+
if (isNaN(idx) || idx < 0)
|
|
13755
|
+
return json(res, { success: false, message: "Invalid agent identifier" }, 400);
|
|
12365
13756
|
let agentsArr;
|
|
12366
13757
|
if (Array.isArray(config2.agents)) {
|
|
12367
13758
|
agentsArr = config2.agents;
|
|
@@ -12387,14 +13778,17 @@ function createManagementHandler(ctx) {
|
|
|
12387
13778
|
}
|
|
12388
13779
|
if (apiPath === "/friends" && method === "GET") {
|
|
12389
13780
|
try {
|
|
12390
|
-
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId
|
|
13781
|
+
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId, {
|
|
13782
|
+
headers: serverClient.authHeaders()
|
|
13783
|
+
});
|
|
12391
13784
|
if (!resp.ok)
|
|
12392
13785
|
return json(res, { error: "Server error: " + await resp.text() }, 502);
|
|
12393
13786
|
const data = await resp.json();
|
|
12394
13787
|
const friends = (data.friends || []).map((f) => {
|
|
12395
|
-
const
|
|
13788
|
+
const friendId = f.id || f.nodeId;
|
|
13789
|
+
const local = store.getFriend(friendId);
|
|
12396
13790
|
return {
|
|
12397
|
-
id:
|
|
13791
|
+
id: friendId,
|
|
12398
13792
|
publicKeyFingerprint: f.publicKeyFingerprint || local?.publicKeyFingerprint || "",
|
|
12399
13793
|
permissions: f.permissions || local?.permissions || [],
|
|
12400
13794
|
addedAt: f.addedAt || local?.addedAt?.toISOString() || null,
|
|
@@ -12426,7 +13820,9 @@ function createManagementHandler(ctx) {
|
|
|
12426
13820
|
let friendId = target;
|
|
12427
13821
|
if (isTempNumber) {
|
|
12428
13822
|
try {
|
|
12429
|
-
const resolveResp = await fetch(serverUrl + "/api/v1/temp-number/" + target
|
|
13823
|
+
const resolveResp = await fetch(serverUrl + "/api/v1/temp-number/" + target, {
|
|
13824
|
+
headers: serverClient.authHeaders()
|
|
13825
|
+
});
|
|
12430
13826
|
if (!resolveResp.ok)
|
|
12431
13827
|
return json(res, { success: false, message: "Temp number not found or expired" });
|
|
12432
13828
|
const resolveData = await resolveResp.json();
|
|
@@ -12439,7 +13835,7 @@ function createManagementHandler(ctx) {
|
|
|
12439
13835
|
try {
|
|
12440
13836
|
const hsResp = await fetch(serverUrl + "/api/v1/handshake/initiate", {
|
|
12441
13837
|
method: "POST",
|
|
12442
|
-
headers:
|
|
13838
|
+
headers: serverClient.authHeaders(),
|
|
12443
13839
|
body: JSON.stringify({ requesterId: aicqAgentId, targetTempNumber: target })
|
|
12444
13840
|
});
|
|
12445
13841
|
if (!hsResp.ok)
|
|
@@ -12462,7 +13858,7 @@ function createManagementHandler(ctx) {
|
|
|
12462
13858
|
try {
|
|
12463
13859
|
const rmResp = await fetch(serverUrl + "/api/v1/friends/" + friendId, {
|
|
12464
13860
|
method: "DELETE",
|
|
12465
|
-
headers:
|
|
13861
|
+
headers: serverClient.authHeaders(),
|
|
12466
13862
|
body: JSON.stringify({ nodeId: aicqAgentId })
|
|
12467
13863
|
});
|
|
12468
13864
|
if (!rmResp.ok)
|
|
@@ -12488,7 +13884,7 @@ function createManagementHandler(ctx) {
|
|
|
12488
13884
|
try {
|
|
12489
13885
|
const resp = await fetch(serverUrl + "/api/v1/friends/" + friendId + "/permissions", {
|
|
12490
13886
|
method: "PUT",
|
|
12491
|
-
headers:
|
|
13887
|
+
headers: serverClient.authHeaders(),
|
|
12492
13888
|
body: JSON.stringify({ nodeId: aicqAgentId, permissions })
|
|
12493
13889
|
});
|
|
12494
13890
|
if (!resp.ok)
|
|
@@ -12507,7 +13903,9 @@ function createManagementHandler(ctx) {
|
|
|
12507
13903
|
}
|
|
12508
13904
|
if (apiPath === "/friends/requests" && method === "GET") {
|
|
12509
13905
|
try {
|
|
12510
|
-
const resp = await fetch(serverUrl + "/api/v1/friends/requests?
|
|
13906
|
+
const resp = await fetch(serverUrl + "/api/v1/friends/requests?accountId=" + aicqAgentId, {
|
|
13907
|
+
headers: serverClient.authHeaders()
|
|
13908
|
+
});
|
|
12511
13909
|
if (!resp.ok)
|
|
12512
13910
|
return json(res, { error: "Server error: " + await resp.text() }, 502);
|
|
12513
13911
|
const data = await resp.json();
|
|
@@ -12530,8 +13928,8 @@ function createManagementHandler(ctx) {
|
|
|
12530
13928
|
try {
|
|
12531
13929
|
const resp = await fetch(serverUrl + "/api/v1/friends/requests/" + requestId + "/accept", {
|
|
12532
13930
|
method: "POST",
|
|
12533
|
-
headers:
|
|
12534
|
-
body: JSON.stringify({ permissions: body.permissions || ["chat"] })
|
|
13931
|
+
headers: serverClient.authHeaders(),
|
|
13932
|
+
body: JSON.stringify({ accountId: aicqAgentId, permissions: body.permissions || ["chat"] })
|
|
12535
13933
|
});
|
|
12536
13934
|
if (!resp.ok)
|
|
12537
13935
|
return json(res, { success: false, message: "Failed: " + await resp.text() });
|
|
@@ -12549,8 +13947,8 @@ function createManagementHandler(ctx) {
|
|
|
12549
13947
|
try {
|
|
12550
13948
|
const resp = await fetch(serverUrl + "/api/v1/friends/requests/" + requestId + "/reject", {
|
|
12551
13949
|
method: "POST",
|
|
12552
|
-
headers:
|
|
12553
|
-
body: JSON.stringify({})
|
|
13950
|
+
headers: serverClient.authHeaders(),
|
|
13951
|
+
body: JSON.stringify({ accountId: aicqAgentId })
|
|
12554
13952
|
});
|
|
12555
13953
|
if (!resp.ok)
|
|
12556
13954
|
return json(res, { success: false, message: "Failed: " + await resp.text() });
|
|
@@ -12572,9 +13970,37 @@ function createManagementHandler(ctx) {
|
|
|
12572
13970
|
if (apiPath === "/models" && method === "GET") {
|
|
12573
13971
|
const result = readConfig();
|
|
12574
13972
|
if (!result)
|
|
12575
|
-
return json(res, { providers: MODEL_PROVIDERS, currentModels: [], error: "No config file found" });
|
|
13973
|
+
return json(res, { providers: MODEL_PROVIDERS, currentModels: [], defaultModel: "", error: "No config file found" });
|
|
12576
13974
|
return json(res, getModelProviders(result.config));
|
|
12577
13975
|
}
|
|
13976
|
+
if (apiPath === "/models/default" && method === "GET") {
|
|
13977
|
+
const result = readConfig();
|
|
13978
|
+
if (!result)
|
|
13979
|
+
return json(res, { defaultModel: "", error: "No config file found" });
|
|
13980
|
+
const modelsSection = result.config.models;
|
|
13981
|
+
const defaultModel = modelsSection?.defaultModel || "";
|
|
13982
|
+
let defaultProvider = "";
|
|
13983
|
+
let defaultModelName = "";
|
|
13984
|
+
if (defaultModel && modelsSection?.providers) {
|
|
13985
|
+
const providersObj = modelsSection.providers;
|
|
13986
|
+
for (const [provId, provConfig] of Object.entries(providersObj)) {
|
|
13987
|
+
const pc = provConfig;
|
|
13988
|
+
if (Array.isArray(pc.models)) {
|
|
13989
|
+
const found = pc.models.find((m) => m.id === defaultModel);
|
|
13990
|
+
if (found) {
|
|
13991
|
+
defaultProvider = provId;
|
|
13992
|
+
defaultModelName = found.name || found.id || "";
|
|
13993
|
+
break;
|
|
13994
|
+
}
|
|
13995
|
+
}
|
|
13996
|
+
}
|
|
13997
|
+
}
|
|
13998
|
+
return json(res, {
|
|
13999
|
+
defaultModel,
|
|
14000
|
+
defaultProvider,
|
|
14001
|
+
defaultModelName
|
|
14002
|
+
});
|
|
14003
|
+
}
|
|
12578
14004
|
if (apiPath.match(/^\/models\/[^/]+$/) && method === "PUT") {
|
|
12579
14005
|
const providerId = decodeURIComponent(apiPath.slice("/models/".length));
|
|
12580
14006
|
const body = await readBody(req);
|
|
@@ -12608,6 +14034,140 @@ function createManagementHandler(ctx) {
|
|
|
12608
14034
|
logger.info("[API] Model config saved for provider: " + providerId);
|
|
12609
14035
|
return json(res, { success: true, message: "Model configuration saved for " + provider.name });
|
|
12610
14036
|
}
|
|
14037
|
+
if (apiPath === "/models/custom" && method === "POST") {
|
|
14038
|
+
const body = await readBody(req);
|
|
14039
|
+
const name = body.name?.trim();
|
|
14040
|
+
const apiKey = body.apiKey;
|
|
14041
|
+
const modelId = body.modelId;
|
|
14042
|
+
const baseUrl = body.baseUrl;
|
|
14043
|
+
const description = body.description;
|
|
14044
|
+
if (!name)
|
|
14045
|
+
return json(res, { success: false, message: "Provider name is required" }, 400);
|
|
14046
|
+
if (!apiKey && !modelId && !baseUrl) {
|
|
14047
|
+
return json(res, { success: false, message: "At least one of API Key, Model ID, or Base URL is required" }, 400);
|
|
14048
|
+
}
|
|
14049
|
+
const result = readConfig();
|
|
14050
|
+
if (!result)
|
|
14051
|
+
return json(res, { success: false, message: "No config file found" }, 400);
|
|
14052
|
+
const config2 = result.config;
|
|
14053
|
+
if (!config2.providers || typeof config2.providers !== "object") {
|
|
14054
|
+
config2.providers = {};
|
|
14055
|
+
}
|
|
14056
|
+
const providers = config2.providers;
|
|
14057
|
+
if (!Array.isArray(providers.custom)) {
|
|
14058
|
+
providers.custom = [];
|
|
14059
|
+
}
|
|
14060
|
+
const customArr = providers.custom;
|
|
14061
|
+
const newId = "custom-" + crypto.randomUUID().replace(/-/g, "").substring(0, 8);
|
|
14062
|
+
const duplicate = customArr.find((c) => c.name?.toLowerCase() === name.toLowerCase());
|
|
14063
|
+
if (duplicate) {
|
|
14064
|
+
return json(res, { success: false, message: "A provider with this name already exists" }, 409);
|
|
14065
|
+
}
|
|
14066
|
+
const newProvider = {
|
|
14067
|
+
id: newId,
|
|
14068
|
+
name,
|
|
14069
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
14070
|
+
};
|
|
14071
|
+
if (apiKey)
|
|
14072
|
+
newProvider.apiKey = apiKey;
|
|
14073
|
+
if (modelId)
|
|
14074
|
+
newProvider.modelId = modelId;
|
|
14075
|
+
if (baseUrl)
|
|
14076
|
+
newProvider.baseUrl = baseUrl;
|
|
14077
|
+
if (description)
|
|
14078
|
+
newProvider.description = description;
|
|
14079
|
+
customArr.push(newProvider);
|
|
14080
|
+
const written = writeConfig(config2);
|
|
14081
|
+
if (!written)
|
|
14082
|
+
return json(res, { success: false, message: "Failed to write config file" }, 500);
|
|
14083
|
+
logger.info("[API] Custom provider added: " + name + " (" + newId + ")");
|
|
14084
|
+
return json(res, { success: true, message: "Custom provider '" + name + "' added", providerId: newId });
|
|
14085
|
+
}
|
|
14086
|
+
if (apiPath.match(/^\/models\/custom\/[^/]+$/) && method === "PUT") {
|
|
14087
|
+
const customId = decodeURIComponent(apiPath.slice("/models/custom/".length));
|
|
14088
|
+
const body = await readBody(req);
|
|
14089
|
+
const result = readConfig();
|
|
14090
|
+
if (!result)
|
|
14091
|
+
return json(res, { success: false, message: "No config file found" }, 400);
|
|
14092
|
+
const config2 = result.config;
|
|
14093
|
+
const providers = config2.providers;
|
|
14094
|
+
const customArr = providers?.custom;
|
|
14095
|
+
if (!Array.isArray(customArr)) {
|
|
14096
|
+
return json(res, { success: false, message: "No custom providers found" }, 404);
|
|
14097
|
+
}
|
|
14098
|
+
const idx = customArr.findIndex((c) => c.id === customId);
|
|
14099
|
+
if (idx === -1) {
|
|
14100
|
+
return json(res, { success: false, message: "Custom provider not found" }, 404);
|
|
14101
|
+
}
|
|
14102
|
+
const cp = customArr[idx];
|
|
14103
|
+
if (body.name)
|
|
14104
|
+
cp.name = body.name.trim();
|
|
14105
|
+
if (body.apiKey !== void 0)
|
|
14106
|
+
cp.apiKey = body.apiKey;
|
|
14107
|
+
if (body.modelId !== void 0)
|
|
14108
|
+
cp.modelId = body.modelId;
|
|
14109
|
+
if (body.baseUrl !== void 0)
|
|
14110
|
+
cp.baseUrl = body.baseUrl;
|
|
14111
|
+
if (body.description !== void 0)
|
|
14112
|
+
cp.description = body.description;
|
|
14113
|
+
const written = writeConfig(config2);
|
|
14114
|
+
if (!written)
|
|
14115
|
+
return json(res, { success: false, message: "Failed to write config file" }, 500);
|
|
14116
|
+
logger.info("[API] Custom provider updated: " + customId);
|
|
14117
|
+
return json(res, { success: true, message: "Custom provider updated" });
|
|
14118
|
+
}
|
|
14119
|
+
if (apiPath.match(/^\/models\/custom\/[^/]+$/) && method === "DELETE") {
|
|
14120
|
+
const customId = decodeURIComponent(apiPath.slice("/models/custom/".length));
|
|
14121
|
+
const result = readConfig();
|
|
14122
|
+
if (!result)
|
|
14123
|
+
return json(res, { success: false, message: "No config file found" }, 400);
|
|
14124
|
+
const config2 = result.config;
|
|
14125
|
+
const providers = config2.providers;
|
|
14126
|
+
const customArr = providers?.custom;
|
|
14127
|
+
if (!Array.isArray(customArr)) {
|
|
14128
|
+
return json(res, { success: false, message: "No custom providers found" }, 404);
|
|
14129
|
+
}
|
|
14130
|
+
const idx = customArr.findIndex((c) => c.id === customId);
|
|
14131
|
+
if (idx === -1) {
|
|
14132
|
+
return json(res, { success: false, message: "Custom provider not found" }, 404);
|
|
14133
|
+
}
|
|
14134
|
+
const removed = customArr.splice(idx, 1)[0];
|
|
14135
|
+
const written = writeConfig(config2);
|
|
14136
|
+
if (!written)
|
|
14137
|
+
return json(res, { success: false, message: "Failed to write config file" }, 500);
|
|
14138
|
+
logger.info("[API] Custom provider deleted: " + (removed.name || customId));
|
|
14139
|
+
return json(res, { success: true, message: "Custom provider '" + (removed.name || customId) + "' deleted" });
|
|
14140
|
+
}
|
|
14141
|
+
if (apiPath === "/openclaw-config" && method === "GET") {
|
|
14142
|
+
const result = readConfig();
|
|
14143
|
+
if (!result)
|
|
14144
|
+
return json(res, { error: "No config file found" }, 400);
|
|
14145
|
+
const config2 = result.config;
|
|
14146
|
+
return json(res, {
|
|
14147
|
+
agents: config2.agents || null,
|
|
14148
|
+
bindings: config2.bindings || [],
|
|
14149
|
+
channels: config2.channels || null,
|
|
14150
|
+
configPath: result.configPath
|
|
14151
|
+
});
|
|
14152
|
+
}
|
|
14153
|
+
if (apiPath === "/openclaw-config" && method === "PUT") {
|
|
14154
|
+
const body = await readBody(req);
|
|
14155
|
+
const result = readConfig();
|
|
14156
|
+
if (!result)
|
|
14157
|
+
return json(res, { success: false, message: "No config file found" }, 400);
|
|
14158
|
+
const config2 = result.config;
|
|
14159
|
+
if (body.agents !== void 0)
|
|
14160
|
+
config2.agents = body.agents;
|
|
14161
|
+
if (body.bindings !== void 0)
|
|
14162
|
+
config2.bindings = body.bindings;
|
|
14163
|
+
if (body.channels !== void 0)
|
|
14164
|
+
config2.channels = body.channels;
|
|
14165
|
+
const written = writeConfig(config2);
|
|
14166
|
+
if (!written)
|
|
14167
|
+
return json(res, { success: false, message: "Failed to write config" }, 500);
|
|
14168
|
+
logger.info("[API] OpenClaw config updated");
|
|
14169
|
+
return json(res, { success: true, message: "Configuration saved" });
|
|
14170
|
+
}
|
|
12611
14171
|
if (apiPath === "/settings" && method === "GET") {
|
|
12612
14172
|
const result = readConfig();
|
|
12613
14173
|
const aicqSection = result?.config?.aicq ?? {};
|
|
@@ -12959,10 +14519,7 @@ function createManagementHandler(ctx) {
|
|
|
12959
14519
|
return json(res, { success: true, message: "Agent added", index: config2.agents.length - 1 });
|
|
12960
14520
|
}
|
|
12961
14521
|
if (apiPath.startsWith("/agents/") && method === "PUT") {
|
|
12962
|
-
const
|
|
12963
|
-
const idx = parseInt(idxStr, 10);
|
|
12964
|
-
if (isNaN(idx) || idx < 0)
|
|
12965
|
-
return json(res, { success: false, message: "Invalid agent index" }, 400);
|
|
14522
|
+
const identifier = decodeURIComponent(apiPath.slice("/agents/".length));
|
|
12966
14523
|
const body = await readBody(req);
|
|
12967
14524
|
const updates = body.agent;
|
|
12968
14525
|
if (!updates || typeof updates !== "object") {
|
|
@@ -12972,11 +14529,42 @@ function createManagementHandler(ctx) {
|
|
|
12972
14529
|
if (!result)
|
|
12973
14530
|
return json(res, { success: false, message: "No config file found" }, 400);
|
|
12974
14531
|
const config2 = result.config;
|
|
12975
|
-
|
|
14532
|
+
const providerModelMatch = identifier.match(/^provider:([^:]+):(\d+)$/);
|
|
14533
|
+
if (providerModelMatch) {
|
|
14534
|
+
const providerId = providerModelMatch[1];
|
|
14535
|
+
const modelIdx = parseInt(providerModelMatch[2], 10);
|
|
14536
|
+
const modelsSection = config2.models;
|
|
14537
|
+
const providersObj = modelsSection?.providers;
|
|
14538
|
+
const provConfig = providersObj?.[providerId];
|
|
14539
|
+
const modelsArr = provConfig?.models;
|
|
14540
|
+
if (!modelsArr || modelIdx >= modelsArr.length) {
|
|
14541
|
+
return json(res, { success: false, message: "Provider model index out of range" }, 400);
|
|
14542
|
+
}
|
|
14543
|
+
const modelEntry = modelsArr[modelIdx];
|
|
14544
|
+
if (updates.name)
|
|
14545
|
+
modelEntry.name = updates.name;
|
|
14546
|
+
if (updates.id)
|
|
14547
|
+
modelEntry.id = updates.id;
|
|
14548
|
+
if (updates.provider)
|
|
14549
|
+
modelEntry.provider = updates.provider;
|
|
14550
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
14551
|
+
if (!["name", "id", "provider", "_source", "_providerId", "_modelIndex", "_configPath", "isDefault"].includes(k)) {
|
|
14552
|
+
modelEntry[k] = v;
|
|
14553
|
+
}
|
|
14554
|
+
}
|
|
14555
|
+
const written2 = writeConfig(config2);
|
|
14556
|
+
if (!written2)
|
|
14557
|
+
return json(res, { success: false, message: "Failed to write config" }, 500);
|
|
14558
|
+
logger.info("[API] Provider model updated: " + providerId + ".models[" + modelIdx + "]");
|
|
14559
|
+
return json(res, { success: true, message: "Model updated in " + providerId });
|
|
14560
|
+
}
|
|
14561
|
+
const idx = parseInt(identifier, 10);
|
|
14562
|
+
if (isNaN(idx) || idx < 0)
|
|
14563
|
+
return json(res, { success: false, message: "Invalid agent identifier" }, 400);
|
|
12976
14564
|
if (!Array.isArray(config2.agents)) {
|
|
12977
14565
|
return json(res, { success: false, message: "No agents array in config" }, 400);
|
|
12978
14566
|
}
|
|
12979
|
-
agentsArr = config2.agents;
|
|
14567
|
+
const agentsArr = config2.agents;
|
|
12980
14568
|
if (idx >= agentsArr.length) {
|
|
12981
14569
|
return json(res, { success: false, message: "Agent index out of range" }, 400);
|
|
12982
14570
|
}
|
|
@@ -13235,9 +14823,13 @@ var plugin = definePluginEntry({
|
|
|
13235
14823
|
try {
|
|
13236
14824
|
switch (action) {
|
|
13237
14825
|
case "request-temp-number": {
|
|
14826
|
+
const authHeaders = { "Content-Type": "application/json" };
|
|
14827
|
+
const token = serverClient.getAuthToken();
|
|
14828
|
+
if (token)
|
|
14829
|
+
authHeaders["Authorization"] = "Bearer " + token;
|
|
13238
14830
|
const resp = await fetch(serverUrl + "/api/v1/temp-number/request", {
|
|
13239
14831
|
method: "POST",
|
|
13240
|
-
headers:
|
|
14832
|
+
headers: authHeaders,
|
|
13241
14833
|
body: JSON.stringify({ nodeId: aicqAgentId })
|
|
13242
14834
|
});
|
|
13243
14835
|
if (!resp.ok)
|
|
@@ -13246,7 +14838,11 @@ var plugin = definePluginEntry({
|
|
|
13246
14838
|
return { success: true, tempNumber: data.number, message: "Temp number: " + data.number };
|
|
13247
14839
|
}
|
|
13248
14840
|
case "list": {
|
|
13249
|
-
const
|
|
14841
|
+
const listAuthHeaders = {};
|
|
14842
|
+
const listToken = serverClient.getAuthToken();
|
|
14843
|
+
if (listToken)
|
|
14844
|
+
listAuthHeaders["Authorization"] = "Bearer " + listToken;
|
|
14845
|
+
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId, { headers: listAuthHeaders });
|
|
13250
14846
|
if (!resp.ok)
|
|
13251
14847
|
return { error: "Server error: " + await resp.text() };
|
|
13252
14848
|
const data = await resp.json();
|
|
@@ -13259,15 +14855,23 @@ var plugin = definePluginEntry({
|
|
|
13259
14855
|
const isTempNumber = /^\d{6}$/.test(target);
|
|
13260
14856
|
let friendId = target;
|
|
13261
14857
|
if (isTempNumber) {
|
|
13262
|
-
const
|
|
14858
|
+
const resolveAuthHeaders = {};
|
|
14859
|
+
const resolveToken = serverClient.getAuthToken();
|
|
14860
|
+
if (resolveToken)
|
|
14861
|
+
resolveAuthHeaders["Authorization"] = "Bearer " + resolveToken;
|
|
14862
|
+
const resolveResp = await fetch(serverUrl + "/api/v1/temp-number/" + target, { headers: resolveAuthHeaders });
|
|
13263
14863
|
if (!resolveResp.ok)
|
|
13264
14864
|
return { error: "Temp number not found or expired" };
|
|
13265
14865
|
const resolveData = await resolveResp.json();
|
|
13266
14866
|
friendId = resolveData.nodeId;
|
|
13267
14867
|
}
|
|
14868
|
+
const hsAuthHeaders = { "Content-Type": "application/json" };
|
|
14869
|
+
const hsToken = serverClient.getAuthToken();
|
|
14870
|
+
if (hsToken)
|
|
14871
|
+
hsAuthHeaders["Authorization"] = "Bearer " + hsToken;
|
|
13268
14872
|
const hsResp = await fetch(serverUrl + "/api/v1/handshake/initiate", {
|
|
13269
14873
|
method: "POST",
|
|
13270
|
-
headers:
|
|
14874
|
+
headers: hsAuthHeaders,
|
|
13271
14875
|
body: JSON.stringify({ requesterId: aicqAgentId, targetTempNumber: target })
|
|
13272
14876
|
});
|
|
13273
14877
|
if (!hsResp.ok)
|
|
@@ -13279,9 +14883,13 @@ var plugin = definePluginEntry({
|
|
|
13279
14883
|
const target = params?.target;
|
|
13280
14884
|
if (!target)
|
|
13281
14885
|
return { error: "Missing target (friend ID to remove)" };
|
|
14886
|
+
const rmAuthHeaders = { "Content-Type": "application/json" };
|
|
14887
|
+
const rmToken = serverClient.getAuthToken();
|
|
14888
|
+
if (rmToken)
|
|
14889
|
+
rmAuthHeaders["Authorization"] = "Bearer " + rmToken;
|
|
13282
14890
|
const rmResp = await fetch(serverUrl + "/api/v1/friends/" + target, {
|
|
13283
14891
|
method: "DELETE",
|
|
13284
|
-
headers:
|
|
14892
|
+
headers: rmAuthHeaders,
|
|
13285
14893
|
body: JSON.stringify({ nodeId: aicqAgentId })
|
|
13286
14894
|
});
|
|
13287
14895
|
if (!rmResp.ok)
|
|
@@ -13289,9 +14897,16 @@ var plugin = definePluginEntry({
|
|
|
13289
14897
|
return { success: true, message: "Friend " + target + " removed" };
|
|
13290
14898
|
}
|
|
13291
14899
|
case "revoke-temp-number": {
|
|
13292
|
-
const
|
|
14900
|
+
const revokeTarget = params?.target;
|
|
14901
|
+
if (!revokeTarget)
|
|
14902
|
+
return { error: "Missing target (temp number to revoke)" };
|
|
14903
|
+
const revokeAuthHeaders = { "Content-Type": "application/json" };
|
|
14904
|
+
const revokeToken = serverClient.getAuthToken();
|
|
14905
|
+
if (revokeToken)
|
|
14906
|
+
revokeAuthHeaders["Authorization"] = "Bearer " + revokeToken;
|
|
14907
|
+
const resp = await fetch(serverUrl + "/api/v1/temp-number/" + revokeTarget + "?nodeId=" + aicqAgentId, {
|
|
13293
14908
|
method: "DELETE",
|
|
13294
|
-
headers:
|
|
14909
|
+
headers: revokeAuthHeaders,
|
|
13295
14910
|
body: JSON.stringify({ nodeId: aicqAgentId })
|
|
13296
14911
|
});
|
|
13297
14912
|
if (!resp.ok)
|
|
@@ -13377,21 +14992,25 @@ var plugin = definePluginEntry({
|
|
|
13377
14992
|
});
|
|
13378
14993
|
api.registerGatewayMethod("aicq.friends.list", async (params) => {
|
|
13379
14994
|
try {
|
|
13380
|
-
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId
|
|
14995
|
+
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId, {
|
|
14996
|
+
headers: serverClient.authHeaders()
|
|
14997
|
+
});
|
|
13381
14998
|
if (!resp.ok)
|
|
13382
14999
|
return { error: "Server error: " + await resp.text() };
|
|
13383
15000
|
const data = await resp.json();
|
|
13384
15001
|
const friends = data.friends || [];
|
|
13385
15002
|
const enriched = friends.map((f) => {
|
|
13386
|
-
const
|
|
15003
|
+
const friendId = f.id || f.nodeId;
|
|
15004
|
+
const localFriend = store.getFriend(friendId);
|
|
13387
15005
|
return {
|
|
13388
|
-
id:
|
|
15006
|
+
id: friendId,
|
|
13389
15007
|
publicKeyFingerprint: f.publicKeyFingerprint || (localFriend?.publicKeyFingerprint || ""),
|
|
13390
15008
|
permissions: f.permissions || localFriend?.permissions || [],
|
|
13391
15009
|
addedAt: f.addedAt || localFriend?.addedAt?.toISOString() || null,
|
|
13392
15010
|
lastMessageAt: f.lastMessageAt || localFriend?.lastMessageAt?.toISOString() || null,
|
|
13393
15011
|
friendType: f.friendType || localFriend?.friendType || null,
|
|
13394
|
-
aiName: f.aiName || localFriend?.aiName || null
|
|
15012
|
+
aiName: f.aiName || localFriend?.aiName || null,
|
|
15013
|
+
nodeId: f.nodeId || null
|
|
13395
15014
|
};
|
|
13396
15015
|
});
|
|
13397
15016
|
return { friends: enriched };
|
|
@@ -13409,7 +15028,9 @@ var plugin = definePluginEntry({
|
|
|
13409
15028
|
const isTempNumber = /^\d{6}$/.test(target);
|
|
13410
15029
|
let friendId = target;
|
|
13411
15030
|
if (isTempNumber) {
|
|
13412
|
-
const resolveResp = await fetch(serverUrl + "/api/v1/temp-number/" + target
|
|
15031
|
+
const resolveResp = await fetch(serverUrl + "/api/v1/temp-number/" + target, {
|
|
15032
|
+
headers: serverClient.authHeaders()
|
|
15033
|
+
});
|
|
13413
15034
|
if (!resolveResp.ok)
|
|
13414
15035
|
return { success: false, message: "Temp number not found or expired" };
|
|
13415
15036
|
const resolveData = await resolveResp.json();
|
|
@@ -13417,7 +15038,7 @@ var plugin = definePluginEntry({
|
|
|
13417
15038
|
}
|
|
13418
15039
|
const hsResp = await fetch(serverUrl + "/api/v1/handshake/initiate", {
|
|
13419
15040
|
method: "POST",
|
|
13420
|
-
headers:
|
|
15041
|
+
headers: serverClient.authHeaders(),
|
|
13421
15042
|
body: JSON.stringify({ requesterId: aicqAgentId, targetTempNumber: target })
|
|
13422
15043
|
});
|
|
13423
15044
|
if (!hsResp.ok)
|
|
@@ -13443,7 +15064,7 @@ var plugin = definePluginEntry({
|
|
|
13443
15064
|
return { success: false, message: "Missing friendId parameter" };
|
|
13444
15065
|
const rmResp = await fetch(serverUrl + "/api/v1/friends/" + friendId, {
|
|
13445
15066
|
method: "DELETE",
|
|
13446
|
-
headers:
|
|
15067
|
+
headers: serverClient.authHeaders(),
|
|
13447
15068
|
body: JSON.stringify({ nodeId: aicqAgentId })
|
|
13448
15069
|
});
|
|
13449
15070
|
if (!rmResp.ok)
|
|
@@ -13462,7 +15083,9 @@ var plugin = definePluginEntry({
|
|
|
13462
15083
|
const friendId = p.friendId;
|
|
13463
15084
|
if (!friendId)
|
|
13464
15085
|
return { error: "Missing friendId parameter" };
|
|
13465
|
-
const resp = await fetch(serverUrl + "/api/v1/friends/" + friendId + "/permissions?nodeId=" + aicqAgentId
|
|
15086
|
+
const resp = await fetch(serverUrl + "/api/v1/friends/" + friendId + "/permissions?nodeId=" + aicqAgentId, {
|
|
15087
|
+
headers: serverClient.authHeaders()
|
|
15088
|
+
});
|
|
13466
15089
|
if (!resp.ok)
|
|
13467
15090
|
return { error: "Server error: " + await resp.text() };
|
|
13468
15091
|
const data = await resp.json();
|
|
@@ -13484,7 +15107,7 @@ var plugin = definePluginEntry({
|
|
|
13484
15107
|
}
|
|
13485
15108
|
const resp = await fetch(serverUrl + "/api/v1/friends/" + friendId + "/permissions", {
|
|
13486
15109
|
method: "PUT",
|
|
13487
|
-
headers:
|
|
15110
|
+
headers: serverClient.authHeaders(),
|
|
13488
15111
|
body: JSON.stringify({ nodeId: aicqAgentId, permissions })
|
|
13489
15112
|
});
|
|
13490
15113
|
if (!resp.ok)
|
|
@@ -13503,7 +15126,9 @@ var plugin = definePluginEntry({
|
|
|
13503
15126
|
});
|
|
13504
15127
|
api.registerGatewayMethod("aicq.friends.requests", async (params) => {
|
|
13505
15128
|
try {
|
|
13506
|
-
const resp = await fetch(serverUrl + "/api/v1/friends/requests?
|
|
15129
|
+
const resp = await fetch(serverUrl + "/api/v1/friends/requests?accountId=" + aicqAgentId, {
|
|
15130
|
+
headers: serverClient.authHeaders()
|
|
15131
|
+
});
|
|
13507
15132
|
if (!resp.ok)
|
|
13508
15133
|
return { error: "Server error: " + await resp.text() };
|
|
13509
15134
|
const data = await resp.json();
|
|
@@ -13525,7 +15150,7 @@ var plugin = definePluginEntry({
|
|
|
13525
15150
|
}
|
|
13526
15151
|
const resp = await fetch(serverUrl + "/api/v1/friends/requests/" + requestId + "/accept", {
|
|
13527
15152
|
method: "POST",
|
|
13528
|
-
headers:
|
|
15153
|
+
headers: serverClient.authHeaders(),
|
|
13529
15154
|
body: JSON.stringify(body)
|
|
13530
15155
|
});
|
|
13531
15156
|
if (!resp.ok)
|
|
@@ -13545,7 +15170,7 @@ var plugin = definePluginEntry({
|
|
|
13545
15170
|
return { success: false, message: "Missing requestId parameter" };
|
|
13546
15171
|
const resp = await fetch(serverUrl + "/api/v1/friends/requests/" + requestId + "/reject", {
|
|
13547
15172
|
method: "POST",
|
|
13548
|
-
headers:
|
|
15173
|
+
headers: serverClient.authHeaders(),
|
|
13549
15174
|
body: JSON.stringify({})
|
|
13550
15175
|
});
|
|
13551
15176
|
if (!resp.ok)
|