aicq-openclaw-plugin 1.0.2 → 1.0.4
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 +1046 -775
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7769,6 +7769,7 @@ var require_dist = __commonJS({
|
|
|
7769
7769
|
// dist/index.js
|
|
7770
7770
|
var dotenv = __toESM(require_main(), 1);
|
|
7771
7771
|
import * as path6 from "path";
|
|
7772
|
+
import * as http from "http";
|
|
7772
7773
|
import { definePluginEntry } from "openclaw/plugin-sdk/core";
|
|
7773
7774
|
|
|
7774
7775
|
// dist/config.js
|
|
@@ -9816,670 +9817,859 @@ var CSS = `
|
|
|
9816
9817
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9817
9818
|
:root {
|
|
9818
9819
|
--bg: #0f1117; --bg2: #1a1d27; --bg3: #242836; --bg4: #2e3347;
|
|
9819
|
-
--text: #e4e6ef; --text2: #9499b3; --text3: #5c6080;
|
|
9820
|
-
--accent: #
|
|
9821
|
-
--
|
|
9822
|
-
--
|
|
9820
|
+
--bg5: #353a50; --text: #e4e6ef; --text2: #9499b3; --text3: #5c6080;
|
|
9821
|
+
--accent: #6366f1; --accent2: #818cf8; --accent-bg: rgba(99,102,241,.12);
|
|
9822
|
+
--ok: #34d399; --ok-bg: rgba(52,211,153,.12); --warn: #fbbf24; --warn-bg: rgba(251,191,36,.12);
|
|
9823
|
+
--danger: #ef4444; --danger-bg: rgba(239,68,68,.12); --info: #60a5fa; --info-bg: rgba(96,165,250,.12);
|
|
9824
|
+
--border: #2e3347; --radius: 8px; --radius-lg: 12px; --shadow: 0 2px 12px rgba(0,0,0,.3);
|
|
9825
|
+
--sidebar-w: 240px; --header-h: 56px;
|
|
9826
|
+
--transition: .2s cubic-bezier(.4,0,.2,1);
|
|
9823
9827
|
}
|
|
9824
|
-
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.
|
|
9828
|
+
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; }
|
|
9825
9829
|
a { color: var(--info); text-decoration: none; }
|
|
9826
|
-
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
.
|
|
9833
|
-
|
|
9834
|
-
|
|
9835
|
-
.
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9830
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
9831
|
+
::-webkit-scrollbar-track { background: transparent; }
|
|
9832
|
+
::-webkit-scrollbar-thumb { background: var(--bg4); border-radius: 3px; }
|
|
9833
|
+
::-webkit-scrollbar-thumb:hover { background: var(--bg5); }
|
|
9834
|
+
|
|
9835
|
+
/* Layout */
|
|
9836
|
+
.app { display: flex; height: 100vh; width: 100vw; overflow: hidden; }
|
|
9837
|
+
|
|
9838
|
+
/* Sidebar */
|
|
9839
|
+
.sidebar {
|
|
9840
|
+
width: var(--sidebar-w); min-width: var(--sidebar-w); height: 100vh;
|
|
9841
|
+
background: var(--bg2); border-right: 1px solid var(--border);
|
|
9842
|
+
display: flex; flex-direction: column; transition: width var(--transition), min-width var(--transition);
|
|
9843
|
+
z-index: 20; overflow: hidden;
|
|
9844
|
+
}
|
|
9845
|
+
.sidebar.collapsed { width: 60px; min-width: 60px; }
|
|
9846
|
+
.sidebar.collapsed .nav-label, .sidebar.collapsed .sidebar-header-text, .sidebar.collapsed .sidebar-footer-text { display: none; }
|
|
9847
|
+
.sidebar.collapsed .sidebar-header { justify-content: center; padding: 0 8px; }
|
|
9848
|
+
.sidebar.collapsed .nav-item { justify-content: center; padding: 10px 0; }
|
|
9849
|
+
.sidebar.collapsed .nav-item .nav-icon { margin-right: 0; }
|
|
9850
|
+
|
|
9851
|
+
.sidebar-header {
|
|
9852
|
+
display: flex; align-items: center; gap: 12px; padding: 16px 20px;
|
|
9853
|
+
border-bottom: 1px solid var(--border); min-height: var(--header-h);
|
|
9854
|
+
}
|
|
9855
|
+
.sidebar-logo {
|
|
9856
|
+
width: 32px; height: 32px; border-radius: 8px; background: linear-gradient(135deg, var(--accent), #a855f7);
|
|
9857
|
+
display: grid; place-items: center; font-size: 13px; font-weight: 800; color: #fff; flex-shrink: 0;
|
|
9858
|
+
}
|
|
9859
|
+
.sidebar-header-text h1 { font-size: 14px; font-weight: 700; line-height: 1.2; }
|
|
9860
|
+
.sidebar-header-text span { font-size: 11px; color: var(--text3); }
|
|
9861
|
+
|
|
9862
|
+
.sidebar-nav { flex: 1; overflow-y: auto; padding: 8px; }
|
|
9863
|
+
.nav-group { margin-bottom: 4px; }
|
|
9864
|
+
.nav-group-title { font-size: 10px; font-weight: 600; color: var(--text3); text-transform: uppercase; letter-spacing: .8px; padding: 12px 12px 6px; white-space: nowrap; }
|
|
9865
|
+
.nav-item {
|
|
9866
|
+
display: flex; align-items: center; padding: 9px 12px; border-radius: var(--radius);
|
|
9867
|
+
cursor: pointer; transition: all var(--transition); color: var(--text2); white-space: nowrap;
|
|
9868
|
+
position: relative; user-select: none;
|
|
9869
|
+
}
|
|
9870
|
+
.nav-item:hover { background: var(--bg3); color: var(--text); }
|
|
9871
|
+
.nav-item.active { background: var(--accent-bg); color: var(--accent2); }
|
|
9872
|
+
.nav-item.active::before {
|
|
9873
|
+
content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
|
|
9874
|
+
width: 3px; height: 20px; background: var(--accent); border-radius: 0 2px 2px 0;
|
|
9875
|
+
}
|
|
9876
|
+
.nav-icon { width: 20px; text-align: center; margin-right: 10px; font-size: 15px; flex-shrink: 0; }
|
|
9877
|
+
.nav-label { font-size: 13px; font-weight: 500; }
|
|
9878
|
+
.nav-badge {
|
|
9879
|
+
margin-left: auto; background: var(--accent); color: #fff; font-size: 10px; font-weight: 600;
|
|
9880
|
+
padding: 1px 7px; border-radius: 10px; min-width: 18px; text-align: center;
|
|
9844
9881
|
}
|
|
9845
|
-
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
|
|
9846
|
-
input::placeholder { color: var(--text3); }
|
|
9847
|
-
select { cursor: pointer; appearance: auto; }
|
|
9848
9882
|
|
|
9849
|
-
.
|
|
9850
|
-
display: flex; align-items: center; gap:
|
|
9883
|
+
.sidebar-footer {
|
|
9884
|
+
padding: 12px 16px; border-top: 1px solid var(--border); display: flex; align-items: center; gap: 8px;
|
|
9885
|
+
cursor: pointer; transition: background var(--transition); white-space: nowrap;
|
|
9886
|
+
}
|
|
9887
|
+
.sidebar-footer:hover { background: var(--bg3); }
|
|
9888
|
+
.sidebar-footer-text { font-size: 11px; color: var(--text3); }
|
|
9889
|
+
|
|
9890
|
+
/* Main area */
|
|
9891
|
+
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; min-width: 0; }
|
|
9892
|
+
.main-header {
|
|
9893
|
+
height: var(--header-h); min-height: var(--header-h);
|
|
9894
|
+
display: flex; align-items: center; gap: 16px; padding: 0 24px;
|
|
9851
9895
|
background: var(--bg2); border-bottom: 1px solid var(--border);
|
|
9852
|
-
position: sticky; top: 0; z-index: 10;
|
|
9853
9896
|
}
|
|
9854
|
-
.
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
9897
|
+
.toggle-btn {
|
|
9898
|
+
width: 32px; height: 32px; border-radius: 6px; background: var(--bg3);
|
|
9899
|
+
display: grid; place-items: center; cursor: pointer; color: var(--text2); border: none;
|
|
9900
|
+
font-size: 16px; transition: all var(--transition);
|
|
9901
|
+
}
|
|
9902
|
+
.toggle-btn:hover { background: var(--bg4); color: var(--text); }
|
|
9903
|
+
.main-header h2 { font-size: 16px; font-weight: 600; flex: 1; }
|
|
9904
|
+
.header-status { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text2); }
|
|
9905
|
+
.dot { width: 8px; height: 8px; border-radius: 50%; }
|
|
9858
9906
|
.dot-ok { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
|
|
9859
9907
|
.dot-err { background: var(--danger); box-shadow: 0 0 6px var(--danger); }
|
|
9860
|
-
|
|
9861
|
-
.
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
}
|
|
9865
|
-
.
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9908
|
+
.dot-warn { background: var(--warn); box-shadow: 0 0 6px var(--warn); }
|
|
9909
|
+
.header-actions { display: flex; gap: 8px; }
|
|
9910
|
+
|
|
9911
|
+
.main-content { flex: 1; overflow-y: auto; padding: 24px; }
|
|
9912
|
+
.page { display: none; }
|
|
9913
|
+
.page.active { display: block; animation: fadeIn .2s ease-out; }
|
|
9914
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
|
9915
|
+
|
|
9916
|
+
/* Components */
|
|
9917
|
+
.btn {
|
|
9918
|
+
font: inherit; cursor: pointer; border: none; border-radius: var(--radius);
|
|
9919
|
+
padding: 7px 16px; font-size: 13px; font-weight: 500; transition: all var(--transition);
|
|
9920
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
9869
9921
|
}
|
|
9870
|
-
.
|
|
9871
|
-
.
|
|
9922
|
+
.btn:disabled { opacity: .45; cursor: default; }
|
|
9923
|
+
.btn-default { background: var(--bg3); color: var(--text); }
|
|
9924
|
+
.btn-default:hover:not(:disabled) { background: var(--bg4); }
|
|
9925
|
+
.btn-primary { background: var(--accent); color: #fff; }
|
|
9926
|
+
.btn-primary:hover:not(:disabled) { background: var(--accent2); }
|
|
9927
|
+
.btn-danger { background: var(--danger-bg); color: #fca5a5; border: 1px solid rgba(239,68,68,.2); }
|
|
9928
|
+
.btn-danger:hover:not(:disabled) { background: rgba(239,68,68,.2); }
|
|
9929
|
+
.btn-ok { background: var(--ok-bg); color: #6ee7b7; border: 1px solid rgba(52,211,153,.2); }
|
|
9930
|
+
.btn-ok:hover:not(:disabled) { background: rgba(52,211,153,.2); }
|
|
9931
|
+
.btn-warn { background: var(--warn-bg); color: #fde68a; border: 1px solid rgba(251,191,36,.2); }
|
|
9932
|
+
.btn-warn:hover:not(:disabled) { background: rgba(251,191,36,.2); }
|
|
9933
|
+
.btn-ghost { background: transparent; color: var(--text2); }
|
|
9934
|
+
.btn-ghost:hover:not(:disabled) { background: var(--bg3); color: var(--text); }
|
|
9935
|
+
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
|
9936
|
+
.btn-icon { width: 32px; height: 32px; padding: 0; justify-content: center; border-radius: 6px; }
|
|
9872
9937
|
|
|
9873
|
-
|
|
9874
|
-
|
|
9938
|
+
input, select, textarea {
|
|
9939
|
+
font: inherit; background: var(--bg); color: var(--text); border: 1px solid var(--border);
|
|
9940
|
+
border-radius: var(--radius); padding: 8px 12px; width: 100%; outline: none; transition: border-color var(--transition);
|
|
9941
|
+
}
|
|
9942
|
+
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg); }
|
|
9943
|
+
input::placeholder, textarea::placeholder { color: var(--text3); }
|
|
9944
|
+
select { cursor: pointer; }
|
|
9945
|
+
textarea { resize: vertical; min-height: 80px; }
|
|
9875
9946
|
|
|
9876
9947
|
.card {
|
|
9877
|
-
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius);
|
|
9948
|
+
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
9878
9949
|
padding: 20px; margin-bottom: 16px;
|
|
9879
9950
|
}
|
|
9880
|
-
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
|
9881
|
-
.card-title { font-size: 15px; font-weight: 600; }
|
|
9951
|
+
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; flex-wrap: wrap; gap: 8px; }
|
|
9952
|
+
.card-title { font-size: 15px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
|
9953
|
+
.card-desc { font-size: 12px; color: var(--text3); margin-top: 2px; }
|
|
9882
9954
|
|
|
9883
9955
|
.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
|
|
9956
|
+
.search-box { position: relative; min-width: 220px; }
|
|
9957
|
+
.search-box input { padding-left: 34px; }
|
|
9958
|
+
.search-box::before { content: '\u{1F50D}'; position: absolute; left: 10px; top: 50%; transform: translateY(-50%); font-size: 13px; pointer-events: none; }
|
|
9959
|
+
.filter-group { display: flex; gap: 4px; }
|
|
9960
|
+
.filter-btn { padding: 4px 12px; font-size: 12px; border-radius: 20px; border: 1px solid var(--border); background: transparent; color: var(--text2); cursor: pointer; transition: all var(--transition); }
|
|
9961
|
+
.filter-btn.active, .filter-btn:hover { background: var(--accent-bg); color: var(--accent2); border-color: var(--accent); }
|
|
9884
9962
|
|
|
9885
9963
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
9886
|
-
thead th {
|
|
9887
|
-
|
|
9964
|
+
thead th {
|
|
9965
|
+
text-align: left; padding: 10px 14px; color: var(--text3); font-weight: 600; font-size: 11px;
|
|
9966
|
+
text-transform: uppercase; letter-spacing: .5px; border-bottom: 1px solid var(--border); white-space: nowrap;
|
|
9967
|
+
position: sticky; top: 0; background: var(--bg2); z-index: 1;
|
|
9968
|
+
}
|
|
9969
|
+
tbody td { padding: 10px 14px; border-bottom: 1px solid var(--border); vertical-align: middle; }
|
|
9970
|
+
tbody tr { transition: background var(--transition); }
|
|
9888
9971
|
tbody tr:hover { background: var(--bg3); }
|
|
9889
|
-
.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; color: var(--text2); word-break: break-all; }
|
|
9890
|
-
|
|
9891
|
-
.badge
|
|
9892
|
-
.badge-
|
|
9893
|
-
.badge-
|
|
9894
|
-
.badge-danger { background:
|
|
9972
|
+
.mono { font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; font-size: 12px; color: var(--text2); word-break: break-all; }
|
|
9973
|
+
|
|
9974
|
+
.badge { display: inline-flex; align-items: center; gap: 4px; padding: 2px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; }
|
|
9975
|
+
.badge-ok { background: var(--ok-bg); color: var(--ok); }
|
|
9976
|
+
.badge-warn { background: var(--warn-bg); color: var(--warn); }
|
|
9977
|
+
.badge-danger { background: var(--danger-bg); color: var(--danger); }
|
|
9978
|
+
.badge-info { background: var(--info-bg); color: var(--info); }
|
|
9895
9979
|
.badge-ghost { background: var(--bg3); color: var(--text2); }
|
|
9980
|
+
.badge-accent { background: var(--accent-bg); color: var(--accent2); }
|
|
9896
9981
|
|
|
9897
|
-
.
|
|
9898
|
-
.empty .icon { font-size: 40px; margin-bottom: 12px; opacity: .4; }
|
|
9899
|
-
.empty p { font-size: 14px; }
|
|
9982
|
+
.tag { display: inline-flex; align-items: center; gap: 4px; background: var(--bg3); padding: 2px 8px; border-radius: 4px; font-size: 11px; color: var(--text2); }
|
|
9900
9983
|
|
|
9984
|
+
/* Stats */
|
|
9985
|
+
.stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
|
9986
|
+
.stat-card { background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 20px; transition: border-color var(--transition); }
|
|
9987
|
+
.stat-card:hover { border-color: var(--accent); }
|
|
9988
|
+
.stat-icon { width: 36px; height: 36px; border-radius: 8px; display: grid; place-items: center; font-size: 16px; margin-bottom: 10px; }
|
|
9989
|
+
.stat-label { font-size: 11px; color: var(--text3); text-transform: uppercase; letter-spacing: .5px; font-weight: 600; }
|
|
9990
|
+
.stat-value { font-size: 24px; font-weight: 700; margin-top: 2px; line-height: 1.2; }
|
|
9991
|
+
.stat-sub { font-size: 11px; color: var(--text3); margin-top: 4px; }
|
|
9992
|
+
|
|
9993
|
+
/* Provider grid */
|
|
9994
|
+
.provider-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
|
|
9995
|
+
.provider-card {
|
|
9996
|
+
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
9997
|
+
padding: 18px; transition: all var(--transition); cursor: pointer;
|
|
9998
|
+
}
|
|
9999
|
+
.provider-card:hover { border-color: var(--accent); transform: translateY(-1px); box-shadow: var(--shadow); }
|
|
10000
|
+
.provider-card .prov-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
|
|
10001
|
+
.provider-card .prov-name { font-weight: 600; font-size: 14px; display: flex; align-items: center; gap: 8px; }
|
|
10002
|
+
.provider-card .prov-desc { font-size: 12px; color: var(--text3); margin-bottom: 10px; }
|
|
10003
|
+
.provider-card .prov-model { font-size: 11px; color: var(--text2); background: var(--bg3); padding: 3px 8px; border-radius: 4px; display: inline-block; }
|
|
10004
|
+
.provider-card .prov-actions { margin-top: 12px; display: flex; gap: 6px; }
|
|
10005
|
+
|
|
10006
|
+
/* Modal */
|
|
9901
10007
|
.modal-overlay {
|
|
9902
|
-
position: fixed; inset: 0; background: rgba(0,0,0,.
|
|
10008
|
+
position: fixed; inset: 0; background: rgba(0,0,0,.65); display: flex;
|
|
9903
10009
|
align-items: center; justify-content: center; z-index: 100;
|
|
10010
|
+
animation: fadeIn .15s ease-out;
|
|
9904
10011
|
}
|
|
9905
10012
|
.modal-overlay.hidden { display: none; }
|
|
9906
10013
|
.modal {
|
|
9907
|
-
background: var(--bg2); border: 1px solid var(--border); border-radius:
|
|
9908
|
-
padding:
|
|
10014
|
+
background: var(--bg2); border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
10015
|
+
padding: 28px; width: 90%; max-width: 520px; box-shadow: 0 8px 32px rgba(0,0,0,.5);
|
|
10016
|
+
max-height: 85vh; overflow-y: auto; animation: modalIn .2s ease-out;
|
|
9909
10017
|
}
|
|
9910
|
-
.
|
|
9911
|
-
.
|
|
9912
|
-
.
|
|
9913
|
-
.
|
|
9914
|
-
.
|
|
9915
|
-
|
|
9916
|
-
.
|
|
9917
|
-
.
|
|
10018
|
+
@keyframes modalIn { from { transform: scale(.95); opacity: 0; } to { transform: scale(1); opacity: 1; } }
|
|
10019
|
+
.modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
|
|
10020
|
+
.modal-header h3 { font-size: 17px; font-weight: 600; display: flex; align-items: center; gap: 8px; }
|
|
10021
|
+
.modal-close { width: 28px; height: 28px; border-radius: 6px; background: var(--bg3); display: grid; place-items: center; cursor: pointer; border: none; color: var(--text2); font-size: 16px; }
|
|
10022
|
+
.modal-close:hover { background: var(--bg4); color: var(--text); }
|
|
10023
|
+
|
|
10024
|
+
.form-group { margin-bottom: 16px; }
|
|
10025
|
+
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
10026
|
+
.form-group label { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; color: var(--text2); margin-bottom: 6px; text-transform: uppercase; letter-spacing: .3px; }
|
|
10027
|
+
.form-group .hint { font-size: 11px; color: var(--text3); margin-top: 4px; }
|
|
10028
|
+
.form-group .input-prefix { position: relative; }
|
|
10029
|
+
.form-group .input-prefix input { padding-left: 36px; }
|
|
10030
|
+
.form-group .input-prefix .prefix { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text3); font-size: 12px; pointer-events: none; }
|
|
10031
|
+
.form-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--border); }
|
|
10032
|
+
|
|
10033
|
+
.perm-checks { display: flex; gap: 16px; }
|
|
10034
|
+
.perm-checks label { display: flex; align-items: center; gap: 8px; cursor: pointer; font-size: 13px; color: var(--text); text-transform: none; letter-spacing: normal; font-weight: 400; }
|
|
9918
10035
|
.perm-checks input[type=checkbox] { width: 16px; height: 16px; accent-color: var(--accent); }
|
|
9919
10036
|
|
|
9920
|
-
|
|
9921
|
-
.
|
|
9922
|
-
.
|
|
9923
|
-
.
|
|
9924
|
-
|
|
9925
|
-
.provider-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
|
|
9926
|
-
.provider-card {
|
|
9927
|
-
background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius);
|
|
9928
|
-
padding: 16px; cursor: pointer; transition: border-color .15s;
|
|
9929
|
-
}
|
|
9930
|
-
.provider-card:hover { border-color: var(--accent); }
|
|
9931
|
-
.provider-card .name { font-weight: 600; margin-bottom: 4px; }
|
|
9932
|
-
.provider-card .desc { font-size: 12px; color: var(--text3); }
|
|
9933
|
-
.provider-card .status-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 6px; }
|
|
9934
|
-
|
|
9935
|
-
.section-desc { font-size: 13px; color: var(--text2); margin-bottom: 16px; }
|
|
9936
|
-
|
|
9937
|
-
.tag { display: inline-flex; align-items: center; gap: 4px; background: var(--bg3); padding: 2px 8px; border-radius: 4px; font-size: 11px; color: var(--text2); }
|
|
10037
|
+
/* Empty state */
|
|
10038
|
+
.empty { text-align: center; padding: 60px 24px; color: var(--text3); }
|
|
10039
|
+
.empty .icon { font-size: 48px; margin-bottom: 16px; opacity: .35; }
|
|
10040
|
+
.empty p { font-size: 14px; margin-bottom: 4px; }
|
|
10041
|
+
.empty .sub { font-size: 12px; color: var(--text3); margin-top: 8px; }
|
|
9938
10042
|
|
|
9939
|
-
|
|
9940
|
-
.
|
|
10043
|
+
/* Loading */
|
|
10044
|
+
.loading-mask { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 60px 20px; color: var(--text3); }
|
|
10045
|
+
.spinner { width: 24px; height: 24px; border: 2.5px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .6s linear infinite; margin-bottom: 12px; }
|
|
9941
10046
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
9942
10047
|
|
|
10048
|
+
/* Toast */
|
|
10049
|
+
.toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
|
|
9943
10050
|
.toast {
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
10051
|
+
padding: 12px 20px; border-radius: var(--radius); color: #fff; font-size: 13px;
|
|
10052
|
+
animation: slideIn .2s ease-out; box-shadow: var(--shadow); display: flex; align-items: center; gap: 8px;
|
|
10053
|
+
max-width: 400px;
|
|
9947
10054
|
}
|
|
9948
10055
|
.toast.hidden { display: none; }
|
|
9949
|
-
.toast-ok { background: #065f46; }
|
|
9950
|
-
.toast-err { background: #
|
|
9951
|
-
.toast-info { background: #1e3a5f; }
|
|
9952
|
-
|
|
10056
|
+
.toast-ok { background: #065f46; border: 1px solid var(--ok); }
|
|
10057
|
+
.toast-err { background: #7f1d1d; border: 1px solid var(--danger); }
|
|
10058
|
+
.toast-info { background: #1e3a5f; border: 1px solid var(--info); }
|
|
10059
|
+
.toast-warn { background: #78350f; border: 1px solid var(--warn); }
|
|
10060
|
+
@keyframes slideIn { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
9953
10061
|
|
|
10062
|
+
/* Actions cell */
|
|
9954
10063
|
.actions-cell { display: flex; gap: 4px; }
|
|
9955
|
-
.truncate { max-width:
|
|
10064
|
+
.truncate { max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
10065
|
+
|
|
10066
|
+
/* Detail panel */
|
|
10067
|
+
.detail-row { display: flex; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
|
|
10068
|
+
.detail-row:last-child { border-bottom: none; }
|
|
10069
|
+
.detail-key { width: 140px; flex-shrink: 0; font-size: 12px; color: var(--text3); font-weight: 500; padding-top: 2px; }
|
|
10070
|
+
.detail-val { flex: 1; font-size: 13px; word-break: break-all; }
|
|
10071
|
+
|
|
10072
|
+
/* Section desc */
|
|
10073
|
+
.section-desc { font-size: 13px; color: var(--text2); margin-bottom: 20px; line-height: 1.6; }
|
|
9956
10074
|
|
|
10075
|
+
/* Responsive */
|
|
9957
10076
|
@media (max-width: 768px) {
|
|
9958
|
-
.
|
|
9959
|
-
.
|
|
9960
|
-
|
|
9961
|
-
.stats-
|
|
10077
|
+
.sidebar { position: fixed; left: -260px; z-index: 50; height: 100vh; transition: left var(--transition); }
|
|
10078
|
+
.sidebar.mobile-open { left: 0; }
|
|
10079
|
+
.main-content { padding: 16px; }
|
|
10080
|
+
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
|
9962
10081
|
.provider-grid { grid-template-columns: 1fr; }
|
|
10082
|
+
.form-row { grid-template-columns: 1fr; }
|
|
9963
10083
|
}
|
|
9964
10084
|
`;
|
|
9965
10085
|
var JS = `
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
let
|
|
9969
|
-
let
|
|
9970
|
-
|
|
9971
|
-
// \u2500\u2500
|
|
9972
|
-
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
}
|
|
9976
|
-
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
10086
|
+
// \u2500\u2500 Globals \u2500\u2500
|
|
10087
|
+
const API = '/api';
|
|
10088
|
+
let currentPage = 'dashboard';
|
|
10089
|
+
let refreshTimer = null;
|
|
10090
|
+
|
|
10091
|
+
// \u2500\u2500 jQuery-style helpers \u2500\u2500
|
|
10092
|
+
const $ = (sel, ctx) => (ctx || document).querySelector(sel);
|
|
10093
|
+
const $$ = (sel, ctx) => Array.from((ctx || document).querySelectorAll(sel));
|
|
10094
|
+
const html = (el, content) => { if (typeof el === 'string') el = $(el); if (el) el.innerHTML = content; return el; };
|
|
10095
|
+
const show = (el) => { if (typeof el === 'string') el = $(el); if (el) el.classList.remove('hidden'); return el; };
|
|
10096
|
+
const hide = (el) => { if (typeof el === 'string') el = $(el); if (el) el.classList.add('hidden'); return el; };
|
|
10097
|
+
const toggle = (el) => { if (typeof el === 'string') el = $(el); if (el) el.classList.toggle('hidden'); return el; };
|
|
10098
|
+
|
|
10099
|
+
// \u2500\u2500 Toast \u2500\u2500
|
|
9980
10100
|
function toast(msg, type = 'info') {
|
|
9981
|
-
const
|
|
9982
|
-
t
|
|
10101
|
+
const container = $('#toast-container') || createToastContainer();
|
|
10102
|
+
const t = document.createElement('div');
|
|
10103
|
+
const icons = { ok: '\u2705', err: '\u274C', info: '\u2139\uFE0F', warn: '\u26A0\uFE0F' };
|
|
9983
10104
|
t.className = 'toast toast-' + type;
|
|
9984
|
-
t.
|
|
9985
|
-
|
|
9986
|
-
t.
|
|
10105
|
+
t.innerHTML = '<span>' + (icons[type] || '') + '</span><span>' + escHtml(msg) + '</span>';
|
|
10106
|
+
container.appendChild(t);
|
|
10107
|
+
setTimeout(() => { t.style.opacity = '0'; t.style.transform = 'translateX(20px)'; t.style.transition = '.2s'; setTimeout(() => t.remove(), 200); }, 3500);
|
|
10108
|
+
}
|
|
10109
|
+
function createToastContainer() {
|
|
10110
|
+
const c = document.createElement('div');
|
|
10111
|
+
c.id = 'toast-container';
|
|
10112
|
+
c.className = 'toast-container';
|
|
10113
|
+
document.body.appendChild(c);
|
|
10114
|
+
return c;
|
|
9987
10115
|
}
|
|
9988
10116
|
|
|
9989
|
-
|
|
9990
|
-
function
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
return
|
|
10117
|
+
// \u2500\u2500 API \u2500\u2500
|
|
10118
|
+
async function api(path, opts = {}) {
|
|
10119
|
+
try {
|
|
10120
|
+
const res = await fetch(API + path, { headers: { 'Content-Type': 'application/json', ...opts.headers }, ...opts });
|
|
10121
|
+
const data = await res.json();
|
|
10122
|
+
if (!res.ok && !data.error) data.error = 'HTTP ' + res.status;
|
|
10123
|
+
return data;
|
|
10124
|
+
} catch (e) { return { error: e.message }; }
|
|
9997
10125
|
}
|
|
9998
10126
|
|
|
10127
|
+
// \u2500\u2500 Utilities \u2500\u2500
|
|
10128
|
+
function escHtml(s) { if (s == null) return ''; const d = document.createElement('div'); d.textContent = String(s); return d.innerHTML; }
|
|
9999
10129
|
function timeAgo(iso) {
|
|
10000
|
-
if (!iso) return '
|
|
10130
|
+
if (!iso) return '\u2014';
|
|
10001
10131
|
const diff = Date.now() - new Date(iso).getTime();
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
if (
|
|
10005
|
-
|
|
10006
|
-
if (hrs < 24) return hrs + 'h ago';
|
|
10007
|
-
return Math.floor(hrs / 24) + 'd ago';
|
|
10132
|
+
if (diff < 0) return 'just now';
|
|
10133
|
+
const m = Math.floor(diff / 60000), h = Math.floor(m / 60), d = Math.floor(h / 24);
|
|
10134
|
+
if (m < 1) return 'just now'; if (m < 60) return m + ' min ago'; if (h < 24) return h + 'h ago'; if (d < 30) return d + 'd ago';
|
|
10135
|
+
return new Date(iso).toLocaleDateString();
|
|
10008
10136
|
}
|
|
10009
|
-
|
|
10010
|
-
function copyText(text) {
|
|
10011
|
-
|
|
10137
|
+
function maskKey(s) { if (!s || s.length < 12) return s || ''; return s.substring(0, 6) + '\u2022\u2022\u2022\u2022\u2022\u2022' + s.slice(-4); }
|
|
10138
|
+
function copyText(text) { navigator.clipboard.writeText(text).then(() => toast('Copied to clipboard', 'ok')).catch(() => toast('Copy failed', 'err')); }
|
|
10139
|
+
|
|
10140
|
+
// \u2500\u2500 Modal \u2500\u2500
|
|
10141
|
+
function showModal(id) { show(id); }
|
|
10142
|
+
function hideModal(id) { hide(id); }
|
|
10143
|
+
|
|
10144
|
+
// \u2500\u2500 Sidebar navigation \u2500\u2500
|
|
10145
|
+
function navigate(page) {
|
|
10146
|
+
currentPage = page;
|
|
10147
|
+
$$('.nav-item').forEach(n => n.classList.toggle('active', n.dataset.page === page));
|
|
10148
|
+
$$('.page').forEach(p => p.classList.toggle('active', p.id === 'page-' + page));
|
|
10149
|
+
$('#main-title').textContent = ($('.nav-item.active .nav-label') || {}).textContent || page;
|
|
10150
|
+
loadPage(page);
|
|
10151
|
+
// Close mobile sidebar
|
|
10152
|
+
$('.sidebar')?.classList.remove('mobile-open');
|
|
10012
10153
|
}
|
|
10013
10154
|
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
$$('.content').forEach(c => c.classList.toggle('hidden', c.id !== 'tab-' + tab));
|
|
10019
|
-
if (tab === 'agents') loadAgents();
|
|
10020
|
-
else if (tab === 'aicq') loadAICQ();
|
|
10021
|
-
else if (tab === 'models') loadModels();
|
|
10155
|
+
function toggleSidebar() {
|
|
10156
|
+
const sb = $('.sidebar');
|
|
10157
|
+
if (window.innerWidth <= 768) { sb.classList.toggle('mobile-open'); }
|
|
10158
|
+
else { sb.classList.toggle('collapsed'); }
|
|
10022
10159
|
}
|
|
10023
10160
|
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
dot.className = 'dot dot-ok';
|
|
10032
|
-
txt.textContent = 'Connected';
|
|
10033
|
-
} else {
|
|
10034
|
-
dot.className = 'dot dot-err';
|
|
10035
|
-
txt.textContent = 'Disconnected';
|
|
10036
|
-
}
|
|
10037
|
-
} catch (e) {
|
|
10038
|
-
$('#status-dot').className = 'dot dot-err';
|
|
10039
|
-
$('#status-text').textContent = 'Error';
|
|
10161
|
+
function loadPage(page) {
|
|
10162
|
+
switch (page) {
|
|
10163
|
+
case 'dashboard': loadDashboard(); break;
|
|
10164
|
+
case 'agents': loadAgents(); break;
|
|
10165
|
+
case 'friends': loadFriends(); break;
|
|
10166
|
+
case 'models': loadModels(); break;
|
|
10167
|
+
case 'settings': loadSettings(); break;
|
|
10040
10168
|
}
|
|
10041
10169
|
}
|
|
10042
10170
|
|
|
10043
|
-
// \
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10171
|
+
// \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
|
|
10172
|
+
// PAGE: Dashboard
|
|
10173
|
+
// \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
|
|
10174
|
+
async function loadDashboard() {
|
|
10175
|
+
const el = $('#dashboard-content');
|
|
10176
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>Loading dashboard...</div>');
|
|
10177
|
+
const [status, friends, identity] = await Promise.all([api('/status'), api('/friends'), api('/identity')]);
|
|
10178
|
+
if (status.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>Failed to connect to AICQ plugin</p></div>'); return; }
|
|
10179
|
+
const connCls = status.connected ? 'dot-ok' : 'dot-err';
|
|
10180
|
+
const connText = status.connected ? 'Connected' : 'Disconnected';
|
|
10181
|
+
const friendList = friends.friends || [];
|
|
10182
|
+
const aiFriends = friendList.filter(f => f.friendType === 'ai').length;
|
|
10183
|
+
const humanFriends = friendList.filter(f => f.friendType !== 'ai').length;
|
|
10184
|
+
|
|
10185
|
+
html(el, \\\`
|
|
10186
|
+
<div class="stats-grid">
|
|
10187
|
+
<div class="stat-card">
|
|
10188
|
+
<div class="stat-icon" style="background:var(--accent-bg)">\u{1F4E1}</div>
|
|
10189
|
+
<div class="stat-label">Server Status</div>
|
|
10190
|
+
<div class="stat-value" style="font-size:16px;display:flex;align-items:center;gap:8px">
|
|
10191
|
+
<span class="dot \${connCls}"></span> \${connText}
|
|
10192
|
+
</div>
|
|
10193
|
+
<div class="stat-sub">\${escHtml(status.serverUrl)}</div>
|
|
10194
|
+
</div>
|
|
10195
|
+
<div class="stat-card">
|
|
10196
|
+
<div class="stat-icon" style="background:var(--ok-bg)">\u{1F465}</div>
|
|
10197
|
+
<div class="stat-label">Total Friends</div>
|
|
10198
|
+
<div class="stat-value">\${friendList.length}</div>
|
|
10199
|
+
<div class="stat-sub">\${aiFriends} AI \xB7 \${humanFriends} Human</div>
|
|
10200
|
+
</div>
|
|
10201
|
+
<div class="stat-card">
|
|
10202
|
+
<div class="stat-icon" style="background:var(--info-bg)">\u{1F517}</div>
|
|
10203
|
+
<div class="stat-label">Active Sessions</div>
|
|
10204
|
+
<div class="stat-value">\${status.sessionCount || 0}</div>
|
|
10205
|
+
<div class="stat-sub">Encrypted sessions</div>
|
|
10206
|
+
</div>
|
|
10207
|
+
<div class="stat-card">
|
|
10208
|
+
<div class="stat-icon" style="background:var(--warn-bg)">\u{1F511}</div>
|
|
10209
|
+
<div class="stat-label">Agent ID</div>
|
|
10210
|
+
<div class="stat-value mono" style="font-size:13px">\${escHtml(status.agentId)}</div>
|
|
10211
|
+
<div class="stat-sub">Fingerprint: \${escHtml(status.fingerprint)}</div>
|
|
10212
|
+
</div>
|
|
10213
|
+
</div>
|
|
10214
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
|
|
10215
|
+
<div class="card">
|
|
10216
|
+
<div class="card-header"><div class="card-title">\u{1F4CB} Recent Friends</div><button class="btn btn-sm btn-ghost" onclick="navigate('friends')">View All \u2192</button></div>
|
|
10217
|
+
\${renderMiniFriendList(friendList.slice(0, 5))}
|
|
10218
|
+
</div>
|
|
10219
|
+
<div class="card">
|
|
10220
|
+
<div class="card-header"><div class="card-title">\u{1F916} Identity Info</div></div>
|
|
10221
|
+
<div class="detail-row"><div class="detail-key">Agent ID</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${identity.agentId}')">\${escHtml(identity.agentId)} \u{1F4CB}</div></div>
|
|
10222
|
+
<div class="detail-row"><div class="detail-key">Fingerprint</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint)}</div></div>
|
|
10223
|
+
<div class="detail-row"><div class="detail-key">Server URL</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${identity.serverUrl}')">\${escHtml(identity.serverUrl)} \u{1F4CB}</div></div>
|
|
10224
|
+
<div class="detail-row"><div class="detail-key">Connection</div><div class="detail-val"><span class="badge badge-\${identity.connected ? 'ok' : 'danger'}">\${identity.connected ? 'Online' : 'Offline'}</span></div></div>
|
|
10225
|
+
</div>
|
|
10226
|
+
</div>
|
|
10227
|
+
\\\`);
|
|
10228
|
+
}
|
|
10229
|
+
|
|
10230
|
+
function renderMiniFriendList(friends) {
|
|
10231
|
+
if (!friends.length) return '<div class="empty"><p>No friends yet</p></div>';
|
|
10232
|
+
let html = '';
|
|
10233
|
+
friends.forEach(f => {
|
|
10234
|
+
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>';
|
|
10235
|
+
});
|
|
10236
|
+
return html;
|
|
10054
10237
|
}
|
|
10055
10238
|
|
|
10056
|
-
|
|
10239
|
+
// \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
|
|
10240
|
+
// PAGE: Agent Management (from openclaw.json / stableclaw.json)
|
|
10241
|
+
// \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
|
|
10242
|
+
async function loadAgents() {
|
|
10057
10243
|
const el = $('#agents-content');
|
|
10058
|
-
|
|
10059
|
-
const
|
|
10060
|
-
|
|
10061
|
-
<div class="stat-card"><div class="label">Total Agents</div><div class="value">\${agents.length}</div></div>
|
|
10062
|
-
<div class="stat-card"><div class="label">Current Agent</div><div class="value" style="font-size:16px">\${escHtml(data.currentAgentId || '-')}</div></div>
|
|
10063
|
-
<div class="stat-card"><div class="label">Fingerprint</div><div class="value mono" style="font-size:11px">\${escHtml(data.fingerprint || '-')}</div></div>
|
|
10064
|
-
<div class="stat-card"><div class="label">Server</div><div class="value" style="font-size:13px">\${data.connected ? '\u{1F7E2}' : '\u{1F534}'} Online</div></div>
|
|
10065
|
-
</div>\`;
|
|
10244
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>Loading agents...</div>');
|
|
10245
|
+
const data = await api('/agents');
|
|
10246
|
+
if (data.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(data.error) + '</p></div>'); return; }
|
|
10066
10247
|
|
|
10067
|
-
|
|
10068
|
-
|
|
10069
|
-
return;
|
|
10070
|
-
}
|
|
10248
|
+
const agents = data.agents || [];
|
|
10249
|
+
const configSource = data.configSource || 'unknown';
|
|
10071
10250
|
|
|
10072
10251
|
let rows = '';
|
|
10073
|
-
agents.forEach(a => {
|
|
10074
|
-
const
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
<td>\${
|
|
10080
|
-
<td class="mono
|
|
10081
|
-
<td>\${
|
|
10252
|
+
agents.forEach((a, i) => {
|
|
10253
|
+
const modelBadge = a.model ? '<span class="badge badge-accent">' + escHtml(a.model) + '</span>' : '<span class="badge badge-ghost">default</span>';
|
|
10254
|
+
const providerBadge = a.provider ? '<span class="tag">' + escHtml(a.provider) + '</span>' : '';
|
|
10255
|
+
const statusBadge = a.enabled !== false ? '<span class="badge badge-ok">active</span>' : '<span class="badge badge-warn">disabled</span>';
|
|
10256
|
+
|
|
10257
|
+
rows += \\\`<tr>
|
|
10258
|
+
<td>\${statusBadge}</td>
|
|
10259
|
+
<td><div style="font-weight:600">\${escHtml(a.name || a.id || 'Agent ' + (i + 1))}</div><div class="mono" style="font-size:11px;color:var(--text3)">\${escHtml(a.id || '\u2014')}</div></td>
|
|
10260
|
+
<td>\${modelBadge}</td>
|
|
10261
|
+
<td>\${providerBadge}</td>
|
|
10262
|
+
<td>\${escHtml(a.systemPrompt ? a.systemPrompt.substring(0, 60) + '...' : '\u2014')}</td>
|
|
10082
10263
|
<td>
|
|
10083
10264
|
<div class="actions-cell">
|
|
10084
|
-
<button class="btn btn-sm btn-ghost" onclick="
|
|
10085
|
-
|
|
10265
|
+
<button class="btn btn-sm btn-ghost" onclick="viewAgent(\${i})" title="View">\u{1F441}\uFE0F</button>
|
|
10266
|
+
<button class="btn btn-sm btn-danger" onclick="deleteAgent(\${i})" title="Delete">\u{1F5D1}\uFE0F</button>
|
|
10086
10267
|
</div>
|
|
10087
10268
|
</td>
|
|
10088
|
-
</tr
|
|
10269
|
+
</tr>\\\`;
|
|
10089
10270
|
});
|
|
10090
10271
|
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
<
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10272
|
+
if (!agents.length) {
|
|
10273
|
+
html(el, \\\`
|
|
10274
|
+
<p class="section-desc">Reads agent configurations from <strong>\${escHtml(configSource)}</strong>. Configure your agents in the config file.</p>
|
|
10275
|
+
<div class="empty"><div class="icon">\u{1F916}</div><p>No agents configured</p><p class="sub">Add agents to your openclaw.json or stableclaw.json config file</p></div>
|
|
10276
|
+
\\\`);
|
|
10277
|
+
return;
|
|
10278
|
+
}
|
|
10279
|
+
|
|
10280
|
+
html(el, \\\`
|
|
10281
|
+
<div class="toolbar">
|
|
10282
|
+
<div class="search-box"><input type="text" placeholder="Search agents..." id="agent-search" oninput="filterAgentTable()"></div>
|
|
10283
|
+
<button class="btn btn-sm btn-default" onclick="loadAgents()">\u{1F504} Refresh</button>
|
|
10284
|
+
</div>
|
|
10285
|
+
<p class="section-desc">Agent list from <strong style="color:var(--accent2)">\${escHtml(configSource)}</strong>. Total: <strong>\${agents.length}</strong> agents configured.</p>
|
|
10286
|
+
<div class="card" style="padding:0;overflow:hidden">
|
|
10099
10287
|
<div style="overflow-x:auto">
|
|
10100
10288
|
<table>
|
|
10101
|
-
<thead><tr><th>
|
|
10102
|
-
<tbody>\${rows}</tbody>
|
|
10289
|
+
<thead><tr><th style="width:60px">Status</th><th>Agent</th><th>Model</th><th>Provider</th><th>System Prompt</th><th style="width:90px">Actions</th></tr></thead>
|
|
10290
|
+
<tbody id="agent-table-body">\${rows}</tbody>
|
|
10103
10291
|
</table>
|
|
10104
10292
|
</div>
|
|
10105
|
-
</div
|
|
10293
|
+
</div>
|
|
10294
|
+
\\\`);
|
|
10106
10295
|
}
|
|
10107
10296
|
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
else { toast(r.message || 'Delete failed', 'err'); }
|
|
10114
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10297
|
+
function filterAgentTable() {
|
|
10298
|
+
const q = ($('#agent-search')?.value || '').toLowerCase();
|
|
10299
|
+
$$('#agent-table-body tr').forEach(tr => {
|
|
10300
|
+
tr.style.display = tr.textContent.toLowerCase().includes(q) ? '' : 'none';
|
|
10301
|
+
});
|
|
10115
10302
|
}
|
|
10116
10303
|
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
sessionsData = sessions;
|
|
10132
|
-
identityData = identity;
|
|
10133
|
-
renderAICQSubTabs();
|
|
10134
|
-
switchAICQSubTab(aicqSubTab);
|
|
10135
|
-
} catch (e) {
|
|
10136
|
-
toast('Failed to load AICQ data: ' + e.message, 'err');
|
|
10137
|
-
}
|
|
10138
|
-
setLoading('aicq', false);
|
|
10304
|
+
function viewAgent(index) {
|
|
10305
|
+
const agents = window._lastAgentsData?.agents || [];
|
|
10306
|
+
const a = agents[index];
|
|
10307
|
+
if (!a) return;
|
|
10308
|
+
let details = '';
|
|
10309
|
+
for (const [k, v] of Object.entries(a)) {
|
|
10310
|
+
if (v != null && v !== '') {
|
|
10311
|
+
const display = typeof v === 'string' && v.length > 200 ? escHtml(v.substring(0, 200)) + '...' : escHtml(String(v));
|
|
10312
|
+
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>';
|
|
10313
|
+
}
|
|
10314
|
+
}
|
|
10315
|
+
html('#view-agent-body', details || '<div class="empty"><p>No data</p></div>');
|
|
10316
|
+
$('#view-agent-title').textContent = a.name || a.id || 'Agent';
|
|
10317
|
+
showModal('modal-view-agent');
|
|
10139
10318
|
}
|
|
10140
10319
|
|
|
10141
|
-
function
|
|
10142
|
-
|
|
10143
|
-
const
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
<button class="tab-btn \${aicqSubTab==='friends'?'active':''}" onclick="switchAICQSubTab('friends')">Friends (\${friendCount})</button>
|
|
10147
|
-
<button class="tab-btn \${aicqSubTab==='requests'?'active':''}" onclick="switchAICQSubTab('requests')">Requests (\${reqCount})</button>
|
|
10148
|
-
<button class="tab-btn \${aicqSubTab==='sessions'?'active':''}" onclick="switchAICQSubTab('sessions')">Sessions (\${sessCount})</button>
|
|
10149
|
-
\`;
|
|
10320
|
+
async function deleteAgent(index) {
|
|
10321
|
+
if (!confirm('Are you sure you want to delete this agent?')) return;
|
|
10322
|
+
const r = await api('/agents/' + index, { method: 'DELETE' });
|
|
10323
|
+
if (r.success) { toast('Agent deleted', 'ok'); loadAgents(); }
|
|
10324
|
+
else { toast(r.message || r.error || 'Delete failed', 'err'); }
|
|
10150
10325
|
}
|
|
10151
10326
|
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10327
|
+
// \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
|
|
10328
|
+
// PAGE: Friends Management
|
|
10329
|
+
// \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
|
|
10330
|
+
let friendsFilter = 'all';
|
|
10331
|
+
|
|
10332
|
+
async function loadFriends() {
|
|
10333
|
+
const el = $('#friends-content');
|
|
10334
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>Loading friends...</div>');
|
|
10335
|
+
const [friends, requests, sessions] = await Promise.all([api('/friends'), api('/friends/requests'), api('/sessions')]);
|
|
10336
|
+
|
|
10337
|
+
// Sub-tabs
|
|
10338
|
+
const friendCount = (friends.friends || []).length;
|
|
10339
|
+
const reqCount = (requests.requests || []).length;
|
|
10340
|
+
const sessCount = (sessions.sessions || []).length;
|
|
10341
|
+
|
|
10342
|
+
html('#friends-tabs', \\\`
|
|
10343
|
+
<button class="filter-btn \${friendsSubTab==='friends'?'active':''}" onclick="friendsSubTab='friends';loadFriends()">\u{1F465} Friends (<span id="fc">\${friendCount}</span>)</button>
|
|
10344
|
+
<button class="filter-btn \${friendsSubTab==='requests'?'active':''}" onclick="friendsSubTab='requests';loadFriends()">\u{1F4E8} Requests (<span id="rc">\${reqCount}</span>)</button>
|
|
10345
|
+
<button class="filter-btn \${friendsSubTab==='sessions'?'active':''}" onclick="friendsSubTab='sessions';loadFriends()">\u{1F517} Sessions (<span id="sc">\${sessCount}</span>)</button>
|
|
10346
|
+
\\\`);
|
|
10347
|
+
|
|
10348
|
+
window._friendsData = friends;
|
|
10349
|
+
window._requestsData = requests;
|
|
10350
|
+
window._sessionsData = sessions;
|
|
10351
|
+
|
|
10352
|
+
if (friendsSubTab === 'friends') renderFriendsList(friends.friends || []);
|
|
10353
|
+
else if (friendsSubTab === 'requests') renderRequestsList(requests.requests || []);
|
|
10354
|
+
else renderSessionsList(sessions.sessions || []);
|
|
10159
10355
|
}
|
|
10356
|
+
window.friendsSubTab = 'friends';
|
|
10160
10357
|
|
|
10161
|
-
function
|
|
10162
|
-
const el = $('#
|
|
10163
|
-
el.classList.remove('hidden');
|
|
10164
|
-
const friends = friendsData?.friends || [];
|
|
10165
|
-
|
|
10166
|
-
if (friends.length === 0) {
|
|
10167
|
-
el.innerHTML = '<div class="empty"><div class="icon">\u{1F465}</div><p>No friends yet</p><p style="font-size:12px;margin-top:8px">Add a friend using their temp number or ID</p></div>';
|
|
10168
|
-
return;
|
|
10169
|
-
}
|
|
10170
|
-
|
|
10358
|
+
function renderFriendsList(friends) {
|
|
10359
|
+
const el = $('#friends-content');
|
|
10171
10360
|
let rows = '';
|
|
10172
10361
|
friends.forEach(f => {
|
|
10173
|
-
const perms = (f.permissions || []).map(p => '<span class="badge badge-' + (p === 'exec' ? 'warn' : 'ok') + '">' + p + '</span>').join(' ');
|
|
10174
|
-
rows +=
|
|
10175
|
-
<td class="
|
|
10176
|
-
<td>\${escHtml(f.aiName || '-')}</td>
|
|
10177
|
-
<td><span class="badge badge-\${f.friendType === 'ai' ? 'info' : 'ghost'}">\${escHtml(f.friendType || '?')}</span></td>
|
|
10362
|
+
const perms = (f.permissions || []).map(p => '<span class="badge badge-' + (p === 'exec' ? 'warn' : 'ok') + '">' + escHtml(p) + '</span>').join(' ');
|
|
10363
|
+
rows += \\\`<tr data-type="\${f.friendType || ''}" data-search="\${escHtml(f.id + ' ' + (f.aiName || ''))}">
|
|
10364
|
+
<td><span class="badge badge-\${f.friendType === 'ai' ? 'info' : 'ghost'}" style="font-size:10px">\${(f.friendType || 'unknown').toUpperCase()}</span></td>
|
|
10365
|
+
<td><div style="font-weight:500">\${escHtml(f.aiName || f.id?.substring(0, 12) || '\u2014')}</div><div class="mono" style="font-size:11px;color:var(--text3);cursor:pointer" onclick="copyText('\${escHtml(f.id)}')">\${escHtml(f.id)} \u{1F4CB}</div></td>
|
|
10178
10366
|
<td>\${perms || '<span class="badge badge-ghost">none</span>'}</td>
|
|
10179
|
-
<td class="mono" style="font-size:11px">\${escHtml(f.publicKeyFingerprint || '
|
|
10180
|
-
<td>\${timeAgo(f.lastMessageAt)}</td>
|
|
10367
|
+
<td class="mono" style="font-size:11px">\${escHtml(f.publicKeyFingerprint || '\u2014')}</td>
|
|
10368
|
+
<td style="white-space:nowrap">\${timeAgo(f.lastMessageAt)}</td>
|
|
10181
10369
|
<td>
|
|
10182
10370
|
<div class="actions-cell">
|
|
10183
|
-
<button class="btn btn-sm btn-ghost" onclick="
|
|
10184
|
-
<button class="btn btn-sm btn-danger" onclick="removeFriend('\${escHtml(f.id)}')">\u{1F5D1}\uFE0F</button>
|
|
10185
|
-
<button class="btn btn-sm btn-ghost" onclick="copyText('\${escHtml(f.id)}')" title="Copy ID">\u{1F4CB}</button>
|
|
10371
|
+
<button class="btn btn-sm btn-ghost" onclick="editFriendPerms('\${escHtml(f.id)}',\${JSON.stringify(f.permissions || [])})" title="Permissions">\u2699\uFE0F</button>
|
|
10372
|
+
<button class="btn btn-sm btn-danger" onclick="removeFriend('\${escHtml(f.id)}')" title="Remove">\u{1F5D1}\uFE0F</button>
|
|
10186
10373
|
</div>
|
|
10187
10374
|
</td>
|
|
10188
|
-
</tr
|
|
10375
|
+
</tr>\\\`;
|
|
10189
10376
|
});
|
|
10190
10377
|
|
|
10191
|
-
el
|
|
10192
|
-
<div class="
|
|
10193
|
-
<div class="
|
|
10194
|
-
|
|
10195
|
-
<
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
</div>
|
|
10378
|
+
html(el, \\\`
|
|
10379
|
+
<div class="toolbar">
|
|
10380
|
+
<div class="search-box"><input type="text" placeholder="Search friends..." id="friend-search" oninput="filterFriendTable()"></div>
|
|
10381
|
+
<div class="filter-group">
|
|
10382
|
+
<button class="filter-btn \${friendsFilter==='all'?'active':''}" onclick="friendsFilter='all';filterFriendTable()">All</button>
|
|
10383
|
+
<button class="filter-btn \${friendsFilter==='ai'?'active':''}" onclick="friendsFilter='ai';filterFriendTable()">AI</button>
|
|
10384
|
+
<button class="filter-btn \${friendsFilter==='human'?'active':''}" onclick="friendsFilter='human';filterFriendTable()">Human</button>
|
|
10199
10385
|
</div>
|
|
10200
|
-
<
|
|
10386
|
+
<span style="flex:1"></span>
|
|
10387
|
+
<button class="btn btn-sm btn-primary" onclick="showAddFriendModal()">\u2795 Add Friend</button>
|
|
10388
|
+
<button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504}</button>
|
|
10389
|
+
</div>
|
|
10390
|
+
<div class="card" style="padding:0;overflow:hidden">
|
|
10391
|
+
<div style="overflow-x:auto;max-height:calc(100vh - 280px);overflow-y:auto">
|
|
10201
10392
|
<table>
|
|
10202
|
-
<thead><tr><th
|
|
10203
|
-
<tbody>\${rows}</tbody>
|
|
10393
|
+
<thead><tr><th style="width:60px">Type</th><th>Friend</th><th>Permissions</th><th>Fingerprint</th><th>Last Message</th><th style="width:80px">Actions</th></tr></thead>
|
|
10394
|
+
<tbody id="friend-table-body">\${rows}</tbody>
|
|
10204
10395
|
</table>
|
|
10205
10396
|
</div>
|
|
10206
|
-
|
|
10397
|
+
\${!friends.length ? '<div class="empty"><div class="icon">\u{1F465}</div><p>No friends yet</p><p class="sub">Add a friend using their 6-digit temp number or node ID</p></div>' : ''}
|
|
10398
|
+
</div>
|
|
10399
|
+
\\\`);
|
|
10207
10400
|
}
|
|
10208
10401
|
|
|
10209
|
-
function
|
|
10210
|
-
const
|
|
10211
|
-
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
}
|
|
10402
|
+
function filterFriendTable() {
|
|
10403
|
+
const q = ($('#friend-search')?.value || '').toLowerCase();
|
|
10404
|
+
$$('#friend-table-body tr').forEach(tr => {
|
|
10405
|
+
const matchSearch = tr.dataset.search?.toLowerCase().includes(q);
|
|
10406
|
+
const matchFilter = friendsFilter === 'all' || tr.dataset.type === friendsFilter;
|
|
10407
|
+
tr.style.display = matchSearch && matchFilter ? '' : 'none';
|
|
10408
|
+
});
|
|
10409
|
+
}
|
|
10218
10410
|
|
|
10411
|
+
function renderRequestsList(requests) {
|
|
10412
|
+
const el = $('#friends-content');
|
|
10219
10413
|
let rows = '';
|
|
10220
|
-
|
|
10221
|
-
const
|
|
10222
|
-
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
<td class="
|
|
10226
|
-
<td class="mono">\${escHtml(r.fromId || r.requesterId || '-')}</td>
|
|
10227
|
-
<td>\${statusBadge}</td>
|
|
10414
|
+
requests.forEach(r => {
|
|
10415
|
+
const stCls = r.status === 'pending' ? 'warn' : r.status === 'accepted' ? 'ok' : 'ghost';
|
|
10416
|
+
rows += \\\`<tr>
|
|
10417
|
+
<td class="mono" style="font-size:11px">\${escHtml(r.id)}</td>
|
|
10418
|
+
<td class="mono" style="font-size:12px">\${escHtml(r.fromId || r.requesterId || '\u2014')}</td>
|
|
10419
|
+
<td><span class="badge badge-\${stCls}">\${escHtml(r.status)}</span></td>
|
|
10228
10420
|
<td>\${timeAgo(r.createdAt)}</td>
|
|
10229
|
-
<td>\${escHtml(r.message || '-')}</td>
|
|
10230
10421
|
<td>
|
|
10231
|
-
<div class="actions-cell">
|
|
10232
|
-
\${r.status === 'pending' ? \`
|
|
10233
|
-
<button class="btn btn-sm btn-ok" onclick="acceptRequest('\${escHtml(r.id)}')">\u2713 Accept</button>
|
|
10234
|
-
<button class="btn btn-sm btn-danger" onclick="rejectRequest('\${escHtml(r.id)}')">\u2717 Reject</button>
|
|
10235
|
-
\` : '-'}
|
|
10236
|
-
</div>
|
|
10422
|
+
\${r.status === 'pending' ? '<div class="actions-cell"><button class="btn btn-sm btn-ok" onclick="acceptFriendReq(\\'' + escHtml(r.id) + '\\')">\u2713 Accept</button><button class="btn btn-sm btn-danger" onclick="rejectFriendReq(\\'' + escHtml(r.id) + '\\')">\u2717 Reject</button></div>' : '\u2014'}
|
|
10237
10423
|
</td>
|
|
10238
|
-
</tr
|
|
10424
|
+
</tr>\\\`;
|
|
10239
10425
|
});
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
<div class="card">
|
|
10243
|
-
<div
|
|
10244
|
-
<
|
|
10245
|
-
<
|
|
10246
|
-
</div>
|
|
10247
|
-
<div
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
<tbody>\${rows}</tbody>
|
|
10251
|
-
</table>
|
|
10252
|
-
</div>
|
|
10253
|
-
</div>\`;
|
|
10426
|
+
html(el, \\\`
|
|
10427
|
+
<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} Refresh</button></div>
|
|
10428
|
+
<div class="card" style="padding:0;overflow:hidden">
|
|
10429
|
+
<div style="overflow-x:auto"><table>
|
|
10430
|
+
<thead><tr><th>Request ID</th><th>From</th><th>Status</th><th>Time</th><th style="width:160px">Actions</th></tr></thead>
|
|
10431
|
+
<tbody>\${rows}</tbody>
|
|
10432
|
+
</table></div>
|
|
10433
|
+
\${!requests.length ? '<div class="empty"><div class="icon">\u{1F4E8}</div><p>No pending requests</p></div>' : ''}
|
|
10434
|
+
</div>
|
|
10435
|
+
\\\`);
|
|
10254
10436
|
}
|
|
10255
10437
|
|
|
10256
|
-
function
|
|
10257
|
-
const el = $('#
|
|
10258
|
-
el.classList.remove('hidden');
|
|
10259
|
-
const sessions = sessionsData?.sessions || [];
|
|
10260
|
-
|
|
10261
|
-
if (sessions.length === 0) {
|
|
10262
|
-
el.innerHTML = '<div class="empty"><div class="icon">\u{1F517}</div><p>No active encrypted sessions</p></div>';
|
|
10263
|
-
return;
|
|
10264
|
-
}
|
|
10265
|
-
|
|
10438
|
+
function renderSessionsList(sessions) {
|
|
10439
|
+
const el = $('#friends-content');
|
|
10266
10440
|
let rows = '';
|
|
10267
10441
|
sessions.forEach(s => {
|
|
10268
|
-
rows +=
|
|
10269
|
-
<td class="mono">\${escHtml(s.peerId)}</td>
|
|
10442
|
+
rows += \\\`<tr>
|
|
10443
|
+
<td class="mono" style="font-size:12px;cursor:pointer" onclick="copyText('\${escHtml(s.peerId)}')">\${escHtml(s.peerId)} \u{1F4CB}</td>
|
|
10270
10444
|
<td>\${timeAgo(s.createdAt)}</td>
|
|
10271
|
-
<td><span class="badge badge-info">\${s.messageCount}
|
|
10272
|
-
</tr
|
|
10445
|
+
<td><span class="badge badge-info">\${s.messageCount} messages</span></td>
|
|
10446
|
+
</tr>\\\`;
|
|
10273
10447
|
});
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
<div class="card">
|
|
10277
|
-
<div
|
|
10278
|
-
<
|
|
10279
|
-
<
|
|
10280
|
-
</div>
|
|
10281
|
-
<div
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
<tbody>\${rows}</tbody>
|
|
10285
|
-
</table>
|
|
10286
|
-
</div>
|
|
10287
|
-
</div>\`;
|
|
10288
|
-
}
|
|
10289
|
-
|
|
10290
|
-
async function showAddFriend() {
|
|
10291
|
-
showModal('modal-add-friend');
|
|
10292
|
-
$('#add-friend-target').value = '';
|
|
10293
|
-
$('#add-friend-target').focus();
|
|
10448
|
+
html(el, \\\`
|
|
10449
|
+
<div class="toolbar"><button class="btn btn-sm btn-default" onclick="loadFriends()">\u{1F504} Refresh</button></div>
|
|
10450
|
+
<div class="card" style="padding:0;overflow:hidden">
|
|
10451
|
+
<div style="overflow-x:auto"><table>
|
|
10452
|
+
<thead><tr><th>Peer ID</th><th>Established</th><th>Messages</th></tr></thead>
|
|
10453
|
+
<tbody>\${rows}</tbody>
|
|
10454
|
+
</table></div>
|
|
10455
|
+
\${!sessions.length ? '<div class="empty"><div class="icon">\u{1F517}</div><p>No active sessions</p></div>' : ''}
|
|
10456
|
+
</div>
|
|
10457
|
+
\\\`);
|
|
10294
10458
|
}
|
|
10295
10459
|
|
|
10460
|
+
function showAddFriendModal() { $('#add-friend-target').value = ''; showModal('modal-add-friend'); setTimeout(() => $('#add-friend-target')?.focus(), 100); }
|
|
10296
10461
|
async function addFriend() {
|
|
10297
10462
|
const target = $('#add-friend-target').value.trim();
|
|
10298
|
-
if (!target) { toast('Enter a temp number or
|
|
10463
|
+
if (!target) { toast('Enter a temp number or node ID', 'warn'); return; }
|
|
10299
10464
|
hideModal('modal-add-friend');
|
|
10300
10465
|
toast('Sending friend request...', 'info');
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
else { toast(r.message || 'Failed to add friend', 'err'); }
|
|
10305
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10466
|
+
const r = await api('/friends', { method: 'POST', body: JSON.stringify({ target }) });
|
|
10467
|
+
if (r.success) { toast(r.message || 'Friend request sent!', 'ok'); loadFriends(); }
|
|
10468
|
+
else { toast(r.message || r.error || 'Failed to add friend', 'err'); }
|
|
10306
10469
|
}
|
|
10307
|
-
|
|
10308
10470
|
async function removeFriend(id) {
|
|
10309
|
-
if (!confirm('Remove friend ' + id + '?
|
|
10310
|
-
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
else { toast(r.message || 'Failed to remove', 'err'); }
|
|
10314
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10471
|
+
if (!confirm('Remove friend ' + id + '?')) return;
|
|
10472
|
+
const r = await api('/friends/' + encodeURIComponent(id), { method: 'DELETE' });
|
|
10473
|
+
if (r.success) { toast('Friend removed', 'ok'); loadFriends(); }
|
|
10474
|
+
else { toast(r.message || r.error || 'Failed', 'err'); }
|
|
10315
10475
|
}
|
|
10316
10476
|
|
|
10317
|
-
let
|
|
10318
|
-
function
|
|
10319
|
-
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
$('#perm-chat').checked = chatChecked;
|
|
10323
|
-
$('#perm-exec').checked = execChecked;
|
|
10477
|
+
let _editFriendId = null;
|
|
10478
|
+
function editFriendPerms(id, perms) {
|
|
10479
|
+
_editFriendId = id;
|
|
10480
|
+
$('#perm-chat').checked = (perms || []).includes('chat');
|
|
10481
|
+
$('#perm-exec').checked = (perms || []).includes('exec');
|
|
10324
10482
|
showModal('modal-permissions');
|
|
10325
10483
|
}
|
|
10326
|
-
|
|
10327
|
-
async function savePermissions() {
|
|
10484
|
+
async function saveFriendPerms() {
|
|
10328
10485
|
const perms = [];
|
|
10329
10486
|
if ($('#perm-chat').checked) perms.push('chat');
|
|
10330
10487
|
if ($('#perm-exec').checked) perms.push('exec');
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
});
|
|
10335
|
-
if (r.success) { toast('Permissions updated', 'ok'); hideModal('modal-permissions'); loadAICQ(); }
|
|
10336
|
-
else { toast(r.message || 'Failed to update', 'err'); }
|
|
10337
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10488
|
+
const r = await api('/friends/' + encodeURIComponent(_editFriendId) + '/permissions', { method: 'PUT', body: JSON.stringify({ permissions: perms }) });
|
|
10489
|
+
if (r.success) { toast('Permissions updated', 'ok'); hideModal('modal-permissions'); loadFriends(); }
|
|
10490
|
+
else { toast(r.message || r.error || 'Failed', 'err'); }
|
|
10338
10491
|
}
|
|
10339
|
-
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/accept', { method: 'POST', body: JSON.stringify({ permissions: ['chat'] }) });
|
|
10343
|
-
if (r.success) { toast('Request accepted', 'ok'); loadAICQ(); }
|
|
10344
|
-
else { toast(r.message || 'Failed', 'err'); }
|
|
10345
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10492
|
+
async function acceptFriendReq(id) {
|
|
10493
|
+
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/accept', { method: 'POST', body: JSON.stringify({ permissions: ['chat'] }) });
|
|
10494
|
+
if (r.success) { toast('Request accepted', 'ok'); loadFriends(); } else { toast(r.message || r.error || 'Failed', 'err'); }
|
|
10346
10495
|
}
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/reject', { method: 'POST', body: JSON.stringify({}) });
|
|
10351
|
-
if (r.success) { toast('Request rejected', 'ok'); loadAICQ(); }
|
|
10352
|
-
else { toast(r.message || 'Failed', 'err'); }
|
|
10353
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10496
|
+
async function rejectFriendReq(id) {
|
|
10497
|
+
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/reject', { method: 'POST', body: JSON.stringify({}) });
|
|
10498
|
+
if (r.success) { toast('Request rejected', 'ok'); loadFriends(); } else { toast(r.message || r.error || 'Failed', 'err'); }
|
|
10354
10499
|
}
|
|
10355
10500
|
|
|
10356
|
-
// \
|
|
10501
|
+
// \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
|
|
10502
|
+
// PAGE: Model Management
|
|
10503
|
+
// \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
|
|
10504
|
+
let _modelProviders = null;
|
|
10505
|
+
|
|
10357
10506
|
async function loadModels() {
|
|
10358
|
-
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
}
|
|
10365
|
-
setLoading('models', false);
|
|
10507
|
+
const el = $('#models-content');
|
|
10508
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>Loading model configuration...</div>');
|
|
10509
|
+
const data = await api('/models');
|
|
10510
|
+
if (data.error) { html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(data.error) + '</p></div>'); return; }
|
|
10511
|
+
_modelProviders = data;
|
|
10512
|
+
renderModels(data);
|
|
10366
10513
|
}
|
|
10367
10514
|
|
|
10368
10515
|
function renderModels(data) {
|
|
10369
10516
|
const el = $('#models-content');
|
|
10370
10517
|
const providers = data.providers || [];
|
|
10371
|
-
const configured = providers.filter(p => p.configured);
|
|
10518
|
+
const configured = providers.filter(p => p.configured).length;
|
|
10372
10519
|
|
|
10373
|
-
let
|
|
10520
|
+
let cards = '';
|
|
10374
10521
|
providers.forEach(p => {
|
|
10375
|
-
const
|
|
10376
|
-
const
|
|
10377
|
-
|
|
10378
|
-
<
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10522
|
+
const icon = p.id === 'openai' ? '\u{1F7E2}' : p.id === 'anthropic' ? '\u{1F7E0}' : p.id === 'google' ? '\u{1F535}' : p.id === 'ollama' ? '\u{1F7E3}' : p.id === 'deepseek' ? '\u{1F537}' : p.id === 'groq' ? '\u26A1' : p.id === 'openrouter' ? '\u{1F310}' : '\u26AA';
|
|
10523
|
+
const statusBadge = p.configured
|
|
10524
|
+
? '<span class="badge badge-ok">\u25CF Configured</span>'
|
|
10525
|
+
: '<span class="badge badge-ghost">Not set</span>';
|
|
10526
|
+
const currentModel = p.modelId ? '<span class="prov-model">' + escHtml(p.modelId) + '</span>' : '';
|
|
10527
|
+
|
|
10528
|
+
cards += \\\`
|
|
10529
|
+
<div class="provider-card" onclick="showModelConfigModal('\${escHtml(p.id)}')">
|
|
10530
|
+
<div class="prov-head">
|
|
10531
|
+
<div class="prov-name">\${icon} \${escHtml(p.name)}</div>
|
|
10532
|
+
\${statusBadge}
|
|
10533
|
+
</div>
|
|
10534
|
+
<div class="prov-desc">\${escHtml(p.description)}</div>
|
|
10535
|
+
\${currentModel}
|
|
10536
|
+
<div class="prov-actions">
|
|
10537
|
+
<button class="btn btn-sm btn-primary" onclick="event.stopPropagation();showModelConfigModal('\${escHtml(p.id)}')">Configure</button>
|
|
10538
|
+
</div>
|
|
10539
|
+
</div>\\\`;
|
|
10383
10540
|
});
|
|
10384
10541
|
|
|
10385
|
-
let
|
|
10386
|
-
if (data.currentModels && data.currentModels.length
|
|
10542
|
+
let activeModelsSection = '';
|
|
10543
|
+
if (data.currentModels && data.currentModels.length) {
|
|
10387
10544
|
let rows = '';
|
|
10388
10545
|
data.currentModels.forEach(m => {
|
|
10389
|
-
rows +=
|
|
10390
|
-
<td>\${escHtml(m.provider
|
|
10391
|
-
<td class="mono">\${escHtml(m.modelId
|
|
10392
|
-
<td
|
|
10393
|
-
<td class="mono">\${escHtml(m.baseUrl || '
|
|
10394
|
-
<td><button class="btn btn-sm btn-ghost" onclick="
|
|
10395
|
-
</tr
|
|
10546
|
+
rows += \\\`<tr>
|
|
10547
|
+
<td style="font-weight:500">\${escHtml(m.provider)}</td>
|
|
10548
|
+
<td class="mono">\${escHtml(m.modelId)}</td>
|
|
10549
|
+
<td><span class="badge badge-ok">\u25CF Key set</span></td>
|
|
10550
|
+
<td class="mono" style="font-size:11px">\${escHtml(m.baseUrl || 'default')}</td>
|
|
10551
|
+
<td><button class="btn btn-sm btn-ghost" onclick="showModelConfigModal('\${escHtml(m.providerId)}')">Edit</button></td>
|
|
10552
|
+
</tr>\\\`;
|
|
10396
10553
|
});
|
|
10397
|
-
|
|
10398
|
-
<div class="card">
|
|
10399
|
-
<div class="card-header">
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
<tbody>\${rows}</tbody>
|
|
10406
|
-
</table>
|
|
10407
|
-
</div>
|
|
10408
|
-
</div>\`;
|
|
10554
|
+
activeModelsSection = \\\`
|
|
10555
|
+
<div class="card" style="margin-top:20px">
|
|
10556
|
+
<div class="card-header"><div class="card-title">\u{1F4CA} Active Model Configurations</div></div>
|
|
10557
|
+
<div style="overflow-x:auto"><table>
|
|
10558
|
+
<thead><tr><th>Provider</th><th>Model</th><th>API Key</th><th>Base URL</th><th>Actions</th></tr></thead>
|
|
10559
|
+
<tbody>\${rows}</tbody>
|
|
10560
|
+
</table></div>
|
|
10561
|
+
</div>\\\`;
|
|
10409
10562
|
}
|
|
10410
10563
|
|
|
10411
|
-
el
|
|
10412
|
-
<
|
|
10413
|
-
|
|
10414
|
-
|
|
10415
|
-
|
|
10564
|
+
html(el, \\\`
|
|
10565
|
+
<div class="stats-grid" style="margin-bottom:24px">
|
|
10566
|
+
<div class="stat-card">
|
|
10567
|
+
<div class="stat-icon" style="background:var(--accent-bg)">\u{1F9E0}</div>
|
|
10568
|
+
<div class="stat-label">Configured</div>
|
|
10569
|
+
<div class="stat-value">\${configured} / \${providers.length}</div>
|
|
10570
|
+
<div class="stat-sub">Providers with API keys</div>
|
|
10571
|
+
</div>
|
|
10572
|
+
</div>
|
|
10573
|
+
<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>
|
|
10574
|
+
<div class="provider-grid">\${cards}</div>
|
|
10575
|
+
\${activeModelsSection}
|
|
10576
|
+
\\\`);
|
|
10416
10577
|
}
|
|
10417
10578
|
|
|
10418
|
-
let
|
|
10419
|
-
function
|
|
10420
|
-
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
$('#model-
|
|
10425
|
-
$('#model-api-key').value =
|
|
10426
|
-
$('#model-api-key').placeholder =
|
|
10427
|
-
$('#model-model-id').value =
|
|
10428
|
-
$('#model-model-id').placeholder =
|
|
10429
|
-
$('#model-base-url').value =
|
|
10430
|
-
$('#model-base-url').placeholder =
|
|
10431
|
-
|
|
10579
|
+
let _editProviderId = null;
|
|
10580
|
+
function showModelConfigModal(id) {
|
|
10581
|
+
const p = (_modelProviders?.providers || []).find(x => x.id === id);
|
|
10582
|
+
if (!p) { toast('Provider not found', 'err'); return; }
|
|
10583
|
+
_editProviderId = id;
|
|
10584
|
+
$('#model-name').textContent = p.name;
|
|
10585
|
+
$('#model-icon').textContent = p.id === 'openai' ? '\u{1F7E2}' : p.id === 'anthropic' ? '\u{1F7E0}' : '\u{1F7E2}';
|
|
10586
|
+
$('#model-api-key').value = '';
|
|
10587
|
+
$('#model-api-key').placeholder = p.apiKeyHint || 'Enter API key';
|
|
10588
|
+
$('#model-model-id').value = p.modelId || '';
|
|
10589
|
+
$('#model-model-id').placeholder = p.modelHint || 'Model ID';
|
|
10590
|
+
$('#model-base-url').value = p.baseUrl || '';
|
|
10591
|
+
$('#model-base-url').placeholder = p.baseUrlHint || 'Default URL';
|
|
10592
|
+
$('#model-current-key').textContent = p.apiKeyHasValue ? 'Current: ' + p.apiKey : 'No API key configured';
|
|
10432
10593
|
showModal('modal-model-config');
|
|
10433
10594
|
}
|
|
10434
|
-
|
|
10435
10595
|
async function saveModelConfig() {
|
|
10436
10596
|
const apiKey = $('#model-api-key').value.trim();
|
|
10437
10597
|
const modelId = $('#model-model-id').value.trim();
|
|
10438
10598
|
const baseUrl = $('#model-base-url').value.trim();
|
|
10599
|
+
if (!apiKey && !modelId) { toast('Enter at least an API key or model ID', 'warn'); return; }
|
|
10600
|
+
hideModal('modal-model-config');
|
|
10601
|
+
toast('Saving configuration...', 'info');
|
|
10602
|
+
const r = await api('/models/' + encodeURIComponent(_editProviderId), { method: 'PUT', body: JSON.stringify({ apiKey, modelId, baseUrl }) });
|
|
10603
|
+
if (r.success) { toast(r.message || 'Configuration saved!', 'ok'); loadModels(); }
|
|
10604
|
+
else { toast(r.message || r.error || 'Failed to save', 'err'); }
|
|
10605
|
+
}
|
|
10606
|
+
|
|
10607
|
+
// \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
|
|
10608
|
+
// PAGE: Settings
|
|
10609
|
+
// \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
|
|
10610
|
+
async function loadSettings() {
|
|
10611
|
+
const el = $('#settings-content');
|
|
10612
|
+
html(el, '<div class="loading-mask"><div class="spinner"></div>Loading settings...</div>');
|
|
10613
|
+
const [status, identity, config] = await Promise.all([api('/status'), api('/identity'), api('/config')]);
|
|
10439
10614
|
|
|
10440
|
-
if (
|
|
10441
|
-
|
|
10615
|
+
if (config.error) {
|
|
10616
|
+
html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(config.error) + '</p></div>');
|
|
10442
10617
|
return;
|
|
10443
10618
|
}
|
|
10444
10619
|
|
|
10445
|
-
|
|
10446
|
-
|
|
10620
|
+
html(el, \\\`
|
|
10621
|
+
<p class="section-desc">AICQ plugin runtime configuration and system information.</p>
|
|
10447
10622
|
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
if (r.success) { toast('Model config saved!', 'ok'); loadModels(); }
|
|
10454
|
-
else { toast(r.message || 'Failed to save', 'err'); }
|
|
10455
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10456
|
-
}
|
|
10623
|
+
<div class="card">
|
|
10624
|
+
<div class="card-header"><div class="card-title">\u{1F50C} Connection Settings</div></div>
|
|
10625
|
+
<div class="detail-row"><div class="detail-key">Server URL</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(status.serverUrl)}')">\${escHtml(status.serverUrl)} \u{1F4CB}</div></div>
|
|
10626
|
+
<div class="detail-row"><div class="detail-key">WebSocket Status</div><div class="detail-val"><span class="badge badge-\${status.connected ? 'ok' : 'danger'}">\${status.connected ? 'Connected' : 'Disconnected'}</span></div></div>
|
|
10627
|
+
</div>
|
|
10457
10628
|
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10629
|
+
<div class="card">
|
|
10630
|
+
<div class="card-header"><div class="card-title">\u{1F916} Agent Identity</div></div>
|
|
10631
|
+
<div class="detail-row"><div class="detail-key">Agent ID</div><div class="detail-val mono" style="cursor:pointer" onclick="copyText('\${escHtml(identity.agentId)}')">\${escHtml(identity.agentId)} \u{1F4CB}</div></div>
|
|
10632
|
+
<div class="detail-row"><div class="detail-key">Public Key Fingerprint</div><div class="detail-val mono">\${escHtml(identity.publicKeyFingerprint)}</div></div>
|
|
10633
|
+
</div>
|
|
10634
|
+
|
|
10635
|
+
<div class="card">
|
|
10636
|
+
<div class="card-header"><div class="card-title">\u{1F4C1} Config File</div></div>
|
|
10637
|
+
<div class="detail-row"><div class="detail-key">Source</div><div class="detail-val" style="cursor:pointer" onclick="copyText('\${escHtml(config.configPath || '')}')">\${escHtml(config.configPath || 'Not found')} \u{1F4CB}</div></div>
|
|
10638
|
+
<div class="detail-row"><div class="detail-key">Config Size</div><div class="detail-val">\${config.configSize || 0} bytes</div></div>
|
|
10639
|
+
</div>
|
|
10640
|
+
|
|
10641
|
+
<div class="card">
|
|
10642
|
+
<div class="card-header"><div class="card-title">\u{1F4CA} Statistics</div></div>
|
|
10643
|
+
<div class="detail-row"><div class="detail-key">Friends Count</div><div class="detail-val">\${status.friendCount || 0}</div></div>
|
|
10644
|
+
<div class="detail-row"><div class="detail-key">Active Sessions</div><div class="detail-val">\${status.sessionCount || 0}</div></div>
|
|
10645
|
+
<div class="detail-row"><div class="detail-key">Plugin Version</div><div class="detail-val">1.0.4</div></div>
|
|
10646
|
+
</div>
|
|
10647
|
+
\\\`);
|
|
10472
10648
|
}
|
|
10473
10649
|
|
|
10474
|
-
// \
|
|
10650
|
+
// \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
|
|
10651
|
+
// INIT
|
|
10652
|
+
// \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
|
|
10475
10653
|
document.addEventListener('DOMContentLoaded', () => {
|
|
10476
|
-
$$('.
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10654
|
+
$$('.nav-item').forEach(n => n.addEventListener('click', () => navigate(n.dataset.page)));
|
|
10655
|
+
$('.toggle-btn')?.addEventListener('click', toggleSidebar);
|
|
10656
|
+
|
|
10657
|
+
// Load dashboard
|
|
10658
|
+
navigate('dashboard');
|
|
10659
|
+
|
|
10481
10660
|
// Auto-refresh status every 30s
|
|
10482
|
-
setInterval(
|
|
10661
|
+
refreshTimer = setInterval(() => {
|
|
10662
|
+
if (currentPage === 'dashboard') loadDashboard();
|
|
10663
|
+
// Update status dot
|
|
10664
|
+
api('/status').then(s => {
|
|
10665
|
+
if (!s.error) {
|
|
10666
|
+
const dot = $('#header-dot');
|
|
10667
|
+
if (dot) { dot.className = 'dot ' + (s.connected ? 'dot-ok' : 'dot-err'); }
|
|
10668
|
+
const txt = $('#header-status');
|
|
10669
|
+
if (txt) txt.textContent = s.connected ? 'Connected' : 'Disconnected';
|
|
10670
|
+
}
|
|
10671
|
+
});
|
|
10672
|
+
}, 30000);
|
|
10483
10673
|
});
|
|
10484
10674
|
`;
|
|
10485
10675
|
var HTML = `<!DOCTYPE html>
|
|
@@ -10487,62 +10677,86 @@ var HTML = `<!DOCTYPE html>
|
|
|
10487
10677
|
<head>
|
|
10488
10678
|
<meta charset="UTF-8">
|
|
10489
10679
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10490
|
-
<title>AICQ Management</title>
|
|
10680
|
+
<title>AICQ Management Console</title>
|
|
10491
10681
|
<style>${CSS}</style>
|
|
10492
10682
|
</head>
|
|
10493
10683
|
<body>
|
|
10494
|
-
<div class="
|
|
10495
|
-
|
|
10496
|
-
<
|
|
10497
|
-
<
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
</div>
|
|
10684
|
+
<div class="app">
|
|
10685
|
+
<!-- Sidebar -->
|
|
10686
|
+
<aside class="sidebar">
|
|
10687
|
+
<div class="sidebar-header">
|
|
10688
|
+
<div class="sidebar-logo">AQ</div>
|
|
10689
|
+
<div class="sidebar-header-text"><h1>AICQ</h1><span>Management Console</span></div>
|
|
10690
|
+
</div>
|
|
10691
|
+
<nav class="sidebar-nav">
|
|
10692
|
+
<div class="nav-group">
|
|
10693
|
+
<div class="nav-group-title">Overview</div>
|
|
10694
|
+
<div class="nav-item active" data-page="dashboard"><span class="nav-icon">\u{1F4CA}</span><span class="nav-label">Dashboard</span></div>
|
|
10695
|
+
</div>
|
|
10696
|
+
<div class="nav-group">
|
|
10697
|
+
<div class="nav-group-title">Management</div>
|
|
10698
|
+
<div class="nav-item" data-page="agents"><span class="nav-icon">\u{1F916}</span><span class="nav-label">Agents</span></div>
|
|
10699
|
+
<div class="nav-item" data-page="friends"><span class="nav-icon">\u{1F465}</span><span class="nav-label">Friends</span><span class="nav-badge" id="friend-badge">0</span></div>
|
|
10700
|
+
<div class="nav-item" data-page="models"><span class="nav-icon">\u{1F9E0}</span><span class="nav-label">Models</span></div>
|
|
10701
|
+
</div>
|
|
10702
|
+
<div class="nav-group">
|
|
10703
|
+
<div class="nav-group-title">System</div>
|
|
10704
|
+
<div class="nav-item" data-page="settings"><span class="nav-icon">\u2699\uFE0F</span><span class="nav-label">Settings</span></div>
|
|
10705
|
+
</div>
|
|
10706
|
+
</nav>
|
|
10707
|
+
<div class="sidebar-footer" onclick="toggleSidebar()">
|
|
10708
|
+
<span>\u25C0</span><span class="sidebar-footer-text">Collapse sidebar</span>
|
|
10709
|
+
</div>
|
|
10710
|
+
</aside>
|
|
10711
|
+
|
|
10712
|
+
<!-- Main -->
|
|
10713
|
+
<main class="main">
|
|
10714
|
+
<header class="main-header">
|
|
10715
|
+
<button class="toggle-btn" onclick="toggleSidebar()">\u2630</button>
|
|
10716
|
+
<h2 id="main-title">Dashboard</h2>
|
|
10717
|
+
<div class="header-status">
|
|
10718
|
+
<span class="dot dot-err" id="header-dot"></span>
|
|
10719
|
+
<span id="header-status">Connecting...</span>
|
|
10720
|
+
</div>
|
|
10721
|
+
<div class="header-actions">
|
|
10722
|
+
<button class="btn btn-sm btn-default" onclick="loadPage(currentPage)">\u{1F504} Refresh</button>
|
|
10723
|
+
</div>
|
|
10724
|
+
</header>
|
|
10725
|
+
<div class="main-content">
|
|
10501
10726
|
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
<button class="tab-btn" data-tab="aicq" onclick="switchTab('aicq')">\u{1F4AC} AICQ Management</button>
|
|
10505
|
-
<button class="tab-btn" data-tab="models" onclick="switchTab('models')">\u{1F9E0} Model Management</button>
|
|
10506
|
-
</div>
|
|
10727
|
+
<!-- Dashboard -->
|
|
10728
|
+
<div class="page active" id="page-dashboard"><div id="dashboard-content"><div class="loading-mask"><div class="spinner"></div>Loading...</div></div></div>
|
|
10507
10729
|
|
|
10508
|
-
<!--
|
|
10509
|
-
<div class="
|
|
10510
|
-
<p class="section-desc">View and manage AICQ agent identities. Each agent has its own Ed25519 key pair and encrypted session state.</p>
|
|
10511
|
-
<div id="agents-content">
|
|
10512
|
-
<div class="loading"><div class="spinner"></div> Loading agents...</div>
|
|
10513
|
-
</div>
|
|
10514
|
-
</div>
|
|
10730
|
+
<!-- Agents -->
|
|
10731
|
+
<div class="page" id="page-agents"><div id="agents-content"></div></div>
|
|
10515
10732
|
|
|
10516
|
-
<!--
|
|
10517
|
-
<div class="
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
<div id="aicq-friends" class="hidden"></div>
|
|
10522
|
-
<div id="aicq-requests" class="hidden"></div>
|
|
10523
|
-
<div id="aicq-sessions" class="hidden"></div>
|
|
10524
|
-
</div>
|
|
10525
|
-
</div>
|
|
10733
|
+
<!-- Friends -->
|
|
10734
|
+
<div class="page" id="page-friends">
|
|
10735
|
+
<div id="friends-tabs" style="display:flex;gap:6px;margin-bottom:16px"></div>
|
|
10736
|
+
<div id="friends-content"></div>
|
|
10737
|
+
</div>
|
|
10526
10738
|
|
|
10527
|
-
<!--
|
|
10528
|
-
<div class="
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10739
|
+
<!-- Models -->
|
|
10740
|
+
<div class="page" id="page-models"><div id="models-content"></div></div>
|
|
10741
|
+
|
|
10742
|
+
<!-- Settings -->
|
|
10743
|
+
<div class="page" id="page-settings"><div id="settings-content"></div></div>
|
|
10744
|
+
|
|
10745
|
+
</div>
|
|
10746
|
+
</main>
|
|
10533
10747
|
</div>
|
|
10534
10748
|
|
|
10535
10749
|
<!-- Modal: Add Friend -->
|
|
10536
10750
|
<div class="modal-overlay hidden" id="modal-add-friend" onclick="if(event.target===this)hideModal('modal-add-friend')">
|
|
10537
10751
|
<div class="modal">
|
|
10538
|
-
<h3>\u2795 Add Friend</h3>
|
|
10752
|
+
<div class="modal-header"><h3>\u2795 Add Friend</h3><button class="modal-close" onclick="hideModal('modal-add-friend')">\u2715</button></div>
|
|
10539
10753
|
<div class="form-group">
|
|
10540
|
-
<label>Temp Number or
|
|
10541
|
-
<input id="add-friend-target" type="text" placeholder="
|
|
10542
|
-
<div class="hint">Enter the 6-digit temporary number
|
|
10754
|
+
<label>Temp Number or Node ID</label>
|
|
10755
|
+
<input id="add-friend-target" type="text" placeholder="6-digit number or node ID" onkeydown="if(event.key==='Enter')addFriend()">
|
|
10756
|
+
<div class="hint">Enter the 6-digit temporary number or the full node ID of your friend.</div>
|
|
10543
10757
|
</div>
|
|
10544
10758
|
<div class="form-actions">
|
|
10545
|
-
<button class="btn" onclick="hideModal('modal-add-friend')">Cancel</button>
|
|
10759
|
+
<button class="btn btn-default" onclick="hideModal('modal-add-friend')">Cancel</button>
|
|
10546
10760
|
<button class="btn btn-primary" onclick="addFriend()">Send Request</button>
|
|
10547
10761
|
</div>
|
|
10548
10762
|
</div>
|
|
@@ -10551,49 +10765,59 @@ var HTML = `<!DOCTYPE html>
|
|
|
10551
10765
|
<!-- Modal: Edit Permissions -->
|
|
10552
10766
|
<div class="modal-overlay hidden" id="modal-permissions" onclick="if(event.target===this)hideModal('modal-permissions')">
|
|
10553
10767
|
<div class="modal">
|
|
10554
|
-
<h3>\u2699\uFE0F Edit Permissions</h3>
|
|
10768
|
+
<div class="modal-header"><h3>\u2699\uFE0F Edit Permissions</h3><button class="modal-close" onclick="hideModal('modal-permissions')">\u2715</button></div>
|
|
10555
10769
|
<div class="form-group">
|
|
10556
|
-
<label>Permissions
|
|
10557
|
-
<div class="perm-checks" style="margin-top:
|
|
10558
|
-
<label><input type="checkbox" id="perm-chat" checked> Chat <span style="color:var(--text3);font-size:11px">(send/receive messages)</span></label>
|
|
10559
|
-
<label><input type="checkbox" id="perm-exec"> Exec <span style="color:var(--text3);font-size:11px">(execute tools)</span></label>
|
|
10770
|
+
<label>Friend Permissions</label>
|
|
10771
|
+
<div class="perm-checks" style="margin-top:10px">
|
|
10772
|
+
<label><input type="checkbox" id="perm-chat" checked> \u{1F4AC} Chat <span style="color:var(--text3);font-size:11px">(send/receive messages)</span></label>
|
|
10773
|
+
<label><input type="checkbox" id="perm-exec"> \u{1F527} Exec <span style="color:var(--text3);font-size:11px">(execute tools/commands)</span></label>
|
|
10560
10774
|
</div>
|
|
10561
10775
|
</div>
|
|
10562
10776
|
<div class="form-actions">
|
|
10563
|
-
<button class="btn" onclick="hideModal('modal-permissions')">Cancel</button>
|
|
10564
|
-
<button class="btn btn-primary" onclick="
|
|
10777
|
+
<button class="btn btn-default" onclick="hideModal('modal-permissions')">Cancel</button>
|
|
10778
|
+
<button class="btn btn-primary" onclick="saveFriendPerms()">Save Permissions</button>
|
|
10565
10779
|
</div>
|
|
10566
10780
|
</div>
|
|
10567
10781
|
</div>
|
|
10568
10782
|
|
|
10569
10783
|
<!-- Modal: Model Config -->
|
|
10570
10784
|
<div class="modal-overlay hidden" id="modal-model-config" onclick="if(event.target===this)hideModal('modal-model-config')">
|
|
10571
|
-
<div class="modal" style="max-width:
|
|
10572
|
-
<h3>\u{
|
|
10785
|
+
<div class="modal" style="max-width:560px">
|
|
10786
|
+
<div class="modal-header"><h3><span id="model-icon">\u{1F7E2}</span> <span id="model-name">Provider</span></h3><button class="modal-close" onclick="hideModal('modal-model-config')">\u2715</button></div>
|
|
10787
|
+
<div style="margin-bottom:16px;font-size:12px;color:var(--text3)" id="model-current-key"></div>
|
|
10573
10788
|
<div class="form-group">
|
|
10574
|
-
<label
|
|
10575
|
-
<input id="model-api-key" type="password" placeholder="sk-...">
|
|
10576
|
-
<div class="hint">
|
|
10789
|
+
<label>\u{1F511} API Key</label>
|
|
10790
|
+
<div class="input-prefix"><span class="prefix">\u{1F511}</span><input id="model-api-key" type="password" placeholder="sk-..."></div>
|
|
10791
|
+
<div class="hint">Leave blank to keep the existing key. Enter a new key to replace it.</div>
|
|
10577
10792
|
</div>
|
|
10578
10793
|
<div class="form-group">
|
|
10579
|
-
<label
|
|
10580
|
-
<input id="model-model-id" type="text" placeholder="gpt-4o">
|
|
10581
|
-
<div class="hint">The model
|
|
10794
|
+
<label>\u{1F916} Model ID</label>
|
|
10795
|
+
<div class="input-prefix"><span class="prefix">\u{1F916}</span><input id="model-model-id" type="text" placeholder="gpt-4o"></div>
|
|
10796
|
+
<div class="hint">The model to use. E.g. gpt-4o, claude-sonnet-4-20250514, etc.</div>
|
|
10582
10797
|
</div>
|
|
10583
10798
|
<div class="form-group">
|
|
10584
|
-
<label
|
|
10585
|
-
<input id="model-base-url" type="text" placeholder="https
|
|
10586
|
-
<div class="hint">Custom
|
|
10799
|
+
<label>\u{1F310} Base URL <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--text3)">(optional)</span></label>
|
|
10800
|
+
<div class="input-prefix"><span class="prefix">\u{1F310}</span><input id="model-base-url" type="text" placeholder="https://..."></div>
|
|
10801
|
+
<div class="hint">Custom endpoint URL. Only needed for proxies or self-hosted models.</div>
|
|
10587
10802
|
</div>
|
|
10588
10803
|
<div class="form-actions">
|
|
10589
|
-
<button class="btn" onclick="hideModal('modal-model-config')">Cancel</button>
|
|
10590
|
-
<button class="btn btn-primary" onclick="saveModelConfig()"
|
|
10804
|
+
<button class="btn btn-default" onclick="hideModal('modal-model-config')">Cancel</button>
|
|
10805
|
+
<button class="btn btn-primary" onclick="saveModelConfig()">\u{1F4BE} Save Configuration</button>
|
|
10591
10806
|
</div>
|
|
10592
10807
|
</div>
|
|
10593
10808
|
</div>
|
|
10594
10809
|
|
|
10595
|
-
<!--
|
|
10596
|
-
<div class="
|
|
10810
|
+
<!-- Modal: View Agent -->
|
|
10811
|
+
<div class="modal-overlay hidden" id="modal-view-agent" onclick="if(event.target===this)hideModal('modal-view-agent')">
|
|
10812
|
+
<div class="modal">
|
|
10813
|
+
<div class="modal-header"><h3>\u{1F916} <span id="view-agent-title">Agent</span></h3><button class="modal-close" onclick="hideModal('modal-view-agent')">\u2715</button></div>
|
|
10814
|
+
<div id="view-agent-body"></div>
|
|
10815
|
+
<div class="form-actions"><button class="btn btn-default" onclick="hideModal('modal-view-agent')">Close</button></div>
|
|
10816
|
+
</div>
|
|
10817
|
+
</div>
|
|
10818
|
+
|
|
10819
|
+
<!-- Toast Container -->
|
|
10820
|
+
<div id="toast-container" class="toast-container"></div>
|
|
10597
10821
|
|
|
10598
10822
|
<script>${JS}</script>
|
|
10599
10823
|
</body>
|
|
@@ -10607,113 +10831,60 @@ import * as fs5 from "fs";
|
|
|
10607
10831
|
import * as path5 from "path";
|
|
10608
10832
|
import * as os from "os";
|
|
10609
10833
|
var MODEL_PROVIDERS = [
|
|
10610
|
-
{
|
|
10611
|
-
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
},
|
|
10619
|
-
{
|
|
10620
|
-
id: "anthropic",
|
|
10621
|
-
name: "Anthropic",
|
|
10622
|
-
description: "Claude 4, Claude 3.5 Sonnet, Haiku",
|
|
10623
|
-
apiKeyHint: "sk-ant-...",
|
|
10624
|
-
modelHint: "claude-sonnet-4-20250514",
|
|
10625
|
-
baseUrlHint: "https://api.anthropic.com",
|
|
10626
|
-
configKey: "anthropic"
|
|
10627
|
-
},
|
|
10628
|
-
{
|
|
10629
|
-
id: "google",
|
|
10630
|
-
name: "Google AI",
|
|
10631
|
-
description: "Gemini 2.5 Pro, Gemini 2.5 Flash",
|
|
10632
|
-
apiKeyHint: "AI...",
|
|
10633
|
-
modelHint: "gemini-2.5-pro",
|
|
10634
|
-
baseUrlHint: "",
|
|
10635
|
-
configKey: "google"
|
|
10636
|
-
},
|
|
10637
|
-
{
|
|
10638
|
-
id: "groq",
|
|
10639
|
-
name: "Groq",
|
|
10640
|
-
description: "Llama 3, Mixtral \u2014 ultra fast inference",
|
|
10641
|
-
apiKeyHint: "gsk_...",
|
|
10642
|
-
modelHint: "llama-3.3-70b-versatile",
|
|
10643
|
-
baseUrlHint: "https://api.groq.com/openai/v1",
|
|
10644
|
-
configKey: "groq"
|
|
10645
|
-
},
|
|
10646
|
-
{
|
|
10647
|
-
id: "deepseek",
|
|
10648
|
-
name: "DeepSeek",
|
|
10649
|
-
description: "DeepSeek V3, DeepSeek R1",
|
|
10650
|
-
apiKeyHint: "sk-...",
|
|
10651
|
-
modelHint: "deepseek-chat",
|
|
10652
|
-
baseUrlHint: "https://api.deepseek.com/v1",
|
|
10653
|
-
configKey: "deepseek"
|
|
10654
|
-
},
|
|
10655
|
-
{
|
|
10656
|
-
id: "ollama",
|
|
10657
|
-
name: "Ollama (Local)",
|
|
10658
|
-
description: "Run models locally on your machine",
|
|
10659
|
-
apiKeyHint: "(no key needed)",
|
|
10660
|
-
modelHint: "llama3",
|
|
10661
|
-
baseUrlHint: "http://localhost:11434/v1",
|
|
10662
|
-
configKey: "ollama"
|
|
10663
|
-
},
|
|
10664
|
-
{
|
|
10665
|
-
id: "openrouter",
|
|
10666
|
-
name: "OpenRouter",
|
|
10667
|
-
description: "Unified API for 200+ models",
|
|
10668
|
-
apiKeyHint: "sk-or-...",
|
|
10669
|
-
modelHint: "openai/gpt-4o",
|
|
10670
|
-
baseUrlHint: "https://openrouter.ai/api/v1",
|
|
10671
|
-
configKey: "openrouter"
|
|
10672
|
-
},
|
|
10673
|
-
{
|
|
10674
|
-
id: "mistral",
|
|
10675
|
-
name: "Mistral AI",
|
|
10676
|
-
description: "Mistral Large, Medium, Small",
|
|
10677
|
-
apiKeyHint: "(your key)",
|
|
10678
|
-
modelHint: "mistral-large-latest",
|
|
10679
|
-
baseUrlHint: "https://api.mistral.ai/v1",
|
|
10680
|
-
configKey: "mistral"
|
|
10681
|
-
}
|
|
10834
|
+
{ id: "openai", name: "OpenAI", description: "GPT-4o, GPT-4, GPT-3.5, o1, o3", apiKeyHint: "sk-...", modelHint: "gpt-4o", baseUrlHint: "https://api.openai.com/v1", configKey: "openai" },
|
|
10835
|
+
{ id: "anthropic", name: "Anthropic", description: "Claude 4, Claude 3.5 Sonnet, Haiku, Opus", apiKeyHint: "sk-ant-...", modelHint: "claude-sonnet-4-20250514", baseUrlHint: "https://api.anthropic.com", configKey: "anthropic" },
|
|
10836
|
+
{ id: "google", name: "Google AI", description: "Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini Pro", apiKeyHint: "AI...", modelHint: "gemini-2.5-pro", baseUrlHint: "", configKey: "google" },
|
|
10837
|
+
{ id: "groq", name: "Groq", description: "Llama 3.3, Mixtral \u2014 ultra fast inference", apiKeyHint: "gsk_...", modelHint: "llama-3.3-70b-versatile", baseUrlHint: "https://api.groq.com/openai/v1", configKey: "groq" },
|
|
10838
|
+
{ id: "deepseek", name: "DeepSeek", description: "DeepSeek V3, DeepSeek R1 \u2014 strong reasoning", apiKeyHint: "sk-...", modelHint: "deepseek-chat", baseUrlHint: "https://api.deepseek.com/v1", configKey: "deepseek" },
|
|
10839
|
+
{ id: "ollama", name: "Ollama (Local)", description: "Run models locally \u2014 Llama, Mistral, Phi, etc.", apiKeyHint: "(no key needed)", modelHint: "llama3", baseUrlHint: "http://localhost:11434/v1", configKey: "ollama" },
|
|
10840
|
+
{ 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" },
|
|
10841
|
+
{ 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" },
|
|
10842
|
+
{ 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" },
|
|
10843
|
+
{ 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" }
|
|
10682
10844
|
];
|
|
10683
|
-
function
|
|
10684
|
-
const
|
|
10845
|
+
function findConfigPath() {
|
|
10846
|
+
const openclawPaths = [
|
|
10685
10847
|
path5.join(process.cwd(), "openclaw.json"),
|
|
10686
|
-
path5.join(process.cwd(), "stableclaw.json"),
|
|
10687
10848
|
path5.join(os.homedir(), ".config", "openclaw", "openclaw.json"),
|
|
10688
|
-
path5.join(os.homedir(), ".config", "stableclaw", "stableclaw.json"),
|
|
10689
10849
|
path5.join(os.homedir(), ".openclaw", "openclaw.json"),
|
|
10850
|
+
path5.join(os.homedir(), "openclaw.json")
|
|
10851
|
+
];
|
|
10852
|
+
for (const p of openclawPaths) {
|
|
10853
|
+
try {
|
|
10854
|
+
if (fs5.existsSync(p))
|
|
10855
|
+
return p;
|
|
10856
|
+
} catch {
|
|
10857
|
+
}
|
|
10858
|
+
}
|
|
10859
|
+
const stableclawPaths = [
|
|
10860
|
+
path5.join(process.cwd(), "stableclaw.json"),
|
|
10861
|
+
path5.join(os.homedir(), ".config", "stableclaw", "stableclaw.json"),
|
|
10690
10862
|
path5.join(os.homedir(), ".stableclaw", "stableclaw.json"),
|
|
10691
|
-
path5.join(os.homedir(), "openclaw.json"),
|
|
10692
10863
|
path5.join(os.homedir(), "stableclaw.json")
|
|
10693
10864
|
];
|
|
10694
|
-
for (const p of
|
|
10865
|
+
for (const p of stableclawPaths) {
|
|
10695
10866
|
try {
|
|
10696
|
-
if (fs5.existsSync(p))
|
|
10867
|
+
if (fs5.existsSync(p))
|
|
10697
10868
|
return p;
|
|
10698
|
-
}
|
|
10699
10869
|
} catch {
|
|
10700
10870
|
}
|
|
10701
10871
|
}
|
|
10702
10872
|
return null;
|
|
10703
10873
|
}
|
|
10704
|
-
function
|
|
10705
|
-
const configPath =
|
|
10874
|
+
function readConfig() {
|
|
10875
|
+
const configPath = findConfigPath();
|
|
10706
10876
|
if (!configPath)
|
|
10707
10877
|
return null;
|
|
10708
10878
|
try {
|
|
10709
10879
|
const raw = fs5.readFileSync(configPath, "utf-8");
|
|
10710
|
-
|
|
10880
|
+
const config2 = JSON.parse(raw);
|
|
10881
|
+
return { config: config2, configPath };
|
|
10711
10882
|
} catch {
|
|
10712
10883
|
return null;
|
|
10713
10884
|
}
|
|
10714
10885
|
}
|
|
10715
|
-
function
|
|
10716
|
-
const configPath =
|
|
10886
|
+
function writeConfig(config2) {
|
|
10887
|
+
const configPath = findConfigPath();
|
|
10717
10888
|
if (!configPath)
|
|
10718
10889
|
return false;
|
|
10719
10890
|
try {
|
|
@@ -10723,24 +10894,54 @@ function writeOpenClawConfig(config2) {
|
|
|
10723
10894
|
return false;
|
|
10724
10895
|
}
|
|
10725
10896
|
}
|
|
10726
|
-
function
|
|
10897
|
+
function extractAgentsFromConfig(config2) {
|
|
10898
|
+
const agents = [];
|
|
10899
|
+
const agentsVal = config2.agents;
|
|
10900
|
+
if (Array.isArray(agentsVal)) {
|
|
10901
|
+
for (const a of agentsVal) {
|
|
10902
|
+
if (typeof a === "object" && a !== null) {
|
|
10903
|
+
agents.push(a);
|
|
10904
|
+
}
|
|
10905
|
+
}
|
|
10906
|
+
}
|
|
10907
|
+
const agentVal = config2.agent;
|
|
10908
|
+
if (typeof agentVal === "object" && agentVal !== null && !Array.isArray(agentVal)) {
|
|
10909
|
+
agents.unshift(agentVal);
|
|
10910
|
+
}
|
|
10911
|
+
if (agents.length === 0) {
|
|
10912
|
+
for (const [key, val] of Object.entries(config2)) {
|
|
10913
|
+
if (typeof val === "object" && val !== null && !Array.isArray(val)) {
|
|
10914
|
+
const v = val;
|
|
10915
|
+
if (v.model || v.systemPrompt || v.provider || v.apiKey) {
|
|
10916
|
+
agents.push({ _configKey: key, ...v });
|
|
10917
|
+
}
|
|
10918
|
+
}
|
|
10919
|
+
}
|
|
10920
|
+
}
|
|
10921
|
+
return agents;
|
|
10922
|
+
}
|
|
10923
|
+
function getModelProviders(config2) {
|
|
10924
|
+
let providersSection = config2.providers;
|
|
10925
|
+
if (!providersSection || typeof providersSection !== "object") {
|
|
10926
|
+
const model = config2.model;
|
|
10927
|
+
if (model?.providers)
|
|
10928
|
+
providersSection = model.providers;
|
|
10929
|
+
}
|
|
10727
10930
|
const providers = MODEL_PROVIDERS.map((p) => {
|
|
10728
|
-
const
|
|
10729
|
-
const
|
|
10730
|
-
const
|
|
10731
|
-
const
|
|
10732
|
-
const baseUrl = providerConfig?.baseUrl || providerConfig?.baseURL || "";
|
|
10931
|
+
const pc = providersSection?.[p.configKey] ?? config2[p.configKey];
|
|
10932
|
+
const apiKey = pc?.apiKey || "";
|
|
10933
|
+
const modelId = pc?.model || pc?.defaultModel || "";
|
|
10934
|
+
const baseUrl = pc?.baseUrl || pc?.baseURL || "";
|
|
10733
10935
|
return {
|
|
10734
10936
|
...p,
|
|
10735
10937
|
configured: Boolean(apiKey || p.id === "ollama" && baseUrl),
|
|
10736
|
-
apiKey: apiKey ? apiKey.substring(0,
|
|
10938
|
+
apiKey: apiKey ? apiKey.substring(0, 6) + "\u2022\u2022\u2022\u2022\u2022\u2022" + apiKey.slice(-4) : "",
|
|
10737
10939
|
apiKeyHasValue: Boolean(apiKey),
|
|
10738
10940
|
modelId,
|
|
10739
10941
|
baseUrl
|
|
10740
10942
|
};
|
|
10741
10943
|
});
|
|
10742
10944
|
const currentModels = [];
|
|
10743
|
-
const providersSection = config2.providers;
|
|
10744
10945
|
for (const p of MODEL_PROVIDERS) {
|
|
10745
10946
|
const pc = providersSection?.[p.configKey] ?? config2[p.configKey];
|
|
10746
10947
|
if (pc?.apiKey) {
|
|
@@ -10749,25 +10950,38 @@ function getModelConfig(config2) {
|
|
|
10749
10950
|
providerId: p.id,
|
|
10750
10951
|
modelId: pc.model || pc.defaultModel || p.modelHint,
|
|
10751
10952
|
hasApiKey: true,
|
|
10752
|
-
baseUrl: pc.baseUrl || pc.baseURL ||
|
|
10953
|
+
baseUrl: pc.baseUrl || pc.baseURL || p.baseUrlHint
|
|
10753
10954
|
});
|
|
10754
10955
|
}
|
|
10755
10956
|
}
|
|
10756
10957
|
return { providers, currentModels };
|
|
10757
10958
|
}
|
|
10758
|
-
function
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10959
|
+
function parseApiPath(reqUrl) {
|
|
10960
|
+
if (!reqUrl)
|
|
10961
|
+
return "/";
|
|
10962
|
+
const urlPath = reqUrl.split("?")[0];
|
|
10963
|
+
const gatewayPrefix = "/plugins/aicq-chat";
|
|
10964
|
+
if (urlPath.startsWith(gatewayPrefix)) {
|
|
10965
|
+
return urlPath.slice(gatewayPrefix.length) || "/";
|
|
10966
|
+
}
|
|
10967
|
+
return urlPath;
|
|
10764
10968
|
}
|
|
10765
10969
|
function json(res, data, status = 200) {
|
|
10766
10970
|
if (!res.headersSent) {
|
|
10767
|
-
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
|
|
10971
|
+
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*" });
|
|
10768
10972
|
}
|
|
10769
10973
|
res.end(JSON.stringify(data));
|
|
10770
10974
|
}
|
|
10975
|
+
function corsHeaders(res) {
|
|
10976
|
+
if (!res.headersSent) {
|
|
10977
|
+
res.writeHead(200, {
|
|
10978
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
10979
|
+
"Access-Control-Allow-Origin": "*",
|
|
10980
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
10981
|
+
"Access-Control-Allow-Headers": "Content-Type"
|
|
10982
|
+
});
|
|
10983
|
+
}
|
|
10984
|
+
}
|
|
10771
10985
|
async function readBody(req) {
|
|
10772
10986
|
return new Promise((resolve3) => {
|
|
10773
10987
|
const chunks = [];
|
|
@@ -10786,19 +11000,24 @@ async function readBody(req) {
|
|
|
10786
11000
|
function createManagementHandler(ctx) {
|
|
10787
11001
|
const { store, identityService, serverClient, serverUrl, aicqAgentId, logger, html } = ctx;
|
|
10788
11002
|
return async (req, res) => {
|
|
10789
|
-
const
|
|
10790
|
-
|
|
11003
|
+
const urlPath = parseApiPath(req.url || "/");
|
|
11004
|
+
const method = (req.method || "GET").toUpperCase();
|
|
11005
|
+
if (method === "OPTIONS") {
|
|
11006
|
+
corsHeaders(res);
|
|
11007
|
+
res.end();
|
|
11008
|
+
return;
|
|
11009
|
+
}
|
|
11010
|
+
if (urlPath === "/" || urlPath === "/ui" || urlPath === "/index.html") {
|
|
10791
11011
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
10792
11012
|
res.end(html);
|
|
10793
11013
|
return;
|
|
10794
11014
|
}
|
|
10795
|
-
if (!
|
|
11015
|
+
if (!urlPath.startsWith("/api/")) {
|
|
10796
11016
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
10797
11017
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
10798
11018
|
return;
|
|
10799
11019
|
}
|
|
10800
|
-
const apiPath =
|
|
10801
|
-
const method = (req.method || "GET").toUpperCase();
|
|
11020
|
+
const apiPath = urlPath.slice(4);
|
|
10802
11021
|
try {
|
|
10803
11022
|
if (apiPath === "/status" && method === "GET") {
|
|
10804
11023
|
return json(res, {
|
|
@@ -10820,62 +11039,70 @@ function createManagementHandler(ctx) {
|
|
|
10820
11039
|
sessionCount: store.sessions.size
|
|
10821
11040
|
});
|
|
10822
11041
|
}
|
|
11042
|
+
if (apiPath === "/config" && method === "GET") {
|
|
11043
|
+
const result = readConfig();
|
|
11044
|
+
if (!result)
|
|
11045
|
+
return json(res, { error: "No config file found. Create openclaw.json or stableclaw.json." });
|
|
11046
|
+
const stats = fs5.statSync(result.configPath);
|
|
11047
|
+
return json(res, {
|
|
11048
|
+
configPath: result.configPath,
|
|
11049
|
+
configSize: stats.size,
|
|
11050
|
+
configModified: stats.mtime.toISOString()
|
|
11051
|
+
});
|
|
11052
|
+
}
|
|
10823
11053
|
if (apiPath === "/agents" && method === "GET") {
|
|
10824
|
-
const
|
|
10825
|
-
|
|
10826
|
-
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
let serverFriends = [];
|
|
10834
|
-
try {
|
|
10835
|
-
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId);
|
|
10836
|
-
if (resp.ok) {
|
|
10837
|
-
const data = await resp.json();
|
|
10838
|
-
serverFriends = data.friends || [];
|
|
10839
|
-
}
|
|
10840
|
-
} catch {
|
|
10841
|
-
}
|
|
10842
|
-
const localIds = new Set(localFriends.map((f) => f.id));
|
|
10843
|
-
for (const sf of serverFriends) {
|
|
10844
|
-
const sfId = sf.nodeId;
|
|
10845
|
-
if (sfId && !localIds.has(sfId)) {
|
|
10846
|
-
localFriends.push({
|
|
10847
|
-
id: sfId,
|
|
10848
|
-
name: sf.aiName || sfId.substring(0, 8),
|
|
10849
|
-
friendType: sf.friendType || void 0,
|
|
10850
|
-
publicKeyFingerprint: sf.publicKeyFingerprint || "",
|
|
10851
|
-
permissions: sf.permissions || [],
|
|
10852
|
-
lastMessageAt: sf.lastMessageAt || null,
|
|
10853
|
-
sessionCount: 0
|
|
10854
|
-
});
|
|
10855
|
-
}
|
|
11054
|
+
const result = readConfig();
|
|
11055
|
+
if (!result) {
|
|
11056
|
+
return json(res, {
|
|
11057
|
+
agents: [],
|
|
11058
|
+
configSource: "none",
|
|
11059
|
+
currentAgentId: aicqAgentId,
|
|
11060
|
+
fingerprint: identityService.getPublicKeyFingerprint(),
|
|
11061
|
+
connected: serverClient.isConnected()
|
|
11062
|
+
});
|
|
10856
11063
|
}
|
|
11064
|
+
const agents = extractAgentsFromConfig(result.config);
|
|
11065
|
+
globalThis.__aicq_agents = agents;
|
|
10857
11066
|
return json(res, {
|
|
10858
|
-
agents
|
|
11067
|
+
agents,
|
|
11068
|
+
configSource: path5.basename(result.configPath),
|
|
11069
|
+
configPath: result.configPath,
|
|
10859
11070
|
currentAgentId: aicqAgentId,
|
|
10860
11071
|
fingerprint: identityService.getPublicKeyFingerprint(),
|
|
10861
11072
|
connected: serverClient.isConnected()
|
|
10862
11073
|
});
|
|
10863
11074
|
}
|
|
10864
11075
|
if (apiPath.startsWith("/agents/") && method === "DELETE") {
|
|
10865
|
-
const
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
11076
|
+
const idxStr = decodeURIComponent(apiPath.slice("/agents/".length));
|
|
11077
|
+
const idx = parseInt(idxStr, 10);
|
|
11078
|
+
if (isNaN(idx) || idx < 0)
|
|
11079
|
+
return json(res, { success: false, message: "Invalid agent index" }, 400);
|
|
11080
|
+
const result = readConfig();
|
|
11081
|
+
if (!result)
|
|
11082
|
+
return json(res, { success: false, message: "No config file found" }, 400);
|
|
11083
|
+
const config2 = result.config;
|
|
11084
|
+
let agentsArr;
|
|
11085
|
+
if (Array.isArray(config2.agents)) {
|
|
11086
|
+
agentsArr = config2.agents;
|
|
11087
|
+
} else if (typeof config2.agent === "object" && config2.agent !== null && idx === 0) {
|
|
11088
|
+
delete config2.agent;
|
|
11089
|
+
const written2 = writeConfig(config2);
|
|
11090
|
+
if (written2) {
|
|
11091
|
+
logger.info("[API] Agent deleted from config");
|
|
11092
|
+
return json(res, { success: true, message: "Agent removed" });
|
|
11093
|
+
}
|
|
11094
|
+
return json(res, { success: false, message: "Failed to write config" }, 500);
|
|
11095
|
+
}
|
|
11096
|
+
if (!agentsArr || idx >= agentsArr.length) {
|
|
11097
|
+
return json(res, { success: false, message: "Agent index out of range" }, 400);
|
|
11098
|
+
}
|
|
11099
|
+
agentsArr.splice(idx, 1);
|
|
11100
|
+
const written = writeConfig(config2);
|
|
11101
|
+
if (written) {
|
|
11102
|
+
logger.info("[API] Agent deleted at index " + idx);
|
|
11103
|
+
return json(res, { success: true, message: "Agent removed" });
|
|
10875
11104
|
}
|
|
10876
|
-
|
|
10877
|
-
logger.info("[API] Agent/friend deleted: " + friendId);
|
|
10878
|
-
return json(res, { success: true, message: "Agent removed" });
|
|
11105
|
+
return json(res, { success: false, message: "Failed to write config" }, 500);
|
|
10879
11106
|
}
|
|
10880
11107
|
if (apiPath === "/friends" && method === "GET") {
|
|
10881
11108
|
try {
|
|
@@ -10909,26 +11136,36 @@ function createManagementHandler(ctx) {
|
|
|
10909
11136
|
const isTempNumber = /^\d{6}$/.test(target);
|
|
10910
11137
|
let friendId = target;
|
|
10911
11138
|
if (isTempNumber) {
|
|
10912
|
-
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
}
|
|
10923
|
-
|
|
10924
|
-
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
11139
|
+
try {
|
|
11140
|
+
const resolveResp = await fetch(serverUrl + "/api/v1/temp-number/" + target);
|
|
11141
|
+
if (!resolveResp.ok)
|
|
11142
|
+
return json(res, { success: false, message: "Temp number not found or expired" });
|
|
11143
|
+
const resolveData = await resolveResp.json();
|
|
11144
|
+
friendId = resolveData.nodeId;
|
|
11145
|
+
} catch (err) {
|
|
11146
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11147
|
+
return json(res, { success: false, message: "Failed to resolve temp number: " + msg }, 502);
|
|
11148
|
+
}
|
|
11149
|
+
}
|
|
11150
|
+
try {
|
|
11151
|
+
const hsResp = await fetch(serverUrl + "/api/v1/handshake/initiate", {
|
|
11152
|
+
method: "POST",
|
|
11153
|
+
headers: { "Content-Type": "application/json" },
|
|
11154
|
+
body: JSON.stringify({ requesterId: aicqAgentId, targetTempNumber: target })
|
|
11155
|
+
});
|
|
11156
|
+
if (!hsResp.ok)
|
|
11157
|
+
return json(res, { success: false, message: "Handshake failed: " + await hsResp.text() });
|
|
11158
|
+
const hsData = await hsResp.json();
|
|
11159
|
+
logger.info("[API] Friend request sent to " + friendId);
|
|
11160
|
+
return json(res, { success: true, message: "Friend request sent to " + friendId, sessionId: hsData.sessionId, targetNodeId: friendId });
|
|
11161
|
+
} catch (err) {
|
|
11162
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11163
|
+
return json(res, { success: false, message: "Handshake request failed: " + msg }, 502);
|
|
11164
|
+
}
|
|
10928
11165
|
}
|
|
10929
|
-
if (apiPath.startsWith("/friends/") &&
|
|
11166
|
+
if (apiPath.startsWith("/friends/") && method === "DELETE") {
|
|
10930
11167
|
if (apiPath.includes("/requests/")) {
|
|
10931
|
-
|
|
11168
|
+
} else if (apiPath.includes("/permissions")) {
|
|
10932
11169
|
} else {
|
|
10933
11170
|
const friendId = decodeURIComponent(apiPath.slice("/friends/".length));
|
|
10934
11171
|
if (!friendId)
|
|
@@ -10966,7 +11203,7 @@ function createManagementHandler(ctx) {
|
|
|
10966
11203
|
body: JSON.stringify({ nodeId: aicqAgentId, permissions })
|
|
10967
11204
|
});
|
|
10968
11205
|
if (!resp.ok)
|
|
10969
|
-
return json(res, { success: false, message: "Failed
|
|
11206
|
+
return json(res, { success: false, message: "Failed: " + await resp.text() });
|
|
10970
11207
|
const localFriend = store.getFriend(friendId);
|
|
10971
11208
|
if (localFriend) {
|
|
10972
11209
|
localFriend.permissions = permissions;
|
|
@@ -10996,29 +11233,39 @@ function createManagementHandler(ctx) {
|
|
|
10996
11233
|
if (!requestId)
|
|
10997
11234
|
return json(res, { success: false, message: "Missing request ID" }, 400);
|
|
10998
11235
|
const body = await readBody(req);
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11236
|
+
try {
|
|
11237
|
+
const resp = await fetch(serverUrl + "/api/v1/friends/requests/" + requestId + "/accept", {
|
|
11238
|
+
method: "POST",
|
|
11239
|
+
headers: { "Content-Type": "application/json" },
|
|
11240
|
+
body: JSON.stringify({ permissions: body.permissions || ["chat"] })
|
|
11241
|
+
});
|
|
11242
|
+
if (!resp.ok)
|
|
11243
|
+
return json(res, { success: false, message: "Failed: " + await resp.text() });
|
|
11244
|
+
logger.info("[API] Friend request accepted: " + requestId);
|
|
11245
|
+
return json(res, { success: true, message: "Friend request accepted" });
|
|
11246
|
+
} catch (err) {
|
|
11247
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11248
|
+
return json(res, { success: false, message: msg }, 500);
|
|
11249
|
+
}
|
|
11008
11250
|
}
|
|
11009
11251
|
if (apiPath.match(/^\/friends\/requests\/[^/]+\/reject$/) && method === "POST") {
|
|
11010
11252
|
const requestId = decodeURIComponent(apiPath.split("/")[3]);
|
|
11011
11253
|
if (!requestId)
|
|
11012
11254
|
return json(res, { success: false, message: "Missing request ID" }, 400);
|
|
11013
|
-
|
|
11014
|
-
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11255
|
+
try {
|
|
11256
|
+
const resp = await fetch(serverUrl + "/api/v1/friends/requests/" + requestId + "/reject", {
|
|
11257
|
+
method: "POST",
|
|
11258
|
+
headers: { "Content-Type": "application/json" },
|
|
11259
|
+
body: JSON.stringify({})
|
|
11260
|
+
});
|
|
11261
|
+
if (!resp.ok)
|
|
11262
|
+
return json(res, { success: false, message: "Failed: " + await resp.text() });
|
|
11263
|
+
logger.info("[API] Friend request rejected: " + requestId);
|
|
11264
|
+
return json(res, { success: true, message: "Friend request rejected" });
|
|
11265
|
+
} catch (err) {
|
|
11266
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11267
|
+
return json(res, { success: false, message: msg }, 500);
|
|
11268
|
+
}
|
|
11022
11269
|
}
|
|
11023
11270
|
if (apiPath === "/sessions" && method === "GET") {
|
|
11024
11271
|
const sessions = Array.from(store.sessions.values()).map((s) => ({
|
|
@@ -11029,9 +11276,10 @@ function createManagementHandler(ctx) {
|
|
|
11029
11276
|
return json(res, { sessions });
|
|
11030
11277
|
}
|
|
11031
11278
|
if (apiPath === "/models" && method === "GET") {
|
|
11032
|
-
const
|
|
11033
|
-
|
|
11034
|
-
|
|
11279
|
+
const result = readConfig();
|
|
11280
|
+
if (!result)
|
|
11281
|
+
return json(res, { providers: MODEL_PROVIDERS, currentModels: [], error: "No config file found" });
|
|
11282
|
+
return json(res, getModelProviders(result.config));
|
|
11035
11283
|
}
|
|
11036
11284
|
if (apiPath.match(/^\/models\/[^/]+$/) && method === "PUT") {
|
|
11037
11285
|
const providerId = decodeURIComponent(apiPath.slice("/models/".length));
|
|
@@ -11042,15 +11290,15 @@ function createManagementHandler(ctx) {
|
|
|
11042
11290
|
const provider = MODEL_PROVIDERS.find((p) => p.id === providerId);
|
|
11043
11291
|
if (!provider)
|
|
11044
11292
|
return json(res, { success: false, message: "Unknown provider: " + providerId }, 400);
|
|
11045
|
-
const
|
|
11046
|
-
if (!
|
|
11047
|
-
return json(res, { success: false, message: "
|
|
11048
|
-
|
|
11049
|
-
if (!config2.providers) {
|
|
11293
|
+
const result = readConfig();
|
|
11294
|
+
if (!result)
|
|
11295
|
+
return json(res, { success: false, message: "No config file found. Create openclaw.json or stableclaw.json." }, 400);
|
|
11296
|
+
const config2 = result.config;
|
|
11297
|
+
if (!config2.providers || typeof config2.providers !== "object") {
|
|
11050
11298
|
config2.providers = {};
|
|
11051
11299
|
}
|
|
11052
11300
|
const providers = config2.providers;
|
|
11053
|
-
if (!providers[provider.configKey]) {
|
|
11301
|
+
if (!providers[provider.configKey] || typeof providers[provider.configKey] !== "object") {
|
|
11054
11302
|
providers[provider.configKey] = {};
|
|
11055
11303
|
}
|
|
11056
11304
|
const provConfig = providers[provider.configKey];
|
|
@@ -11060,10 +11308,9 @@ function createManagementHandler(ctx) {
|
|
|
11060
11308
|
provConfig.model = modelId;
|
|
11061
11309
|
if (baseUrl)
|
|
11062
11310
|
provConfig.baseUrl = baseUrl;
|
|
11063
|
-
const written =
|
|
11064
|
-
if (!written)
|
|
11311
|
+
const written = writeConfig(config2);
|
|
11312
|
+
if (!written)
|
|
11065
11313
|
return json(res, { success: false, message: "Failed to write config file" }, 500);
|
|
11066
|
-
}
|
|
11067
11314
|
logger.info("[API] Model config saved for provider: " + providerId);
|
|
11068
11315
|
return json(res, { success: true, message: "Model configuration saved for " + provider.name });
|
|
11069
11316
|
}
|
|
@@ -11619,28 +11866,52 @@ var plugin = definePluginEntry({
|
|
|
11619
11866
|
});
|
|
11620
11867
|
logger.info("[Init] Registered 13 gateway methods for management UI");
|
|
11621
11868
|
}
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11869
|
+
const managementHtml = getManagementHTML();
|
|
11870
|
+
const managementHandler = createManagementHandler({
|
|
11871
|
+
store,
|
|
11872
|
+
identityService,
|
|
11873
|
+
serverClient,
|
|
11874
|
+
serverUrl,
|
|
11875
|
+
aicqAgentId,
|
|
11876
|
+
logger,
|
|
11877
|
+
html: managementHtml
|
|
11878
|
+
});
|
|
11879
|
+
const mgmtPort = parseInt(process.env.AICQ_MGMT_PORT || "461099", 10);
|
|
11880
|
+
const mgmtServer = http.createServer((req, res) => {
|
|
11881
|
+
managementHandler(req, res).catch((err) => {
|
|
11882
|
+
logger.error("[HTTP] Management server error: " + (err instanceof Error ? err.message : err));
|
|
11883
|
+
if (!res.headersSent) {
|
|
11884
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
11885
|
+
}
|
|
11886
|
+
res.end("Internal Server Error");
|
|
11632
11887
|
});
|
|
11888
|
+
});
|
|
11889
|
+
mgmtServer.listen(mgmtPort, "127.0.0.1", () => {
|
|
11890
|
+
logger.info("[Init] Management UI HTTP server running at http://127.0.0.1:" + mgmtPort + "/");
|
|
11891
|
+
});
|
|
11892
|
+
mgmtServer.on("error", (err) => {
|
|
11893
|
+
if (err.code === "EADDRINUSE") {
|
|
11894
|
+
logger.warn("[Init] Management UI port " + mgmtPort + " already in use, trying " + (mgmtPort + 1));
|
|
11895
|
+
mgmtServer.close();
|
|
11896
|
+
mgmtServer.listen(mgmtPort + 1, "127.0.0.1", () => {
|
|
11897
|
+
logger.info("[Init] Management UI HTTP server running at http://127.0.0.1:" + (mgmtPort + 1) + "/");
|
|
11898
|
+
});
|
|
11899
|
+
} else {
|
|
11900
|
+
logger.error("[Init] Management UI HTTP server error: " + err.message);
|
|
11901
|
+
}
|
|
11902
|
+
});
|
|
11903
|
+
if (api.registerHttpRoute) {
|
|
11633
11904
|
api.registerHttpRoute({
|
|
11634
11905
|
path: "/aicq-chat",
|
|
11635
11906
|
auth: "gateway",
|
|
11636
11907
|
match: "prefix",
|
|
11637
11908
|
handler: managementHandler
|
|
11638
11909
|
});
|
|
11639
|
-
logger.info("[Init] Management UI registered at /plugins/aicq-chat/");
|
|
11910
|
+
logger.info("[Init] Management UI also registered via gateway at /plugins/aicq-chat/");
|
|
11640
11911
|
}
|
|
11641
11912
|
logger.info("\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");
|
|
11642
11913
|
logger.info(" AICQ Plugin activated successfully!");
|
|
11643
|
-
logger.info(" Management UI: /
|
|
11914
|
+
logger.info(" Management UI: http://127.0.0.1:" + mgmtPort + "/");
|
|
11644
11915
|
logger.info("\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");
|
|
11645
11916
|
}
|
|
11646
11917
|
});
|