codexmate 0.0.5 → 0.0.7
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/.github/workflows/ci.yml +26 -0
- package/CHANGELOG.md +7 -0
- package/CHANGELOG.zh-CN.md +7 -0
- package/README.md +37 -29
- package/README.zh-CN.md +37 -29
- package/cli.js +1179 -301
- package/package.json +4 -3
- package/tests/e2e/recent-health.e2e.js +136 -135
- package/tests/e2e/run.js +63 -0
- package/web-ui.html +2154 -211
package/web-ui.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Codex Mate</title>
|
|
7
7
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
8
|
+
<script src="https://unpkg.com/json5@2/dist/index.min.js"></script>
|
|
8
9
|
<style>
|
|
9
10
|
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&family=Source+Sans+3:wght@400;500;600&family=Space+Grotesk:wght@400;500;600;700&display=swap');
|
|
10
11
|
|
|
@@ -920,6 +921,51 @@
|
|
|
920
921
|
letter-spacing: -0.01em;
|
|
921
922
|
}
|
|
922
923
|
|
|
924
|
+
.btn-session-open {
|
|
925
|
+
border: 1px solid var(--color-border-soft);
|
|
926
|
+
border-radius: var(--radius-sm);
|
|
927
|
+
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
928
|
+
color: var(--color-text-secondary);
|
|
929
|
+
padding: 8px 12px;
|
|
930
|
+
font-size: var(--font-size-secondary);
|
|
931
|
+
font-weight: var(--font-weight-secondary);
|
|
932
|
+
cursor: pointer;
|
|
933
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
934
|
+
white-space: nowrap;
|
|
935
|
+
box-shadow: var(--shadow-subtle);
|
|
936
|
+
letter-spacing: -0.01em;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.btn-session-clone {
|
|
940
|
+
border: 1px solid var(--color-border-soft);
|
|
941
|
+
border-radius: var(--radius-sm);
|
|
942
|
+
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
943
|
+
color: var(--color-text-secondary);
|
|
944
|
+
padding: 8px 12px;
|
|
945
|
+
font-size: var(--font-size-secondary);
|
|
946
|
+
font-weight: var(--font-weight-secondary);
|
|
947
|
+
cursor: pointer;
|
|
948
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
949
|
+
white-space: nowrap;
|
|
950
|
+
box-shadow: var(--shadow-subtle);
|
|
951
|
+
letter-spacing: -0.01em;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.btn-session-delete {
|
|
955
|
+
border: 1px solid rgba(189, 70, 68, 0.4);
|
|
956
|
+
border-radius: var(--radius-sm);
|
|
957
|
+
background: linear-gradient(to bottom, rgba(255, 245, 245, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
|
|
958
|
+
color: #b74545;
|
|
959
|
+
padding: 8px 12px;
|
|
960
|
+
font-size: var(--font-size-secondary);
|
|
961
|
+
font-weight: var(--font-weight-secondary);
|
|
962
|
+
cursor: pointer;
|
|
963
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
964
|
+
white-space: nowrap;
|
|
965
|
+
box-shadow: var(--shadow-subtle);
|
|
966
|
+
letter-spacing: -0.01em;
|
|
967
|
+
}
|
|
968
|
+
|
|
923
969
|
.btn-session-refresh {
|
|
924
970
|
border: 1px solid var(--color-border-soft);
|
|
925
971
|
border-radius: var(--radius-sm);
|
|
@@ -947,13 +993,39 @@
|
|
|
947
993
|
transform: none;
|
|
948
994
|
}
|
|
949
995
|
|
|
950
|
-
.btn-session-export:hover
|
|
996
|
+
.btn-session-export:hover,
|
|
997
|
+
.btn-session-open:hover {
|
|
998
|
+
border-color: var(--color-brand);
|
|
999
|
+
color: var(--color-brand);
|
|
1000
|
+
transform: translateY(-1px);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
.btn-session-export:disabled,
|
|
1004
|
+
.btn-session-open:disabled {
|
|
1005
|
+
opacity: 0.5;
|
|
1006
|
+
cursor: not-allowed;
|
|
1007
|
+
transform: none;
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.btn-session-clone:hover {
|
|
951
1011
|
border-color: var(--color-brand);
|
|
952
1012
|
color: var(--color-brand);
|
|
953
1013
|
transform: translateY(-1px);
|
|
954
1014
|
}
|
|
955
1015
|
|
|
956
|
-
.btn-session-
|
|
1016
|
+
.btn-session-clone:disabled {
|
|
1017
|
+
opacity: 0.5;
|
|
1018
|
+
cursor: not-allowed;
|
|
1019
|
+
transform: none;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.btn-session-delete:hover {
|
|
1023
|
+
border-color: rgba(189, 70, 68, 0.8);
|
|
1024
|
+
color: #9f3b3b;
|
|
1025
|
+
transform: translateY(-1px);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
.btn-session-delete:disabled {
|
|
957
1029
|
opacity: 0.5;
|
|
958
1030
|
cursor: not-allowed;
|
|
959
1031
|
transform: none;
|
|
@@ -990,11 +1062,38 @@
|
|
|
990
1062
|
min-height: 520px;
|
|
991
1063
|
}
|
|
992
1064
|
|
|
993
|
-
.session-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
1065
|
+
.session-layout.session-standalone {
|
|
1066
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
.session-standalone-page {
|
|
1070
|
+
max-width: 960px;
|
|
1071
|
+
margin: 0 auto;
|
|
1072
|
+
padding: var(--spacing-sm) 0;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.session-standalone-title {
|
|
1076
|
+
font-size: var(--font-size-title);
|
|
1077
|
+
font-weight: var(--font-weight-title);
|
|
1078
|
+
color: var(--color-text-primary);
|
|
1079
|
+
margin-bottom: var(--spacing-sm);
|
|
1080
|
+
letter-spacing: -0.01em;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.session-standalone-text {
|
|
1084
|
+
white-space: pre-wrap;
|
|
1085
|
+
font-family: var(--font-family-body);
|
|
1086
|
+
font-size: var(--font-size-body);
|
|
1087
|
+
line-height: 1.7;
|
|
1088
|
+
color: var(--color-text-primary);
|
|
1089
|
+
word-break: break-word;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.session-list {
|
|
1093
|
+
display: flex;
|
|
1094
|
+
flex-direction: column;
|
|
1095
|
+
gap: var(--spacing-xs);
|
|
1096
|
+
position: sticky;
|
|
998
1097
|
top: 12px;
|
|
999
1098
|
height: 100%;
|
|
1000
1099
|
max-height: none;
|
|
@@ -1108,6 +1207,21 @@
|
|
|
1108
1207
|
transition: all var(--transition-fast) var(--ease-spring);
|
|
1109
1208
|
}
|
|
1110
1209
|
|
|
1210
|
+
.session-item-delete {
|
|
1211
|
+
border: 1px solid rgba(189, 70, 68, 0.35);
|
|
1212
|
+
background: rgba(189, 70, 68, 0.08);
|
|
1213
|
+
color: #b74545;
|
|
1214
|
+
width: 28px;
|
|
1215
|
+
height: 28px;
|
|
1216
|
+
border-radius: 8px;
|
|
1217
|
+
display: inline-flex;
|
|
1218
|
+
align-items: center;
|
|
1219
|
+
justify-content: center;
|
|
1220
|
+
cursor: pointer;
|
|
1221
|
+
flex-shrink: 0;
|
|
1222
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1111
1225
|
.session-item-copy:hover {
|
|
1112
1226
|
border-color: rgba(70, 86, 110, 0.7);
|
|
1113
1227
|
background: rgba(70, 86, 110, 0.16);
|
|
@@ -1115,17 +1229,35 @@
|
|
|
1115
1229
|
transform: translateY(-1px);
|
|
1116
1230
|
}
|
|
1117
1231
|
|
|
1232
|
+
.session-item-delete:hover {
|
|
1233
|
+
border-color: rgba(189, 70, 68, 0.7);
|
|
1234
|
+
background: rgba(189, 70, 68, 0.18);
|
|
1235
|
+
color: #9f3b3b;
|
|
1236
|
+
transform: translateY(-1px);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1118
1239
|
.session-item-copy:disabled {
|
|
1119
1240
|
opacity: 0.5;
|
|
1120
1241
|
cursor: not-allowed;
|
|
1121
1242
|
transform: none;
|
|
1122
1243
|
}
|
|
1123
1244
|
|
|
1245
|
+
.session-item-delete:disabled {
|
|
1246
|
+
opacity: 0.5;
|
|
1247
|
+
cursor: not-allowed;
|
|
1248
|
+
transform: none;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1124
1251
|
.session-item-copy svg {
|
|
1125
1252
|
width: 16px;
|
|
1126
1253
|
height: 16px;
|
|
1127
1254
|
}
|
|
1128
1255
|
|
|
1256
|
+
.session-item-delete svg {
|
|
1257
|
+
width: 16px;
|
|
1258
|
+
height: 16px;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1129
1261
|
.session-item-sub {
|
|
1130
1262
|
font-size: var(--font-size-caption);
|
|
1131
1263
|
color: var(--color-text-tertiary);
|
|
@@ -1461,6 +1593,9 @@
|
|
|
1461
1593
|
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.98) 100%);
|
|
1462
1594
|
width: 90%;
|
|
1463
1595
|
max-width: 400px;
|
|
1596
|
+
max-height: 90vh;
|
|
1597
|
+
overflow-y: auto;
|
|
1598
|
+
overscroll-behavior: contain;
|
|
1464
1599
|
border-radius: var(--radius-lg);
|
|
1465
1600
|
padding: var(--spacing-md);
|
|
1466
1601
|
box-shadow: var(--shadow-modal);
|
|
@@ -1480,6 +1615,25 @@
|
|
|
1480
1615
|
letter-spacing: -0.01em;
|
|
1481
1616
|
}
|
|
1482
1617
|
|
|
1618
|
+
.modal-header {
|
|
1619
|
+
display: flex;
|
|
1620
|
+
align-items: center;
|
|
1621
|
+
justify-content: space-between;
|
|
1622
|
+
gap: var(--spacing-sm);
|
|
1623
|
+
margin-bottom: var(--spacing-md);
|
|
1624
|
+
flex-wrap: wrap;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
.modal-header .modal-title {
|
|
1628
|
+
margin-bottom: 0;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.btn-modal-copy {
|
|
1632
|
+
padding: 6px 12px;
|
|
1633
|
+
white-space: nowrap;
|
|
1634
|
+
flex-shrink: 0;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1483
1637
|
.form-group {
|
|
1484
1638
|
margin-bottom: var(--spacing-sm);
|
|
1485
1639
|
}
|
|
@@ -1555,133 +1709,407 @@
|
|
|
1555
1709
|
opacity: 0.8;
|
|
1556
1710
|
}
|
|
1557
1711
|
|
|
1558
|
-
.
|
|
1559
|
-
display: flex;
|
|
1560
|
-
gap: var(--spacing-sm);
|
|
1712
|
+
.quick-section {
|
|
1561
1713
|
margin-top: var(--spacing-md);
|
|
1714
|
+
padding: var(--spacing-sm);
|
|
1715
|
+
border-radius: var(--radius-lg);
|
|
1716
|
+
border: 1px solid var(--color-border-soft);
|
|
1717
|
+
background: linear-gradient(140deg, rgba(255, 252, 247, 0.95), rgba(255, 255, 255, 0.6));
|
|
1562
1718
|
}
|
|
1563
1719
|
|
|
1564
|
-
.
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1720
|
+
.quick-header {
|
|
1721
|
+
display: flex;
|
|
1722
|
+
flex-wrap: wrap;
|
|
1723
|
+
gap: var(--spacing-xs);
|
|
1724
|
+
align-items: flex-start;
|
|
1725
|
+
justify-content: space-between;
|
|
1726
|
+
margin-bottom: var(--spacing-sm);
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
.quick-title {
|
|
1730
|
+
font-size: var(--font-size-secondary);
|
|
1569
1731
|
font-weight: var(--font-weight-secondary);
|
|
1570
|
-
cursor: pointer;
|
|
1571
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
1572
|
-
border: 1px solid var(--color-border-soft);
|
|
1573
|
-
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.95) 100%);
|
|
1574
1732
|
color: var(--color-text-secondary);
|
|
1575
|
-
box-shadow: var(--shadow-subtle);
|
|
1576
|
-
letter-spacing: -0.01em;
|
|
1577
1733
|
}
|
|
1578
1734
|
|
|
1579
|
-
.
|
|
1580
|
-
|
|
1735
|
+
.quick-actions {
|
|
1736
|
+
display: flex;
|
|
1737
|
+
flex-wrap: wrap;
|
|
1738
|
+
gap: var(--spacing-xs);
|
|
1581
1739
|
}
|
|
1582
1740
|
|
|
1583
|
-
.
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1741
|
+
.quick-steps {
|
|
1742
|
+
display: flex;
|
|
1743
|
+
flex-wrap: wrap;
|
|
1744
|
+
gap: var(--spacing-xs);
|
|
1745
|
+
margin-bottom: var(--spacing-sm);
|
|
1587
1746
|
}
|
|
1588
1747
|
|
|
1589
|
-
.
|
|
1590
|
-
|
|
1748
|
+
.quick-step {
|
|
1749
|
+
display: inline-flex;
|
|
1750
|
+
align-items: center;
|
|
1751
|
+
gap: 6px;
|
|
1752
|
+
padding: 4px 10px;
|
|
1753
|
+
border-radius: 999px;
|
|
1754
|
+
border: 1px dashed var(--color-border-soft);
|
|
1755
|
+
background: var(--color-surface);
|
|
1756
|
+
font-size: var(--font-size-caption);
|
|
1757
|
+
color: var(--color-text-secondary);
|
|
1591
1758
|
}
|
|
1592
1759
|
|
|
1593
|
-
.
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1760
|
+
.step-badge {
|
|
1761
|
+
width: 20px;
|
|
1762
|
+
height: 20px;
|
|
1763
|
+
border-radius: 999px;
|
|
1764
|
+
display: inline-flex;
|
|
1765
|
+
align-items: center;
|
|
1766
|
+
justify-content: center;
|
|
1767
|
+
background: var(--color-brand);
|
|
1768
|
+
color: #fff;
|
|
1769
|
+
font-size: 12px;
|
|
1770
|
+
font-weight: var(--font-weight-secondary);
|
|
1598
1771
|
}
|
|
1599
1772
|
|
|
1600
|
-
.
|
|
1601
|
-
|
|
1602
|
-
|
|
1773
|
+
.quick-grid {
|
|
1774
|
+
display: grid;
|
|
1775
|
+
gap: var(--spacing-sm);
|
|
1776
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
1603
1777
|
}
|
|
1604
1778
|
|
|
1605
|
-
.
|
|
1606
|
-
background:
|
|
1607
|
-
|
|
1608
|
-
border:
|
|
1779
|
+
.quick-card {
|
|
1780
|
+
background: var(--color-surface);
|
|
1781
|
+
border: 1px solid var(--color-border-soft);
|
|
1782
|
+
border-radius: var(--radius-sm);
|
|
1783
|
+
padding: var(--spacing-sm);
|
|
1784
|
+
box-shadow: var(--shadow-subtle);
|
|
1609
1785
|
}
|
|
1610
1786
|
|
|
1611
|
-
.
|
|
1612
|
-
|
|
1613
|
-
|
|
1787
|
+
.quick-option {
|
|
1788
|
+
display: flex;
|
|
1789
|
+
align-items: center;
|
|
1790
|
+
gap: 8px;
|
|
1791
|
+
font-size: var(--font-size-caption);
|
|
1792
|
+
color: var(--color-text-secondary);
|
|
1793
|
+
margin-bottom: 6px;
|
|
1614
1794
|
}
|
|
1615
1795
|
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
============================================ */
|
|
1619
|
-
.model-list {
|
|
1620
|
-
max-height: 200px;
|
|
1621
|
-
overflow-y: auto;
|
|
1622
|
-
border: 1px solid rgba(208, 196, 182, 0.4);
|
|
1623
|
-
border-radius: var(--radius-sm);
|
|
1624
|
-
margin-bottom: var(--spacing-sm);
|
|
1625
|
-
scrollbar-width: none;
|
|
1626
|
-
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.8) 100%);
|
|
1627
|
-
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
1796
|
+
.quick-option input {
|
|
1797
|
+
accent-color: var(--color-brand);
|
|
1628
1798
|
}
|
|
1629
1799
|
|
|
1630
|
-
.
|
|
1631
|
-
|
|
1800
|
+
.structured-section {
|
|
1801
|
+
margin-top: var(--spacing-md);
|
|
1802
|
+
padding: var(--spacing-sm);
|
|
1803
|
+
border-radius: var(--radius-lg);
|
|
1804
|
+
border: 1px solid var(--color-border-soft);
|
|
1805
|
+
background: rgba(255, 255, 255, 0.6);
|
|
1632
1806
|
}
|
|
1633
1807
|
|
|
1634
|
-
.
|
|
1808
|
+
.structured-header {
|
|
1635
1809
|
display: flex;
|
|
1810
|
+
flex-wrap: wrap;
|
|
1811
|
+
gap: var(--spacing-xs);
|
|
1812
|
+
align-items: baseline;
|
|
1636
1813
|
justify-content: space-between;
|
|
1637
|
-
|
|
1638
|
-
padding: 11px var(--spacing-sm);
|
|
1639
|
-
border-bottom: 1px solid rgba(208, 196, 182, 0.3);
|
|
1640
|
-
font-size: var(--font-size-body);
|
|
1641
|
-
color: var(--color-text-primary);
|
|
1642
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
1643
|
-
letter-spacing: -0.005em;
|
|
1814
|
+
margin-bottom: var(--spacing-sm);
|
|
1644
1815
|
}
|
|
1645
1816
|
|
|
1646
|
-
.
|
|
1647
|
-
|
|
1817
|
+
.structured-title {
|
|
1818
|
+
font-size: var(--font-size-secondary);
|
|
1819
|
+
font-weight: var(--font-weight-secondary);
|
|
1820
|
+
color: var(--color-text-secondary);
|
|
1648
1821
|
}
|
|
1649
1822
|
|
|
1650
|
-
.
|
|
1651
|
-
|
|
1823
|
+
.structured-grid {
|
|
1824
|
+
display: grid;
|
|
1825
|
+
gap: var(--spacing-sm);
|
|
1826
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
1652
1827
|
}
|
|
1653
1828
|
|
|
1654
|
-
.
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
border-radius: var(--radius-full);
|
|
1661
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
1662
|
-
background: transparent;
|
|
1663
|
-
border: 1px solid rgba(139, 118, 104, 0.2);
|
|
1664
|
-
letter-spacing: 0.03em;
|
|
1829
|
+
.structured-card {
|
|
1830
|
+
background: var(--color-surface);
|
|
1831
|
+
border: 1px solid var(--color-border-soft);
|
|
1832
|
+
border-radius: var(--radius-sm);
|
|
1833
|
+
padding: var(--spacing-sm);
|
|
1834
|
+
box-shadow: var(--shadow-subtle);
|
|
1665
1835
|
}
|
|
1666
1836
|
|
|
1667
|
-
.
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
border-color: transparent;
|
|
1837
|
+
.structured-card-title {
|
|
1838
|
+
font-size: var(--font-size-body);
|
|
1839
|
+
font-weight: var(--font-weight-secondary);
|
|
1840
|
+
color: var(--color-text-secondary);
|
|
1841
|
+
margin-bottom: 8px;
|
|
1673
1842
|
}
|
|
1674
1843
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
padding:
|
|
1844
|
+
.provider-list {
|
|
1845
|
+
display: flex;
|
|
1846
|
+
flex-direction: column;
|
|
1847
|
+
gap: var(--spacing-xs);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
.provider-item {
|
|
1851
|
+
border: 1px dashed var(--color-border-soft);
|
|
1852
|
+
border-radius: var(--radius-sm);
|
|
1853
|
+
padding: var(--spacing-xs);
|
|
1854
|
+
background: var(--color-surface-alt);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
.provider-header {
|
|
1858
|
+
display: flex;
|
|
1859
|
+
flex-wrap: wrap;
|
|
1860
|
+
gap: var(--spacing-xs);
|
|
1861
|
+
align-items: center;
|
|
1862
|
+
margin-bottom: 6px;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
.provider-name {
|
|
1866
|
+
font-weight: var(--font-weight-secondary);
|
|
1867
|
+
color: var(--color-text-secondary);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
.provider-source {
|
|
1871
|
+
font-size: var(--font-size-caption);
|
|
1872
|
+
color: var(--color-text-tertiary);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
.provider-fields {
|
|
1876
|
+
display: grid;
|
|
1877
|
+
gap: 6px;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
.provider-field {
|
|
1881
|
+
display: flex;
|
|
1882
|
+
flex-wrap: wrap;
|
|
1883
|
+
gap: 6px;
|
|
1884
|
+
align-items: baseline;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
.provider-field-key {
|
|
1888
|
+
font-family: var(--font-family-mono);
|
|
1889
|
+
font-size: var(--font-size-caption);
|
|
1890
|
+
color: var(--color-text-muted);
|
|
1891
|
+
min-width: 110px;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.provider-field-value {
|
|
1895
|
+
font-family: var(--font-family-mono);
|
|
1896
|
+
font-size: var(--font-size-caption);
|
|
1897
|
+
color: var(--color-text-secondary);
|
|
1898
|
+
word-break: break-all;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
.agent-list {
|
|
1902
|
+
display: flex;
|
|
1903
|
+
flex-direction: column;
|
|
1904
|
+
gap: var(--spacing-xs);
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
.agent-item {
|
|
1908
|
+
border: 1px dashed var(--color-border-soft);
|
|
1909
|
+
border-radius: var(--radius-sm);
|
|
1910
|
+
padding: var(--spacing-xs);
|
|
1911
|
+
background: var(--color-surface-alt);
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
.agent-header {
|
|
1915
|
+
display: flex;
|
|
1916
|
+
flex-wrap: wrap;
|
|
1917
|
+
gap: var(--spacing-xs);
|
|
1918
|
+
align-items: center;
|
|
1919
|
+
margin-bottom: 6px;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
.agent-name {
|
|
1923
|
+
font-weight: var(--font-weight-secondary);
|
|
1924
|
+
color: var(--color-text-secondary);
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
.agent-id {
|
|
1928
|
+
font-size: var(--font-size-caption);
|
|
1929
|
+
color: var(--color-text-tertiary);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
.agent-meta {
|
|
1933
|
+
display: flex;
|
|
1934
|
+
flex-wrap: wrap;
|
|
1935
|
+
gap: 8px;
|
|
1936
|
+
font-size: var(--font-size-caption);
|
|
1937
|
+
color: var(--color-text-secondary);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
.list-row {
|
|
1941
|
+
display: flex;
|
|
1942
|
+
flex-wrap: wrap;
|
|
1943
|
+
gap: var(--spacing-xs);
|
|
1944
|
+
align-items: center;
|
|
1945
|
+
margin-bottom: var(--spacing-xs);
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
.list-row:last-child {
|
|
1949
|
+
margin-bottom: 0;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
.list-row .form-input {
|
|
1953
|
+
flex: 1;
|
|
1954
|
+
min-width: 140px;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
.btn-mini {
|
|
1958
|
+
padding: 6px 10px;
|
|
1959
|
+
border-radius: var(--radius-sm);
|
|
1960
|
+
border: 1px solid var(--color-border-soft);
|
|
1961
|
+
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.85) 100%);
|
|
1962
|
+
font-size: var(--font-size-caption);
|
|
1963
|
+
font-weight: var(--font-weight-secondary);
|
|
1964
|
+
color: var(--color-text-secondary);
|
|
1965
|
+
cursor: pointer;
|
|
1966
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
1967
|
+
box-shadow: var(--shadow-subtle);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
.btn-mini:hover {
|
|
1971
|
+
border-color: var(--color-brand);
|
|
1972
|
+
color: var(--color-brand);
|
|
1973
|
+
transform: translateY(-1px);
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
.btn-mini.delete {
|
|
1977
|
+
color: var(--color-error);
|
|
1978
|
+
border-color: rgba(193, 72, 59, 0.35);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
.btn-mini.delete:hover {
|
|
1982
|
+
border-color: rgba(193, 72, 59, 0.7);
|
|
1983
|
+
color: var(--color-error);
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
.btn-group {
|
|
1987
|
+
display: flex;
|
|
1988
|
+
gap: var(--spacing-sm);
|
|
1989
|
+
margin-top: var(--spacing-md);
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
.btn {
|
|
1993
|
+
flex: 1;
|
|
1994
|
+
padding: 14px var(--spacing-sm);
|
|
1995
|
+
border-radius: var(--radius-sm);
|
|
1996
|
+
font-size: var(--font-size-body);
|
|
1997
|
+
font-weight: var(--font-weight-secondary);
|
|
1998
|
+
cursor: pointer;
|
|
1999
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
2000
|
+
border: 1px solid var(--color-border-soft);
|
|
2001
|
+
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.95) 100%);
|
|
2002
|
+
color: var(--color-text-secondary);
|
|
2003
|
+
box-shadow: var(--shadow-subtle);
|
|
2004
|
+
letter-spacing: -0.01em;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
.btn:active {
|
|
2008
|
+
transform: scale(0.97);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
.btn-cancel {
|
|
2012
|
+
background: linear-gradient(to bottom, var(--color-bg) 0%, rgba(247, 241, 232, 0.8) 100%);
|
|
2013
|
+
color: var(--color-text-primary);
|
|
2014
|
+
border: 1px solid var(--color-border-soft);
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
.btn-cancel:hover {
|
|
2018
|
+
background: linear-gradient(to bottom, var(--color-border) 0%, rgba(208, 196, 182, 0.5) 100%);
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
.btn-confirm {
|
|
2022
|
+
background: linear-gradient(135deg, var(--color-brand) 0%, var(--color-brand-dark) 100%);
|
|
2023
|
+
color: white;
|
|
2024
|
+
box-shadow: 0 2px 4px rgba(210, 107, 90, 0.2);
|
|
2025
|
+
border: none;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
.btn-confirm:hover {
|
|
2029
|
+
box-shadow: 0 4px 8px rgba(210, 107, 90, 0.25);
|
|
2030
|
+
filter: brightness(1.05);
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
.btn-confirm.secondary {
|
|
2034
|
+
background: linear-gradient(135deg, var(--color-success) 0%, rgba(90, 139, 106, 0.85) 100%);
|
|
2035
|
+
box-shadow: 0 2px 4px rgba(90, 139, 106, 0.2);
|
|
2036
|
+
border: none;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
.btn-confirm.secondary:hover {
|
|
2040
|
+
box-shadow: 0 4px 8px rgba(90, 139, 106, 0.25);
|
|
2041
|
+
filter: brightness(1.05);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
/* ============================================
|
|
2045
|
+
模型列表
|
|
2046
|
+
============================================ */
|
|
2047
|
+
.model-list {
|
|
2048
|
+
max-height: 200px;
|
|
2049
|
+
overflow-y: auto;
|
|
2050
|
+
border: 1px solid rgba(208, 196, 182, 0.4);
|
|
2051
|
+
border-radius: var(--radius-sm);
|
|
2052
|
+
margin-bottom: var(--spacing-sm);
|
|
2053
|
+
scrollbar-width: none;
|
|
2054
|
+
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.8) 100%);
|
|
2055
|
+
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.02);
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
.model-list::-webkit-scrollbar {
|
|
2059
|
+
display: none;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
.model-item {
|
|
2063
|
+
display: flex;
|
|
2064
|
+
justify-content: space-between;
|
|
2065
|
+
align-items: center;
|
|
2066
|
+
padding: 11px var(--spacing-sm);
|
|
2067
|
+
border-bottom: 1px solid rgba(208, 196, 182, 0.3);
|
|
2068
|
+
font-size: var(--font-size-body);
|
|
2069
|
+
color: var(--color-text-primary);
|
|
2070
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
2071
|
+
letter-spacing: -0.005em;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
.model-item:last-child {
|
|
2075
|
+
border-bottom: none;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
.model-item:hover {
|
|
2079
|
+
background: linear-gradient(to right, rgba(247, 241, 232, 0.6) 0%, rgba(247, 241, 232, 0.3) 100%);
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
.btn-remove-model {
|
|
2083
|
+
font-size: var(--font-size-caption);
|
|
2084
|
+
font-weight: var(--font-weight-caption);
|
|
2085
|
+
color: var(--color-text-tertiary);
|
|
2086
|
+
cursor: pointer;
|
|
2087
|
+
padding: 5px 10px;
|
|
2088
|
+
border-radius: var(--radius-full);
|
|
2089
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
2090
|
+
background: transparent;
|
|
2091
|
+
border: 1px solid rgba(139, 118, 104, 0.2);
|
|
2092
|
+
letter-spacing: 0.03em;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
.btn-remove-model:hover {
|
|
2096
|
+
background: linear-gradient(135deg, var(--color-error) 0%, rgba(200, 74, 58, 0.9) 100%);
|
|
2097
|
+
color: white;
|
|
2098
|
+
transform: scale(1.08);
|
|
2099
|
+
box-shadow: 0 2px 6px rgba(200, 74, 58, 0.25);
|
|
2100
|
+
border-color: transparent;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
/* ============================================
|
|
2104
|
+
Toast - 顶部横幅
|
|
2105
|
+
============================================ */
|
|
2106
|
+
.toast {
|
|
2107
|
+
position: fixed;
|
|
2108
|
+
top: 16px;
|
|
2109
|
+
left: 50%;
|
|
2110
|
+
transform: translateX(-50%);
|
|
2111
|
+
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.95) 100%);
|
|
2112
|
+
padding: 12px 24px;
|
|
1685
2113
|
border-radius: var(--radius-full);
|
|
1686
2114
|
box-shadow: var(--shadow-raised);
|
|
1687
2115
|
z-index: 200;
|
|
@@ -1813,14 +2241,14 @@
|
|
|
1813
2241
|
<body>
|
|
1814
2242
|
<div id="app" class="container" v-cloak>
|
|
1815
2243
|
<!-- 主标题 -->
|
|
1816
|
-
<h1 class="main-title">
|
|
2244
|
+
<h1 v-if="!sessionStandalone" class="main-title">
|
|
1817
2245
|
Codex<br>
|
|
1818
2246
|
<span class="accent">Mate.</span>
|
|
1819
2247
|
</h1>
|
|
1820
|
-
<p class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</p>
|
|
2248
|
+
<p v-if="!sessionStandalone" class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</p>
|
|
1821
2249
|
|
|
1822
2250
|
<!-- 模式切换器 -->
|
|
1823
|
-
<div class="segmented-control">
|
|
2251
|
+
<div v-if="!sessionStandalone" class="segmented-control">
|
|
1824
2252
|
<button :class="['segment', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
|
|
1825
2253
|
Codex 配置
|
|
1826
2254
|
</button>
|
|
@@ -1874,7 +2302,7 @@
|
|
|
1874
2302
|
placeholder="例如: gpt-5.3-codex"
|
|
1875
2303
|
>
|
|
1876
2304
|
<div class="config-template-hint" v-if="modelsSource === 'unlimited'">
|
|
1877
|
-
|
|
2305
|
+
当前提供商未提供模型列表,视为不限。模型可手动输入。
|
|
1878
2306
|
</div>
|
|
1879
2307
|
<div class="config-template-hint" v-if="modelsSource === 'error'">
|
|
1880
2308
|
模型列表获取失败,请检查接口或手动输入。
|
|
@@ -1927,30 +2355,9 @@
|
|
|
1927
2355
|
<div class="selector-header">
|
|
1928
2356
|
<span class="selector-title">配置健康检查</span>
|
|
1929
2357
|
</div>
|
|
1930
|
-
<div class="config-template-hint">
|
|
1931
|
-
检测 base_url、API Key 与模型可用性(可选远程探测会发起真实请求,可能产生费用)。
|
|
1932
|
-
</div>
|
|
1933
|
-
<div class="config-template-hint">
|
|
1934
|
-
<label class="health-remote-toggle">
|
|
1935
|
-
<input type="checkbox" v-model="healthCheckRemote">
|
|
1936
|
-
启用远程探测(请求 base_url 与 /v1/models)
|
|
1937
|
-
</label>
|
|
1938
|
-
</div>
|
|
1939
2358
|
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
|
|
1940
2359
|
{{ healthCheckLoading ? '检查中...' : '运行检查' }}
|
|
1941
2360
|
</button>
|
|
1942
|
-
<div v-if="healthCheckResult" class="health-report">
|
|
1943
|
-
<div v-if="healthCheckResult.ok" class="health-ok">未发现问题</div>
|
|
1944
|
-
<div v-else>
|
|
1945
|
-
<div
|
|
1946
|
-
v-for="(issue, index) in healthCheckResult.issues"
|
|
1947
|
-
:key="(issue.code || 'issue') + index"
|
|
1948
|
-
class="health-issue">
|
|
1949
|
-
<div class="health-issue-title">{{ issue.message }}</div>
|
|
1950
|
-
<div v-if="issue.suggestion" class="health-issue-suggestion">{{ issue.suggestion }}</div>
|
|
1951
|
-
</div>
|
|
1952
|
-
</div>
|
|
1953
|
-
</div>
|
|
1954
2361
|
</div>
|
|
1955
2362
|
|
|
1956
2363
|
<div v-if="!loading && !initError" class="card-list">
|
|
@@ -2067,7 +2474,7 @@
|
|
|
2067
2474
|
添加 OpenClaw 配置
|
|
2068
2475
|
</button>
|
|
2069
2476
|
<div class="config-template-hint">
|
|
2070
|
-
默认应用到 <code>~/.openclaw/openclaw.json</code
|
|
2477
|
+
默认应用到 <code>~/.openclaw/openclaw.json</code>。支持 JSON5(注释/尾逗号)。
|
|
2071
2478
|
</div>
|
|
2072
2479
|
|
|
2073
2480
|
<div class="selector-section">
|
|
@@ -2082,6 +2489,22 @@
|
|
|
2082
2489
|
</button>
|
|
2083
2490
|
</div>
|
|
2084
2491
|
|
|
2492
|
+
<div class="selector-section">
|
|
2493
|
+
<div class="selector-header">
|
|
2494
|
+
<span class="selector-title">工作区文件</span>
|
|
2495
|
+
</div>
|
|
2496
|
+
<input
|
|
2497
|
+
class="form-input"
|
|
2498
|
+
v-model="openclawWorkspaceFileName"
|
|
2499
|
+
placeholder="例如: SOUL.md">
|
|
2500
|
+
<div class="config-template-hint">
|
|
2501
|
+
仅支持 OpenClaw Workspace 内的 <code>.md</code> 文件。
|
|
2502
|
+
</div>
|
|
2503
|
+
<button class="btn-tool" @click="openOpenclawWorkspaceEditor" :disabled="loading || !!initError || agentsLoading">
|
|
2504
|
+
{{ agentsLoading ? '加载中...' : '打开工作区文件' }}
|
|
2505
|
+
</button>
|
|
2506
|
+
</div>
|
|
2507
|
+
|
|
2085
2508
|
<div class="card-list">
|
|
2086
2509
|
<div v-for="(config, name) in openclawConfigs" :key="name"
|
|
2087
2510
|
:class="['card', { active: currentOpenclawConfig === name }]"
|
|
@@ -2116,12 +2539,29 @@
|
|
|
2116
2539
|
</div>
|
|
2117
2540
|
</div>
|
|
2118
2541
|
|
|
2119
|
-
<!-- 会话浏览模式 -->
|
|
2120
|
-
<div v-show="configMode === 'sessions'" class="mode-content">
|
|
2121
|
-
<div class="
|
|
2122
|
-
<div class="
|
|
2123
|
-
|
|
2124
|
-
</div>
|
|
2542
|
+
<!-- 会话浏览模式 -->
|
|
2543
|
+
<div v-show="configMode === 'sessions'" class="mode-content">
|
|
2544
|
+
<div v-if="sessionStandalone" class="session-standalone-page">
|
|
2545
|
+
<div v-if="sessionStandaloneLoading" class="state-message">
|
|
2546
|
+
加载中...
|
|
2547
|
+
</div>
|
|
2548
|
+
<div v-else-if="sessionStandaloneError" class="state-message error">
|
|
2549
|
+
{{ sessionStandaloneError }}
|
|
2550
|
+
</div>
|
|
2551
|
+
<div v-else>
|
|
2552
|
+
<div class="session-standalone-title">
|
|
2553
|
+
{{ sessionStandaloneTitle }}
|
|
2554
|
+
<span v-if="sessionStandaloneSourceLabel"> · {{ sessionStandaloneSourceLabel }}</span>
|
|
2555
|
+
</div>
|
|
2556
|
+
<pre class="session-standalone-text">{{ sessionStandaloneText }}</pre>
|
|
2557
|
+
</div>
|
|
2558
|
+
</div>
|
|
2559
|
+
|
|
2560
|
+
<div v-else>
|
|
2561
|
+
<div v-if="!sessionStandalone" class="selector-section">
|
|
2562
|
+
<div class="selector-header">
|
|
2563
|
+
<span class="selector-title">会话来源</span>
|
|
2564
|
+
</div>
|
|
2125
2565
|
<div class="session-toolbar">
|
|
2126
2566
|
<div class="session-toolbar-group">
|
|
2127
2567
|
<select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange" :disabled="sessionsLoading">
|
|
@@ -2143,8 +2583,8 @@
|
|
|
2143
2583
|
class="session-query-input"
|
|
2144
2584
|
v-model="sessionQuery"
|
|
2145
2585
|
@keyup.enter="loadSessions"
|
|
2146
|
-
disabled
|
|
2147
|
-
placeholder="
|
|
2586
|
+
:disabled="sessionsLoading || !isSessionQueryEnabled"
|
|
2587
|
+
:placeholder="sessionQueryPlaceholder">
|
|
2148
2588
|
</div>
|
|
2149
2589
|
<div class="session-toolbar-group">
|
|
2150
2590
|
<select
|
|
@@ -2178,21 +2618,22 @@
|
|
|
2178
2618
|
</div>
|
|
2179
2619
|
</div>
|
|
2180
2620
|
<div class="session-hint">
|
|
2181
|
-
|
|
2621
|
+
关键词检索仅 Codex 可用;<br>
|
|
2622
|
+
角色/时间筛选暂不可用;<br>
|
|
2182
2623
|
仅支持来源与路径筛选,右侧仅查看/导出。
|
|
2183
2624
|
</div>
|
|
2184
2625
|
</div>
|
|
2185
2626
|
|
|
2186
|
-
<div v-if="sessionsLoading" class="state-message">
|
|
2627
|
+
<div v-if="!sessionStandalone && sessionsLoading" class="state-message">
|
|
2187
2628
|
会话加载中...
|
|
2188
2629
|
</div>
|
|
2189
2630
|
|
|
2190
|
-
<div v-else-if="sessionsList.length === 0" class="session-empty">
|
|
2631
|
+
<div v-else-if="!sessionStandalone && sessionsList.length === 0" class="session-empty">
|
|
2191
2632
|
暂无可用会话记录
|
|
2192
2633
|
</div>
|
|
2193
2634
|
|
|
2194
|
-
<div v-else class="session-layout">
|
|
2195
|
-
<div class="session-list">
|
|
2635
|
+
<div v-else :class="['session-layout', { 'session-standalone': sessionStandalone }]">
|
|
2636
|
+
<div v-if="!sessionStandalone" class="session-list">
|
|
2196
2637
|
<div
|
|
2197
2638
|
v-for="session in sessionsList"
|
|
2198
2639
|
:key="session.source + '-' + session.sessionId + '-' + session.filePath"
|
|
@@ -2220,6 +2661,21 @@
|
|
|
2220
2661
|
<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"></path>
|
|
2221
2662
|
</svg>
|
|
2222
2663
|
</button>
|
|
2664
|
+
<button
|
|
2665
|
+
v-if="isDeleteAvailable(session)"
|
|
2666
|
+
class="session-item-delete"
|
|
2667
|
+
@click.stop="deleteSession(session)"
|
|
2668
|
+
:disabled="sessionsLoading || sessionDeleting[getSessionExportKey(session)]"
|
|
2669
|
+
aria-label="删除会话"
|
|
2670
|
+
title="删除会话">
|
|
2671
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2672
|
+
<path d="M3 6h18"></path>
|
|
2673
|
+
<path d="M8 6V4h8v2"></path>
|
|
2674
|
+
<path d="M19 6l-1 14H6L5 6"></path>
|
|
2675
|
+
<path d="M10 11v6"></path>
|
|
2676
|
+
<path d="M14 11v6"></path>
|
|
2677
|
+
</svg>
|
|
2678
|
+
</button>
|
|
2223
2679
|
</div>
|
|
2224
2680
|
</div>
|
|
2225
2681
|
<div class="session-item-meta">
|
|
@@ -2248,16 +2704,37 @@
|
|
|
2248
2704
|
<span class="session-preview-meta-item">{{ activeSession.cwd }}</span>
|
|
2249
2705
|
</div>
|
|
2250
2706
|
</div>
|
|
2251
|
-
<div class="session-actions">
|
|
2707
|
+
<div v-if="!sessionStandalone" class="session-actions">
|
|
2252
2708
|
<button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession">
|
|
2253
2709
|
{{ sessionDetailLoading ? '加载中...' : '刷新内容' }}
|
|
2254
2710
|
</button>
|
|
2711
|
+
<button
|
|
2712
|
+
v-if="isCloneAvailable(activeSession)"
|
|
2713
|
+
class="btn-session-clone"
|
|
2714
|
+
@click="cloneSession(activeSession)"
|
|
2715
|
+
:disabled="!activeSession || sessionsLoading || sessionCloning[getSessionExportKey(activeSession)]">
|
|
2716
|
+
{{ (activeSession && sessionCloning[getSessionExportKey(activeSession)]) ? '克隆中...' : '克隆会话' }}
|
|
2717
|
+
</button>
|
|
2718
|
+
<button
|
|
2719
|
+
v-if="isDeleteAvailable(activeSession)"
|
|
2720
|
+
class="btn-session-delete"
|
|
2721
|
+
@click="deleteSession(activeSession)"
|
|
2722
|
+
:disabled="!activeSession || sessionsLoading || sessionDeleting[getSessionExportKey(activeSession)]">
|
|
2723
|
+
{{ (activeSession && sessionDeleting[getSessionExportKey(activeSession)]) ? '删除中...' : '删除会话' }}
|
|
2724
|
+
</button>
|
|
2255
2725
|
<button
|
|
2256
2726
|
class="btn-session-export"
|
|
2257
2727
|
@click="exportSession(activeSession)"
|
|
2258
2728
|
:disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]">
|
|
2259
2729
|
{{ (activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? '导出中...' : '导出记录' }}
|
|
2260
2730
|
</button>
|
|
2731
|
+
<button
|
|
2732
|
+
v-if="!sessionStandalone"
|
|
2733
|
+
class="btn-session-open"
|
|
2734
|
+
@click="openSessionStandalone(activeSession)"
|
|
2735
|
+
:disabled="!activeSession">
|
|
2736
|
+
新页查看
|
|
2737
|
+
</button>
|
|
2261
2738
|
</div>
|
|
2262
2739
|
</div>
|
|
2263
2740
|
|
|
@@ -2293,12 +2770,14 @@
|
|
|
2293
2770
|
</div>
|
|
2294
2771
|
</template>
|
|
2295
2772
|
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
</div>
|
|
2300
|
-
</div>
|
|
2301
|
-
|
|
2773
|
+
<div v-else class="session-preview-empty">
|
|
2774
|
+
<span v-if="sessionStandaloneError">{{ sessionStandaloneError }}</span>
|
|
2775
|
+
<span v-else>请先在左侧选择一个会话</span>
|
|
2776
|
+
</div>
|
|
2777
|
+
</div>
|
|
2778
|
+
</div>
|
|
2779
|
+
</div>
|
|
2780
|
+
</div>
|
|
2302
2781
|
|
|
2303
2782
|
<!-- 加载状态 -->
|
|
2304
2783
|
<div v-if="loading" class="state-message">
|
|
@@ -2471,6 +2950,233 @@
|
|
|
2471
2950
|
</div>
|
|
2472
2951
|
</div>
|
|
2473
2952
|
|
|
2953
|
+
<div class="quick-section">
|
|
2954
|
+
<div class="quick-header">
|
|
2955
|
+
<div>
|
|
2956
|
+
<div class="quick-title">新手快速配置</div>
|
|
2957
|
+
<div class="form-hint">按 3 步完成:填 Provider 和模型,写入编辑器,保存并应用。</div>
|
|
2958
|
+
</div>
|
|
2959
|
+
<div class="quick-actions">
|
|
2960
|
+
<button class="btn-mini" @click="syncOpenclawQuickFromText">从编辑器读取</button>
|
|
2961
|
+
<button class="btn-mini" @click="resetOpenclawQuick">清空</button>
|
|
2962
|
+
</div>
|
|
2963
|
+
</div>
|
|
2964
|
+
<div class="quick-steps">
|
|
2965
|
+
<div class="quick-step"><span class="step-badge">1</span><span>填写 Provider 与模型</span></div>
|
|
2966
|
+
<div class="quick-step"><span class="step-badge">2</span><span>点击写入编辑器</span></div>
|
|
2967
|
+
<div class="quick-step"><span class="step-badge">3</span><span>保存并应用</span></div>
|
|
2968
|
+
</div>
|
|
2969
|
+
<div class="quick-grid">
|
|
2970
|
+
<div class="quick-card">
|
|
2971
|
+
<div class="structured-card-title">Provider</div>
|
|
2972
|
+
<div class="form-group">
|
|
2973
|
+
<label class="form-label">Provider 名称</label>
|
|
2974
|
+
<input v-model="openclawQuick.providerName" class="form-input" placeholder="例如: custom-myapi">
|
|
2975
|
+
<div class="form-hint">会拼成 provider/model 作为主模型标识。</div>
|
|
2976
|
+
</div>
|
|
2977
|
+
<div class="form-group">
|
|
2978
|
+
<label class="form-label">Base URL</label>
|
|
2979
|
+
<input v-model="openclawQuick.baseUrl" class="form-input" placeholder="https://api.example.com/v1">
|
|
2980
|
+
</div>
|
|
2981
|
+
<div class="form-group">
|
|
2982
|
+
<label class="form-label">API Key</label>
|
|
2983
|
+
<div class="list-row">
|
|
2984
|
+
<input v-model="openclawQuick.apiKey" class="form-input" :type="openclawQuick.showKey ? 'text' : 'password'" placeholder="sk-...">
|
|
2985
|
+
<button class="btn-mini" @click="toggleOpenclawQuickKey">
|
|
2986
|
+
{{ openclawQuick.showKey ? '隐藏' : '显示' }}
|
|
2987
|
+
</button>
|
|
2988
|
+
</div>
|
|
2989
|
+
<div class="form-hint">留空表示不覆盖现有 key。</div>
|
|
2990
|
+
</div>
|
|
2991
|
+
<div class="form-group">
|
|
2992
|
+
<label class="form-label">API 类型</label>
|
|
2993
|
+
<input v-model="openclawQuick.apiType" class="form-input" list="openclawApiTypeList" placeholder="例如: openai-responses">
|
|
2994
|
+
<datalist id="openclawApiTypeList">
|
|
2995
|
+
<option value="openai-responses"></option>
|
|
2996
|
+
<option value="openai-chat"></option>
|
|
2997
|
+
<option value="anthropic"></option>
|
|
2998
|
+
<option value="custom"></option>
|
|
2999
|
+
</datalist>
|
|
3000
|
+
</div>
|
|
3001
|
+
</div>
|
|
3002
|
+
|
|
3003
|
+
<div class="quick-card">
|
|
3004
|
+
<div class="structured-card-title">模型</div>
|
|
3005
|
+
<div class="form-group">
|
|
3006
|
+
<label class="form-label">模型 ID</label>
|
|
3007
|
+
<input v-model="openclawQuick.modelId" class="form-input" placeholder="例如: gpt-4.1">
|
|
3008
|
+
</div>
|
|
3009
|
+
<div class="form-group">
|
|
3010
|
+
<label class="form-label">展示名称</label>
|
|
3011
|
+
<input v-model="openclawQuick.modelName" class="form-input" placeholder="留空则使用模型 ID">
|
|
3012
|
+
</div>
|
|
3013
|
+
<div class="form-group">
|
|
3014
|
+
<label class="form-label">上下文与最大输出</label>
|
|
3015
|
+
<div class="list-row">
|
|
3016
|
+
<input v-model="openclawQuick.contextWindow" class="form-input" placeholder="上下文长度">
|
|
3017
|
+
<input v-model="openclawQuick.maxTokens" class="form-input" placeholder="最大输出">
|
|
3018
|
+
</div>
|
|
3019
|
+
<div class="form-hint">留空表示不改动已有配置。</div>
|
|
3020
|
+
</div>
|
|
3021
|
+
</div>
|
|
3022
|
+
|
|
3023
|
+
<div class="quick-card">
|
|
3024
|
+
<div class="structured-card-title">选项</div>
|
|
3025
|
+
<label class="quick-option">
|
|
3026
|
+
<input type="checkbox" v-model="openclawQuick.setPrimary">
|
|
3027
|
+
设为主模型
|
|
3028
|
+
</label>
|
|
3029
|
+
<label class="quick-option">
|
|
3030
|
+
<input type="checkbox" v-model="openclawQuick.overrideProvider">
|
|
3031
|
+
覆盖同名 Provider 基础信息
|
|
3032
|
+
</label>
|
|
3033
|
+
<label class="quick-option">
|
|
3034
|
+
<input type="checkbox" v-model="openclawQuick.overrideModels">
|
|
3035
|
+
覆盖同名模型列表
|
|
3036
|
+
</label>
|
|
3037
|
+
<div class="form-hint">关闭覆盖会只补空缺字段。</div>
|
|
3038
|
+
</div>
|
|
3039
|
+
</div>
|
|
3040
|
+
<div class="btn-group">
|
|
3041
|
+
<button class="btn btn-confirm" @click="applyOpenclawQuickToText">写入编辑器</button>
|
|
3042
|
+
</div>
|
|
3043
|
+
</div>
|
|
3044
|
+
|
|
3045
|
+
<div class="structured-section">
|
|
3046
|
+
<div class="structured-header">
|
|
3047
|
+
<span class="structured-title">结构化配置(高级)</span>
|
|
3048
|
+
<span class="form-hint">写入编辑器会重排 JSON,注释可能丢失。</span>
|
|
3049
|
+
</div>
|
|
3050
|
+
<div class="structured-grid">
|
|
3051
|
+
<div class="structured-card">
|
|
3052
|
+
<div class="structured-card-title">Agents Defaults</div>
|
|
3053
|
+
<div class="form-group">
|
|
3054
|
+
<label class="form-label">主模型</label>
|
|
3055
|
+
<input v-model="openclawStructured.agentPrimary" class="form-input" placeholder="例如: provider/model">
|
|
3056
|
+
</div>
|
|
3057
|
+
<div class="form-group">
|
|
3058
|
+
<label class="form-label">备选模型</label>
|
|
3059
|
+
<div class="list-row" v-for="(item, index) in openclawStructured.agentFallbacks" :key="'fallback-' + index">
|
|
3060
|
+
<input v-model="openclawStructured.agentFallbacks[index]" class="form-input" placeholder="例如: provider/model">
|
|
3061
|
+
<button class="btn-mini delete" @click="removeOpenclawFallback(index)">删除</button>
|
|
3062
|
+
</div>
|
|
3063
|
+
<button class="btn-mini" @click="addOpenclawFallback">添加备选</button>
|
|
3064
|
+
</div>
|
|
3065
|
+
<div class="form-group">
|
|
3066
|
+
<label class="form-label">Workspace</label>
|
|
3067
|
+
<input v-model="openclawStructured.workspace" class="form-input" placeholder="例如: ~/.openclaw/workspace">
|
|
3068
|
+
</div>
|
|
3069
|
+
<div class="form-group">
|
|
3070
|
+
<label class="form-label">Timeout(s)</label>
|
|
3071
|
+
<input v-model="openclawStructured.timeout" class="form-input" placeholder="例如: 600">
|
|
3072
|
+
</div>
|
|
3073
|
+
<div class="form-group">
|
|
3074
|
+
<label class="form-label">Context Tokens</label>
|
|
3075
|
+
<input v-model="openclawStructured.contextTokens" class="form-input" placeholder="例如: 4096">
|
|
3076
|
+
</div>
|
|
3077
|
+
<div class="form-group">
|
|
3078
|
+
<label class="form-label">Max Concurrent</label>
|
|
3079
|
+
<input v-model="openclawStructured.maxConcurrent" class="form-input" placeholder="例如: 2">
|
|
3080
|
+
</div>
|
|
3081
|
+
</div>
|
|
3082
|
+
|
|
3083
|
+
<div class="structured-card">
|
|
3084
|
+
<div class="structured-card-title">Env</div>
|
|
3085
|
+
<div class="form-group">
|
|
3086
|
+
<label class="form-label">环境变量</label>
|
|
3087
|
+
<div class="list-row" v-for="(item, index) in openclawStructured.envItems" :key="'env-' + index">
|
|
3088
|
+
<input v-model="item.key" class="form-input" placeholder="KEY">
|
|
3089
|
+
<input v-model="item.value" class="form-input" :type="item.show ? 'text' : 'password'" placeholder="VALUE">
|
|
3090
|
+
<button class="btn-mini" @click="toggleOpenclawEnvItem(index)">
|
|
3091
|
+
{{ item.show ? '隐藏' : '显示' }}
|
|
3092
|
+
</button>
|
|
3093
|
+
<button class="btn-mini delete" @click="removeOpenclawEnvItem(index)">删除</button>
|
|
3094
|
+
</div>
|
|
3095
|
+
<button class="btn-mini" @click="addOpenclawEnvItem">添加变量</button>
|
|
3096
|
+
</div>
|
|
3097
|
+
</div>
|
|
3098
|
+
|
|
3099
|
+
<div class="structured-card">
|
|
3100
|
+
<div class="structured-card-title">Tools</div>
|
|
3101
|
+
<div class="form-group">
|
|
3102
|
+
<label class="form-label">Profile</label>
|
|
3103
|
+
<select v-model="openclawStructured.toolsProfile" class="form-input">
|
|
3104
|
+
<option value="default">default</option>
|
|
3105
|
+
<option value="strict">strict</option>
|
|
3106
|
+
<option value="permissive">permissive</option>
|
|
3107
|
+
<option value="custom">custom</option>
|
|
3108
|
+
</select>
|
|
3109
|
+
</div>
|
|
3110
|
+
<div class="form-group">
|
|
3111
|
+
<label class="form-label">Allow</label>
|
|
3112
|
+
<div class="list-row" v-for="(item, index) in openclawStructured.toolsAllow" :key="'allow-' + index">
|
|
3113
|
+
<input v-model="openclawStructured.toolsAllow[index]" class="form-input" placeholder="例如: fs.read*">
|
|
3114
|
+
<button class="btn-mini delete" @click="removeOpenclawToolsAllow(index)">删除</button>
|
|
3115
|
+
</div>
|
|
3116
|
+
<button class="btn-mini" @click="addOpenclawToolsAllow">添加 allow</button>
|
|
3117
|
+
</div>
|
|
3118
|
+
<div class="form-group">
|
|
3119
|
+
<label class="form-label">Deny</label>
|
|
3120
|
+
<div class="list-row" v-for="(item, index) in openclawStructured.toolsDeny" :key="'deny-' + index">
|
|
3121
|
+
<input v-model="openclawStructured.toolsDeny[index]" class="form-input" placeholder="例如: net.*">
|
|
3122
|
+
<button class="btn-mini delete" @click="removeOpenclawToolsDeny(index)">删除</button>
|
|
3123
|
+
</div>
|
|
3124
|
+
<button class="btn-mini" @click="addOpenclawToolsDeny">添加 deny</button>
|
|
3125
|
+
</div>
|
|
3126
|
+
</div>
|
|
3127
|
+
|
|
3128
|
+
<div class="structured-card">
|
|
3129
|
+
<div class="structured-card-title">Providers(只读)</div>
|
|
3130
|
+
<div v-if="openclawProviders.length === 0" class="form-hint">
|
|
3131
|
+
未发现 providers 配置(可能使用内置 provider 或 auth profiles)。
|
|
3132
|
+
</div>
|
|
3133
|
+
<div v-else class="provider-list">
|
|
3134
|
+
<div class="provider-item" v-for="(provider, index) in openclawProviders" :key="provider.key + '-' + provider.source + '-' + index">
|
|
3135
|
+
<div class="provider-header">
|
|
3136
|
+
<span class="provider-name">{{ provider.key }}</span>
|
|
3137
|
+
<span class="provider-source">来源: {{ provider.source }}</span>
|
|
3138
|
+
<span v-if="provider.isActive" class="pill configured">使用中</span>
|
|
3139
|
+
</div>
|
|
3140
|
+
<div v-if="provider.fields.length === 0" class="form-hint">未配置字段</div>
|
|
3141
|
+
<div v-else class="provider-fields">
|
|
3142
|
+
<div class="provider-field" v-for="field in provider.fields" :key="provider.key + '-' + field.key">
|
|
3143
|
+
<span class="provider-field-key">{{ field.key }}</span>
|
|
3144
|
+
<span class="provider-field-value">{{ field.value }}</span>
|
|
3145
|
+
</div>
|
|
3146
|
+
</div>
|
|
3147
|
+
</div>
|
|
3148
|
+
</div>
|
|
3149
|
+
<div v-if="openclawMissingProviders.length" class="form-hint">
|
|
3150
|
+
使用中的 provider 未在配置中显示:{{ openclawMissingProviders.join(', ') }}。
|
|
3151
|
+
</div>
|
|
3152
|
+
</div>
|
|
3153
|
+
|
|
3154
|
+
<div class="structured-card">
|
|
3155
|
+
<div class="structured-card-title">Agents(只读)</div>
|
|
3156
|
+
<div v-if="openclawAgentsList.length === 0" class="form-hint">
|
|
3157
|
+
未发现 agents.list 配置。
|
|
3158
|
+
</div>
|
|
3159
|
+
<div v-else class="agent-list">
|
|
3160
|
+
<div class="agent-item" v-for="(agent, index) in openclawAgentsList" :key="agent.key + '-' + index">
|
|
3161
|
+
<div class="agent-header">
|
|
3162
|
+
<span class="agent-name">{{ agent.name }}</span>
|
|
3163
|
+
<span class="agent-id">ID: {{ agent.id }}</span>
|
|
3164
|
+
</div>
|
|
3165
|
+
<div class="agent-meta" v-if="agent.theme || agent.emoji || agent.avatar">
|
|
3166
|
+
<span v-if="agent.theme">主题: {{ agent.theme }}</span>
|
|
3167
|
+
<span v-if="agent.emoji">表情: {{ agent.emoji }}</span>
|
|
3168
|
+
<span v-if="agent.avatar">头像: {{ agent.avatar }}</span>
|
|
3169
|
+
</div>
|
|
3170
|
+
</div>
|
|
3171
|
+
</div>
|
|
3172
|
+
</div>
|
|
3173
|
+
</div>
|
|
3174
|
+
<div class="btn-group">
|
|
3175
|
+
<button class="btn btn-confirm secondary" @click="syncOpenclawStructuredFromText">从文本刷新</button>
|
|
3176
|
+
<button class="btn btn-confirm" @click="applyOpenclawStructuredToText">写入编辑器</button>
|
|
3177
|
+
</div>
|
|
3178
|
+
</div>
|
|
3179
|
+
|
|
2474
3180
|
<div class="form-group">
|
|
2475
3181
|
<label class="form-label">OpenClaw 配置(JSON5)</label>
|
|
2476
3182
|
<textarea
|
|
@@ -2479,7 +3185,7 @@
|
|
|
2479
3185
|
spellcheck="false"
|
|
2480
3186
|
placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
|
|
2481
3187
|
<div class="template-editor-warning">
|
|
2482
|
-
|
|
3188
|
+
保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。
|
|
2483
3189
|
</div>
|
|
2484
3190
|
</div>
|
|
2485
3191
|
|
|
@@ -2507,7 +3213,7 @@
|
|
|
2507
3213
|
spellcheck="false"
|
|
2508
3214
|
placeholder="在这里编辑 config.toml 模板内容"></textarea>
|
|
2509
3215
|
<div class="template-editor-warning">
|
|
2510
|
-
工具不会自动改动 `config.toml
|
|
3216
|
+
工具不会自动改动 `config.toml`。只有点击“确认应用模板”后才写入。
|
|
2511
3217
|
</div>
|
|
2512
3218
|
</div>
|
|
2513
3219
|
|
|
@@ -2522,7 +3228,15 @@
|
|
|
2522
3228
|
|
|
2523
3229
|
<div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
|
|
2524
3230
|
<div class="modal modal-wide">
|
|
2525
|
-
<div class="modal-
|
|
3231
|
+
<div class="modal-header">
|
|
3232
|
+
<div class="modal-title">{{ agentsModalTitle }}</div>
|
|
3233
|
+
<button
|
|
3234
|
+
class="btn-mini btn-modal-copy"
|
|
3235
|
+
@click="copyAgentsContent"
|
|
3236
|
+
:disabled="agentsLoading">
|
|
3237
|
+
复制
|
|
3238
|
+
</button>
|
|
3239
|
+
</div>
|
|
2526
3240
|
|
|
2527
3241
|
<div class="form-group">
|
|
2528
3242
|
<label class="form-label">目标文件</label>
|
|
@@ -2564,7 +3278,9 @@
|
|
|
2564
3278
|
|
|
2565
3279
|
<script>
|
|
2566
3280
|
const { createApp } = Vue;
|
|
2567
|
-
const API_BASE = '
|
|
3281
|
+
const API_BASE = (location && location.origin && location.origin !== 'null')
|
|
3282
|
+
? location.origin
|
|
3283
|
+
: 'http://localhost:3737';
|
|
2568
3284
|
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
2569
3285
|
// OpenClaw config (JSON5)
|
|
2570
3286
|
agent: {
|
|
@@ -2646,13 +3362,22 @@
|
|
|
2646
3362
|
},
|
|
2647
3363
|
sessionPathRequestSeq: 0,
|
|
2648
3364
|
sessionExporting: {},
|
|
3365
|
+
sessionCloning: {},
|
|
3366
|
+
sessionDeleting: {},
|
|
2649
3367
|
activeSession: null,
|
|
2650
3368
|
activeSessionMessages: [],
|
|
2651
3369
|
activeSessionDetailError: '',
|
|
2652
3370
|
activeSessionDetailClipped: false,
|
|
2653
|
-
sessionDetailLoading: false,
|
|
2654
|
-
sessionDetailRequestSeq: 0,
|
|
2655
|
-
|
|
3371
|
+
sessionDetailLoading: false,
|
|
3372
|
+
sessionDetailRequestSeq: 0,
|
|
3373
|
+
sessionStandalone: false,
|
|
3374
|
+
sessionStandaloneError: '',
|
|
3375
|
+
sessionStandaloneText: '',
|
|
3376
|
+
sessionStandaloneTitle: '',
|
|
3377
|
+
sessionStandaloneSourceLabel: '',
|
|
3378
|
+
sessionStandaloneLoading: false,
|
|
3379
|
+
sessionStandaloneRequestSeq: 0,
|
|
3380
|
+
speedResults: {},
|
|
2656
3381
|
speedLoading: {},
|
|
2657
3382
|
newProvider: { name: '', url: '', key: '' },
|
|
2658
3383
|
editingProvider: { name: '', url: '', key: '' },
|
|
@@ -2688,6 +3413,37 @@
|
|
|
2688
3413
|
openclawFileLoading: false,
|
|
2689
3414
|
openclawSaving: false,
|
|
2690
3415
|
openclawApplying: false,
|
|
3416
|
+
openclawWorkspaceFileName: 'SOUL.md',
|
|
3417
|
+
agentsWorkspaceFileName: '',
|
|
3418
|
+
openclawStructured: {
|
|
3419
|
+
agentPrimary: '',
|
|
3420
|
+
agentFallbacks: [],
|
|
3421
|
+
workspace: '',
|
|
3422
|
+
timeout: '',
|
|
3423
|
+
contextTokens: '',
|
|
3424
|
+
maxConcurrent: '',
|
|
3425
|
+
envItems: [],
|
|
3426
|
+
toolsProfile: 'default',
|
|
3427
|
+
toolsAllow: [],
|
|
3428
|
+
toolsDeny: []
|
|
3429
|
+
},
|
|
3430
|
+
openclawQuick: {
|
|
3431
|
+
providerName: '',
|
|
3432
|
+
baseUrl: '',
|
|
3433
|
+
apiKey: '',
|
|
3434
|
+
apiType: 'openai-responses',
|
|
3435
|
+
modelId: '',
|
|
3436
|
+
modelName: '',
|
|
3437
|
+
contextWindow: '',
|
|
3438
|
+
maxTokens: '',
|
|
3439
|
+
setPrimary: true,
|
|
3440
|
+
overrideProvider: true,
|
|
3441
|
+
overrideModels: true,
|
|
3442
|
+
showKey: false
|
|
3443
|
+
},
|
|
3444
|
+
openclawAgentsList: [],
|
|
3445
|
+
openclawProviders: [],
|
|
3446
|
+
openclawMissingProviders: [],
|
|
2691
3447
|
recentConfigs: [],
|
|
2692
3448
|
recentLoading: false,
|
|
2693
3449
|
healthCheckLoading: false,
|
|
@@ -2696,6 +3452,7 @@
|
|
|
2696
3452
|
}
|
|
2697
3453
|
},
|
|
2698
3454
|
mounted() {
|
|
3455
|
+
this.initSessionStandalone();
|
|
2699
3456
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
2700
3457
|
if (savedConfigs) {
|
|
2701
3458
|
try {
|
|
@@ -2742,6 +3499,15 @@
|
|
|
2742
3499
|
}
|
|
2743
3500
|
this.loadAll();
|
|
2744
3501
|
},
|
|
3502
|
+
|
|
3503
|
+
computed: {
|
|
3504
|
+
isSessionQueryEnabled() {
|
|
3505
|
+
return this.sessionFilterSource === 'codex';
|
|
3506
|
+
},
|
|
3507
|
+
sessionQueryPlaceholder() {
|
|
3508
|
+
return this.isSessionQueryEnabled ? '关键词检索' : '仅 Codex 支持关键词检索';
|
|
3509
|
+
}
|
|
3510
|
+
},
|
|
2745
3511
|
methods: {
|
|
2746
3512
|
async loadAll() {
|
|
2747
3513
|
this.loading = true;
|
|
@@ -2758,7 +3524,7 @@
|
|
|
2758
3524
|
this.providersList = listRes.providers;
|
|
2759
3525
|
await this.loadModelsForProvider(this.currentProvider);
|
|
2760
3526
|
if (statusRes.configReady === false) {
|
|
2761
|
-
this.showMessage(statusRes.configNotice || '未检测到 config.toml
|
|
3527
|
+
this.showMessage(statusRes.configNotice || '未检测到 config.toml,已加载默认模板。请在模板编辑器确认后创建。', 'info');
|
|
2762
3528
|
}
|
|
2763
3529
|
if (statusRes.initNotice) {
|
|
2764
3530
|
this.showMessage(statusRes.initNotice, 'info');
|
|
@@ -2901,6 +3667,100 @@
|
|
|
2901
3667
|
}
|
|
2902
3668
|
},
|
|
2903
3669
|
|
|
3670
|
+
getSessionStandaloneContext() {
|
|
3671
|
+
try {
|
|
3672
|
+
const url = new URL(window.location.href);
|
|
3673
|
+
if (url.pathname !== '/session') {
|
|
3674
|
+
return { requested: false, params: null, error: '' };
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
const source = (url.searchParams.get('source') || '').trim().toLowerCase();
|
|
3678
|
+
const sessionId = (url.searchParams.get('sessionId') || url.searchParams.get('id') || '').trim();
|
|
3679
|
+
const filePath = (url.searchParams.get('filePath') || url.searchParams.get('path') || '').trim();
|
|
3680
|
+
let error = '';
|
|
3681
|
+
if (!source) {
|
|
3682
|
+
error = '缺少 source 参数';
|
|
3683
|
+
} else if (source !== 'codex' && source !== 'claude') {
|
|
3684
|
+
error = 'source 仅支持 codex 或 claude';
|
|
3685
|
+
}
|
|
3686
|
+
if (!sessionId && !filePath) {
|
|
3687
|
+
error = error ? `${error},还缺少 sessionId 或 filePath` : '缺少 sessionId 或 filePath 参数';
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
if (error) {
|
|
3691
|
+
return { requested: true, params: null, error };
|
|
3692
|
+
}
|
|
3693
|
+
|
|
3694
|
+
return {
|
|
3695
|
+
requested: true,
|
|
3696
|
+
params: {
|
|
3697
|
+
source,
|
|
3698
|
+
sessionId,
|
|
3699
|
+
filePath
|
|
3700
|
+
},
|
|
3701
|
+
error: ''
|
|
3702
|
+
};
|
|
3703
|
+
} catch (_) {
|
|
3704
|
+
return { requested: false, params: null, error: '' };
|
|
3705
|
+
}
|
|
3706
|
+
},
|
|
3707
|
+
|
|
3708
|
+
initSessionStandalone() {
|
|
3709
|
+
const context = this.getSessionStandaloneContext();
|
|
3710
|
+
if (!context.requested) return;
|
|
3711
|
+
|
|
3712
|
+
this.sessionStandalone = true;
|
|
3713
|
+
this.configMode = 'sessions';
|
|
3714
|
+
|
|
3715
|
+
if (context.error || !context.params) {
|
|
3716
|
+
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
const sourceLabel = context.params.source === 'codex' ? 'Codex' : 'Claude Code';
|
|
3721
|
+
this.activeSession = {
|
|
3722
|
+
source: context.params.source,
|
|
3723
|
+
sourceLabel,
|
|
3724
|
+
sessionId: context.params.sessionId,
|
|
3725
|
+
filePath: context.params.filePath,
|
|
3726
|
+
title: context.params.sessionId || context.params.filePath || '会话'
|
|
3727
|
+
};
|
|
3728
|
+
this.activeSessionMessages = [];
|
|
3729
|
+
this.activeSessionDetailError = '';
|
|
3730
|
+
this.activeSessionDetailClipped = false;
|
|
3731
|
+
this.sessionStandaloneError = '';
|
|
3732
|
+
this.sessionStandaloneText = '';
|
|
3733
|
+
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
3734
|
+
this.sessionStandaloneSourceLabel = sourceLabel;
|
|
3735
|
+
this.loadSessionStandalonePlain();
|
|
3736
|
+
},
|
|
3737
|
+
|
|
3738
|
+
buildSessionStandaloneUrl(session) {
|
|
3739
|
+
if (!session) return '';
|
|
3740
|
+
const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
|
|
3741
|
+
if (!source || (source !== 'codex' && source !== 'claude')) return '';
|
|
3742
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
3743
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
3744
|
+
if (!sessionId && !filePath) return '';
|
|
3745
|
+
const origin = window.location.origin && window.location.origin !== 'null'
|
|
3746
|
+
? window.location.origin
|
|
3747
|
+
: API_BASE;
|
|
3748
|
+
const params = new URLSearchParams();
|
|
3749
|
+
params.set('source', source);
|
|
3750
|
+
if (sessionId) params.set('sessionId', sessionId);
|
|
3751
|
+
if (filePath) params.set('filePath', filePath);
|
|
3752
|
+
return `${origin}/session?${params.toString()}`;
|
|
3753
|
+
},
|
|
3754
|
+
|
|
3755
|
+
openSessionStandalone(session) {
|
|
3756
|
+
const url = this.buildSessionStandaloneUrl(session);
|
|
3757
|
+
if (!url) {
|
|
3758
|
+
this.showMessage('当前会话无法生成新页链接', 'error');
|
|
3759
|
+
return;
|
|
3760
|
+
}
|
|
3761
|
+
window.open(url, '_blank', 'noopener');
|
|
3762
|
+
},
|
|
3763
|
+
|
|
2904
3764
|
getSessionExportKey(session) {
|
|
2905
3765
|
return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
|
|
2906
3766
|
},
|
|
@@ -2912,6 +3772,23 @@
|
|
|
2912
3772
|
return source === 'codex' && !!sessionId;
|
|
2913
3773
|
},
|
|
2914
3774
|
|
|
3775
|
+
isCloneAvailable(session) {
|
|
3776
|
+
if (!session) return false;
|
|
3777
|
+
const source = String(session.source || '').trim().toLowerCase();
|
|
3778
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
3779
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
3780
|
+
return source === 'codex' && (!!sessionId || !!filePath);
|
|
3781
|
+
},
|
|
3782
|
+
|
|
3783
|
+
isDeleteAvailable(session) {
|
|
3784
|
+
if (!session) return false;
|
|
3785
|
+
const source = String(session.source || '').trim().toLowerCase();
|
|
3786
|
+
if (source !== 'codex' && source !== 'claude') return false;
|
|
3787
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
3788
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
3789
|
+
return !!sessionId || !!filePath;
|
|
3790
|
+
},
|
|
3791
|
+
|
|
2915
3792
|
buildResumeCommand(session) {
|
|
2916
3793
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
2917
3794
|
return `codex resume ${this.quoteResumeArg(sessionId)}`;
|
|
@@ -2948,6 +3825,20 @@
|
|
|
2948
3825
|
}
|
|
2949
3826
|
},
|
|
2950
3827
|
|
|
3828
|
+
copyAgentsContent() {
|
|
3829
|
+
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3830
|
+
if (!text) {
|
|
3831
|
+
this.showMessage('没有可复制的内容', 'info');
|
|
3832
|
+
return;
|
|
3833
|
+
}
|
|
3834
|
+
const ok = this.fallbackCopyText(text);
|
|
3835
|
+
if (ok) {
|
|
3836
|
+
this.showMessage('已复制 AGENTS.md 内容', 'success');
|
|
3837
|
+
return;
|
|
3838
|
+
}
|
|
3839
|
+
this.showMessage('复制失败,请手动复制内容', 'error');
|
|
3840
|
+
},
|
|
3841
|
+
|
|
2951
3842
|
async copyResumeCommand(session) {
|
|
2952
3843
|
if (!this.isResumeCommandAvailable(session)) {
|
|
2953
3844
|
this.showMessage('当前会话不支持生成恢复命令', 'error');
|
|
@@ -2971,6 +3862,71 @@
|
|
|
2971
3862
|
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
2972
3863
|
},
|
|
2973
3864
|
|
|
3865
|
+
async cloneSession(session) {
|
|
3866
|
+
if (!this.isCloneAvailable(session)) {
|
|
3867
|
+
this.showMessage('当前会话不支持克隆', 'error');
|
|
3868
|
+
return;
|
|
3869
|
+
}
|
|
3870
|
+
const key = this.getSessionExportKey(session);
|
|
3871
|
+
if (this.sessionCloning[key]) {
|
|
3872
|
+
return;
|
|
3873
|
+
}
|
|
3874
|
+
this.sessionCloning[key] = true;
|
|
3875
|
+
try {
|
|
3876
|
+
const res = await api('clone-session', {
|
|
3877
|
+
source: session.source,
|
|
3878
|
+
sessionId: session.sessionId,
|
|
3879
|
+
filePath: session.filePath
|
|
3880
|
+
});
|
|
3881
|
+
if (res.error) {
|
|
3882
|
+
this.showMessage(res.error, 'error');
|
|
3883
|
+
return;
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3886
|
+
this.showMessage('会话已克隆', 'success');
|
|
3887
|
+
await this.loadSessions();
|
|
3888
|
+
if (res.sessionId) {
|
|
3889
|
+
const matched = this.sessionsList.find(item => item.source === 'codex' && item.sessionId === res.sessionId);
|
|
3890
|
+
if (matched) {
|
|
3891
|
+
await this.selectSession(matched);
|
|
3892
|
+
}
|
|
3893
|
+
}
|
|
3894
|
+
} catch (e) {
|
|
3895
|
+
this.showMessage('克隆失败: ' + e.message, 'error');
|
|
3896
|
+
} finally {
|
|
3897
|
+
this.sessionCloning[key] = false;
|
|
3898
|
+
}
|
|
3899
|
+
},
|
|
3900
|
+
|
|
3901
|
+
async deleteSession(session) {
|
|
3902
|
+
if (!this.isDeleteAvailable(session)) {
|
|
3903
|
+
this.showMessage('当前会话不支持删除', 'error');
|
|
3904
|
+
return;
|
|
3905
|
+
}
|
|
3906
|
+
const key = this.getSessionExportKey(session);
|
|
3907
|
+
if (this.sessionDeleting[key]) {
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
this.sessionDeleting[key] = true;
|
|
3911
|
+
try {
|
|
3912
|
+
const res = await api('delete-session', {
|
|
3913
|
+
source: session.source,
|
|
3914
|
+
sessionId: session.sessionId,
|
|
3915
|
+
filePath: session.filePath
|
|
3916
|
+
});
|
|
3917
|
+
if (res.error) {
|
|
3918
|
+
this.showMessage(res.error, 'error');
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3921
|
+
this.showMessage('会话已删除', 'success');
|
|
3922
|
+
await this.loadSessions();
|
|
3923
|
+
} catch (e) {
|
|
3924
|
+
this.showMessage('删除失败: ' + e.message, 'error');
|
|
3925
|
+
} finally {
|
|
3926
|
+
this.sessionDeleting[key] = false;
|
|
3927
|
+
}
|
|
3928
|
+
},
|
|
3929
|
+
|
|
2974
3930
|
normalizeSessionPathValue(value) {
|
|
2975
3931
|
if (typeof value !== 'string') return '';
|
|
2976
3932
|
return value.trim();
|
|
@@ -3134,7 +4090,7 @@
|
|
|
3134
4090
|
if (this.sessionsLoading) return;
|
|
3135
4091
|
this.sessionsLoading = true;
|
|
3136
4092
|
this.activeSessionDetailError = '';
|
|
3137
|
-
const query = this.sessionQuery;
|
|
4093
|
+
const query = this.isSessionQueryEnabled ? this.sessionQuery : '';
|
|
3138
4094
|
try {
|
|
3139
4095
|
const res = await api('list-sessions', {
|
|
3140
4096
|
source: this.sessionFilterSource,
|
|
@@ -3142,7 +4098,7 @@
|
|
|
3142
4098
|
query,
|
|
3143
4099
|
queryMode: 'and',
|
|
3144
4100
|
queryScope: 'content',
|
|
3145
|
-
contentScanLimit:
|
|
4101
|
+
contentScanLimit: 50,
|
|
3146
4102
|
roleFilter: this.sessionRoleFilter,
|
|
3147
4103
|
timeRangePreset: this.sessionTimePreset,
|
|
3148
4104
|
limit: 200,
|
|
@@ -3184,21 +4140,66 @@
|
|
|
3184
4140
|
}
|
|
3185
4141
|
},
|
|
3186
4142
|
|
|
3187
|
-
async selectSession(session) {
|
|
3188
|
-
if (!session) return;
|
|
3189
|
-
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
3190
|
-
this.activeSession = session;
|
|
3191
|
-
this.activeSessionMessages = [];
|
|
3192
|
-
this.activeSessionDetailError = '';
|
|
3193
|
-
this.activeSessionDetailClipped = false;
|
|
3194
|
-
await this.loadActiveSessionDetail();
|
|
3195
|
-
},
|
|
3196
|
-
|
|
3197
|
-
async
|
|
3198
|
-
if (!this.activeSession) {
|
|
3199
|
-
this.
|
|
3200
|
-
this.
|
|
3201
|
-
this.
|
|
4143
|
+
async selectSession(session) {
|
|
4144
|
+
if (!session) return;
|
|
4145
|
+
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
4146
|
+
this.activeSession = session;
|
|
4147
|
+
this.activeSessionMessages = [];
|
|
4148
|
+
this.activeSessionDetailError = '';
|
|
4149
|
+
this.activeSessionDetailClipped = false;
|
|
4150
|
+
await this.loadActiveSessionDetail();
|
|
4151
|
+
},
|
|
4152
|
+
|
|
4153
|
+
async loadSessionStandalonePlain() {
|
|
4154
|
+
if (!this.activeSession) {
|
|
4155
|
+
this.sessionStandaloneText = '';
|
|
4156
|
+
this.sessionStandaloneTitle = '会话';
|
|
4157
|
+
this.sessionStandaloneSourceLabel = '';
|
|
4158
|
+
this.sessionStandaloneError = '';
|
|
4159
|
+
return;
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4162
|
+
const requestSeq = ++this.sessionStandaloneRequestSeq;
|
|
4163
|
+
this.sessionStandaloneLoading = true;
|
|
4164
|
+
this.sessionStandaloneError = '';
|
|
4165
|
+
try {
|
|
4166
|
+
const res = await api('session-plain', {
|
|
4167
|
+
source: this.activeSession.source,
|
|
4168
|
+
sessionId: this.activeSession.sessionId,
|
|
4169
|
+
filePath: this.activeSession.filePath
|
|
4170
|
+
});
|
|
4171
|
+
|
|
4172
|
+
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4173
|
+
return;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
if (res.error) {
|
|
4177
|
+
this.sessionStandaloneText = '';
|
|
4178
|
+
this.sessionStandaloneError = res.error;
|
|
4179
|
+
return;
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
this.sessionStandaloneSourceLabel = res.sourceLabel || this.activeSession.sourceLabel || '';
|
|
4183
|
+
this.sessionStandaloneTitle = res.sessionId || this.activeSession.title || '会话';
|
|
4184
|
+
this.sessionStandaloneText = typeof res.text === 'string' ? res.text : '';
|
|
4185
|
+
} catch (e) {
|
|
4186
|
+
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4187
|
+
return;
|
|
4188
|
+
}
|
|
4189
|
+
this.sessionStandaloneText = '';
|
|
4190
|
+
this.sessionStandaloneError = '加载会话内容失败: ' + e.message;
|
|
4191
|
+
} finally {
|
|
4192
|
+
if (requestSeq === this.sessionStandaloneRequestSeq) {
|
|
4193
|
+
this.sessionStandaloneLoading = false;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
},
|
|
4197
|
+
|
|
4198
|
+
async loadActiveSessionDetail() {
|
|
4199
|
+
if (!this.activeSession) {
|
|
4200
|
+
this.activeSessionMessages = [];
|
|
4201
|
+
this.activeSessionDetailError = '';
|
|
4202
|
+
this.activeSessionDetailClipped = false;
|
|
3202
4203
|
return;
|
|
3203
4204
|
}
|
|
3204
4205
|
|
|
@@ -3226,6 +4227,20 @@
|
|
|
3226
4227
|
|
|
3227
4228
|
this.activeSessionMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
3228
4229
|
this.activeSessionDetailClipped = !!res.clipped;
|
|
4230
|
+
if (this.activeSession) {
|
|
4231
|
+
if (res.sourceLabel) {
|
|
4232
|
+
this.activeSession.sourceLabel = res.sourceLabel;
|
|
4233
|
+
}
|
|
4234
|
+
if (res.sessionId) {
|
|
4235
|
+
this.activeSession.sessionId = res.sessionId;
|
|
4236
|
+
if (!this.activeSession.title) {
|
|
4237
|
+
this.activeSession.title = res.sessionId;
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
if (res.filePath) {
|
|
4241
|
+
this.activeSession.filePath = res.filePath;
|
|
4242
|
+
}
|
|
4243
|
+
}
|
|
3229
4244
|
if (res.updatedAt) {
|
|
3230
4245
|
this.activeSession.updatedAt = res.updatedAt;
|
|
3231
4246
|
}
|
|
@@ -3270,17 +4285,22 @@
|
|
|
3270
4285
|
sessionId: session.sessionId,
|
|
3271
4286
|
filePath: session.filePath
|
|
3272
4287
|
});
|
|
3273
|
-
if (res.error) {
|
|
3274
|
-
this.showMessage(res.error, 'error');
|
|
3275
|
-
return;
|
|
3276
|
-
}
|
|
3277
|
-
|
|
3278
|
-
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
3279
|
-
this.downloadTextFile(fileName, res.content || '');
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
4288
|
+
if (res.error) {
|
|
4289
|
+
this.showMessage(res.error, 'error');
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
4294
|
+
this.downloadTextFile(fileName, res.content || '');
|
|
4295
|
+
if (res.truncated) {
|
|
4296
|
+
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
4297
|
+
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
4298
|
+
} else {
|
|
4299
|
+
this.showMessage('会话导出完成', 'success');
|
|
4300
|
+
}
|
|
4301
|
+
} catch (e) {
|
|
4302
|
+
this.showMessage('导出失败: ' + e.message, 'error');
|
|
4303
|
+
} finally {
|
|
3284
4304
|
this.sessionExporting[key] = false;
|
|
3285
4305
|
}
|
|
3286
4306
|
},
|
|
@@ -3328,14 +4348,44 @@
|
|
|
3328
4348
|
this.healthCheckResult = null;
|
|
3329
4349
|
try {
|
|
3330
4350
|
const res = await api('config-health-check', {
|
|
3331
|
-
remote:
|
|
4351
|
+
remote: false
|
|
3332
4352
|
});
|
|
3333
4353
|
if (res && typeof res === 'object') {
|
|
3334
|
-
|
|
3335
|
-
|
|
4354
|
+
const issues = Array.isArray(res.issues) ? [...res.issues] : [];
|
|
4355
|
+
let remote = res.remote || null;
|
|
4356
|
+
{
|
|
4357
|
+
const providers = (this.providersList || [])
|
|
4358
|
+
.filter(provider => provider && provider.name);
|
|
4359
|
+
const tasks = providers.map(provider =>
|
|
4360
|
+
this.runSpeedTest(provider.name, { silent: true })
|
|
4361
|
+
.then(result => ({ name: provider.name, result }))
|
|
4362
|
+
.catch(err => ({
|
|
4363
|
+
name: provider.name,
|
|
4364
|
+
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
4365
|
+
}))
|
|
4366
|
+
);
|
|
4367
|
+
const pairs = await Promise.all(tasks);
|
|
4368
|
+
const results = {};
|
|
4369
|
+
for (const pair of pairs) {
|
|
4370
|
+
results[pair.name] = pair.result || null;
|
|
4371
|
+
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
4372
|
+
if (issue) issues.push(issue);
|
|
4373
|
+
}
|
|
4374
|
+
remote = {
|
|
4375
|
+
type: 'speed-test',
|
|
4376
|
+
results
|
|
4377
|
+
};
|
|
4378
|
+
}
|
|
4379
|
+
|
|
4380
|
+
const ok = issues.length === 0;
|
|
4381
|
+
this.healthCheckResult = {
|
|
4382
|
+
...res,
|
|
4383
|
+
ok,
|
|
4384
|
+
issues,
|
|
4385
|
+
remote
|
|
4386
|
+
};
|
|
4387
|
+
if (ok) {
|
|
3336
4388
|
this.showMessage('健康检查通过', 'success');
|
|
3337
|
-
} else {
|
|
3338
|
-
this.showMessage('发现配置问题,请查看详情', 'error');
|
|
3339
4389
|
}
|
|
3340
4390
|
} else {
|
|
3341
4391
|
this.healthCheckResult = null;
|
|
@@ -3456,7 +4506,44 @@
|
|
|
3456
4506
|
}
|
|
3457
4507
|
},
|
|
3458
4508
|
|
|
3459
|
-
|
|
4509
|
+
async openOpenclawWorkspaceEditor() {
|
|
4510
|
+
const fileName = (this.openclawWorkspaceFileName || '').trim();
|
|
4511
|
+
if (!fileName) {
|
|
4512
|
+
this.showMessage('请输入工作区文件名', 'error');
|
|
4513
|
+
return;
|
|
4514
|
+
}
|
|
4515
|
+
this.setAgentsModalContext('openclaw-workspace', { fileName });
|
|
4516
|
+
this.agentsLoading = true;
|
|
4517
|
+
try {
|
|
4518
|
+
const res = await api('get-openclaw-workspace-file', { fileName });
|
|
4519
|
+
if (res.error) {
|
|
4520
|
+
this.showMessage(res.error, 'error');
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
if (res.configError) {
|
|
4524
|
+
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
4525
|
+
}
|
|
4526
|
+
this.agentsContent = res.content || '';
|
|
4527
|
+
this.agentsPath = res.path || '';
|
|
4528
|
+
this.agentsExists = !!res.exists;
|
|
4529
|
+
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
4530
|
+
this.showAgentsModal = true;
|
|
4531
|
+
} catch (e) {
|
|
4532
|
+
this.showMessage('加载 OpenClaw 工作区文件失败: ' + e.message, 'error');
|
|
4533
|
+
} finally {
|
|
4534
|
+
this.agentsLoading = false;
|
|
4535
|
+
}
|
|
4536
|
+
},
|
|
4537
|
+
|
|
4538
|
+
setAgentsModalContext(context, options = {}) {
|
|
4539
|
+
if (context === 'openclaw-workspace') {
|
|
4540
|
+
const fileName = (options.fileName || this.openclawWorkspaceFileName || 'AGENTS.md').trim();
|
|
4541
|
+
this.agentsContext = 'openclaw-workspace';
|
|
4542
|
+
this.agentsWorkspaceFileName = fileName;
|
|
4543
|
+
this.agentsModalTitle = `OpenClaw 工作区文件: ${fileName}`;
|
|
4544
|
+
this.agentsModalHint = `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`;
|
|
4545
|
+
return;
|
|
4546
|
+
}
|
|
3460
4547
|
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
3461
4548
|
if (this.agentsContext === 'openclaw') {
|
|
3462
4549
|
this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
|
|
@@ -3465,6 +4552,7 @@
|
|
|
3465
4552
|
this.agentsModalTitle = 'AGENTS.md 编辑器';
|
|
3466
4553
|
this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
|
|
3467
4554
|
}
|
|
4555
|
+
this.agentsWorkspaceFileName = '';
|
|
3468
4556
|
},
|
|
3469
4557
|
|
|
3470
4558
|
closeAgentsModal() {
|
|
@@ -3474,27 +4562,36 @@
|
|
|
3474
4562
|
this.agentsExists = false;
|
|
3475
4563
|
this.agentsLineEnding = '\n';
|
|
3476
4564
|
this.agentsSaving = false;
|
|
4565
|
+
this.agentsWorkspaceFileName = '';
|
|
3477
4566
|
this.setAgentsModalContext('codex');
|
|
3478
4567
|
},
|
|
3479
4568
|
|
|
3480
4569
|
async applyAgentsContent() {
|
|
3481
4570
|
this.agentsSaving = true;
|
|
3482
4571
|
try {
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
: 'apply-agents-file';
|
|
3486
|
-
const res = await api(action, {
|
|
4572
|
+
let action = 'apply-agents-file';
|
|
4573
|
+
const params = {
|
|
3487
4574
|
content: this.agentsContent,
|
|
3488
4575
|
lineEnding: this.agentsLineEnding
|
|
3489
|
-
}
|
|
4576
|
+
};
|
|
4577
|
+
if (this.agentsContext === 'openclaw') {
|
|
4578
|
+
action = 'apply-openclaw-agents-file';
|
|
4579
|
+
} else if (this.agentsContext === 'openclaw-workspace') {
|
|
4580
|
+
action = 'apply-openclaw-workspace-file';
|
|
4581
|
+
params.fileName = this.agentsWorkspaceFileName;
|
|
4582
|
+
}
|
|
4583
|
+
const res = await api(action, params);
|
|
3490
4584
|
if (res.error) {
|
|
3491
4585
|
this.showMessage(res.error, 'error');
|
|
3492
4586
|
return;
|
|
3493
4587
|
}
|
|
3494
|
-
this.
|
|
4588
|
+
const successLabel = this.agentsContext === 'openclaw-workspace'
|
|
4589
|
+
? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
|
|
4590
|
+
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存');
|
|
4591
|
+
this.showMessage(successLabel, 'success');
|
|
3495
4592
|
this.closeAgentsModal();
|
|
3496
4593
|
} catch (e) {
|
|
3497
|
-
this.showMessage('
|
|
4594
|
+
this.showMessage('保存文件失败: ' + e.message, 'error');
|
|
3498
4595
|
} finally {
|
|
3499
4596
|
this.agentsSaving = false;
|
|
3500
4597
|
}
|
|
@@ -3780,6 +4877,763 @@
|
|
|
3780
4877
|
};
|
|
3781
4878
|
},
|
|
3782
4879
|
|
|
4880
|
+
getOpenclawParser() {
|
|
4881
|
+
if (window.JSON5 && typeof window.JSON5.parse === 'function' && typeof window.JSON5.stringify === 'function') {
|
|
4882
|
+
return {
|
|
4883
|
+
parse: window.JSON5.parse,
|
|
4884
|
+
stringify: window.JSON5.stringify
|
|
4885
|
+
};
|
|
4886
|
+
}
|
|
4887
|
+
return {
|
|
4888
|
+
parse: JSON.parse,
|
|
4889
|
+
stringify: JSON.stringify
|
|
4890
|
+
};
|
|
4891
|
+
},
|
|
4892
|
+
|
|
4893
|
+
parseOpenclawContent(content, options = {}) {
|
|
4894
|
+
const allowEmpty = !!options.allowEmpty;
|
|
4895
|
+
const raw = typeof content === 'string' ? content.trim() : '';
|
|
4896
|
+
if (!raw) {
|
|
4897
|
+
if (allowEmpty) {
|
|
4898
|
+
return { ok: true, data: {} };
|
|
4899
|
+
}
|
|
4900
|
+
return { ok: false, error: '配置内容为空' };
|
|
4901
|
+
}
|
|
4902
|
+
try {
|
|
4903
|
+
const parser = this.getOpenclawParser();
|
|
4904
|
+
const data = parser.parse(raw);
|
|
4905
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
4906
|
+
return { ok: false, error: '配置格式错误(根节点必须是对象)' };
|
|
4907
|
+
}
|
|
4908
|
+
return { ok: true, data };
|
|
4909
|
+
} catch (e) {
|
|
4910
|
+
return { ok: false, error: e.message || '解析失败' };
|
|
4911
|
+
}
|
|
4912
|
+
},
|
|
4913
|
+
|
|
4914
|
+
stringifyOpenclawConfig(data) {
|
|
4915
|
+
const parser = this.getOpenclawParser();
|
|
4916
|
+
try {
|
|
4917
|
+
return parser.stringify(data, null, 2);
|
|
4918
|
+
} catch (e) {
|
|
4919
|
+
return JSON.stringify(data, null, 2);
|
|
4920
|
+
}
|
|
4921
|
+
},
|
|
4922
|
+
|
|
4923
|
+
resetOpenclawStructured() {
|
|
4924
|
+
this.openclawStructured = {
|
|
4925
|
+
agentPrimary: '',
|
|
4926
|
+
agentFallbacks: [''],
|
|
4927
|
+
workspace: '',
|
|
4928
|
+
timeout: '',
|
|
4929
|
+
contextTokens: '',
|
|
4930
|
+
maxConcurrent: '',
|
|
4931
|
+
envItems: [{ key: '', value: '', show: false }],
|
|
4932
|
+
toolsProfile: 'default',
|
|
4933
|
+
toolsAllow: [''],
|
|
4934
|
+
toolsDeny: ['']
|
|
4935
|
+
};
|
|
4936
|
+
this.openclawAgentsList = [];
|
|
4937
|
+
this.openclawProviders = [];
|
|
4938
|
+
this.openclawMissingProviders = [];
|
|
4939
|
+
},
|
|
4940
|
+
|
|
4941
|
+
getOpenclawQuickDefaults() {
|
|
4942
|
+
return {
|
|
4943
|
+
providerName: '',
|
|
4944
|
+
baseUrl: '',
|
|
4945
|
+
apiKey: '',
|
|
4946
|
+
apiType: 'openai-responses',
|
|
4947
|
+
modelId: '',
|
|
4948
|
+
modelName: '',
|
|
4949
|
+
contextWindow: '',
|
|
4950
|
+
maxTokens: '',
|
|
4951
|
+
setPrimary: true,
|
|
4952
|
+
overrideProvider: true,
|
|
4953
|
+
overrideModels: true,
|
|
4954
|
+
showKey: false
|
|
4955
|
+
};
|
|
4956
|
+
},
|
|
4957
|
+
|
|
4958
|
+
resetOpenclawQuick() {
|
|
4959
|
+
this.openclawQuick = this.getOpenclawQuickDefaults();
|
|
4960
|
+
},
|
|
4961
|
+
|
|
4962
|
+
toggleOpenclawQuickKey() {
|
|
4963
|
+
this.openclawQuick.showKey = !this.openclawQuick.showKey;
|
|
4964
|
+
},
|
|
4965
|
+
|
|
4966
|
+
fillOpenclawQuickFromConfig(config) {
|
|
4967
|
+
const defaults = this.getOpenclawQuickDefaults();
|
|
4968
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
4969
|
+
this.openclawQuick = defaults;
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4972
|
+
|
|
4973
|
+
const agentDefaults = config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
4974
|
+
&& config.agents.defaults && typeof config.agents.defaults === 'object' && !Array.isArray(config.agents.defaults)
|
|
4975
|
+
? config.agents.defaults
|
|
4976
|
+
: {};
|
|
4977
|
+
const modelConfig = agentDefaults.model;
|
|
4978
|
+
const legacyAgent = config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)
|
|
4979
|
+
? config.agent
|
|
4980
|
+
: {};
|
|
4981
|
+
|
|
4982
|
+
let primaryRef = '';
|
|
4983
|
+
if (modelConfig && typeof modelConfig === 'object' && !Array.isArray(modelConfig) && typeof modelConfig.primary === 'string') {
|
|
4984
|
+
primaryRef = modelConfig.primary;
|
|
4985
|
+
} else if (typeof modelConfig === 'string') {
|
|
4986
|
+
primaryRef = modelConfig;
|
|
4987
|
+
}
|
|
4988
|
+
if (!primaryRef) {
|
|
4989
|
+
if (typeof legacyAgent.model === 'string') {
|
|
4990
|
+
primaryRef = legacyAgent.model;
|
|
4991
|
+
} else if (legacyAgent.model && typeof legacyAgent.model === 'object' && typeof legacyAgent.model.primary === 'string') {
|
|
4992
|
+
primaryRef = legacyAgent.model.primary;
|
|
4993
|
+
}
|
|
4994
|
+
}
|
|
4995
|
+
|
|
4996
|
+
let providerName = '';
|
|
4997
|
+
let modelId = '';
|
|
4998
|
+
if (primaryRef) {
|
|
4999
|
+
const parts = primaryRef.split('/');
|
|
5000
|
+
if (parts.length >= 2) {
|
|
5001
|
+
providerName = parts.shift().trim();
|
|
5002
|
+
modelId = parts.join('/').trim();
|
|
5003
|
+
}
|
|
5004
|
+
}
|
|
5005
|
+
|
|
5006
|
+
const providers = config.models && typeof config.models === 'object' && !Array.isArray(config.models)
|
|
5007
|
+
&& config.models.providers && typeof config.models.providers === 'object' && !Array.isArray(config.models.providers)
|
|
5008
|
+
? config.models.providers
|
|
5009
|
+
: null;
|
|
5010
|
+
let providerConfig = providerName && providers ? providers[providerName] : null;
|
|
5011
|
+
if (!providerName && providers) {
|
|
5012
|
+
const providerKeys = Object.keys(providers);
|
|
5013
|
+
if (providerKeys.length === 1) {
|
|
5014
|
+
providerName = providerKeys[0];
|
|
5015
|
+
providerConfig = providers[providerName];
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
5018
|
+
|
|
5019
|
+
let modelEntry = null;
|
|
5020
|
+
if (providerConfig && typeof providerConfig === 'object' && Array.isArray(providerConfig.models)) {
|
|
5021
|
+
if (modelId) {
|
|
5022
|
+
modelEntry = providerConfig.models.find(item => item && item.id === modelId);
|
|
5023
|
+
}
|
|
5024
|
+
if (!modelEntry && providerConfig.models.length === 1) {
|
|
5025
|
+
modelEntry = providerConfig.models[0];
|
|
5026
|
+
if (!modelId && modelEntry && typeof modelEntry.id === 'string') {
|
|
5027
|
+
modelId = modelEntry.id;
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
const baseUrl = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.baseUrl === 'string'
|
|
5033
|
+
? providerConfig.baseUrl
|
|
5034
|
+
: '';
|
|
5035
|
+
const apiKey = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.apiKey === 'string'
|
|
5036
|
+
? providerConfig.apiKey
|
|
5037
|
+
: '';
|
|
5038
|
+
const apiType = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.api === 'string'
|
|
5039
|
+
? providerConfig.api
|
|
5040
|
+
: defaults.apiType;
|
|
5041
|
+
|
|
5042
|
+
this.openclawQuick = {
|
|
5043
|
+
...defaults,
|
|
5044
|
+
providerName,
|
|
5045
|
+
baseUrl,
|
|
5046
|
+
apiKey,
|
|
5047
|
+
apiType,
|
|
5048
|
+
modelId: modelId || '',
|
|
5049
|
+
modelName: modelEntry && typeof modelEntry.name === 'string' ? modelEntry.name : '',
|
|
5050
|
+
contextWindow: modelEntry && typeof modelEntry.contextWindow === 'number'
|
|
5051
|
+
? String(modelEntry.contextWindow)
|
|
5052
|
+
: '',
|
|
5053
|
+
maxTokens: modelEntry && typeof modelEntry.maxTokens === 'number'
|
|
5054
|
+
? String(modelEntry.maxTokens)
|
|
5055
|
+
: ''
|
|
5056
|
+
};
|
|
5057
|
+
},
|
|
5058
|
+
|
|
5059
|
+
syncOpenclawQuickFromText(options = {}) {
|
|
5060
|
+
const silent = !!options.silent;
|
|
5061
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5062
|
+
if (!parsed.ok) {
|
|
5063
|
+
this.resetOpenclawQuick();
|
|
5064
|
+
if (!silent) {
|
|
5065
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5066
|
+
}
|
|
5067
|
+
return false;
|
|
5068
|
+
}
|
|
5069
|
+
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
5070
|
+
if (!silent) {
|
|
5071
|
+
this.showMessage('已从编辑器读取快速配置', 'success');
|
|
5072
|
+
}
|
|
5073
|
+
return true;
|
|
5074
|
+
},
|
|
5075
|
+
|
|
5076
|
+
mergeOpenclawModelEntry(existing, incoming, overwrite = false) {
|
|
5077
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
5078
|
+
return { ...incoming };
|
|
5079
|
+
}
|
|
5080
|
+
if (overwrite) {
|
|
5081
|
+
return { ...incoming };
|
|
5082
|
+
}
|
|
5083
|
+
const merged = { ...existing };
|
|
5084
|
+
for (const [key, value] of Object.entries(incoming || {})) {
|
|
5085
|
+
if (merged[key] === undefined || merged[key] === null || merged[key] === '') {
|
|
5086
|
+
merged[key] = value;
|
|
5087
|
+
}
|
|
5088
|
+
}
|
|
5089
|
+
return merged;
|
|
5090
|
+
},
|
|
5091
|
+
|
|
5092
|
+
fillOpenclawStructured(config) {
|
|
5093
|
+
const defaults = config && config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
5094
|
+
&& config.agents.defaults && typeof config.agents.defaults === 'object' && !Array.isArray(config.agents.defaults)
|
|
5095
|
+
? config.agents.defaults
|
|
5096
|
+
: {};
|
|
5097
|
+
const model = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
5098
|
+
? defaults.model
|
|
5099
|
+
: {};
|
|
5100
|
+
const legacyAgent = config && config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)
|
|
5101
|
+
? config.agent
|
|
5102
|
+
: {};
|
|
5103
|
+
const fallbackList = Array.isArray(model.fallbacks)
|
|
5104
|
+
? model.fallbacks.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
5105
|
+
: [];
|
|
5106
|
+
const env = config && config.env && typeof config.env === 'object' && !Array.isArray(config.env)
|
|
5107
|
+
? config.env
|
|
5108
|
+
: {};
|
|
5109
|
+
const envItems = Object.entries(env).map(([key, value]) => ({
|
|
5110
|
+
key,
|
|
5111
|
+
value: value == null ? '' : String(value),
|
|
5112
|
+
show: false
|
|
5113
|
+
}));
|
|
5114
|
+
const tools = config && config.tools && typeof config.tools === 'object' && !Array.isArray(config.tools)
|
|
5115
|
+
? config.tools
|
|
5116
|
+
: {};
|
|
5117
|
+
|
|
5118
|
+
let primary = typeof model.primary === 'string' ? model.primary : '';
|
|
5119
|
+
if (!primary) {
|
|
5120
|
+
if (typeof legacyAgent.model === 'string') {
|
|
5121
|
+
primary = legacyAgent.model;
|
|
5122
|
+
} else if (legacyAgent.model && typeof legacyAgent.model === 'object' && typeof legacyAgent.model.primary === 'string') {
|
|
5123
|
+
primary = legacyAgent.model.primary;
|
|
5124
|
+
}
|
|
5125
|
+
}
|
|
5126
|
+
|
|
5127
|
+
this.openclawStructured = {
|
|
5128
|
+
agentPrimary: primary,
|
|
5129
|
+
agentFallbacks: fallbackList.length ? fallbackList : [''],
|
|
5130
|
+
workspace: typeof defaults.workspace === 'string' ? defaults.workspace : '',
|
|
5131
|
+
timeout: typeof defaults.timeout === 'number' && Number.isFinite(defaults.timeout)
|
|
5132
|
+
? String(defaults.timeout)
|
|
5133
|
+
: '',
|
|
5134
|
+
contextTokens: typeof defaults.contextTokens === 'number' && Number.isFinite(defaults.contextTokens)
|
|
5135
|
+
? String(defaults.contextTokens)
|
|
5136
|
+
: '',
|
|
5137
|
+
maxConcurrent: typeof defaults.maxConcurrent === 'number' && Number.isFinite(defaults.maxConcurrent)
|
|
5138
|
+
? String(defaults.maxConcurrent)
|
|
5139
|
+
: '',
|
|
5140
|
+
envItems: envItems.length ? envItems : [{ key: '', value: '', show: false }],
|
|
5141
|
+
toolsProfile: typeof tools.profile === 'string' && tools.profile.trim() ? tools.profile : 'default',
|
|
5142
|
+
toolsAllow: Array.isArray(tools.allow) && tools.allow.length
|
|
5143
|
+
? tools.allow.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
5144
|
+
: [''],
|
|
5145
|
+
toolsDeny: Array.isArray(tools.deny) && tools.deny.length
|
|
5146
|
+
? tools.deny.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
5147
|
+
: ['']
|
|
5148
|
+
};
|
|
5149
|
+
},
|
|
5150
|
+
|
|
5151
|
+
syncOpenclawStructuredFromText(options = {}) {
|
|
5152
|
+
const silent = !!options.silent;
|
|
5153
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5154
|
+
if (!parsed.ok) {
|
|
5155
|
+
this.resetOpenclawStructured();
|
|
5156
|
+
this.resetOpenclawQuick();
|
|
5157
|
+
if (!silent) {
|
|
5158
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5159
|
+
}
|
|
5160
|
+
return false;
|
|
5161
|
+
}
|
|
5162
|
+
this.fillOpenclawStructured(parsed.data);
|
|
5163
|
+
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
5164
|
+
this.refreshOpenclawProviders(parsed.data);
|
|
5165
|
+
this.refreshOpenclawAgentsList(parsed.data);
|
|
5166
|
+
if (!silent) {
|
|
5167
|
+
this.showMessage('已从文本刷新结构化配置', 'success');
|
|
5168
|
+
}
|
|
5169
|
+
return true;
|
|
5170
|
+
},
|
|
5171
|
+
|
|
5172
|
+
getOpenclawActiveProviders(config) {
|
|
5173
|
+
const active = new Set();
|
|
5174
|
+
const addProvider = (ref) => {
|
|
5175
|
+
if (typeof ref !== 'string') return;
|
|
5176
|
+
const text = ref.trim();
|
|
5177
|
+
if (!text) return;
|
|
5178
|
+
const parts = text.split('/');
|
|
5179
|
+
if (parts.length < 2) return;
|
|
5180
|
+
const provider = parts[0].trim();
|
|
5181
|
+
if (provider) active.add(provider);
|
|
5182
|
+
};
|
|
5183
|
+
const defaults = config && config.agents && config.agents.defaults
|
|
5184
|
+
? config.agents.defaults
|
|
5185
|
+
: {};
|
|
5186
|
+
const model = defaults && defaults.model;
|
|
5187
|
+
if (model && typeof model === 'object' && !Array.isArray(model)) {
|
|
5188
|
+
addProvider(model.primary);
|
|
5189
|
+
if (Array.isArray(model.fallbacks)) {
|
|
5190
|
+
for (const item of model.fallbacks) {
|
|
5191
|
+
addProvider(item);
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
} else if (typeof model === 'string') {
|
|
5195
|
+
addProvider(model);
|
|
5196
|
+
}
|
|
5197
|
+
const modelsDefaults = config && config.models && config.models.defaults
|
|
5198
|
+
? config.models.defaults
|
|
5199
|
+
: {};
|
|
5200
|
+
if (modelsDefaults && typeof modelsDefaults.provider === 'string' && modelsDefaults.provider.trim()) {
|
|
5201
|
+
active.add(modelsDefaults.provider.trim());
|
|
5202
|
+
}
|
|
5203
|
+
if (modelsDefaults && typeof modelsDefaults.model === 'string') {
|
|
5204
|
+
addProvider(modelsDefaults.model);
|
|
5205
|
+
}
|
|
5206
|
+
return active;
|
|
5207
|
+
},
|
|
5208
|
+
|
|
5209
|
+
maskProviderValue(value) {
|
|
5210
|
+
const text = value == null ? '' : String(value);
|
|
5211
|
+
if (!text) return '****';
|
|
5212
|
+
if (text.length <= 6) return '****';
|
|
5213
|
+
return `${text.slice(0, 3)}****${text.slice(-3)}`;
|
|
5214
|
+
},
|
|
5215
|
+
|
|
5216
|
+
formatProviderValue(key, value) {
|
|
5217
|
+
if (typeof value === 'undefined' || value === null) {
|
|
5218
|
+
return '';
|
|
5219
|
+
}
|
|
5220
|
+
let text = '';
|
|
5221
|
+
if (typeof value === 'string') {
|
|
5222
|
+
text = value;
|
|
5223
|
+
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
5224
|
+
text = String(value);
|
|
5225
|
+
} else {
|
|
5226
|
+
try {
|
|
5227
|
+
text = JSON.stringify(value);
|
|
5228
|
+
} catch (_) {
|
|
5229
|
+
text = String(value);
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
if (!text) return '';
|
|
5233
|
+
if (/key|token|secret|password/i.test(key)) {
|
|
5234
|
+
return this.maskProviderValue(text);
|
|
5235
|
+
}
|
|
5236
|
+
if (text.length > 160) {
|
|
5237
|
+
return `${text.slice(0, 157)}...`;
|
|
5238
|
+
}
|
|
5239
|
+
return text;
|
|
5240
|
+
},
|
|
5241
|
+
|
|
5242
|
+
collectOpenclawProviders(source, providerMap, activeProviders, entries) {
|
|
5243
|
+
if (!providerMap || typeof providerMap !== 'object' || Array.isArray(providerMap)) {
|
|
5244
|
+
return;
|
|
5245
|
+
}
|
|
5246
|
+
const keys = Object.keys(providerMap).sort();
|
|
5247
|
+
for (const key of keys) {
|
|
5248
|
+
const value = providerMap[key];
|
|
5249
|
+
const fields = [];
|
|
5250
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
5251
|
+
const fieldKeys = Object.keys(value).sort();
|
|
5252
|
+
for (const fieldKey of fieldKeys) {
|
|
5253
|
+
const fieldValue = this.formatProviderValue(fieldKey, value[fieldKey]);
|
|
5254
|
+
if (fieldValue === '') continue;
|
|
5255
|
+
fields.push({ key: fieldKey, value: fieldValue });
|
|
5256
|
+
}
|
|
5257
|
+
} else {
|
|
5258
|
+
const fieldValue = this.formatProviderValue('value', value);
|
|
5259
|
+
if (fieldValue !== '') {
|
|
5260
|
+
fields.push({ key: 'value', value: fieldValue });
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5263
|
+
entries.push({
|
|
5264
|
+
key,
|
|
5265
|
+
source,
|
|
5266
|
+
fields,
|
|
5267
|
+
isActive: activeProviders.has(key)
|
|
5268
|
+
});
|
|
5269
|
+
}
|
|
5270
|
+
},
|
|
5271
|
+
|
|
5272
|
+
refreshOpenclawProviders(config) {
|
|
5273
|
+
const activeProviders = this.getOpenclawActiveProviders(config || {});
|
|
5274
|
+
const entries = [];
|
|
5275
|
+
const modelsProviders = config && config.models ? config.models.providers : null;
|
|
5276
|
+
const rootProviders = config && config.providers ? config.providers : null;
|
|
5277
|
+
this.collectOpenclawProviders('models.providers', modelsProviders, activeProviders, entries);
|
|
5278
|
+
this.collectOpenclawProviders('providers', rootProviders, activeProviders, entries);
|
|
5279
|
+
const existing = new Set(entries.map(item => item.key));
|
|
5280
|
+
const missing = [];
|
|
5281
|
+
for (const provider of activeProviders) {
|
|
5282
|
+
if (!existing.has(provider)) {
|
|
5283
|
+
missing.push(provider);
|
|
5284
|
+
}
|
|
5285
|
+
}
|
|
5286
|
+
this.openclawProviders = entries;
|
|
5287
|
+
this.openclawMissingProviders = missing;
|
|
5288
|
+
},
|
|
5289
|
+
|
|
5290
|
+
refreshOpenclawAgentsList(config) {
|
|
5291
|
+
const list = config && config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
5292
|
+
? config.agents.list
|
|
5293
|
+
: null;
|
|
5294
|
+
if (!Array.isArray(list)) {
|
|
5295
|
+
this.openclawAgentsList = [];
|
|
5296
|
+
return;
|
|
5297
|
+
}
|
|
5298
|
+
const entries = [];
|
|
5299
|
+
list.forEach((item, index) => {
|
|
5300
|
+
if (!item || typeof item !== 'object') return;
|
|
5301
|
+
const id = typeof item.id === 'string' && item.id.trim() ? item.id.trim() : `agent-${index + 1}`;
|
|
5302
|
+
const identity = item.identity && typeof item.identity === 'object' && !Array.isArray(item.identity)
|
|
5303
|
+
? item.identity
|
|
5304
|
+
: {};
|
|
5305
|
+
const name = typeof identity.name === 'string' && identity.name.trim()
|
|
5306
|
+
? identity.name.trim()
|
|
5307
|
+
: id;
|
|
5308
|
+
entries.push({
|
|
5309
|
+
key: `${id}-${index}`,
|
|
5310
|
+
id,
|
|
5311
|
+
name,
|
|
5312
|
+
theme: typeof identity.theme === 'string' ? identity.theme : '',
|
|
5313
|
+
emoji: typeof identity.emoji === 'string' ? identity.emoji : '',
|
|
5314
|
+
avatar: typeof identity.avatar === 'string' ? identity.avatar : ''
|
|
5315
|
+
});
|
|
5316
|
+
});
|
|
5317
|
+
this.openclawAgentsList = entries;
|
|
5318
|
+
},
|
|
5319
|
+
|
|
5320
|
+
normalizeStringList(list) {
|
|
5321
|
+
if (!Array.isArray(list)) return [];
|
|
5322
|
+
const result = [];
|
|
5323
|
+
const seen = new Set();
|
|
5324
|
+
for (const item of list) {
|
|
5325
|
+
const value = typeof item === 'string' ? item.trim() : String(item || '').trim();
|
|
5326
|
+
if (!value) continue;
|
|
5327
|
+
const key = value;
|
|
5328
|
+
if (seen.has(key)) continue;
|
|
5329
|
+
seen.add(key);
|
|
5330
|
+
result.push(value);
|
|
5331
|
+
}
|
|
5332
|
+
return result;
|
|
5333
|
+
},
|
|
5334
|
+
|
|
5335
|
+
normalizeEnvItems(items) {
|
|
5336
|
+
if (!Array.isArray(items)) {
|
|
5337
|
+
return { ok: true, items: {} };
|
|
5338
|
+
}
|
|
5339
|
+
const output = {};
|
|
5340
|
+
const seen = new Set();
|
|
5341
|
+
for (const item of items) {
|
|
5342
|
+
const key = item && typeof item.key === 'string' ? item.key.trim() : '';
|
|
5343
|
+
if (!key) continue;
|
|
5344
|
+
if (seen.has(key)) {
|
|
5345
|
+
return { ok: false, error: `环境变量重复: ${key}` };
|
|
5346
|
+
}
|
|
5347
|
+
seen.add(key);
|
|
5348
|
+
const value = item && typeof item.value !== 'undefined' ? String(item.value) : '';
|
|
5349
|
+
output[key] = value;
|
|
5350
|
+
}
|
|
5351
|
+
return { ok: true, items: output };
|
|
5352
|
+
},
|
|
5353
|
+
|
|
5354
|
+
parseOptionalNumber(value, label) {
|
|
5355
|
+
const text = typeof value === 'string' ? value.trim() : String(value || '').trim();
|
|
5356
|
+
if (!text) {
|
|
5357
|
+
return { ok: true, value: null };
|
|
5358
|
+
}
|
|
5359
|
+
const num = Number(text);
|
|
5360
|
+
if (!Number.isFinite(num) || num < 0) {
|
|
5361
|
+
return { ok: false, error: `${label} 请输入有效数字` };
|
|
5362
|
+
}
|
|
5363
|
+
return { ok: true, value: num };
|
|
5364
|
+
},
|
|
5365
|
+
|
|
5366
|
+
applyOpenclawStructuredToText() {
|
|
5367
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5368
|
+
if (!parsed.ok) {
|
|
5369
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5370
|
+
return;
|
|
5371
|
+
}
|
|
5372
|
+
|
|
5373
|
+
const config = parsed.data;
|
|
5374
|
+
const agents = config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
5375
|
+
? config.agents
|
|
5376
|
+
: {};
|
|
5377
|
+
const defaults = agents.defaults && typeof agents.defaults === 'object' && !Array.isArray(agents.defaults)
|
|
5378
|
+
? agents.defaults
|
|
5379
|
+
: {};
|
|
5380
|
+
const model = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
5381
|
+
? defaults.model
|
|
5382
|
+
: {};
|
|
5383
|
+
|
|
5384
|
+
const primary = (this.openclawStructured.agentPrimary || '').trim();
|
|
5385
|
+
const fallbacks = this.normalizeStringList(this.openclawStructured.agentFallbacks);
|
|
5386
|
+
if (primary) {
|
|
5387
|
+
model.primary = primary;
|
|
5388
|
+
}
|
|
5389
|
+
if (fallbacks.length) {
|
|
5390
|
+
model.fallbacks = fallbacks;
|
|
5391
|
+
}
|
|
5392
|
+
if (primary || fallbacks.length) {
|
|
5393
|
+
defaults.model = model;
|
|
5394
|
+
}
|
|
5395
|
+
if (primary && config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)) {
|
|
5396
|
+
config.agent.model = primary;
|
|
5397
|
+
}
|
|
5398
|
+
|
|
5399
|
+
const workspace = (this.openclawStructured.workspace || '').trim();
|
|
5400
|
+
if (workspace) {
|
|
5401
|
+
defaults.workspace = workspace;
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
const timeout = this.parseOptionalNumber(this.openclawStructured.timeout, 'Timeout');
|
|
5405
|
+
if (!timeout.ok) {
|
|
5406
|
+
this.showMessage(timeout.error, 'error');
|
|
5407
|
+
return;
|
|
5408
|
+
}
|
|
5409
|
+
if (timeout.value !== null) {
|
|
5410
|
+
defaults.timeout = timeout.value;
|
|
5411
|
+
}
|
|
5412
|
+
|
|
5413
|
+
const contextTokens = this.parseOptionalNumber(this.openclawStructured.contextTokens, 'Context Tokens');
|
|
5414
|
+
if (!contextTokens.ok) {
|
|
5415
|
+
this.showMessage(contextTokens.error, 'error');
|
|
5416
|
+
return;
|
|
5417
|
+
}
|
|
5418
|
+
if (contextTokens.value !== null) {
|
|
5419
|
+
defaults.contextTokens = contextTokens.value;
|
|
5420
|
+
}
|
|
5421
|
+
|
|
5422
|
+
const maxConcurrent = this.parseOptionalNumber(this.openclawStructured.maxConcurrent, 'Max Concurrent');
|
|
5423
|
+
if (!maxConcurrent.ok) {
|
|
5424
|
+
this.showMessage(maxConcurrent.error, 'error');
|
|
5425
|
+
return;
|
|
5426
|
+
}
|
|
5427
|
+
if (maxConcurrent.value !== null) {
|
|
5428
|
+
defaults.maxConcurrent = maxConcurrent.value;
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5431
|
+
if (Object.keys(defaults).length > 0) {
|
|
5432
|
+
config.agents = agents;
|
|
5433
|
+
config.agents.defaults = defaults;
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
const envResult = this.normalizeEnvItems(this.openclawStructured.envItems);
|
|
5437
|
+
if (!envResult.ok) {
|
|
5438
|
+
this.showMessage(envResult.error, 'error');
|
|
5439
|
+
return;
|
|
5440
|
+
}
|
|
5441
|
+
if (Object.keys(envResult.items).length > 0) {
|
|
5442
|
+
config.env = envResult.items;
|
|
5443
|
+
} else if (config.env) {
|
|
5444
|
+
delete config.env;
|
|
5445
|
+
}
|
|
5446
|
+
|
|
5447
|
+
const profile = (this.openclawStructured.toolsProfile || '').trim();
|
|
5448
|
+
const allowList = this.normalizeStringList(this.openclawStructured.toolsAllow);
|
|
5449
|
+
const denyList = this.normalizeStringList(this.openclawStructured.toolsDeny);
|
|
5450
|
+
const hasTools = profile || allowList.length || denyList.length || (config.tools && typeof config.tools === 'object');
|
|
5451
|
+
if (hasTools) {
|
|
5452
|
+
const tools = config.tools && typeof config.tools === 'object' && !Array.isArray(config.tools)
|
|
5453
|
+
? config.tools
|
|
5454
|
+
: {};
|
|
5455
|
+
tools.profile = profile || tools.profile || 'default';
|
|
5456
|
+
tools.allow = allowList;
|
|
5457
|
+
tools.deny = denyList;
|
|
5458
|
+
config.tools = tools;
|
|
5459
|
+
}
|
|
5460
|
+
|
|
5461
|
+
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
5462
|
+
this.refreshOpenclawProviders(config);
|
|
5463
|
+
this.refreshOpenclawAgentsList(config);
|
|
5464
|
+
this.fillOpenclawQuickFromConfig(config);
|
|
5465
|
+
this.showMessage('已写入编辑器', 'success');
|
|
5466
|
+
},
|
|
5467
|
+
|
|
5468
|
+
applyOpenclawQuickToText() {
|
|
5469
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5470
|
+
if (!parsed.ok) {
|
|
5471
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5472
|
+
return;
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
const providerName = (this.openclawQuick.providerName || '').trim();
|
|
5476
|
+
const modelId = (this.openclawQuick.modelId || '').trim();
|
|
5477
|
+
if (!providerName) {
|
|
5478
|
+
this.showMessage('请填写 Provider 名称', 'error');
|
|
5479
|
+
return;
|
|
5480
|
+
}
|
|
5481
|
+
if (providerName.includes('/')) {
|
|
5482
|
+
this.showMessage('Provider 名称不能包含 "/"', 'error');
|
|
5483
|
+
return;
|
|
5484
|
+
}
|
|
5485
|
+
if (!modelId) {
|
|
5486
|
+
this.showMessage('请填写模型 ID', 'error');
|
|
5487
|
+
return;
|
|
5488
|
+
}
|
|
5489
|
+
|
|
5490
|
+
const config = parsed.data;
|
|
5491
|
+
const ensureObject = (value) => (value && typeof value === 'object' && !Array.isArray(value)) ? value : {};
|
|
5492
|
+
const models = ensureObject(config.models);
|
|
5493
|
+
const providers = ensureObject(models.providers);
|
|
5494
|
+
const provider = ensureObject(providers[providerName]);
|
|
5495
|
+
const baseUrl = (this.openclawQuick.baseUrl || '').trim();
|
|
5496
|
+
if (!baseUrl && !provider.baseUrl) {
|
|
5497
|
+
this.showMessage('请填写 Base URL', 'error');
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
|
|
5501
|
+
const contextWindow = this.parseOptionalNumber(this.openclawQuick.contextWindow, '上下文长度');
|
|
5502
|
+
if (!contextWindow.ok) {
|
|
5503
|
+
this.showMessage(contextWindow.error, 'error');
|
|
5504
|
+
return;
|
|
5505
|
+
}
|
|
5506
|
+
const maxTokens = this.parseOptionalNumber(this.openclawQuick.maxTokens, '最大输出');
|
|
5507
|
+
if (!maxTokens.ok) {
|
|
5508
|
+
this.showMessage(maxTokens.error, 'error');
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
|
|
5512
|
+
const shouldOverrideProvider = !!this.openclawQuick.overrideProvider;
|
|
5513
|
+
const apiKey = (this.openclawQuick.apiKey || '').trim();
|
|
5514
|
+
const apiType = (this.openclawQuick.apiType || '').trim();
|
|
5515
|
+
const setProviderField = (key, value) => {
|
|
5516
|
+
if (!value) return;
|
|
5517
|
+
if (shouldOverrideProvider || provider[key] === undefined || provider[key] === null || provider[key] === '') {
|
|
5518
|
+
provider[key] = value;
|
|
5519
|
+
}
|
|
5520
|
+
};
|
|
5521
|
+
setProviderField('baseUrl', baseUrl);
|
|
5522
|
+
setProviderField('api', apiType);
|
|
5523
|
+
if (apiKey) {
|
|
5524
|
+
setProviderField('apiKey', apiKey);
|
|
5525
|
+
}
|
|
5526
|
+
|
|
5527
|
+
const modelName = (this.openclawQuick.modelName || '').trim() || modelId;
|
|
5528
|
+
const modelEntry = {
|
|
5529
|
+
id: modelId,
|
|
5530
|
+
name: modelName,
|
|
5531
|
+
reasoning: false,
|
|
5532
|
+
input: ['text'],
|
|
5533
|
+
cost: {
|
|
5534
|
+
input: 0,
|
|
5535
|
+
output: 0,
|
|
5536
|
+
cacheRead: 0,
|
|
5537
|
+
cacheWrite: 0
|
|
5538
|
+
}
|
|
5539
|
+
};
|
|
5540
|
+
if (contextWindow.value !== null) {
|
|
5541
|
+
modelEntry.contextWindow = contextWindow.value;
|
|
5542
|
+
}
|
|
5543
|
+
if (maxTokens.value !== null) {
|
|
5544
|
+
modelEntry.maxTokens = maxTokens.value;
|
|
5545
|
+
}
|
|
5546
|
+
|
|
5547
|
+
const existingModels = Array.isArray(provider.models) ? [...provider.models] : [];
|
|
5548
|
+
if (this.openclawQuick.overrideModels || existingModels.length === 0) {
|
|
5549
|
+
provider.models = [modelEntry];
|
|
5550
|
+
} else {
|
|
5551
|
+
const idx = existingModels.findIndex(item => item && item.id === modelId);
|
|
5552
|
+
if (idx >= 0) {
|
|
5553
|
+
existingModels[idx] = this.mergeOpenclawModelEntry(existingModels[idx], modelEntry, false);
|
|
5554
|
+
} else {
|
|
5555
|
+
existingModels.push(modelEntry);
|
|
5556
|
+
}
|
|
5557
|
+
provider.models = existingModels;
|
|
5558
|
+
}
|
|
5559
|
+
|
|
5560
|
+
providers[providerName] = provider;
|
|
5561
|
+
models.providers = providers;
|
|
5562
|
+
config.models = models;
|
|
5563
|
+
|
|
5564
|
+
if (this.openclawQuick.setPrimary) {
|
|
5565
|
+
const agents = ensureObject(config.agents);
|
|
5566
|
+
const defaults = ensureObject(agents.defaults);
|
|
5567
|
+
const modelConfig = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
5568
|
+
? defaults.model
|
|
5569
|
+
: {};
|
|
5570
|
+
modelConfig.primary = `${providerName}/${modelId}`;
|
|
5571
|
+
defaults.model = modelConfig;
|
|
5572
|
+
agents.defaults = defaults;
|
|
5573
|
+
config.agents = agents;
|
|
5574
|
+
if (config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)) {
|
|
5575
|
+
config.agent.model = modelConfig.primary;
|
|
5576
|
+
}
|
|
5577
|
+
}
|
|
5578
|
+
|
|
5579
|
+
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
5580
|
+
this.fillOpenclawStructured(config);
|
|
5581
|
+
this.refreshOpenclawProviders(config);
|
|
5582
|
+
this.refreshOpenclawAgentsList(config);
|
|
5583
|
+
this.showMessage('快速配置已写入编辑器', 'success');
|
|
5584
|
+
},
|
|
5585
|
+
|
|
5586
|
+
addOpenclawFallback() {
|
|
5587
|
+
this.openclawStructured.agentFallbacks.push('');
|
|
5588
|
+
},
|
|
5589
|
+
|
|
5590
|
+
removeOpenclawFallback(index) {
|
|
5591
|
+
this.openclawStructured.agentFallbacks.splice(index, 1);
|
|
5592
|
+
if (this.openclawStructured.agentFallbacks.length === 0) {
|
|
5593
|
+
this.openclawStructured.agentFallbacks.push('');
|
|
5594
|
+
}
|
|
5595
|
+
},
|
|
5596
|
+
|
|
5597
|
+
addOpenclawEnvItem() {
|
|
5598
|
+
this.openclawStructured.envItems.push({ key: '', value: '', show: false });
|
|
5599
|
+
},
|
|
5600
|
+
|
|
5601
|
+
removeOpenclawEnvItem(index) {
|
|
5602
|
+
this.openclawStructured.envItems.splice(index, 1);
|
|
5603
|
+
if (this.openclawStructured.envItems.length === 0) {
|
|
5604
|
+
this.openclawStructured.envItems.push({ key: '', value: '', show: false });
|
|
5605
|
+
}
|
|
5606
|
+
},
|
|
5607
|
+
|
|
5608
|
+
toggleOpenclawEnvItem(index) {
|
|
5609
|
+
const item = this.openclawStructured.envItems[index];
|
|
5610
|
+
if (item) {
|
|
5611
|
+
item.show = !item.show;
|
|
5612
|
+
}
|
|
5613
|
+
},
|
|
5614
|
+
|
|
5615
|
+
addOpenclawToolsAllow() {
|
|
5616
|
+
this.openclawStructured.toolsAllow.push('');
|
|
5617
|
+
},
|
|
5618
|
+
|
|
5619
|
+
removeOpenclawToolsAllow(index) {
|
|
5620
|
+
this.openclawStructured.toolsAllow.splice(index, 1);
|
|
5621
|
+
if (this.openclawStructured.toolsAllow.length === 0) {
|
|
5622
|
+
this.openclawStructured.toolsAllow.push('');
|
|
5623
|
+
}
|
|
5624
|
+
},
|
|
5625
|
+
|
|
5626
|
+
addOpenclawToolsDeny() {
|
|
5627
|
+
this.openclawStructured.toolsDeny.push('');
|
|
5628
|
+
},
|
|
5629
|
+
|
|
5630
|
+
removeOpenclawToolsDeny(index) {
|
|
5631
|
+
this.openclawStructured.toolsDeny.splice(index, 1);
|
|
5632
|
+
if (this.openclawStructured.toolsDeny.length === 0) {
|
|
5633
|
+
this.openclawStructured.toolsDeny.push('');
|
|
5634
|
+
}
|
|
5635
|
+
},
|
|
5636
|
+
|
|
3783
5637
|
openclawHasContent(config) {
|
|
3784
5638
|
return !!(config && typeof config.content === 'string' && config.content.trim());
|
|
3785
5639
|
},
|
|
@@ -3800,23 +5654,24 @@
|
|
|
3800
5654
|
this.openclawEditorTitle = '添加 OpenClaw 配置';
|
|
3801
5655
|
this.openclawEditing = {
|
|
3802
5656
|
name: '',
|
|
3803
|
-
content:
|
|
5657
|
+
content: '',
|
|
3804
5658
|
lockName: false
|
|
3805
5659
|
};
|
|
3806
5660
|
this.openclawConfigPath = '';
|
|
3807
5661
|
this.openclawConfigExists = false;
|
|
3808
5662
|
this.openclawLineEnding = '\n';
|
|
5663
|
+
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
3809
5664
|
this.showOpenclawConfigModal = true;
|
|
3810
5665
|
},
|
|
3811
5666
|
|
|
3812
5667
|
openOpenclawEditModal(name) {
|
|
3813
|
-
const config = this.openclawConfigs[name];
|
|
3814
5668
|
this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
|
|
3815
5669
|
this.openclawEditing = {
|
|
3816
5670
|
name,
|
|
3817
|
-
content:
|
|
5671
|
+
content: '',
|
|
3818
5672
|
lockName: true
|
|
3819
5673
|
};
|
|
5674
|
+
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
3820
5675
|
this.showOpenclawConfigModal = true;
|
|
3821
5676
|
},
|
|
3822
5677
|
|
|
@@ -3825,27 +5680,41 @@
|
|
|
3825
5680
|
this.openclawEditing = { name: '', content: '', lockName: false };
|
|
3826
5681
|
this.openclawSaving = false;
|
|
3827
5682
|
this.openclawApplying = false;
|
|
5683
|
+
this.resetOpenclawStructured();
|
|
5684
|
+
this.resetOpenclawQuick();
|
|
3828
5685
|
},
|
|
3829
5686
|
|
|
3830
|
-
async loadOpenclawConfigFromFile() {
|
|
5687
|
+
async loadOpenclawConfigFromFile(options = {}) {
|
|
5688
|
+
const silent = !!options.silent;
|
|
5689
|
+
const force = !!options.force;
|
|
5690
|
+
const fallbackToTemplate = options.fallbackToTemplate !== false;
|
|
3831
5691
|
this.openclawFileLoading = true;
|
|
3832
5692
|
try {
|
|
3833
5693
|
const res = await api('get-openclaw-config');
|
|
3834
5694
|
if (res.error) {
|
|
3835
|
-
|
|
5695
|
+
if (!silent) {
|
|
5696
|
+
this.showMessage(res.error, 'error');
|
|
5697
|
+
}
|
|
3836
5698
|
return;
|
|
3837
5699
|
}
|
|
3838
5700
|
this.openclawConfigPath = res.path || '';
|
|
3839
5701
|
this.openclawConfigExists = !!res.exists;
|
|
3840
5702
|
this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3841
|
-
|
|
5703
|
+
const hasContent = !!(res.content && res.content.trim());
|
|
5704
|
+
const shouldOverride = force || !this.openclawEditing.content || !this.openclawEditing.content.trim();
|
|
5705
|
+
if (hasContent && shouldOverride) {
|
|
3842
5706
|
this.openclawEditing.content = res.content;
|
|
3843
|
-
} else if (!
|
|
5707
|
+
} else if (!hasContent && shouldOverride && fallbackToTemplate) {
|
|
3844
5708
|
this.openclawEditing.content = DEFAULT_OPENCLAW_TEMPLATE;
|
|
3845
5709
|
}
|
|
3846
|
-
this.
|
|
5710
|
+
this.syncOpenclawStructuredFromText({ silent: true });
|
|
5711
|
+
if (!silent) {
|
|
5712
|
+
this.showMessage('已加载当前 OpenClaw 配置', 'success');
|
|
5713
|
+
}
|
|
3847
5714
|
} catch (e) {
|
|
3848
|
-
|
|
5715
|
+
if (!silent) {
|
|
5716
|
+
this.showMessage('加载 OpenClaw 配置失败: ' + e.message, 'error');
|
|
5717
|
+
}
|
|
3849
5718
|
} finally {
|
|
3850
5719
|
this.openclawFileLoading = false;
|
|
3851
5720
|
}
|
|
@@ -3954,19 +5823,93 @@
|
|
|
3954
5823
|
return `${ms}ms`;
|
|
3955
5824
|
},
|
|
3956
5825
|
|
|
3957
|
-
|
|
3958
|
-
if (!name ||
|
|
5826
|
+
buildSpeedTestIssue(name, result) {
|
|
5827
|
+
if (!name || !result) return null;
|
|
5828
|
+
if (result.error) {
|
|
5829
|
+
const error = String(result.error || '');
|
|
5830
|
+
const errorLower = error.toLowerCase();
|
|
5831
|
+
if (error === 'Provider not found') {
|
|
5832
|
+
return {
|
|
5833
|
+
code: 'remote-speedtest-provider-missing',
|
|
5834
|
+
message: `提供商 ${name} 未找到,无法测速`,
|
|
5835
|
+
suggestion: '检查配置是否存在该 provider'
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5838
|
+
if (error === 'Provider missing URL' || error === 'Missing name or url') {
|
|
5839
|
+
return {
|
|
5840
|
+
code: 'remote-speedtest-baseurl-missing',
|
|
5841
|
+
message: `提供商 ${name} 缺少 base_url`,
|
|
5842
|
+
suggestion: '补全 base_url 后重试'
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5845
|
+
if (errorLower.includes('invalid url')) {
|
|
5846
|
+
return {
|
|
5847
|
+
code: 'remote-speedtest-invalid-url',
|
|
5848
|
+
message: `提供商 ${name} 的 base_url 无效`,
|
|
5849
|
+
suggestion: '请设置为 http/https 的完整 URL'
|
|
5850
|
+
};
|
|
5851
|
+
}
|
|
5852
|
+
if (errorLower.includes('timeout')) {
|
|
5853
|
+
return {
|
|
5854
|
+
code: 'remote-speedtest-timeout',
|
|
5855
|
+
message: `提供商 ${name} 远程测速超时`,
|
|
5856
|
+
suggestion: '检查网络或 base_url 是否可达'
|
|
5857
|
+
};
|
|
5858
|
+
}
|
|
5859
|
+
return {
|
|
5860
|
+
code: 'remote-speedtest-unreachable',
|
|
5861
|
+
message: `提供商 ${name} 远程测速失败:${error || '无法连接'}`,
|
|
5862
|
+
suggestion: '检查网络或 base_url 是否可用'
|
|
5863
|
+
};
|
|
5864
|
+
}
|
|
5865
|
+
|
|
5866
|
+
const status = typeof result.status === 'number' ? result.status : 0;
|
|
5867
|
+
if (status === 401 || status === 403) {
|
|
5868
|
+
return {
|
|
5869
|
+
code: 'remote-speedtest-auth-failed',
|
|
5870
|
+
message: `提供商 ${name} 远程测速鉴权失败(401/403)`,
|
|
5871
|
+
suggestion: '检查 API Key 或认证方式'
|
|
5872
|
+
};
|
|
5873
|
+
}
|
|
5874
|
+
if (status >= 400) {
|
|
5875
|
+
return {
|
|
5876
|
+
code: 'remote-speedtest-http-error',
|
|
5877
|
+
message: `提供商 ${name} 远程测速返回异常状态: ${status}`,
|
|
5878
|
+
suggestion: '检查 base_url 或服务状态'
|
|
5879
|
+
};
|
|
5880
|
+
}
|
|
5881
|
+
return null;
|
|
5882
|
+
},
|
|
5883
|
+
|
|
5884
|
+
async runSpeedTest(name, options = {}) {
|
|
5885
|
+
if (!name || this.speedLoading[name]) return null;
|
|
5886
|
+
const silent = !!options.silent;
|
|
3959
5887
|
this.speedLoading[name] = true;
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
5888
|
+
try {
|
|
5889
|
+
const res = await api('speed-test', { name });
|
|
5890
|
+
if (res.error) {
|
|
5891
|
+
this.speedResults[name] = { ok: false, error: res.error };
|
|
5892
|
+
if (!silent) {
|
|
5893
|
+
this.showMessage(res.error, 'error');
|
|
5894
|
+
}
|
|
5895
|
+
return { ok: false, error: res.error };
|
|
5896
|
+
}
|
|
3965
5897
|
this.speedResults[name] = res;
|
|
3966
|
-
|
|
3967
|
-
|
|
5898
|
+
if (!silent) {
|
|
5899
|
+
const status = res.status ? ` (${res.status})` : '';
|
|
5900
|
+
this.showMessage(`Speed ${name}: ${this.formatLatency(res)}${status}`, 'success');
|
|
5901
|
+
}
|
|
5902
|
+
return res;
|
|
5903
|
+
} catch (e) {
|
|
5904
|
+
const message = e && e.message ? e.message : 'Speed test failed';
|
|
5905
|
+
this.speedResults[name] = { ok: false, error: message };
|
|
5906
|
+
if (!silent) {
|
|
5907
|
+
this.showMessage(message, 'error');
|
|
5908
|
+
}
|
|
5909
|
+
return { ok: false, error: message };
|
|
5910
|
+
} finally {
|
|
5911
|
+
this.speedLoading[name] = false;
|
|
3968
5912
|
}
|
|
3969
|
-
this.speedLoading[name] = false;
|
|
3970
5913
|
},
|
|
3971
5914
|
|
|
3972
5915
|
showMessage(text, type) {
|