aicq-openclaw-plugin 1.0.3 → 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 +1010 -765
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9817,670 +9817,859 @@ var CSS = `
|
|
|
9817
9817
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9818
9818
|
:root {
|
|
9819
9819
|
--bg: #0f1117; --bg2: #1a1d27; --bg3: #242836; --bg4: #2e3347;
|
|
9820
|
-
--text: #e4e6ef; --text2: #9499b3; --text3: #5c6080;
|
|
9821
|
-
--accent: #
|
|
9822
|
-
--
|
|
9823
|
-
--
|
|
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);
|
|
9824
9827
|
}
|
|
9825
|
-
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; }
|
|
9826
9829
|
a { color: var(--info); text-decoration: none; }
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
.
|
|
9834
|
-
|
|
9835
|
-
|
|
9836
|
-
.
|
|
9837
|
-
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
|
|
9843
|
-
|
|
9844
|
-
|
|
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;
|
|
9845
9881
|
}
|
|
9846
|
-
input:focus, select:focus, textarea:focus { border-color: var(--accent); }
|
|
9847
|
-
input::placeholder { color: var(--text3); }
|
|
9848
|
-
select { cursor: pointer; appearance: auto; }
|
|
9849
9882
|
|
|
9850
|
-
.
|
|
9851
|
-
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;
|
|
9852
9895
|
background: var(--bg2); border-bottom: 1px solid var(--border);
|
|
9853
|
-
position: sticky; top: 0; z-index: 10;
|
|
9854
9896
|
}
|
|
9855
|
-
.
|
|
9856
|
-
|
|
9857
|
-
|
|
9858
|
-
|
|
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%; }
|
|
9859
9906
|
.dot-ok { background: var(--ok); box-shadow: 0 0 6px var(--ok); }
|
|
9860
9907
|
.dot-err { background: var(--danger); box-shadow: 0 0 6px var(--danger); }
|
|
9861
|
-
|
|
9862
|
-
.
|
|
9863
|
-
|
|
9864
|
-
|
|
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;
|
|
9865
9921
|
}
|
|
9866
|
-
.
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
}
|
|
9871
|
-
.
|
|
9872
|
-
.
|
|
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; }
|
|
9873
9937
|
|
|
9874
|
-
|
|
9875
|
-
|
|
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; }
|
|
9876
9946
|
|
|
9877
9947
|
.card {
|
|
9878
|
-
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);
|
|
9879
9949
|
padding: 20px; margin-bottom: 16px;
|
|
9880
9950
|
}
|
|
9881
|
-
.card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
|
9882
|
-
.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; }
|
|
9883
9954
|
|
|
9884
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); }
|
|
9885
9962
|
|
|
9886
9963
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
9887
|
-
thead th {
|
|
9888
|
-
|
|
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); }
|
|
9889
9971
|
tbody tr:hover { background: var(--bg3); }
|
|
9890
|
-
.mono { font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; color: var(--text2); word-break: break-all; }
|
|
9891
|
-
|
|
9892
|
-
.badge
|
|
9893
|
-
.badge-
|
|
9894
|
-
.badge-
|
|
9895
|
-
.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); }
|
|
9896
9979
|
.badge-ghost { background: var(--bg3); color: var(--text2); }
|
|
9980
|
+
.badge-accent { background: var(--accent-bg); color: var(--accent2); }
|
|
9897
9981
|
|
|
9898
|
-
.
|
|
9899
|
-
.empty .icon { font-size: 40px; margin-bottom: 12px; opacity: .4; }
|
|
9900
|
-
.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); }
|
|
9901
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 */
|
|
9902
10007
|
.modal-overlay {
|
|
9903
|
-
position: fixed; inset: 0; background: rgba(0,0,0,.
|
|
10008
|
+
position: fixed; inset: 0; background: rgba(0,0,0,.65); display: flex;
|
|
9904
10009
|
align-items: center; justify-content: center; z-index: 100;
|
|
10010
|
+
animation: fadeIn .15s ease-out;
|
|
9905
10011
|
}
|
|
9906
10012
|
.modal-overlay.hidden { display: none; }
|
|
9907
10013
|
.modal {
|
|
9908
|
-
background: var(--bg2); border: 1px solid var(--border); border-radius:
|
|
9909
|
-
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;
|
|
9910
10017
|
}
|
|
9911
|
-
.
|
|
9912
|
-
.
|
|
9913
|
-
.
|
|
9914
|
-
.
|
|
9915
|
-
.
|
|
9916
|
-
|
|
9917
|
-
.
|
|
9918
|
-
.
|
|
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; }
|
|
9919
10035
|
.perm-checks input[type=checkbox] { width: 16px; height: 16px; accent-color: var(--accent); }
|
|
9920
10036
|
|
|
9921
|
-
|
|
9922
|
-
.
|
|
9923
|
-
.
|
|
9924
|
-
.
|
|
9925
|
-
|
|
9926
|
-
.provider-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 12px; }
|
|
9927
|
-
.provider-card {
|
|
9928
|
-
background: var(--bg3); border: 1px solid var(--border); border-radius: var(--radius);
|
|
9929
|
-
padding: 16px; cursor: pointer; transition: border-color .15s;
|
|
9930
|
-
}
|
|
9931
|
-
.provider-card:hover { border-color: var(--accent); }
|
|
9932
|
-
.provider-card .name { font-weight: 600; margin-bottom: 4px; }
|
|
9933
|
-
.provider-card .desc { font-size: 12px; color: var(--text3); }
|
|
9934
|
-
.provider-card .status-dot { display: inline-block; width: 6px; height: 6px; border-radius: 50%; margin-right: 6px; }
|
|
9935
|
-
|
|
9936
|
-
.section-desc { font-size: 13px; color: var(--text2); margin-bottom: 16px; }
|
|
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; }
|
|
9937
10042
|
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
.
|
|
9941
|
-
.spinner { width: 20px; height: 20px; border: 2px solid var(--border); border-top-color: var(--accent); border-radius: 50%; animation: spin .6s linear infinite; margin-right: 10px; }
|
|
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; }
|
|
9942
10046
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
9943
10047
|
|
|
10048
|
+
/* Toast */
|
|
10049
|
+
.toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 200; display: flex; flex-direction: column; gap: 8px; }
|
|
9944
10050
|
.toast {
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
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;
|
|
9948
10054
|
}
|
|
9949
10055
|
.toast.hidden { display: none; }
|
|
9950
|
-
.toast-ok { background: #065f46; }
|
|
9951
|
-
.toast-err { background: #
|
|
9952
|
-
.toast-info { background: #1e3a5f; }
|
|
9953
|
-
|
|
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; } }
|
|
9954
10061
|
|
|
10062
|
+
/* Actions cell */
|
|
9955
10063
|
.actions-cell { display: flex; gap: 4px; }
|
|
9956
|
-
.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; }
|
|
9957
10071
|
|
|
10072
|
+
/* Section desc */
|
|
10073
|
+
.section-desc { font-size: 13px; color: var(--text2); margin-bottom: 20px; line-height: 1.6; }
|
|
10074
|
+
|
|
10075
|
+
/* Responsive */
|
|
9958
10076
|
@media (max-width: 768px) {
|
|
9959
|
-
.
|
|
9960
|
-
.
|
|
9961
|
-
|
|
9962
|
-
.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); }
|
|
9963
10081
|
.provider-grid { grid-template-columns: 1fr; }
|
|
10082
|
+
.form-row { grid-template-columns: 1fr; }
|
|
9964
10083
|
}
|
|
9965
10084
|
`;
|
|
9966
10085
|
var JS = `
|
|
9967
|
-
|
|
9968
|
-
|
|
9969
|
-
let
|
|
9970
|
-
let
|
|
9971
|
-
|
|
9972
|
-
// \u2500\u2500
|
|
9973
|
-
|
|
9974
|
-
|
|
9975
|
-
|
|
9976
|
-
}
|
|
9977
|
-
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
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
|
|
9981
10100
|
function toast(msg, type = 'info') {
|
|
9982
|
-
const
|
|
9983
|
-
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' };
|
|
9984
10104
|
t.className = 'toast toast-' + type;
|
|
9985
|
-
t.
|
|
9986
|
-
|
|
9987
|
-
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;
|
|
9988
10115
|
}
|
|
9989
10116
|
|
|
9990
|
-
|
|
9991
|
-
function
|
|
9992
|
-
|
|
9993
|
-
|
|
9994
|
-
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
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 }; }
|
|
9998
10125
|
}
|
|
9999
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; }
|
|
10000
10129
|
function timeAgo(iso) {
|
|
10001
|
-
if (!iso) return '
|
|
10130
|
+
if (!iso) return '\u2014';
|
|
10002
10131
|
const diff = Date.now() - new Date(iso).getTime();
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
if (
|
|
10006
|
-
|
|
10007
|
-
if (hrs < 24) return hrs + 'h ago';
|
|
10008
|
-
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();
|
|
10009
10136
|
}
|
|
10010
|
-
|
|
10011
|
-
function copyText(text) {
|
|
10012
|
-
|
|
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');
|
|
10013
10153
|
}
|
|
10014
10154
|
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10019
|
-
$$('.content').forEach(c => c.classList.toggle('hidden', c.id !== 'tab-' + tab));
|
|
10020
|
-
if (tab === 'agents') loadAgents();
|
|
10021
|
-
else if (tab === 'aicq') loadAICQ();
|
|
10022
|
-
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'); }
|
|
10023
10159
|
}
|
|
10024
10160
|
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
dot.className = 'dot dot-ok';
|
|
10033
|
-
txt.textContent = 'Connected';
|
|
10034
|
-
} else {
|
|
10035
|
-
dot.className = 'dot dot-err';
|
|
10036
|
-
txt.textContent = 'Disconnected';
|
|
10037
|
-
}
|
|
10038
|
-
} catch (e) {
|
|
10039
|
-
$('#status-dot').className = 'dot dot-err';
|
|
10040
|
-
$('#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;
|
|
10041
10168
|
}
|
|
10042
10169
|
}
|
|
10043
10170
|
|
|
10044
|
-
// \
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
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;
|
|
10055
10237
|
}
|
|
10056
10238
|
|
|
10057
|
-
|
|
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() {
|
|
10058
10243
|
const el = $('#agents-content');
|
|
10059
|
-
|
|
10060
|
-
const
|
|
10061
|
-
|
|
10062
|
-
<div class="stat-card"><div class="label">Total Agents</div><div class="value">\${agents.length}</div></div>
|
|
10063
|
-
<div class="stat-card"><div class="label">Current Agent</div><div class="value" style="font-size:16px">\${escHtml(data.currentAgentId || '-')}</div></div>
|
|
10064
|
-
<div class="stat-card"><div class="label">Fingerprint</div><div class="value mono" style="font-size:11px">\${escHtml(data.fingerprint || '-')}</div></div>
|
|
10065
|
-
<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>
|
|
10066
|
-
</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; }
|
|
10067
10247
|
|
|
10068
|
-
|
|
10069
|
-
|
|
10070
|
-
return;
|
|
10071
|
-
}
|
|
10248
|
+
const agents = data.agents || [];
|
|
10249
|
+
const configSource = data.configSource || 'unknown';
|
|
10072
10250
|
|
|
10073
10251
|
let rows = '';
|
|
10074
|
-
agents.forEach(a => {
|
|
10075
|
-
const
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
<td>\${
|
|
10081
|
-
<td class="mono
|
|
10082
|
-
<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>
|
|
10083
10263
|
<td>
|
|
10084
10264
|
<div class="actions-cell">
|
|
10085
|
-
<button class="btn btn-sm btn-ghost" onclick="
|
|
10086
|
-
|
|
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>
|
|
10087
10267
|
</div>
|
|
10088
10268
|
</td>
|
|
10089
|
-
</tr
|
|
10269
|
+
</tr>\\\`;
|
|
10090
10270
|
});
|
|
10091
10271
|
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
<
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
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">
|
|
10100
10287
|
<div style="overflow-x:auto">
|
|
10101
10288
|
<table>
|
|
10102
|
-
<thead><tr><th>
|
|
10103
|
-
<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>
|
|
10104
10291
|
</table>
|
|
10105
10292
|
</div>
|
|
10106
|
-
</div
|
|
10293
|
+
</div>
|
|
10294
|
+
\\\`);
|
|
10107
10295
|
}
|
|
10108
10296
|
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10113
|
-
|
|
10114
|
-
else { toast(r.message || 'Delete failed', 'err'); }
|
|
10115
|
-
} 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
|
+
});
|
|
10116
10302
|
}
|
|
10117
10303
|
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
sessionsData = sessions;
|
|
10133
|
-
identityData = identity;
|
|
10134
|
-
renderAICQSubTabs();
|
|
10135
|
-
switchAICQSubTab(aicqSubTab);
|
|
10136
|
-
} catch (e) {
|
|
10137
|
-
toast('Failed to load AICQ data: ' + e.message, 'err');
|
|
10138
|
-
}
|
|
10139
|
-
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');
|
|
10140
10318
|
}
|
|
10141
10319
|
|
|
10142
|
-
function
|
|
10143
|
-
|
|
10144
|
-
const
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
<button class="tab-btn \${aicqSubTab==='friends'?'active':''}" onclick="switchAICQSubTab('friends')">Friends (\${friendCount})</button>
|
|
10148
|
-
<button class="tab-btn \${aicqSubTab==='requests'?'active':''}" onclick="switchAICQSubTab('requests')">Requests (\${reqCount})</button>
|
|
10149
|
-
<button class="tab-btn \${aicqSubTab==='sessions'?'active':''}" onclick="switchAICQSubTab('sessions')">Sessions (\${sessCount})</button>
|
|
10150
|
-
\`;
|
|
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'); }
|
|
10151
10325
|
}
|
|
10152
10326
|
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
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 || []);
|
|
10160
10355
|
}
|
|
10356
|
+
window.friendsSubTab = 'friends';
|
|
10161
10357
|
|
|
10162
|
-
function
|
|
10163
|
-
const el = $('#
|
|
10164
|
-
el.classList.remove('hidden');
|
|
10165
|
-
const friends = friendsData?.friends || [];
|
|
10166
|
-
|
|
10167
|
-
if (friends.length === 0) {
|
|
10168
|
-
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>';
|
|
10169
|
-
return;
|
|
10170
|
-
}
|
|
10171
|
-
|
|
10358
|
+
function renderFriendsList(friends) {
|
|
10359
|
+
const el = $('#friends-content');
|
|
10172
10360
|
let rows = '';
|
|
10173
10361
|
friends.forEach(f => {
|
|
10174
|
-
const perms = (f.permissions || []).map(p => '<span class="badge badge-' + (p === 'exec' ? 'warn' : 'ok') + '">' + p + '</span>').join(' ');
|
|
10175
|
-
rows +=
|
|
10176
|
-
<td class="
|
|
10177
|
-
<td>\${escHtml(f.aiName || '-')}</td>
|
|
10178
|
-
<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>
|
|
10179
10366
|
<td>\${perms || '<span class="badge badge-ghost">none</span>'}</td>
|
|
10180
|
-
<td class="mono" style="font-size:11px">\${escHtml(f.publicKeyFingerprint || '
|
|
10181
|
-
<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>
|
|
10182
10369
|
<td>
|
|
10183
10370
|
<div class="actions-cell">
|
|
10184
|
-
<button class="btn btn-sm btn-ghost" onclick="
|
|
10185
|
-
<button class="btn btn-sm btn-danger" onclick="removeFriend('\${escHtml(f.id)}')">\u{1F5D1}\uFE0F</button>
|
|
10186
|
-
<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>
|
|
10187
10373
|
</div>
|
|
10188
10374
|
</td>
|
|
10189
|
-
</tr
|
|
10375
|
+
</tr>\\\`;
|
|
10190
10376
|
});
|
|
10191
10377
|
|
|
10192
|
-
el
|
|
10193
|
-
<div class="
|
|
10194
|
-
<div class="
|
|
10195
|
-
|
|
10196
|
-
<
|
|
10197
|
-
|
|
10198
|
-
|
|
10199
|
-
</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>
|
|
10200
10385
|
</div>
|
|
10201
|
-
<
|
|
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">
|
|
10202
10392
|
<table>
|
|
10203
|
-
<thead><tr><th
|
|
10204
|
-
<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>
|
|
10205
10395
|
</table>
|
|
10206
10396
|
</div>
|
|
10207
|
-
|
|
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
|
+
\\\`);
|
|
10208
10400
|
}
|
|
10209
10401
|
|
|
10210
|
-
function
|
|
10211
|
-
const
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
}
|
|
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
|
+
}
|
|
10219
10410
|
|
|
10411
|
+
function renderRequestsList(requests) {
|
|
10412
|
+
const el = $('#friends-content');
|
|
10220
10413
|
let rows = '';
|
|
10221
|
-
|
|
10222
|
-
const
|
|
10223
|
-
|
|
10224
|
-
|
|
10225
|
-
|
|
10226
|
-
<td class="
|
|
10227
|
-
<td class="mono">\${escHtml(r.fromId || r.requesterId || '-')}</td>
|
|
10228
|
-
<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>
|
|
10229
10420
|
<td>\${timeAgo(r.createdAt)}</td>
|
|
10230
|
-
<td>\${escHtml(r.message || '-')}</td>
|
|
10231
10421
|
<td>
|
|
10232
|
-
<div class="actions-cell">
|
|
10233
|
-
\${r.status === 'pending' ? \`
|
|
10234
|
-
<button class="btn btn-sm btn-ok" onclick="acceptRequest('\${escHtml(r.id)}')">\u2713 Accept</button>
|
|
10235
|
-
<button class="btn btn-sm btn-danger" onclick="rejectRequest('\${escHtml(r.id)}')">\u2717 Reject</button>
|
|
10236
|
-
\` : '-'}
|
|
10237
|
-
</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'}
|
|
10238
10423
|
</td>
|
|
10239
|
-
</tr
|
|
10424
|
+
</tr>\\\`;
|
|
10240
10425
|
});
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
<div class="card">
|
|
10244
|
-
<div
|
|
10245
|
-
<
|
|
10246
|
-
<
|
|
10247
|
-
</div>
|
|
10248
|
-
<div
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
<tbody>\${rows}</tbody>
|
|
10252
|
-
</table>
|
|
10253
|
-
</div>
|
|
10254
|
-
</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
|
+
\\\`);
|
|
10255
10436
|
}
|
|
10256
10437
|
|
|
10257
|
-
function
|
|
10258
|
-
const el = $('#
|
|
10259
|
-
el.classList.remove('hidden');
|
|
10260
|
-
const sessions = sessionsData?.sessions || [];
|
|
10261
|
-
|
|
10262
|
-
if (sessions.length === 0) {
|
|
10263
|
-
el.innerHTML = '<div class="empty"><div class="icon">\u{1F517}</div><p>No active encrypted sessions</p></div>';
|
|
10264
|
-
return;
|
|
10265
|
-
}
|
|
10266
|
-
|
|
10438
|
+
function renderSessionsList(sessions) {
|
|
10439
|
+
const el = $('#friends-content');
|
|
10267
10440
|
let rows = '';
|
|
10268
10441
|
sessions.forEach(s => {
|
|
10269
|
-
rows +=
|
|
10270
|
-
<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>
|
|
10271
10444
|
<td>\${timeAgo(s.createdAt)}</td>
|
|
10272
|
-
<td><span class="badge badge-info">\${s.messageCount}
|
|
10273
|
-
</tr
|
|
10445
|
+
<td><span class="badge badge-info">\${s.messageCount} messages</span></td>
|
|
10446
|
+
</tr>\\\`;
|
|
10274
10447
|
});
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
<div class="card">
|
|
10278
|
-
<div
|
|
10279
|
-
<
|
|
10280
|
-
<
|
|
10281
|
-
</div>
|
|
10282
|
-
<div
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
<tbody>\${rows}</tbody>
|
|
10286
|
-
</table>
|
|
10287
|
-
</div>
|
|
10288
|
-
</div>\`;
|
|
10289
|
-
}
|
|
10290
|
-
|
|
10291
|
-
async function showAddFriend() {
|
|
10292
|
-
showModal('modal-add-friend');
|
|
10293
|
-
$('#add-friend-target').value = '';
|
|
10294
|
-
$('#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
|
+
\\\`);
|
|
10295
10458
|
}
|
|
10296
10459
|
|
|
10460
|
+
function showAddFriendModal() { $('#add-friend-target').value = ''; showModal('modal-add-friend'); setTimeout(() => $('#add-friend-target')?.focus(), 100); }
|
|
10297
10461
|
async function addFriend() {
|
|
10298
10462
|
const target = $('#add-friend-target').value.trim();
|
|
10299
|
-
if (!target) { toast('Enter a temp number or
|
|
10463
|
+
if (!target) { toast('Enter a temp number or node ID', 'warn'); return; }
|
|
10300
10464
|
hideModal('modal-add-friend');
|
|
10301
10465
|
toast('Sending friend request...', 'info');
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
else { toast(r.message || 'Failed to add friend', 'err'); }
|
|
10306
|
-
} 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'); }
|
|
10307
10469
|
}
|
|
10308
|
-
|
|
10309
10470
|
async function removeFriend(id) {
|
|
10310
|
-
if (!confirm('Remove friend ' + id + '?
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
|
|
10314
|
-
else { toast(r.message || 'Failed to remove', 'err'); }
|
|
10315
|
-
} 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'); }
|
|
10316
10475
|
}
|
|
10317
10476
|
|
|
10318
|
-
let
|
|
10319
|
-
function
|
|
10320
|
-
|
|
10321
|
-
|
|
10322
|
-
|
|
10323
|
-
$('#perm-chat').checked = chatChecked;
|
|
10324
|
-
$('#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');
|
|
10325
10482
|
showModal('modal-permissions');
|
|
10326
10483
|
}
|
|
10327
|
-
|
|
10328
|
-
async function savePermissions() {
|
|
10484
|
+
async function saveFriendPerms() {
|
|
10329
10485
|
const perms = [];
|
|
10330
10486
|
if ($('#perm-chat').checked) perms.push('chat');
|
|
10331
10487
|
if ($('#perm-exec').checked) perms.push('exec');
|
|
10332
|
-
|
|
10333
|
-
|
|
10334
|
-
|
|
10335
|
-
});
|
|
10336
|
-
if (r.success) { toast('Permissions updated', 'ok'); hideModal('modal-permissions'); loadAICQ(); }
|
|
10337
|
-
else { toast(r.message || 'Failed to update', 'err'); }
|
|
10338
|
-
} 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'); }
|
|
10339
10491
|
}
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/accept', { method: 'POST', body: JSON.stringify({ permissions: ['chat'] }) });
|
|
10344
|
-
if (r.success) { toast('Request accepted', 'ok'); loadAICQ(); }
|
|
10345
|
-
else { toast(r.message || 'Failed', 'err'); }
|
|
10346
|
-
} 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'); }
|
|
10347
10495
|
}
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
|
|
10351
|
-
const r = await api('/friends/requests/' + encodeURIComponent(id) + '/reject', { method: 'POST', body: JSON.stringify({}) });
|
|
10352
|
-
if (r.success) { toast('Request rejected', 'ok'); loadAICQ(); }
|
|
10353
|
-
else { toast(r.message || 'Failed', 'err'); }
|
|
10354
|
-
} 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'); }
|
|
10355
10499
|
}
|
|
10356
10500
|
|
|
10357
|
-
// \
|
|
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
|
+
|
|
10358
10506
|
async function loadModels() {
|
|
10359
|
-
|
|
10360
|
-
|
|
10361
|
-
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
}
|
|
10366
|
-
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);
|
|
10367
10513
|
}
|
|
10368
10514
|
|
|
10369
10515
|
function renderModels(data) {
|
|
10370
10516
|
const el = $('#models-content');
|
|
10371
10517
|
const providers = data.providers || [];
|
|
10372
|
-
const configured = providers.filter(p => p.configured);
|
|
10518
|
+
const configured = providers.filter(p => p.configured).length;
|
|
10373
10519
|
|
|
10374
|
-
let
|
|
10520
|
+
let cards = '';
|
|
10375
10521
|
providers.forEach(p => {
|
|
10376
|
-
const
|
|
10377
|
-
const
|
|
10378
|
-
|
|
10379
|
-
<
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
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>\\\`;
|
|
10384
10540
|
});
|
|
10385
10541
|
|
|
10386
|
-
let
|
|
10387
|
-
if (data.currentModels && data.currentModels.length
|
|
10542
|
+
let activeModelsSection = '';
|
|
10543
|
+
if (data.currentModels && data.currentModels.length) {
|
|
10388
10544
|
let rows = '';
|
|
10389
10545
|
data.currentModels.forEach(m => {
|
|
10390
|
-
rows +=
|
|
10391
|
-
<td>\${escHtml(m.provider
|
|
10392
|
-
<td class="mono">\${escHtml(m.modelId
|
|
10393
|
-
<td
|
|
10394
|
-
<td class="mono">\${escHtml(m.baseUrl || '
|
|
10395
|
-
<td><button class="btn btn-sm btn-ghost" onclick="
|
|
10396
|
-
</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>\\\`;
|
|
10397
10553
|
});
|
|
10398
|
-
|
|
10399
|
-
<div class="card">
|
|
10400
|
-
<div class="card-header">
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
<tbody>\${rows}</tbody>
|
|
10407
|
-
</table>
|
|
10408
|
-
</div>
|
|
10409
|
-
</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>\\\`;
|
|
10410
10562
|
}
|
|
10411
10563
|
|
|
10412
|
-
el
|
|
10413
|
-
<
|
|
10414
|
-
|
|
10415
|
-
|
|
10416
|
-
|
|
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
|
+
\\\`);
|
|
10417
10577
|
}
|
|
10418
10578
|
|
|
10419
|
-
let
|
|
10420
|
-
function
|
|
10421
|
-
|
|
10422
|
-
|
|
10423
|
-
|
|
10424
|
-
|
|
10425
|
-
$('#model-
|
|
10426
|
-
$('#model-api-key').value =
|
|
10427
|
-
$('#model-api-key').placeholder =
|
|
10428
|
-
$('#model-model-id').value =
|
|
10429
|
-
$('#model-model-id').placeholder =
|
|
10430
|
-
$('#model-base-url').value =
|
|
10431
|
-
$('#model-base-url').placeholder =
|
|
10432
|
-
|
|
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';
|
|
10433
10593
|
showModal('modal-model-config');
|
|
10434
10594
|
}
|
|
10435
|
-
|
|
10436
10595
|
async function saveModelConfig() {
|
|
10437
10596
|
const apiKey = $('#model-api-key').value.trim();
|
|
10438
10597
|
const modelId = $('#model-model-id').value.trim();
|
|
10439
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')]);
|
|
10440
10614
|
|
|
10441
|
-
if (
|
|
10442
|
-
|
|
10615
|
+
if (config.error) {
|
|
10616
|
+
html(el, '<div class="empty"><div class="icon">\u26A0\uFE0F</div><p>' + escHtml(config.error) + '</p></div>');
|
|
10443
10617
|
return;
|
|
10444
10618
|
}
|
|
10445
10619
|
|
|
10446
|
-
|
|
10447
|
-
|
|
10620
|
+
html(el, \\\`
|
|
10621
|
+
<p class="section-desc">AICQ plugin runtime configuration and system information.</p>
|
|
10448
10622
|
|
|
10449
|
-
|
|
10450
|
-
|
|
10451
|
-
|
|
10452
|
-
|
|
10453
|
-
|
|
10454
|
-
if (r.success) { toast('Model config saved!', 'ok'); loadModels(); }
|
|
10455
|
-
else { toast(r.message || 'Failed to save', 'err'); }
|
|
10456
|
-
} catch (e) { toast('Error: ' + e.message, 'err'); }
|
|
10457
|
-
}
|
|
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>
|
|
10458
10628
|
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
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
|
+
\\\`);
|
|
10473
10648
|
}
|
|
10474
10649
|
|
|
10475
|
-
// \
|
|
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
|
|
10476
10653
|
document.addEventListener('DOMContentLoaded', () => {
|
|
10477
|
-
$$('.
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
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
|
+
|
|
10482
10660
|
// Auto-refresh status every 30s
|
|
10483
|
-
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);
|
|
10484
10673
|
});
|
|
10485
10674
|
`;
|
|
10486
10675
|
var HTML = `<!DOCTYPE html>
|
|
@@ -10488,62 +10677,86 @@ var HTML = `<!DOCTYPE html>
|
|
|
10488
10677
|
<head>
|
|
10489
10678
|
<meta charset="UTF-8">
|
|
10490
10679
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
10491
|
-
<title>AICQ Management</title>
|
|
10680
|
+
<title>AICQ Management Console</title>
|
|
10492
10681
|
<style>${CSS}</style>
|
|
10493
10682
|
</head>
|
|
10494
10683
|
<body>
|
|
10495
|
-
<div class="
|
|
10496
|
-
|
|
10497
|
-
<
|
|
10498
|
-
<
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
</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">
|
|
10502
10726
|
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
<button class="tab-btn" data-tab="aicq" onclick="switchTab('aicq')">\u{1F4AC} AICQ Management</button>
|
|
10506
|
-
<button class="tab-btn" data-tab="models" onclick="switchTab('models')">\u{1F9E0} Model Management</button>
|
|
10507
|
-
</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>
|
|
10508
10729
|
|
|
10509
|
-
<!--
|
|
10510
|
-
<div class="
|
|
10511
|
-
<p class="section-desc">View and manage AICQ agent identities. Each agent has its own Ed25519 key pair and encrypted session state.</p>
|
|
10512
|
-
<div id="agents-content">
|
|
10513
|
-
<div class="loading"><div class="spinner"></div> Loading agents...</div>
|
|
10514
|
-
</div>
|
|
10515
|
-
</div>
|
|
10730
|
+
<!-- Agents -->
|
|
10731
|
+
<div class="page" id="page-agents"><div id="agents-content"></div></div>
|
|
10516
10732
|
|
|
10517
|
-
<!--
|
|
10518
|
-
<div class="
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
<div id="aicq-friends" class="hidden"></div>
|
|
10523
|
-
<div id="aicq-requests" class="hidden"></div>
|
|
10524
|
-
<div id="aicq-sessions" class="hidden"></div>
|
|
10525
|
-
</div>
|
|
10526
|
-
</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>
|
|
10527
10738
|
|
|
10528
|
-
<!--
|
|
10529
|
-
<div class="
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
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>
|
|
10534
10747
|
</div>
|
|
10535
10748
|
|
|
10536
10749
|
<!-- Modal: Add Friend -->
|
|
10537
10750
|
<div class="modal-overlay hidden" id="modal-add-friend" onclick="if(event.target===this)hideModal('modal-add-friend')">
|
|
10538
10751
|
<div class="modal">
|
|
10539
|
-
<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>
|
|
10540
10753
|
<div class="form-group">
|
|
10541
|
-
<label>Temp Number or
|
|
10542
|
-
<input id="add-friend-target" type="text" placeholder="
|
|
10543
|
-
<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>
|
|
10544
10757
|
</div>
|
|
10545
10758
|
<div class="form-actions">
|
|
10546
|
-
<button class="btn" onclick="hideModal('modal-add-friend')">Cancel</button>
|
|
10759
|
+
<button class="btn btn-default" onclick="hideModal('modal-add-friend')">Cancel</button>
|
|
10547
10760
|
<button class="btn btn-primary" onclick="addFriend()">Send Request</button>
|
|
10548
10761
|
</div>
|
|
10549
10762
|
</div>
|
|
@@ -10552,49 +10765,59 @@ var HTML = `<!DOCTYPE html>
|
|
|
10552
10765
|
<!-- Modal: Edit Permissions -->
|
|
10553
10766
|
<div class="modal-overlay hidden" id="modal-permissions" onclick="if(event.target===this)hideModal('modal-permissions')">
|
|
10554
10767
|
<div class="modal">
|
|
10555
|
-
<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>
|
|
10556
10769
|
<div class="form-group">
|
|
10557
|
-
<label>Permissions
|
|
10558
|
-
<div class="perm-checks" style="margin-top:
|
|
10559
|
-
<label><input type="checkbox" id="perm-chat" checked> Chat <span style="color:var(--text3);font-size:11px">(send/receive messages)</span></label>
|
|
10560
|
-
<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>
|
|
10561
10774
|
</div>
|
|
10562
10775
|
</div>
|
|
10563
10776
|
<div class="form-actions">
|
|
10564
|
-
<button class="btn" onclick="hideModal('modal-permissions')">Cancel</button>
|
|
10565
|
-
<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>
|
|
10566
10779
|
</div>
|
|
10567
10780
|
</div>
|
|
10568
10781
|
</div>
|
|
10569
10782
|
|
|
10570
10783
|
<!-- Modal: Model Config -->
|
|
10571
10784
|
<div class="modal-overlay hidden" id="modal-model-config" onclick="if(event.target===this)hideModal('modal-model-config')">
|
|
10572
|
-
<div class="modal" style="max-width:
|
|
10573
|
-
<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>
|
|
10574
10788
|
<div class="form-group">
|
|
10575
|
-
<label
|
|
10576
|
-
<input id="model-api-key" type="password" placeholder="sk-...">
|
|
10577
|
-
<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>
|
|
10578
10792
|
</div>
|
|
10579
10793
|
<div class="form-group">
|
|
10580
|
-
<label
|
|
10581
|
-
<input id="model-model-id" type="text" placeholder="gpt-4o">
|
|
10582
|
-
<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>
|
|
10583
10797
|
</div>
|
|
10584
10798
|
<div class="form-group">
|
|
10585
|
-
<label
|
|
10586
|
-
<input id="model-base-url" type="text" placeholder="https
|
|
10587
|
-
<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>
|
|
10588
10802
|
</div>
|
|
10589
10803
|
<div class="form-actions">
|
|
10590
|
-
<button class="btn" onclick="hideModal('modal-model-config')">Cancel</button>
|
|
10591
|
-
<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>
|
|
10592
10806
|
</div>
|
|
10593
10807
|
</div>
|
|
10594
10808
|
</div>
|
|
10595
10809
|
|
|
10596
|
-
<!--
|
|
10597
|
-
<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>
|
|
10598
10821
|
|
|
10599
10822
|
<script>${JS}</script>
|
|
10600
10823
|
</body>
|
|
@@ -10608,113 +10831,60 @@ import * as fs5 from "fs";
|
|
|
10608
10831
|
import * as path5 from "path";
|
|
10609
10832
|
import * as os from "os";
|
|
10610
10833
|
var MODEL_PROVIDERS = [
|
|
10611
|
-
{
|
|
10612
|
-
|
|
10613
|
-
|
|
10614
|
-
|
|
10615
|
-
|
|
10616
|
-
|
|
10617
|
-
|
|
10618
|
-
|
|
10619
|
-
},
|
|
10620
|
-
{
|
|
10621
|
-
id: "anthropic",
|
|
10622
|
-
name: "Anthropic",
|
|
10623
|
-
description: "Claude 4, Claude 3.5 Sonnet, Haiku",
|
|
10624
|
-
apiKeyHint: "sk-ant-...",
|
|
10625
|
-
modelHint: "claude-sonnet-4-20250514",
|
|
10626
|
-
baseUrlHint: "https://api.anthropic.com",
|
|
10627
|
-
configKey: "anthropic"
|
|
10628
|
-
},
|
|
10629
|
-
{
|
|
10630
|
-
id: "google",
|
|
10631
|
-
name: "Google AI",
|
|
10632
|
-
description: "Gemini 2.5 Pro, Gemini 2.5 Flash",
|
|
10633
|
-
apiKeyHint: "AI...",
|
|
10634
|
-
modelHint: "gemini-2.5-pro",
|
|
10635
|
-
baseUrlHint: "",
|
|
10636
|
-
configKey: "google"
|
|
10637
|
-
},
|
|
10638
|
-
{
|
|
10639
|
-
id: "groq",
|
|
10640
|
-
name: "Groq",
|
|
10641
|
-
description: "Llama 3, Mixtral \u2014 ultra fast inference",
|
|
10642
|
-
apiKeyHint: "gsk_...",
|
|
10643
|
-
modelHint: "llama-3.3-70b-versatile",
|
|
10644
|
-
baseUrlHint: "https://api.groq.com/openai/v1",
|
|
10645
|
-
configKey: "groq"
|
|
10646
|
-
},
|
|
10647
|
-
{
|
|
10648
|
-
id: "deepseek",
|
|
10649
|
-
name: "DeepSeek",
|
|
10650
|
-
description: "DeepSeek V3, DeepSeek R1",
|
|
10651
|
-
apiKeyHint: "sk-...",
|
|
10652
|
-
modelHint: "deepseek-chat",
|
|
10653
|
-
baseUrlHint: "https://api.deepseek.com/v1",
|
|
10654
|
-
configKey: "deepseek"
|
|
10655
|
-
},
|
|
10656
|
-
{
|
|
10657
|
-
id: "ollama",
|
|
10658
|
-
name: "Ollama (Local)",
|
|
10659
|
-
description: "Run models locally on your machine",
|
|
10660
|
-
apiKeyHint: "(no key needed)",
|
|
10661
|
-
modelHint: "llama3",
|
|
10662
|
-
baseUrlHint: "http://localhost:11434/v1",
|
|
10663
|
-
configKey: "ollama"
|
|
10664
|
-
},
|
|
10665
|
-
{
|
|
10666
|
-
id: "openrouter",
|
|
10667
|
-
name: "OpenRouter",
|
|
10668
|
-
description: "Unified API for 200+ models",
|
|
10669
|
-
apiKeyHint: "sk-or-...",
|
|
10670
|
-
modelHint: "openai/gpt-4o",
|
|
10671
|
-
baseUrlHint: "https://openrouter.ai/api/v1",
|
|
10672
|
-
configKey: "openrouter"
|
|
10673
|
-
},
|
|
10674
|
-
{
|
|
10675
|
-
id: "mistral",
|
|
10676
|
-
name: "Mistral AI",
|
|
10677
|
-
description: "Mistral Large, Medium, Small",
|
|
10678
|
-
apiKeyHint: "(your key)",
|
|
10679
|
-
modelHint: "mistral-large-latest",
|
|
10680
|
-
baseUrlHint: "https://api.mistral.ai/v1",
|
|
10681
|
-
configKey: "mistral"
|
|
10682
|
-
}
|
|
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" }
|
|
10683
10844
|
];
|
|
10684
|
-
function
|
|
10685
|
-
const
|
|
10845
|
+
function findConfigPath() {
|
|
10846
|
+
const openclawPaths = [
|
|
10686
10847
|
path5.join(process.cwd(), "openclaw.json"),
|
|
10687
|
-
path5.join(process.cwd(), "stableclaw.json"),
|
|
10688
10848
|
path5.join(os.homedir(), ".config", "openclaw", "openclaw.json"),
|
|
10689
|
-
path5.join(os.homedir(), ".config", "stableclaw", "stableclaw.json"),
|
|
10690
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"),
|
|
10691
10862
|
path5.join(os.homedir(), ".stableclaw", "stableclaw.json"),
|
|
10692
|
-
path5.join(os.homedir(), "openclaw.json"),
|
|
10693
10863
|
path5.join(os.homedir(), "stableclaw.json")
|
|
10694
10864
|
];
|
|
10695
|
-
for (const p of
|
|
10865
|
+
for (const p of stableclawPaths) {
|
|
10696
10866
|
try {
|
|
10697
|
-
if (fs5.existsSync(p))
|
|
10867
|
+
if (fs5.existsSync(p))
|
|
10698
10868
|
return p;
|
|
10699
|
-
}
|
|
10700
10869
|
} catch {
|
|
10701
10870
|
}
|
|
10702
10871
|
}
|
|
10703
10872
|
return null;
|
|
10704
10873
|
}
|
|
10705
|
-
function
|
|
10706
|
-
const configPath =
|
|
10874
|
+
function readConfig() {
|
|
10875
|
+
const configPath = findConfigPath();
|
|
10707
10876
|
if (!configPath)
|
|
10708
10877
|
return null;
|
|
10709
10878
|
try {
|
|
10710
10879
|
const raw = fs5.readFileSync(configPath, "utf-8");
|
|
10711
|
-
|
|
10880
|
+
const config2 = JSON.parse(raw);
|
|
10881
|
+
return { config: config2, configPath };
|
|
10712
10882
|
} catch {
|
|
10713
10883
|
return null;
|
|
10714
10884
|
}
|
|
10715
10885
|
}
|
|
10716
|
-
function
|
|
10717
|
-
const configPath =
|
|
10886
|
+
function writeConfig(config2) {
|
|
10887
|
+
const configPath = findConfigPath();
|
|
10718
10888
|
if (!configPath)
|
|
10719
10889
|
return false;
|
|
10720
10890
|
try {
|
|
@@ -10724,24 +10894,54 @@ function writeOpenClawConfig(config2) {
|
|
|
10724
10894
|
return false;
|
|
10725
10895
|
}
|
|
10726
10896
|
}
|
|
10727
|
-
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
|
+
}
|
|
10728
10930
|
const providers = MODEL_PROVIDERS.map((p) => {
|
|
10729
|
-
const
|
|
10730
|
-
const
|
|
10731
|
-
const
|
|
10732
|
-
const
|
|
10733
|
-
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 || "";
|
|
10734
10935
|
return {
|
|
10735
10936
|
...p,
|
|
10736
10937
|
configured: Boolean(apiKey || p.id === "ollama" && baseUrl),
|
|
10737
|
-
apiKey: apiKey ? apiKey.substring(0,
|
|
10938
|
+
apiKey: apiKey ? apiKey.substring(0, 6) + "\u2022\u2022\u2022\u2022\u2022\u2022" + apiKey.slice(-4) : "",
|
|
10738
10939
|
apiKeyHasValue: Boolean(apiKey),
|
|
10739
10940
|
modelId,
|
|
10740
10941
|
baseUrl
|
|
10741
10942
|
};
|
|
10742
10943
|
});
|
|
10743
10944
|
const currentModels = [];
|
|
10744
|
-
const providersSection = config2.providers;
|
|
10745
10945
|
for (const p of MODEL_PROVIDERS) {
|
|
10746
10946
|
const pc = providersSection?.[p.configKey] ?? config2[p.configKey];
|
|
10747
10947
|
if (pc?.apiKey) {
|
|
@@ -10750,25 +10950,38 @@ function getModelConfig(config2) {
|
|
|
10750
10950
|
providerId: p.id,
|
|
10751
10951
|
modelId: pc.model || pc.defaultModel || p.modelHint,
|
|
10752
10952
|
hasApiKey: true,
|
|
10753
|
-
baseUrl: pc.baseUrl || pc.baseURL ||
|
|
10953
|
+
baseUrl: pc.baseUrl || pc.baseURL || p.baseUrlHint
|
|
10754
10954
|
});
|
|
10755
10955
|
}
|
|
10756
10956
|
}
|
|
10757
10957
|
return { providers, currentModels };
|
|
10758
10958
|
}
|
|
10759
|
-
function
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
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;
|
|
10765
10968
|
}
|
|
10766
10969
|
function json(res, data, status = 200) {
|
|
10767
10970
|
if (!res.headersSent) {
|
|
10768
|
-
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": "*" });
|
|
10769
10972
|
}
|
|
10770
10973
|
res.end(JSON.stringify(data));
|
|
10771
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
|
+
}
|
|
10772
10985
|
async function readBody(req) {
|
|
10773
10986
|
return new Promise((resolve3) => {
|
|
10774
10987
|
const chunks = [];
|
|
@@ -10787,20 +11000,24 @@ async function readBody(req) {
|
|
|
10787
11000
|
function createManagementHandler(ctx) {
|
|
10788
11001
|
const { store, identityService, serverClient, serverUrl, aicqAgentId, logger, html } = ctx;
|
|
10789
11002
|
return async (req, res) => {
|
|
10790
|
-
const
|
|
10791
|
-
const
|
|
10792
|
-
if (
|
|
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") {
|
|
10793
11011
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
10794
11012
|
res.end(html);
|
|
10795
11013
|
return;
|
|
10796
11014
|
}
|
|
10797
|
-
if (!
|
|
11015
|
+
if (!urlPath.startsWith("/api/")) {
|
|
10798
11016
|
res.writeHead(404, { "Content-Type": "application/json" });
|
|
10799
11017
|
res.end(JSON.stringify({ error: "Not found" }));
|
|
10800
11018
|
return;
|
|
10801
11019
|
}
|
|
10802
|
-
const apiPath =
|
|
10803
|
-
const method = (req.method || "GET").toUpperCase();
|
|
11020
|
+
const apiPath = urlPath.slice(4);
|
|
10804
11021
|
try {
|
|
10805
11022
|
if (apiPath === "/status" && method === "GET") {
|
|
10806
11023
|
return json(res, {
|
|
@@ -10822,62 +11039,70 @@ function createManagementHandler(ctx) {
|
|
|
10822
11039
|
sessionCount: store.sessions.size
|
|
10823
11040
|
});
|
|
10824
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
|
+
}
|
|
10825
11053
|
if (apiPath === "/agents" && method === "GET") {
|
|
10826
|
-
const
|
|
10827
|
-
|
|
10828
|
-
|
|
10829
|
-
|
|
10830
|
-
|
|
10831
|
-
|
|
10832
|
-
|
|
10833
|
-
|
|
10834
|
-
|
|
10835
|
-
let serverFriends = [];
|
|
10836
|
-
try {
|
|
10837
|
-
const resp = await fetch(serverUrl + "/api/v1/friends?nodeId=" + aicqAgentId);
|
|
10838
|
-
if (resp.ok) {
|
|
10839
|
-
const data = await resp.json();
|
|
10840
|
-
serverFriends = data.friends || [];
|
|
10841
|
-
}
|
|
10842
|
-
} catch {
|
|
10843
|
-
}
|
|
10844
|
-
const localIds = new Set(localFriends.map((f) => f.id));
|
|
10845
|
-
for (const sf of serverFriends) {
|
|
10846
|
-
const sfId = sf.nodeId;
|
|
10847
|
-
if (sfId && !localIds.has(sfId)) {
|
|
10848
|
-
localFriends.push({
|
|
10849
|
-
id: sfId,
|
|
10850
|
-
name: sf.aiName || sfId.substring(0, 8),
|
|
10851
|
-
friendType: sf.friendType || void 0,
|
|
10852
|
-
publicKeyFingerprint: sf.publicKeyFingerprint || "",
|
|
10853
|
-
permissions: sf.permissions || [],
|
|
10854
|
-
lastMessageAt: sf.lastMessageAt || null,
|
|
10855
|
-
sessionCount: 0
|
|
10856
|
-
});
|
|
10857
|
-
}
|
|
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
|
+
});
|
|
10858
11063
|
}
|
|
11064
|
+
const agents = extractAgentsFromConfig(result.config);
|
|
11065
|
+
globalThis.__aicq_agents = agents;
|
|
10859
11066
|
return json(res, {
|
|
10860
|
-
agents
|
|
11067
|
+
agents,
|
|
11068
|
+
configSource: path5.basename(result.configPath),
|
|
11069
|
+
configPath: result.configPath,
|
|
10861
11070
|
currentAgentId: aicqAgentId,
|
|
10862
11071
|
fingerprint: identityService.getPublicKeyFingerprint(),
|
|
10863
11072
|
connected: serverClient.isConnected()
|
|
10864
11073
|
});
|
|
10865
11074
|
}
|
|
10866
11075
|
if (apiPath.startsWith("/agents/") && method === "DELETE") {
|
|
10867
|
-
const
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
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);
|
|
10877
11095
|
}
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
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" });
|
|
11104
|
+
}
|
|
11105
|
+
return json(res, { success: false, message: "Failed to write config" }, 500);
|
|
10881
11106
|
}
|
|
10882
11107
|
if (apiPath === "/friends" && method === "GET") {
|
|
10883
11108
|
try {
|
|
@@ -10911,26 +11136,36 @@ function createManagementHandler(ctx) {
|
|
|
10911
11136
|
const isTempNumber = /^\d{6}$/.test(target);
|
|
10912
11137
|
let friendId = target;
|
|
10913
11138
|
if (isTempNumber) {
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
|
|
10923
|
-
|
|
10924
|
-
}
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
|
|
10929
|
-
|
|
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
|
+
}
|
|
10930
11165
|
}
|
|
10931
|
-
if (apiPath.startsWith("/friends/") &&
|
|
11166
|
+
if (apiPath.startsWith("/friends/") && method === "DELETE") {
|
|
10932
11167
|
if (apiPath.includes("/requests/")) {
|
|
10933
|
-
|
|
11168
|
+
} else if (apiPath.includes("/permissions")) {
|
|
10934
11169
|
} else {
|
|
10935
11170
|
const friendId = decodeURIComponent(apiPath.slice("/friends/".length));
|
|
10936
11171
|
if (!friendId)
|
|
@@ -10968,7 +11203,7 @@ function createManagementHandler(ctx) {
|
|
|
10968
11203
|
body: JSON.stringify({ nodeId: aicqAgentId, permissions })
|
|
10969
11204
|
});
|
|
10970
11205
|
if (!resp.ok)
|
|
10971
|
-
return json(res, { success: false, message: "Failed
|
|
11206
|
+
return json(res, { success: false, message: "Failed: " + await resp.text() });
|
|
10972
11207
|
const localFriend = store.getFriend(friendId);
|
|
10973
11208
|
if (localFriend) {
|
|
10974
11209
|
localFriend.permissions = permissions;
|
|
@@ -10998,29 +11233,39 @@ function createManagementHandler(ctx) {
|
|
|
10998
11233
|
if (!requestId)
|
|
10999
11234
|
return json(res, { success: false, message: "Missing request ID" }, 400);
|
|
11000
11235
|
const body = await readBody(req);
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
|
|
11005
|
-
|
|
11006
|
-
|
|
11007
|
-
|
|
11008
|
-
|
|
11009
|
-
|
|
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
|
+
}
|
|
11010
11250
|
}
|
|
11011
11251
|
if (apiPath.match(/^\/friends\/requests\/[^/]+\/reject$/) && method === "POST") {
|
|
11012
11252
|
const requestId = decodeURIComponent(apiPath.split("/")[3]);
|
|
11013
11253
|
if (!requestId)
|
|
11014
11254
|
return json(res, { success: false, message: "Missing request ID" }, 400);
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
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
|
+
}
|
|
11024
11269
|
}
|
|
11025
11270
|
if (apiPath === "/sessions" && method === "GET") {
|
|
11026
11271
|
const sessions = Array.from(store.sessions.values()).map((s) => ({
|
|
@@ -11031,9 +11276,10 @@ function createManagementHandler(ctx) {
|
|
|
11031
11276
|
return json(res, { sessions });
|
|
11032
11277
|
}
|
|
11033
11278
|
if (apiPath === "/models" && method === "GET") {
|
|
11034
|
-
const
|
|
11035
|
-
|
|
11036
|
-
|
|
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));
|
|
11037
11283
|
}
|
|
11038
11284
|
if (apiPath.match(/^\/models\/[^/]+$/) && method === "PUT") {
|
|
11039
11285
|
const providerId = decodeURIComponent(apiPath.slice("/models/".length));
|
|
@@ -11044,15 +11290,15 @@ function createManagementHandler(ctx) {
|
|
|
11044
11290
|
const provider = MODEL_PROVIDERS.find((p) => p.id === providerId);
|
|
11045
11291
|
if (!provider)
|
|
11046
11292
|
return json(res, { success: false, message: "Unknown provider: " + providerId }, 400);
|
|
11047
|
-
const
|
|
11048
|
-
if (!
|
|
11049
|
-
return json(res, { success: false, message: "
|
|
11050
|
-
|
|
11051
|
-
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") {
|
|
11052
11298
|
config2.providers = {};
|
|
11053
11299
|
}
|
|
11054
11300
|
const providers = config2.providers;
|
|
11055
|
-
if (!providers[provider.configKey]) {
|
|
11301
|
+
if (!providers[provider.configKey] || typeof providers[provider.configKey] !== "object") {
|
|
11056
11302
|
providers[provider.configKey] = {};
|
|
11057
11303
|
}
|
|
11058
11304
|
const provConfig = providers[provider.configKey];
|
|
@@ -11062,10 +11308,9 @@ function createManagementHandler(ctx) {
|
|
|
11062
11308
|
provConfig.model = modelId;
|
|
11063
11309
|
if (baseUrl)
|
|
11064
11310
|
provConfig.baseUrl = baseUrl;
|
|
11065
|
-
const written =
|
|
11066
|
-
if (!written)
|
|
11311
|
+
const written = writeConfig(config2);
|
|
11312
|
+
if (!written)
|
|
11067
11313
|
return json(res, { success: false, message: "Failed to write config file" }, 500);
|
|
11068
|
-
}
|
|
11069
11314
|
logger.info("[API] Model config saved for provider: " + providerId);
|
|
11070
11315
|
return json(res, { success: true, message: "Model configuration saved for " + provider.name });
|
|
11071
11316
|
}
|
|
@@ -11631,7 +11876,7 @@ var plugin = definePluginEntry({
|
|
|
11631
11876
|
logger,
|
|
11632
11877
|
html: managementHtml
|
|
11633
11878
|
});
|
|
11634
|
-
const mgmtPort = parseInt(process.env.AICQ_MGMT_PORT || "
|
|
11879
|
+
const mgmtPort = parseInt(process.env.AICQ_MGMT_PORT || "461099", 10);
|
|
11635
11880
|
const mgmtServer = http.createServer((req, res) => {
|
|
11636
11881
|
managementHandler(req, res).catch((err) => {
|
|
11637
11882
|
logger.error("[HTTP] Management server error: " + (err instanceof Error ? err.message : err));
|