codexmate 0.0.5 → 0.0.6
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 +1132 -293
- 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 +2151 -210
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>
|
|
@@ -2646,13 +3360,22 @@
|
|
|
2646
3360
|
},
|
|
2647
3361
|
sessionPathRequestSeq: 0,
|
|
2648
3362
|
sessionExporting: {},
|
|
3363
|
+
sessionCloning: {},
|
|
3364
|
+
sessionDeleting: {},
|
|
2649
3365
|
activeSession: null,
|
|
2650
3366
|
activeSessionMessages: [],
|
|
2651
3367
|
activeSessionDetailError: '',
|
|
2652
3368
|
activeSessionDetailClipped: false,
|
|
2653
|
-
sessionDetailLoading: false,
|
|
2654
|
-
sessionDetailRequestSeq: 0,
|
|
2655
|
-
|
|
3369
|
+
sessionDetailLoading: false,
|
|
3370
|
+
sessionDetailRequestSeq: 0,
|
|
3371
|
+
sessionStandalone: false,
|
|
3372
|
+
sessionStandaloneError: '',
|
|
3373
|
+
sessionStandaloneText: '',
|
|
3374
|
+
sessionStandaloneTitle: '',
|
|
3375
|
+
sessionStandaloneSourceLabel: '',
|
|
3376
|
+
sessionStandaloneLoading: false,
|
|
3377
|
+
sessionStandaloneRequestSeq: 0,
|
|
3378
|
+
speedResults: {},
|
|
2656
3379
|
speedLoading: {},
|
|
2657
3380
|
newProvider: { name: '', url: '', key: '' },
|
|
2658
3381
|
editingProvider: { name: '', url: '', key: '' },
|
|
@@ -2688,6 +3411,37 @@
|
|
|
2688
3411
|
openclawFileLoading: false,
|
|
2689
3412
|
openclawSaving: false,
|
|
2690
3413
|
openclawApplying: false,
|
|
3414
|
+
openclawWorkspaceFileName: 'SOUL.md',
|
|
3415
|
+
agentsWorkspaceFileName: '',
|
|
3416
|
+
openclawStructured: {
|
|
3417
|
+
agentPrimary: '',
|
|
3418
|
+
agentFallbacks: [],
|
|
3419
|
+
workspace: '',
|
|
3420
|
+
timeout: '',
|
|
3421
|
+
contextTokens: '',
|
|
3422
|
+
maxConcurrent: '',
|
|
3423
|
+
envItems: [],
|
|
3424
|
+
toolsProfile: 'default',
|
|
3425
|
+
toolsAllow: [],
|
|
3426
|
+
toolsDeny: []
|
|
3427
|
+
},
|
|
3428
|
+
openclawQuick: {
|
|
3429
|
+
providerName: '',
|
|
3430
|
+
baseUrl: '',
|
|
3431
|
+
apiKey: '',
|
|
3432
|
+
apiType: 'openai-responses',
|
|
3433
|
+
modelId: '',
|
|
3434
|
+
modelName: '',
|
|
3435
|
+
contextWindow: '',
|
|
3436
|
+
maxTokens: '',
|
|
3437
|
+
setPrimary: true,
|
|
3438
|
+
overrideProvider: true,
|
|
3439
|
+
overrideModels: true,
|
|
3440
|
+
showKey: false
|
|
3441
|
+
},
|
|
3442
|
+
openclawAgentsList: [],
|
|
3443
|
+
openclawProviders: [],
|
|
3444
|
+
openclawMissingProviders: [],
|
|
2691
3445
|
recentConfigs: [],
|
|
2692
3446
|
recentLoading: false,
|
|
2693
3447
|
healthCheckLoading: false,
|
|
@@ -2696,6 +3450,7 @@
|
|
|
2696
3450
|
}
|
|
2697
3451
|
},
|
|
2698
3452
|
mounted() {
|
|
3453
|
+
this.initSessionStandalone();
|
|
2699
3454
|
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
2700
3455
|
if (savedConfigs) {
|
|
2701
3456
|
try {
|
|
@@ -2742,6 +3497,15 @@
|
|
|
2742
3497
|
}
|
|
2743
3498
|
this.loadAll();
|
|
2744
3499
|
},
|
|
3500
|
+
|
|
3501
|
+
computed: {
|
|
3502
|
+
isSessionQueryEnabled() {
|
|
3503
|
+
return this.sessionFilterSource === 'codex';
|
|
3504
|
+
},
|
|
3505
|
+
sessionQueryPlaceholder() {
|
|
3506
|
+
return this.isSessionQueryEnabled ? '关键词检索' : '仅 Codex 支持关键词检索';
|
|
3507
|
+
}
|
|
3508
|
+
},
|
|
2745
3509
|
methods: {
|
|
2746
3510
|
async loadAll() {
|
|
2747
3511
|
this.loading = true;
|
|
@@ -2758,7 +3522,7 @@
|
|
|
2758
3522
|
this.providersList = listRes.providers;
|
|
2759
3523
|
await this.loadModelsForProvider(this.currentProvider);
|
|
2760
3524
|
if (statusRes.configReady === false) {
|
|
2761
|
-
this.showMessage(statusRes.configNotice || '未检测到 config.toml
|
|
3525
|
+
this.showMessage(statusRes.configNotice || '未检测到 config.toml,已加载默认模板。请在模板编辑器确认后创建。', 'info');
|
|
2762
3526
|
}
|
|
2763
3527
|
if (statusRes.initNotice) {
|
|
2764
3528
|
this.showMessage(statusRes.initNotice, 'info');
|
|
@@ -2901,6 +3665,100 @@
|
|
|
2901
3665
|
}
|
|
2902
3666
|
},
|
|
2903
3667
|
|
|
3668
|
+
getSessionStandaloneContext() {
|
|
3669
|
+
try {
|
|
3670
|
+
const url = new URL(window.location.href);
|
|
3671
|
+
if (url.pathname !== '/session') {
|
|
3672
|
+
return { requested: false, params: null, error: '' };
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
const source = (url.searchParams.get('source') || '').trim().toLowerCase();
|
|
3676
|
+
const sessionId = (url.searchParams.get('sessionId') || url.searchParams.get('id') || '').trim();
|
|
3677
|
+
const filePath = (url.searchParams.get('filePath') || url.searchParams.get('path') || '').trim();
|
|
3678
|
+
let error = '';
|
|
3679
|
+
if (!source) {
|
|
3680
|
+
error = '缺少 source 参数';
|
|
3681
|
+
} else if (source !== 'codex' && source !== 'claude') {
|
|
3682
|
+
error = 'source 仅支持 codex 或 claude';
|
|
3683
|
+
}
|
|
3684
|
+
if (!sessionId && !filePath) {
|
|
3685
|
+
error = error ? `${error},还缺少 sessionId 或 filePath` : '缺少 sessionId 或 filePath 参数';
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
if (error) {
|
|
3689
|
+
return { requested: true, params: null, error };
|
|
3690
|
+
}
|
|
3691
|
+
|
|
3692
|
+
return {
|
|
3693
|
+
requested: true,
|
|
3694
|
+
params: {
|
|
3695
|
+
source,
|
|
3696
|
+
sessionId,
|
|
3697
|
+
filePath
|
|
3698
|
+
},
|
|
3699
|
+
error: ''
|
|
3700
|
+
};
|
|
3701
|
+
} catch (_) {
|
|
3702
|
+
return { requested: false, params: null, error: '' };
|
|
3703
|
+
}
|
|
3704
|
+
},
|
|
3705
|
+
|
|
3706
|
+
initSessionStandalone() {
|
|
3707
|
+
const context = this.getSessionStandaloneContext();
|
|
3708
|
+
if (!context.requested) return;
|
|
3709
|
+
|
|
3710
|
+
this.sessionStandalone = true;
|
|
3711
|
+
this.configMode = 'sessions';
|
|
3712
|
+
|
|
3713
|
+
if (context.error || !context.params) {
|
|
3714
|
+
this.sessionStandaloneError = `会话链接参数不完整:${context.error || '参数解析失败'}`;
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
const sourceLabel = context.params.source === 'codex' ? 'Codex' : 'Claude Code';
|
|
3719
|
+
this.activeSession = {
|
|
3720
|
+
source: context.params.source,
|
|
3721
|
+
sourceLabel,
|
|
3722
|
+
sessionId: context.params.sessionId,
|
|
3723
|
+
filePath: context.params.filePath,
|
|
3724
|
+
title: context.params.sessionId || context.params.filePath || '会话'
|
|
3725
|
+
};
|
|
3726
|
+
this.activeSessionMessages = [];
|
|
3727
|
+
this.activeSessionDetailError = '';
|
|
3728
|
+
this.activeSessionDetailClipped = false;
|
|
3729
|
+
this.sessionStandaloneError = '';
|
|
3730
|
+
this.sessionStandaloneText = '';
|
|
3731
|
+
this.sessionStandaloneTitle = this.activeSession.title || '会话';
|
|
3732
|
+
this.sessionStandaloneSourceLabel = sourceLabel;
|
|
3733
|
+
this.loadSessionStandalonePlain();
|
|
3734
|
+
},
|
|
3735
|
+
|
|
3736
|
+
buildSessionStandaloneUrl(session) {
|
|
3737
|
+
if (!session) return '';
|
|
3738
|
+
const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
|
|
3739
|
+
if (!source || (source !== 'codex' && source !== 'claude')) return '';
|
|
3740
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
3741
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
3742
|
+
if (!sessionId && !filePath) return '';
|
|
3743
|
+
const origin = window.location.origin && window.location.origin !== 'null'
|
|
3744
|
+
? window.location.origin
|
|
3745
|
+
: API_BASE;
|
|
3746
|
+
const params = new URLSearchParams();
|
|
3747
|
+
params.set('source', source);
|
|
3748
|
+
if (sessionId) params.set('sessionId', sessionId);
|
|
3749
|
+
if (filePath) params.set('filePath', filePath);
|
|
3750
|
+
return `${origin}/session?${params.toString()}`;
|
|
3751
|
+
},
|
|
3752
|
+
|
|
3753
|
+
openSessionStandalone(session) {
|
|
3754
|
+
const url = this.buildSessionStandaloneUrl(session);
|
|
3755
|
+
if (!url) {
|
|
3756
|
+
this.showMessage('当前会话无法生成新页链接', 'error');
|
|
3757
|
+
return;
|
|
3758
|
+
}
|
|
3759
|
+
window.open(url, '_blank', 'noopener');
|
|
3760
|
+
},
|
|
3761
|
+
|
|
2904
3762
|
getSessionExportKey(session) {
|
|
2905
3763
|
return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
|
|
2906
3764
|
},
|
|
@@ -2912,6 +3770,23 @@
|
|
|
2912
3770
|
return source === 'codex' && !!sessionId;
|
|
2913
3771
|
},
|
|
2914
3772
|
|
|
3773
|
+
isCloneAvailable(session) {
|
|
3774
|
+
if (!session) return false;
|
|
3775
|
+
const source = String(session.source || '').trim().toLowerCase();
|
|
3776
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
3777
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
3778
|
+
return source === 'codex' && (!!sessionId || !!filePath);
|
|
3779
|
+
},
|
|
3780
|
+
|
|
3781
|
+
isDeleteAvailable(session) {
|
|
3782
|
+
if (!session) return false;
|
|
3783
|
+
const source = String(session.source || '').trim().toLowerCase();
|
|
3784
|
+
if (source !== 'codex' && source !== 'claude') return false;
|
|
3785
|
+
const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
|
|
3786
|
+
const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
|
|
3787
|
+
return !!sessionId || !!filePath;
|
|
3788
|
+
},
|
|
3789
|
+
|
|
2915
3790
|
buildResumeCommand(session) {
|
|
2916
3791
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
2917
3792
|
return `codex resume ${this.quoteResumeArg(sessionId)}`;
|
|
@@ -2948,6 +3823,20 @@
|
|
|
2948
3823
|
}
|
|
2949
3824
|
},
|
|
2950
3825
|
|
|
3826
|
+
copyAgentsContent() {
|
|
3827
|
+
const text = typeof this.agentsContent === 'string' ? this.agentsContent : '';
|
|
3828
|
+
if (!text) {
|
|
3829
|
+
this.showMessage('没有可复制的内容', 'info');
|
|
3830
|
+
return;
|
|
3831
|
+
}
|
|
3832
|
+
const ok = this.fallbackCopyText(text);
|
|
3833
|
+
if (ok) {
|
|
3834
|
+
this.showMessage('已复制 AGENTS.md 内容', 'success');
|
|
3835
|
+
return;
|
|
3836
|
+
}
|
|
3837
|
+
this.showMessage('复制失败,请手动复制内容', 'error');
|
|
3838
|
+
},
|
|
3839
|
+
|
|
2951
3840
|
async copyResumeCommand(session) {
|
|
2952
3841
|
if (!this.isResumeCommandAvailable(session)) {
|
|
2953
3842
|
this.showMessage('当前会话不支持生成恢复命令', 'error');
|
|
@@ -2971,6 +3860,71 @@
|
|
|
2971
3860
|
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
2972
3861
|
},
|
|
2973
3862
|
|
|
3863
|
+
async cloneSession(session) {
|
|
3864
|
+
if (!this.isCloneAvailable(session)) {
|
|
3865
|
+
this.showMessage('当前会话不支持克隆', 'error');
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
const key = this.getSessionExportKey(session);
|
|
3869
|
+
if (this.sessionCloning[key]) {
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
this.sessionCloning[key] = true;
|
|
3873
|
+
try {
|
|
3874
|
+
const res = await api('clone-session', {
|
|
3875
|
+
source: session.source,
|
|
3876
|
+
sessionId: session.sessionId,
|
|
3877
|
+
filePath: session.filePath
|
|
3878
|
+
});
|
|
3879
|
+
if (res.error) {
|
|
3880
|
+
this.showMessage(res.error, 'error');
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
this.showMessage('会话已克隆', 'success');
|
|
3885
|
+
await this.loadSessions();
|
|
3886
|
+
if (res.sessionId) {
|
|
3887
|
+
const matched = this.sessionsList.find(item => item.source === 'codex' && item.sessionId === res.sessionId);
|
|
3888
|
+
if (matched) {
|
|
3889
|
+
await this.selectSession(matched);
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
} catch (e) {
|
|
3893
|
+
this.showMessage('克隆失败: ' + e.message, 'error');
|
|
3894
|
+
} finally {
|
|
3895
|
+
this.sessionCloning[key] = false;
|
|
3896
|
+
}
|
|
3897
|
+
},
|
|
3898
|
+
|
|
3899
|
+
async deleteSession(session) {
|
|
3900
|
+
if (!this.isDeleteAvailable(session)) {
|
|
3901
|
+
this.showMessage('当前会话不支持删除', 'error');
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
const key = this.getSessionExportKey(session);
|
|
3905
|
+
if (this.sessionDeleting[key]) {
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
this.sessionDeleting[key] = true;
|
|
3909
|
+
try {
|
|
3910
|
+
const res = await api('delete-session', {
|
|
3911
|
+
source: session.source,
|
|
3912
|
+
sessionId: session.sessionId,
|
|
3913
|
+
filePath: session.filePath
|
|
3914
|
+
});
|
|
3915
|
+
if (res.error) {
|
|
3916
|
+
this.showMessage(res.error, 'error');
|
|
3917
|
+
return;
|
|
3918
|
+
}
|
|
3919
|
+
this.showMessage('会话已删除', 'success');
|
|
3920
|
+
await this.loadSessions();
|
|
3921
|
+
} catch (e) {
|
|
3922
|
+
this.showMessage('删除失败: ' + e.message, 'error');
|
|
3923
|
+
} finally {
|
|
3924
|
+
this.sessionDeleting[key] = false;
|
|
3925
|
+
}
|
|
3926
|
+
},
|
|
3927
|
+
|
|
2974
3928
|
normalizeSessionPathValue(value) {
|
|
2975
3929
|
if (typeof value !== 'string') return '';
|
|
2976
3930
|
return value.trim();
|
|
@@ -3134,7 +4088,7 @@
|
|
|
3134
4088
|
if (this.sessionsLoading) return;
|
|
3135
4089
|
this.sessionsLoading = true;
|
|
3136
4090
|
this.activeSessionDetailError = '';
|
|
3137
|
-
const query = this.sessionQuery;
|
|
4091
|
+
const query = this.isSessionQueryEnabled ? this.sessionQuery : '';
|
|
3138
4092
|
try {
|
|
3139
4093
|
const res = await api('list-sessions', {
|
|
3140
4094
|
source: this.sessionFilterSource,
|
|
@@ -3142,7 +4096,7 @@
|
|
|
3142
4096
|
query,
|
|
3143
4097
|
queryMode: 'and',
|
|
3144
4098
|
queryScope: 'content',
|
|
3145
|
-
contentScanLimit:
|
|
4099
|
+
contentScanLimit: 50,
|
|
3146
4100
|
roleFilter: this.sessionRoleFilter,
|
|
3147
4101
|
timeRangePreset: this.sessionTimePreset,
|
|
3148
4102
|
limit: 200,
|
|
@@ -3184,21 +4138,66 @@
|
|
|
3184
4138
|
}
|
|
3185
4139
|
},
|
|
3186
4140
|
|
|
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.
|
|
4141
|
+
async selectSession(session) {
|
|
4142
|
+
if (!session) return;
|
|
4143
|
+
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
4144
|
+
this.activeSession = session;
|
|
4145
|
+
this.activeSessionMessages = [];
|
|
4146
|
+
this.activeSessionDetailError = '';
|
|
4147
|
+
this.activeSessionDetailClipped = false;
|
|
4148
|
+
await this.loadActiveSessionDetail();
|
|
4149
|
+
},
|
|
4150
|
+
|
|
4151
|
+
async loadSessionStandalonePlain() {
|
|
4152
|
+
if (!this.activeSession) {
|
|
4153
|
+
this.sessionStandaloneText = '';
|
|
4154
|
+
this.sessionStandaloneTitle = '会话';
|
|
4155
|
+
this.sessionStandaloneSourceLabel = '';
|
|
4156
|
+
this.sessionStandaloneError = '';
|
|
4157
|
+
return;
|
|
4158
|
+
}
|
|
4159
|
+
|
|
4160
|
+
const requestSeq = ++this.sessionStandaloneRequestSeq;
|
|
4161
|
+
this.sessionStandaloneLoading = true;
|
|
4162
|
+
this.sessionStandaloneError = '';
|
|
4163
|
+
try {
|
|
4164
|
+
const res = await api('session-plain', {
|
|
4165
|
+
source: this.activeSession.source,
|
|
4166
|
+
sessionId: this.activeSession.sessionId,
|
|
4167
|
+
filePath: this.activeSession.filePath
|
|
4168
|
+
});
|
|
4169
|
+
|
|
4170
|
+
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4171
|
+
return;
|
|
4172
|
+
}
|
|
4173
|
+
|
|
4174
|
+
if (res.error) {
|
|
4175
|
+
this.sessionStandaloneText = '';
|
|
4176
|
+
this.sessionStandaloneError = res.error;
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
4179
|
+
|
|
4180
|
+
this.sessionStandaloneSourceLabel = res.sourceLabel || this.activeSession.sourceLabel || '';
|
|
4181
|
+
this.sessionStandaloneTitle = res.sessionId || this.activeSession.title || '会话';
|
|
4182
|
+
this.sessionStandaloneText = typeof res.text === 'string' ? res.text : '';
|
|
4183
|
+
} catch (e) {
|
|
4184
|
+
if (requestSeq !== this.sessionStandaloneRequestSeq) {
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
this.sessionStandaloneText = '';
|
|
4188
|
+
this.sessionStandaloneError = '加载会话内容失败: ' + e.message;
|
|
4189
|
+
} finally {
|
|
4190
|
+
if (requestSeq === this.sessionStandaloneRequestSeq) {
|
|
4191
|
+
this.sessionStandaloneLoading = false;
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
},
|
|
4195
|
+
|
|
4196
|
+
async loadActiveSessionDetail() {
|
|
4197
|
+
if (!this.activeSession) {
|
|
4198
|
+
this.activeSessionMessages = [];
|
|
4199
|
+
this.activeSessionDetailError = '';
|
|
4200
|
+
this.activeSessionDetailClipped = false;
|
|
3202
4201
|
return;
|
|
3203
4202
|
}
|
|
3204
4203
|
|
|
@@ -3226,6 +4225,20 @@
|
|
|
3226
4225
|
|
|
3227
4226
|
this.activeSessionMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
3228
4227
|
this.activeSessionDetailClipped = !!res.clipped;
|
|
4228
|
+
if (this.activeSession) {
|
|
4229
|
+
if (res.sourceLabel) {
|
|
4230
|
+
this.activeSession.sourceLabel = res.sourceLabel;
|
|
4231
|
+
}
|
|
4232
|
+
if (res.sessionId) {
|
|
4233
|
+
this.activeSession.sessionId = res.sessionId;
|
|
4234
|
+
if (!this.activeSession.title) {
|
|
4235
|
+
this.activeSession.title = res.sessionId;
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
if (res.filePath) {
|
|
4239
|
+
this.activeSession.filePath = res.filePath;
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
3229
4242
|
if (res.updatedAt) {
|
|
3230
4243
|
this.activeSession.updatedAt = res.updatedAt;
|
|
3231
4244
|
}
|
|
@@ -3270,17 +4283,22 @@
|
|
|
3270
4283
|
sessionId: session.sessionId,
|
|
3271
4284
|
filePath: session.filePath
|
|
3272
4285
|
});
|
|
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
|
-
|
|
4286
|
+
if (res.error) {
|
|
4287
|
+
this.showMessage(res.error, 'error');
|
|
4288
|
+
return;
|
|
4289
|
+
}
|
|
4290
|
+
|
|
4291
|
+
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
4292
|
+
this.downloadTextFile(fileName, res.content || '');
|
|
4293
|
+
if (res.truncated) {
|
|
4294
|
+
const maxLabel = res.maxMessages === 'all' ? 'all' : res.maxMessages;
|
|
4295
|
+
this.showMessage(`会话导出完成(已截断:最多 ${maxLabel} 条消息)`, 'info');
|
|
4296
|
+
} else {
|
|
4297
|
+
this.showMessage('会话导出完成', 'success');
|
|
4298
|
+
}
|
|
4299
|
+
} catch (e) {
|
|
4300
|
+
this.showMessage('导出失败: ' + e.message, 'error');
|
|
4301
|
+
} finally {
|
|
3284
4302
|
this.sessionExporting[key] = false;
|
|
3285
4303
|
}
|
|
3286
4304
|
},
|
|
@@ -3328,14 +4346,44 @@
|
|
|
3328
4346
|
this.healthCheckResult = null;
|
|
3329
4347
|
try {
|
|
3330
4348
|
const res = await api('config-health-check', {
|
|
3331
|
-
remote:
|
|
4349
|
+
remote: false
|
|
3332
4350
|
});
|
|
3333
4351
|
if (res && typeof res === 'object') {
|
|
3334
|
-
|
|
3335
|
-
|
|
4352
|
+
const issues = Array.isArray(res.issues) ? [...res.issues] : [];
|
|
4353
|
+
let remote = res.remote || null;
|
|
4354
|
+
{
|
|
4355
|
+
const providers = (this.providersList || [])
|
|
4356
|
+
.filter(provider => provider && provider.name);
|
|
4357
|
+
const tasks = providers.map(provider =>
|
|
4358
|
+
this.runSpeedTest(provider.name, { silent: true })
|
|
4359
|
+
.then(result => ({ name: provider.name, result }))
|
|
4360
|
+
.catch(err => ({
|
|
4361
|
+
name: provider.name,
|
|
4362
|
+
result: { ok: false, error: err && err.message ? err.message : 'Speed test failed' }
|
|
4363
|
+
}))
|
|
4364
|
+
);
|
|
4365
|
+
const pairs = await Promise.all(tasks);
|
|
4366
|
+
const results = {};
|
|
4367
|
+
for (const pair of pairs) {
|
|
4368
|
+
results[pair.name] = pair.result || null;
|
|
4369
|
+
const issue = this.buildSpeedTestIssue(pair.name, pair.result);
|
|
4370
|
+
if (issue) issues.push(issue);
|
|
4371
|
+
}
|
|
4372
|
+
remote = {
|
|
4373
|
+
type: 'speed-test',
|
|
4374
|
+
results
|
|
4375
|
+
};
|
|
4376
|
+
}
|
|
4377
|
+
|
|
4378
|
+
const ok = issues.length === 0;
|
|
4379
|
+
this.healthCheckResult = {
|
|
4380
|
+
...res,
|
|
4381
|
+
ok,
|
|
4382
|
+
issues,
|
|
4383
|
+
remote
|
|
4384
|
+
};
|
|
4385
|
+
if (ok) {
|
|
3336
4386
|
this.showMessage('健康检查通过', 'success');
|
|
3337
|
-
} else {
|
|
3338
|
-
this.showMessage('发现配置问题,请查看详情', 'error');
|
|
3339
4387
|
}
|
|
3340
4388
|
} else {
|
|
3341
4389
|
this.healthCheckResult = null;
|
|
@@ -3456,7 +4504,44 @@
|
|
|
3456
4504
|
}
|
|
3457
4505
|
},
|
|
3458
4506
|
|
|
3459
|
-
|
|
4507
|
+
async openOpenclawWorkspaceEditor() {
|
|
4508
|
+
const fileName = (this.openclawWorkspaceFileName || '').trim();
|
|
4509
|
+
if (!fileName) {
|
|
4510
|
+
this.showMessage('请输入工作区文件名', 'error');
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4513
|
+
this.setAgentsModalContext('openclaw-workspace', { fileName });
|
|
4514
|
+
this.agentsLoading = true;
|
|
4515
|
+
try {
|
|
4516
|
+
const res = await api('get-openclaw-workspace-file', { fileName });
|
|
4517
|
+
if (res.error) {
|
|
4518
|
+
this.showMessage(res.error, 'error');
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4521
|
+
if (res.configError) {
|
|
4522
|
+
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
4523
|
+
}
|
|
4524
|
+
this.agentsContent = res.content || '';
|
|
4525
|
+
this.agentsPath = res.path || '';
|
|
4526
|
+
this.agentsExists = !!res.exists;
|
|
4527
|
+
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
4528
|
+
this.showAgentsModal = true;
|
|
4529
|
+
} catch (e) {
|
|
4530
|
+
this.showMessage('加载 OpenClaw 工作区文件失败: ' + e.message, 'error');
|
|
4531
|
+
} finally {
|
|
4532
|
+
this.agentsLoading = false;
|
|
4533
|
+
}
|
|
4534
|
+
},
|
|
4535
|
+
|
|
4536
|
+
setAgentsModalContext(context, options = {}) {
|
|
4537
|
+
if (context === 'openclaw-workspace') {
|
|
4538
|
+
const fileName = (options.fileName || this.openclawWorkspaceFileName || 'AGENTS.md').trim();
|
|
4539
|
+
this.agentsContext = 'openclaw-workspace';
|
|
4540
|
+
this.agentsWorkspaceFileName = fileName;
|
|
4541
|
+
this.agentsModalTitle = `OpenClaw 工作区文件: ${fileName}`;
|
|
4542
|
+
this.agentsModalHint = `保存后会写入 OpenClaw Workspace 下的 ${fileName}。`;
|
|
4543
|
+
return;
|
|
4544
|
+
}
|
|
3460
4545
|
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
3461
4546
|
if (this.agentsContext === 'openclaw') {
|
|
3462
4547
|
this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
|
|
@@ -3465,6 +4550,7 @@
|
|
|
3465
4550
|
this.agentsModalTitle = 'AGENTS.md 编辑器';
|
|
3466
4551
|
this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
|
|
3467
4552
|
}
|
|
4553
|
+
this.agentsWorkspaceFileName = '';
|
|
3468
4554
|
},
|
|
3469
4555
|
|
|
3470
4556
|
closeAgentsModal() {
|
|
@@ -3474,27 +4560,36 @@
|
|
|
3474
4560
|
this.agentsExists = false;
|
|
3475
4561
|
this.agentsLineEnding = '\n';
|
|
3476
4562
|
this.agentsSaving = false;
|
|
4563
|
+
this.agentsWorkspaceFileName = '';
|
|
3477
4564
|
this.setAgentsModalContext('codex');
|
|
3478
4565
|
},
|
|
3479
4566
|
|
|
3480
4567
|
async applyAgentsContent() {
|
|
3481
4568
|
this.agentsSaving = true;
|
|
3482
4569
|
try {
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
: 'apply-agents-file';
|
|
3486
|
-
const res = await api(action, {
|
|
4570
|
+
let action = 'apply-agents-file';
|
|
4571
|
+
const params = {
|
|
3487
4572
|
content: this.agentsContent,
|
|
3488
4573
|
lineEnding: this.agentsLineEnding
|
|
3489
|
-
}
|
|
4574
|
+
};
|
|
4575
|
+
if (this.agentsContext === 'openclaw') {
|
|
4576
|
+
action = 'apply-openclaw-agents-file';
|
|
4577
|
+
} else if (this.agentsContext === 'openclaw-workspace') {
|
|
4578
|
+
action = 'apply-openclaw-workspace-file';
|
|
4579
|
+
params.fileName = this.agentsWorkspaceFileName;
|
|
4580
|
+
}
|
|
4581
|
+
const res = await api(action, params);
|
|
3490
4582
|
if (res.error) {
|
|
3491
4583
|
this.showMessage(res.error, 'error');
|
|
3492
4584
|
return;
|
|
3493
4585
|
}
|
|
3494
|
-
this.
|
|
4586
|
+
const successLabel = this.agentsContext === 'openclaw-workspace'
|
|
4587
|
+
? `工作区文件已保存${this.agentsWorkspaceFileName ? `: ${this.agentsWorkspaceFileName}` : ''}`
|
|
4588
|
+
: (this.agentsContext === 'openclaw' ? 'OpenClaw AGENTS.md 已保存' : 'AGENTS.md 已保存');
|
|
4589
|
+
this.showMessage(successLabel, 'success');
|
|
3495
4590
|
this.closeAgentsModal();
|
|
3496
4591
|
} catch (e) {
|
|
3497
|
-
this.showMessage('
|
|
4592
|
+
this.showMessage('保存文件失败: ' + e.message, 'error');
|
|
3498
4593
|
} finally {
|
|
3499
4594
|
this.agentsSaving = false;
|
|
3500
4595
|
}
|
|
@@ -3780,6 +4875,763 @@
|
|
|
3780
4875
|
};
|
|
3781
4876
|
},
|
|
3782
4877
|
|
|
4878
|
+
getOpenclawParser() {
|
|
4879
|
+
if (window.JSON5 && typeof window.JSON5.parse === 'function' && typeof window.JSON5.stringify === 'function') {
|
|
4880
|
+
return {
|
|
4881
|
+
parse: window.JSON5.parse,
|
|
4882
|
+
stringify: window.JSON5.stringify
|
|
4883
|
+
};
|
|
4884
|
+
}
|
|
4885
|
+
return {
|
|
4886
|
+
parse: JSON.parse,
|
|
4887
|
+
stringify: JSON.stringify
|
|
4888
|
+
};
|
|
4889
|
+
},
|
|
4890
|
+
|
|
4891
|
+
parseOpenclawContent(content, options = {}) {
|
|
4892
|
+
const allowEmpty = !!options.allowEmpty;
|
|
4893
|
+
const raw = typeof content === 'string' ? content.trim() : '';
|
|
4894
|
+
if (!raw) {
|
|
4895
|
+
if (allowEmpty) {
|
|
4896
|
+
return { ok: true, data: {} };
|
|
4897
|
+
}
|
|
4898
|
+
return { ok: false, error: '配置内容为空' };
|
|
4899
|
+
}
|
|
4900
|
+
try {
|
|
4901
|
+
const parser = this.getOpenclawParser();
|
|
4902
|
+
const data = parser.parse(raw);
|
|
4903
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
4904
|
+
return { ok: false, error: '配置格式错误(根节点必须是对象)' };
|
|
4905
|
+
}
|
|
4906
|
+
return { ok: true, data };
|
|
4907
|
+
} catch (e) {
|
|
4908
|
+
return { ok: false, error: e.message || '解析失败' };
|
|
4909
|
+
}
|
|
4910
|
+
},
|
|
4911
|
+
|
|
4912
|
+
stringifyOpenclawConfig(data) {
|
|
4913
|
+
const parser = this.getOpenclawParser();
|
|
4914
|
+
try {
|
|
4915
|
+
return parser.stringify(data, null, 2);
|
|
4916
|
+
} catch (e) {
|
|
4917
|
+
return JSON.stringify(data, null, 2);
|
|
4918
|
+
}
|
|
4919
|
+
},
|
|
4920
|
+
|
|
4921
|
+
resetOpenclawStructured() {
|
|
4922
|
+
this.openclawStructured = {
|
|
4923
|
+
agentPrimary: '',
|
|
4924
|
+
agentFallbacks: [''],
|
|
4925
|
+
workspace: '',
|
|
4926
|
+
timeout: '',
|
|
4927
|
+
contextTokens: '',
|
|
4928
|
+
maxConcurrent: '',
|
|
4929
|
+
envItems: [{ key: '', value: '', show: false }],
|
|
4930
|
+
toolsProfile: 'default',
|
|
4931
|
+
toolsAllow: [''],
|
|
4932
|
+
toolsDeny: ['']
|
|
4933
|
+
};
|
|
4934
|
+
this.openclawAgentsList = [];
|
|
4935
|
+
this.openclawProviders = [];
|
|
4936
|
+
this.openclawMissingProviders = [];
|
|
4937
|
+
},
|
|
4938
|
+
|
|
4939
|
+
getOpenclawQuickDefaults() {
|
|
4940
|
+
return {
|
|
4941
|
+
providerName: '',
|
|
4942
|
+
baseUrl: '',
|
|
4943
|
+
apiKey: '',
|
|
4944
|
+
apiType: 'openai-responses',
|
|
4945
|
+
modelId: '',
|
|
4946
|
+
modelName: '',
|
|
4947
|
+
contextWindow: '',
|
|
4948
|
+
maxTokens: '',
|
|
4949
|
+
setPrimary: true,
|
|
4950
|
+
overrideProvider: true,
|
|
4951
|
+
overrideModels: true,
|
|
4952
|
+
showKey: false
|
|
4953
|
+
};
|
|
4954
|
+
},
|
|
4955
|
+
|
|
4956
|
+
resetOpenclawQuick() {
|
|
4957
|
+
this.openclawQuick = this.getOpenclawQuickDefaults();
|
|
4958
|
+
},
|
|
4959
|
+
|
|
4960
|
+
toggleOpenclawQuickKey() {
|
|
4961
|
+
this.openclawQuick.showKey = !this.openclawQuick.showKey;
|
|
4962
|
+
},
|
|
4963
|
+
|
|
4964
|
+
fillOpenclawQuickFromConfig(config) {
|
|
4965
|
+
const defaults = this.getOpenclawQuickDefaults();
|
|
4966
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) {
|
|
4967
|
+
this.openclawQuick = defaults;
|
|
4968
|
+
return;
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
const agentDefaults = config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
4972
|
+
&& config.agents.defaults && typeof config.agents.defaults === 'object' && !Array.isArray(config.agents.defaults)
|
|
4973
|
+
? config.agents.defaults
|
|
4974
|
+
: {};
|
|
4975
|
+
const modelConfig = agentDefaults.model;
|
|
4976
|
+
const legacyAgent = config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)
|
|
4977
|
+
? config.agent
|
|
4978
|
+
: {};
|
|
4979
|
+
|
|
4980
|
+
let primaryRef = '';
|
|
4981
|
+
if (modelConfig && typeof modelConfig === 'object' && !Array.isArray(modelConfig) && typeof modelConfig.primary === 'string') {
|
|
4982
|
+
primaryRef = modelConfig.primary;
|
|
4983
|
+
} else if (typeof modelConfig === 'string') {
|
|
4984
|
+
primaryRef = modelConfig;
|
|
4985
|
+
}
|
|
4986
|
+
if (!primaryRef) {
|
|
4987
|
+
if (typeof legacyAgent.model === 'string') {
|
|
4988
|
+
primaryRef = legacyAgent.model;
|
|
4989
|
+
} else if (legacyAgent.model && typeof legacyAgent.model === 'object' && typeof legacyAgent.model.primary === 'string') {
|
|
4990
|
+
primaryRef = legacyAgent.model.primary;
|
|
4991
|
+
}
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
let providerName = '';
|
|
4995
|
+
let modelId = '';
|
|
4996
|
+
if (primaryRef) {
|
|
4997
|
+
const parts = primaryRef.split('/');
|
|
4998
|
+
if (parts.length >= 2) {
|
|
4999
|
+
providerName = parts.shift().trim();
|
|
5000
|
+
modelId = parts.join('/').trim();
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
|
|
5004
|
+
const providers = config.models && typeof config.models === 'object' && !Array.isArray(config.models)
|
|
5005
|
+
&& config.models.providers && typeof config.models.providers === 'object' && !Array.isArray(config.models.providers)
|
|
5006
|
+
? config.models.providers
|
|
5007
|
+
: null;
|
|
5008
|
+
let providerConfig = providerName && providers ? providers[providerName] : null;
|
|
5009
|
+
if (!providerName && providers) {
|
|
5010
|
+
const providerKeys = Object.keys(providers);
|
|
5011
|
+
if (providerKeys.length === 1) {
|
|
5012
|
+
providerName = providerKeys[0];
|
|
5013
|
+
providerConfig = providers[providerName];
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
|
|
5017
|
+
let modelEntry = null;
|
|
5018
|
+
if (providerConfig && typeof providerConfig === 'object' && Array.isArray(providerConfig.models)) {
|
|
5019
|
+
if (modelId) {
|
|
5020
|
+
modelEntry = providerConfig.models.find(item => item && item.id === modelId);
|
|
5021
|
+
}
|
|
5022
|
+
if (!modelEntry && providerConfig.models.length === 1) {
|
|
5023
|
+
modelEntry = providerConfig.models[0];
|
|
5024
|
+
if (!modelId && modelEntry && typeof modelEntry.id === 'string') {
|
|
5025
|
+
modelId = modelEntry.id;
|
|
5026
|
+
}
|
|
5027
|
+
}
|
|
5028
|
+
}
|
|
5029
|
+
|
|
5030
|
+
const baseUrl = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.baseUrl === 'string'
|
|
5031
|
+
? providerConfig.baseUrl
|
|
5032
|
+
: '';
|
|
5033
|
+
const apiKey = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.apiKey === 'string'
|
|
5034
|
+
? providerConfig.apiKey
|
|
5035
|
+
: '';
|
|
5036
|
+
const apiType = providerConfig && typeof providerConfig === 'object' && typeof providerConfig.api === 'string'
|
|
5037
|
+
? providerConfig.api
|
|
5038
|
+
: defaults.apiType;
|
|
5039
|
+
|
|
5040
|
+
this.openclawQuick = {
|
|
5041
|
+
...defaults,
|
|
5042
|
+
providerName,
|
|
5043
|
+
baseUrl,
|
|
5044
|
+
apiKey,
|
|
5045
|
+
apiType,
|
|
5046
|
+
modelId: modelId || '',
|
|
5047
|
+
modelName: modelEntry && typeof modelEntry.name === 'string' ? modelEntry.name : '',
|
|
5048
|
+
contextWindow: modelEntry && typeof modelEntry.contextWindow === 'number'
|
|
5049
|
+
? String(modelEntry.contextWindow)
|
|
5050
|
+
: '',
|
|
5051
|
+
maxTokens: modelEntry && typeof modelEntry.maxTokens === 'number'
|
|
5052
|
+
? String(modelEntry.maxTokens)
|
|
5053
|
+
: ''
|
|
5054
|
+
};
|
|
5055
|
+
},
|
|
5056
|
+
|
|
5057
|
+
syncOpenclawQuickFromText(options = {}) {
|
|
5058
|
+
const silent = !!options.silent;
|
|
5059
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5060
|
+
if (!parsed.ok) {
|
|
5061
|
+
this.resetOpenclawQuick();
|
|
5062
|
+
if (!silent) {
|
|
5063
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5064
|
+
}
|
|
5065
|
+
return false;
|
|
5066
|
+
}
|
|
5067
|
+
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
5068
|
+
if (!silent) {
|
|
5069
|
+
this.showMessage('已从编辑器读取快速配置', 'success');
|
|
5070
|
+
}
|
|
5071
|
+
return true;
|
|
5072
|
+
},
|
|
5073
|
+
|
|
5074
|
+
mergeOpenclawModelEntry(existing, incoming, overwrite = false) {
|
|
5075
|
+
if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {
|
|
5076
|
+
return { ...incoming };
|
|
5077
|
+
}
|
|
5078
|
+
if (overwrite) {
|
|
5079
|
+
return { ...incoming };
|
|
5080
|
+
}
|
|
5081
|
+
const merged = { ...existing };
|
|
5082
|
+
for (const [key, value] of Object.entries(incoming || {})) {
|
|
5083
|
+
if (merged[key] === undefined || merged[key] === null || merged[key] === '') {
|
|
5084
|
+
merged[key] = value;
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
return merged;
|
|
5088
|
+
},
|
|
5089
|
+
|
|
5090
|
+
fillOpenclawStructured(config) {
|
|
5091
|
+
const defaults = config && config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
5092
|
+
&& config.agents.defaults && typeof config.agents.defaults === 'object' && !Array.isArray(config.agents.defaults)
|
|
5093
|
+
? config.agents.defaults
|
|
5094
|
+
: {};
|
|
5095
|
+
const model = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
5096
|
+
? defaults.model
|
|
5097
|
+
: {};
|
|
5098
|
+
const legacyAgent = config && config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)
|
|
5099
|
+
? config.agent
|
|
5100
|
+
: {};
|
|
5101
|
+
const fallbackList = Array.isArray(model.fallbacks)
|
|
5102
|
+
? model.fallbacks.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
5103
|
+
: [];
|
|
5104
|
+
const env = config && config.env && typeof config.env === 'object' && !Array.isArray(config.env)
|
|
5105
|
+
? config.env
|
|
5106
|
+
: {};
|
|
5107
|
+
const envItems = Object.entries(env).map(([key, value]) => ({
|
|
5108
|
+
key,
|
|
5109
|
+
value: value == null ? '' : String(value),
|
|
5110
|
+
show: false
|
|
5111
|
+
}));
|
|
5112
|
+
const tools = config && config.tools && typeof config.tools === 'object' && !Array.isArray(config.tools)
|
|
5113
|
+
? config.tools
|
|
5114
|
+
: {};
|
|
5115
|
+
|
|
5116
|
+
let primary = typeof model.primary === 'string' ? model.primary : '';
|
|
5117
|
+
if (!primary) {
|
|
5118
|
+
if (typeof legacyAgent.model === 'string') {
|
|
5119
|
+
primary = legacyAgent.model;
|
|
5120
|
+
} else if (legacyAgent.model && typeof legacyAgent.model === 'object' && typeof legacyAgent.model.primary === 'string') {
|
|
5121
|
+
primary = legacyAgent.model.primary;
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
|
|
5125
|
+
this.openclawStructured = {
|
|
5126
|
+
agentPrimary: primary,
|
|
5127
|
+
agentFallbacks: fallbackList.length ? fallbackList : [''],
|
|
5128
|
+
workspace: typeof defaults.workspace === 'string' ? defaults.workspace : '',
|
|
5129
|
+
timeout: typeof defaults.timeout === 'number' && Number.isFinite(defaults.timeout)
|
|
5130
|
+
? String(defaults.timeout)
|
|
5131
|
+
: '',
|
|
5132
|
+
contextTokens: typeof defaults.contextTokens === 'number' && Number.isFinite(defaults.contextTokens)
|
|
5133
|
+
? String(defaults.contextTokens)
|
|
5134
|
+
: '',
|
|
5135
|
+
maxConcurrent: typeof defaults.maxConcurrent === 'number' && Number.isFinite(defaults.maxConcurrent)
|
|
5136
|
+
? String(defaults.maxConcurrent)
|
|
5137
|
+
: '',
|
|
5138
|
+
envItems: envItems.length ? envItems : [{ key: '', value: '', show: false }],
|
|
5139
|
+
toolsProfile: typeof tools.profile === 'string' && tools.profile.trim() ? tools.profile : 'default',
|
|
5140
|
+
toolsAllow: Array.isArray(tools.allow) && tools.allow.length
|
|
5141
|
+
? tools.allow.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
5142
|
+
: [''],
|
|
5143
|
+
toolsDeny: Array.isArray(tools.deny) && tools.deny.length
|
|
5144
|
+
? tools.deny.filter(item => typeof item === 'string' && item.trim()).map(item => item.trim())
|
|
5145
|
+
: ['']
|
|
5146
|
+
};
|
|
5147
|
+
},
|
|
5148
|
+
|
|
5149
|
+
syncOpenclawStructuredFromText(options = {}) {
|
|
5150
|
+
const silent = !!options.silent;
|
|
5151
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5152
|
+
if (!parsed.ok) {
|
|
5153
|
+
this.resetOpenclawStructured();
|
|
5154
|
+
this.resetOpenclawQuick();
|
|
5155
|
+
if (!silent) {
|
|
5156
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5157
|
+
}
|
|
5158
|
+
return false;
|
|
5159
|
+
}
|
|
5160
|
+
this.fillOpenclawStructured(parsed.data);
|
|
5161
|
+
this.fillOpenclawQuickFromConfig(parsed.data);
|
|
5162
|
+
this.refreshOpenclawProviders(parsed.data);
|
|
5163
|
+
this.refreshOpenclawAgentsList(parsed.data);
|
|
5164
|
+
if (!silent) {
|
|
5165
|
+
this.showMessage('已从文本刷新结构化配置', 'success');
|
|
5166
|
+
}
|
|
5167
|
+
return true;
|
|
5168
|
+
},
|
|
5169
|
+
|
|
5170
|
+
getOpenclawActiveProviders(config) {
|
|
5171
|
+
const active = new Set();
|
|
5172
|
+
const addProvider = (ref) => {
|
|
5173
|
+
if (typeof ref !== 'string') return;
|
|
5174
|
+
const text = ref.trim();
|
|
5175
|
+
if (!text) return;
|
|
5176
|
+
const parts = text.split('/');
|
|
5177
|
+
if (parts.length < 2) return;
|
|
5178
|
+
const provider = parts[0].trim();
|
|
5179
|
+
if (provider) active.add(provider);
|
|
5180
|
+
};
|
|
5181
|
+
const defaults = config && config.agents && config.agents.defaults
|
|
5182
|
+
? config.agents.defaults
|
|
5183
|
+
: {};
|
|
5184
|
+
const model = defaults && defaults.model;
|
|
5185
|
+
if (model && typeof model === 'object' && !Array.isArray(model)) {
|
|
5186
|
+
addProvider(model.primary);
|
|
5187
|
+
if (Array.isArray(model.fallbacks)) {
|
|
5188
|
+
for (const item of model.fallbacks) {
|
|
5189
|
+
addProvider(item);
|
|
5190
|
+
}
|
|
5191
|
+
}
|
|
5192
|
+
} else if (typeof model === 'string') {
|
|
5193
|
+
addProvider(model);
|
|
5194
|
+
}
|
|
5195
|
+
const modelsDefaults = config && config.models && config.models.defaults
|
|
5196
|
+
? config.models.defaults
|
|
5197
|
+
: {};
|
|
5198
|
+
if (modelsDefaults && typeof modelsDefaults.provider === 'string' && modelsDefaults.provider.trim()) {
|
|
5199
|
+
active.add(modelsDefaults.provider.trim());
|
|
5200
|
+
}
|
|
5201
|
+
if (modelsDefaults && typeof modelsDefaults.model === 'string') {
|
|
5202
|
+
addProvider(modelsDefaults.model);
|
|
5203
|
+
}
|
|
5204
|
+
return active;
|
|
5205
|
+
},
|
|
5206
|
+
|
|
5207
|
+
maskProviderValue(value) {
|
|
5208
|
+
const text = value == null ? '' : String(value);
|
|
5209
|
+
if (!text) return '****';
|
|
5210
|
+
if (text.length <= 6) return '****';
|
|
5211
|
+
return `${text.slice(0, 3)}****${text.slice(-3)}`;
|
|
5212
|
+
},
|
|
5213
|
+
|
|
5214
|
+
formatProviderValue(key, value) {
|
|
5215
|
+
if (typeof value === 'undefined' || value === null) {
|
|
5216
|
+
return '';
|
|
5217
|
+
}
|
|
5218
|
+
let text = '';
|
|
5219
|
+
if (typeof value === 'string') {
|
|
5220
|
+
text = value;
|
|
5221
|
+
} else if (typeof value === 'number' || typeof value === 'boolean') {
|
|
5222
|
+
text = String(value);
|
|
5223
|
+
} else {
|
|
5224
|
+
try {
|
|
5225
|
+
text = JSON.stringify(value);
|
|
5226
|
+
} catch (_) {
|
|
5227
|
+
text = String(value);
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
if (!text) return '';
|
|
5231
|
+
if (/key|token|secret|password/i.test(key)) {
|
|
5232
|
+
return this.maskProviderValue(text);
|
|
5233
|
+
}
|
|
5234
|
+
if (text.length > 160) {
|
|
5235
|
+
return `${text.slice(0, 157)}...`;
|
|
5236
|
+
}
|
|
5237
|
+
return text;
|
|
5238
|
+
},
|
|
5239
|
+
|
|
5240
|
+
collectOpenclawProviders(source, providerMap, activeProviders, entries) {
|
|
5241
|
+
if (!providerMap || typeof providerMap !== 'object' || Array.isArray(providerMap)) {
|
|
5242
|
+
return;
|
|
5243
|
+
}
|
|
5244
|
+
const keys = Object.keys(providerMap).sort();
|
|
5245
|
+
for (const key of keys) {
|
|
5246
|
+
const value = providerMap[key];
|
|
5247
|
+
const fields = [];
|
|
5248
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
5249
|
+
const fieldKeys = Object.keys(value).sort();
|
|
5250
|
+
for (const fieldKey of fieldKeys) {
|
|
5251
|
+
const fieldValue = this.formatProviderValue(fieldKey, value[fieldKey]);
|
|
5252
|
+
if (fieldValue === '') continue;
|
|
5253
|
+
fields.push({ key: fieldKey, value: fieldValue });
|
|
5254
|
+
}
|
|
5255
|
+
} else {
|
|
5256
|
+
const fieldValue = this.formatProviderValue('value', value);
|
|
5257
|
+
if (fieldValue !== '') {
|
|
5258
|
+
fields.push({ key: 'value', value: fieldValue });
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
entries.push({
|
|
5262
|
+
key,
|
|
5263
|
+
source,
|
|
5264
|
+
fields,
|
|
5265
|
+
isActive: activeProviders.has(key)
|
|
5266
|
+
});
|
|
5267
|
+
}
|
|
5268
|
+
},
|
|
5269
|
+
|
|
5270
|
+
refreshOpenclawProviders(config) {
|
|
5271
|
+
const activeProviders = this.getOpenclawActiveProviders(config || {});
|
|
5272
|
+
const entries = [];
|
|
5273
|
+
const modelsProviders = config && config.models ? config.models.providers : null;
|
|
5274
|
+
const rootProviders = config && config.providers ? config.providers : null;
|
|
5275
|
+
this.collectOpenclawProviders('models.providers', modelsProviders, activeProviders, entries);
|
|
5276
|
+
this.collectOpenclawProviders('providers', rootProviders, activeProviders, entries);
|
|
5277
|
+
const existing = new Set(entries.map(item => item.key));
|
|
5278
|
+
const missing = [];
|
|
5279
|
+
for (const provider of activeProviders) {
|
|
5280
|
+
if (!existing.has(provider)) {
|
|
5281
|
+
missing.push(provider);
|
|
5282
|
+
}
|
|
5283
|
+
}
|
|
5284
|
+
this.openclawProviders = entries;
|
|
5285
|
+
this.openclawMissingProviders = missing;
|
|
5286
|
+
},
|
|
5287
|
+
|
|
5288
|
+
refreshOpenclawAgentsList(config) {
|
|
5289
|
+
const list = config && config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
5290
|
+
? config.agents.list
|
|
5291
|
+
: null;
|
|
5292
|
+
if (!Array.isArray(list)) {
|
|
5293
|
+
this.openclawAgentsList = [];
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
const entries = [];
|
|
5297
|
+
list.forEach((item, index) => {
|
|
5298
|
+
if (!item || typeof item !== 'object') return;
|
|
5299
|
+
const id = typeof item.id === 'string' && item.id.trim() ? item.id.trim() : `agent-${index + 1}`;
|
|
5300
|
+
const identity = item.identity && typeof item.identity === 'object' && !Array.isArray(item.identity)
|
|
5301
|
+
? item.identity
|
|
5302
|
+
: {};
|
|
5303
|
+
const name = typeof identity.name === 'string' && identity.name.trim()
|
|
5304
|
+
? identity.name.trim()
|
|
5305
|
+
: id;
|
|
5306
|
+
entries.push({
|
|
5307
|
+
key: `${id}-${index}`,
|
|
5308
|
+
id,
|
|
5309
|
+
name,
|
|
5310
|
+
theme: typeof identity.theme === 'string' ? identity.theme : '',
|
|
5311
|
+
emoji: typeof identity.emoji === 'string' ? identity.emoji : '',
|
|
5312
|
+
avatar: typeof identity.avatar === 'string' ? identity.avatar : ''
|
|
5313
|
+
});
|
|
5314
|
+
});
|
|
5315
|
+
this.openclawAgentsList = entries;
|
|
5316
|
+
},
|
|
5317
|
+
|
|
5318
|
+
normalizeStringList(list) {
|
|
5319
|
+
if (!Array.isArray(list)) return [];
|
|
5320
|
+
const result = [];
|
|
5321
|
+
const seen = new Set();
|
|
5322
|
+
for (const item of list) {
|
|
5323
|
+
const value = typeof item === 'string' ? item.trim() : String(item || '').trim();
|
|
5324
|
+
if (!value) continue;
|
|
5325
|
+
const key = value;
|
|
5326
|
+
if (seen.has(key)) continue;
|
|
5327
|
+
seen.add(key);
|
|
5328
|
+
result.push(value);
|
|
5329
|
+
}
|
|
5330
|
+
return result;
|
|
5331
|
+
},
|
|
5332
|
+
|
|
5333
|
+
normalizeEnvItems(items) {
|
|
5334
|
+
if (!Array.isArray(items)) {
|
|
5335
|
+
return { ok: true, items: {} };
|
|
5336
|
+
}
|
|
5337
|
+
const output = {};
|
|
5338
|
+
const seen = new Set();
|
|
5339
|
+
for (const item of items) {
|
|
5340
|
+
const key = item && typeof item.key === 'string' ? item.key.trim() : '';
|
|
5341
|
+
if (!key) continue;
|
|
5342
|
+
if (seen.has(key)) {
|
|
5343
|
+
return { ok: false, error: `环境变量重复: ${key}` };
|
|
5344
|
+
}
|
|
5345
|
+
seen.add(key);
|
|
5346
|
+
const value = item && typeof item.value !== 'undefined' ? String(item.value) : '';
|
|
5347
|
+
output[key] = value;
|
|
5348
|
+
}
|
|
5349
|
+
return { ok: true, items: output };
|
|
5350
|
+
},
|
|
5351
|
+
|
|
5352
|
+
parseOptionalNumber(value, label) {
|
|
5353
|
+
const text = typeof value === 'string' ? value.trim() : String(value || '').trim();
|
|
5354
|
+
if (!text) {
|
|
5355
|
+
return { ok: true, value: null };
|
|
5356
|
+
}
|
|
5357
|
+
const num = Number(text);
|
|
5358
|
+
if (!Number.isFinite(num) || num < 0) {
|
|
5359
|
+
return { ok: false, error: `${label} 请输入有效数字` };
|
|
5360
|
+
}
|
|
5361
|
+
return { ok: true, value: num };
|
|
5362
|
+
},
|
|
5363
|
+
|
|
5364
|
+
applyOpenclawStructuredToText() {
|
|
5365
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5366
|
+
if (!parsed.ok) {
|
|
5367
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
|
|
5371
|
+
const config = parsed.data;
|
|
5372
|
+
const agents = config.agents && typeof config.agents === 'object' && !Array.isArray(config.agents)
|
|
5373
|
+
? config.agents
|
|
5374
|
+
: {};
|
|
5375
|
+
const defaults = agents.defaults && typeof agents.defaults === 'object' && !Array.isArray(agents.defaults)
|
|
5376
|
+
? agents.defaults
|
|
5377
|
+
: {};
|
|
5378
|
+
const model = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
5379
|
+
? defaults.model
|
|
5380
|
+
: {};
|
|
5381
|
+
|
|
5382
|
+
const primary = (this.openclawStructured.agentPrimary || '').trim();
|
|
5383
|
+
const fallbacks = this.normalizeStringList(this.openclawStructured.agentFallbacks);
|
|
5384
|
+
if (primary) {
|
|
5385
|
+
model.primary = primary;
|
|
5386
|
+
}
|
|
5387
|
+
if (fallbacks.length) {
|
|
5388
|
+
model.fallbacks = fallbacks;
|
|
5389
|
+
}
|
|
5390
|
+
if (primary || fallbacks.length) {
|
|
5391
|
+
defaults.model = model;
|
|
5392
|
+
}
|
|
5393
|
+
if (primary && config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)) {
|
|
5394
|
+
config.agent.model = primary;
|
|
5395
|
+
}
|
|
5396
|
+
|
|
5397
|
+
const workspace = (this.openclawStructured.workspace || '').trim();
|
|
5398
|
+
if (workspace) {
|
|
5399
|
+
defaults.workspace = workspace;
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5402
|
+
const timeout = this.parseOptionalNumber(this.openclawStructured.timeout, 'Timeout');
|
|
5403
|
+
if (!timeout.ok) {
|
|
5404
|
+
this.showMessage(timeout.error, 'error');
|
|
5405
|
+
return;
|
|
5406
|
+
}
|
|
5407
|
+
if (timeout.value !== null) {
|
|
5408
|
+
defaults.timeout = timeout.value;
|
|
5409
|
+
}
|
|
5410
|
+
|
|
5411
|
+
const contextTokens = this.parseOptionalNumber(this.openclawStructured.contextTokens, 'Context Tokens');
|
|
5412
|
+
if (!contextTokens.ok) {
|
|
5413
|
+
this.showMessage(contextTokens.error, 'error');
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
if (contextTokens.value !== null) {
|
|
5417
|
+
defaults.contextTokens = contextTokens.value;
|
|
5418
|
+
}
|
|
5419
|
+
|
|
5420
|
+
const maxConcurrent = this.parseOptionalNumber(this.openclawStructured.maxConcurrent, 'Max Concurrent');
|
|
5421
|
+
if (!maxConcurrent.ok) {
|
|
5422
|
+
this.showMessage(maxConcurrent.error, 'error');
|
|
5423
|
+
return;
|
|
5424
|
+
}
|
|
5425
|
+
if (maxConcurrent.value !== null) {
|
|
5426
|
+
defaults.maxConcurrent = maxConcurrent.value;
|
|
5427
|
+
}
|
|
5428
|
+
|
|
5429
|
+
if (Object.keys(defaults).length > 0) {
|
|
5430
|
+
config.agents = agents;
|
|
5431
|
+
config.agents.defaults = defaults;
|
|
5432
|
+
}
|
|
5433
|
+
|
|
5434
|
+
const envResult = this.normalizeEnvItems(this.openclawStructured.envItems);
|
|
5435
|
+
if (!envResult.ok) {
|
|
5436
|
+
this.showMessage(envResult.error, 'error');
|
|
5437
|
+
return;
|
|
5438
|
+
}
|
|
5439
|
+
if (Object.keys(envResult.items).length > 0) {
|
|
5440
|
+
config.env = envResult.items;
|
|
5441
|
+
} else if (config.env) {
|
|
5442
|
+
delete config.env;
|
|
5443
|
+
}
|
|
5444
|
+
|
|
5445
|
+
const profile = (this.openclawStructured.toolsProfile || '').trim();
|
|
5446
|
+
const allowList = this.normalizeStringList(this.openclawStructured.toolsAllow);
|
|
5447
|
+
const denyList = this.normalizeStringList(this.openclawStructured.toolsDeny);
|
|
5448
|
+
const hasTools = profile || allowList.length || denyList.length || (config.tools && typeof config.tools === 'object');
|
|
5449
|
+
if (hasTools) {
|
|
5450
|
+
const tools = config.tools && typeof config.tools === 'object' && !Array.isArray(config.tools)
|
|
5451
|
+
? config.tools
|
|
5452
|
+
: {};
|
|
5453
|
+
tools.profile = profile || tools.profile || 'default';
|
|
5454
|
+
tools.allow = allowList;
|
|
5455
|
+
tools.deny = denyList;
|
|
5456
|
+
config.tools = tools;
|
|
5457
|
+
}
|
|
5458
|
+
|
|
5459
|
+
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
5460
|
+
this.refreshOpenclawProviders(config);
|
|
5461
|
+
this.refreshOpenclawAgentsList(config);
|
|
5462
|
+
this.fillOpenclawQuickFromConfig(config);
|
|
5463
|
+
this.showMessage('已写入编辑器', 'success');
|
|
5464
|
+
},
|
|
5465
|
+
|
|
5466
|
+
applyOpenclawQuickToText() {
|
|
5467
|
+
const parsed = this.parseOpenclawContent(this.openclawEditing.content, { allowEmpty: true });
|
|
5468
|
+
if (!parsed.ok) {
|
|
5469
|
+
this.showMessage('解析 OpenClaw 配置失败: ' + parsed.error, 'error');
|
|
5470
|
+
return;
|
|
5471
|
+
}
|
|
5472
|
+
|
|
5473
|
+
const providerName = (this.openclawQuick.providerName || '').trim();
|
|
5474
|
+
const modelId = (this.openclawQuick.modelId || '').trim();
|
|
5475
|
+
if (!providerName) {
|
|
5476
|
+
this.showMessage('请填写 Provider 名称', 'error');
|
|
5477
|
+
return;
|
|
5478
|
+
}
|
|
5479
|
+
if (providerName.includes('/')) {
|
|
5480
|
+
this.showMessage('Provider 名称不能包含 "/"', 'error');
|
|
5481
|
+
return;
|
|
5482
|
+
}
|
|
5483
|
+
if (!modelId) {
|
|
5484
|
+
this.showMessage('请填写模型 ID', 'error');
|
|
5485
|
+
return;
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5488
|
+
const config = parsed.data;
|
|
5489
|
+
const ensureObject = (value) => (value && typeof value === 'object' && !Array.isArray(value)) ? value : {};
|
|
5490
|
+
const models = ensureObject(config.models);
|
|
5491
|
+
const providers = ensureObject(models.providers);
|
|
5492
|
+
const provider = ensureObject(providers[providerName]);
|
|
5493
|
+
const baseUrl = (this.openclawQuick.baseUrl || '').trim();
|
|
5494
|
+
if (!baseUrl && !provider.baseUrl) {
|
|
5495
|
+
this.showMessage('请填写 Base URL', 'error');
|
|
5496
|
+
return;
|
|
5497
|
+
}
|
|
5498
|
+
|
|
5499
|
+
const contextWindow = this.parseOptionalNumber(this.openclawQuick.contextWindow, '上下文长度');
|
|
5500
|
+
if (!contextWindow.ok) {
|
|
5501
|
+
this.showMessage(contextWindow.error, 'error');
|
|
5502
|
+
return;
|
|
5503
|
+
}
|
|
5504
|
+
const maxTokens = this.parseOptionalNumber(this.openclawQuick.maxTokens, '最大输出');
|
|
5505
|
+
if (!maxTokens.ok) {
|
|
5506
|
+
this.showMessage(maxTokens.error, 'error');
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5509
|
+
|
|
5510
|
+
const shouldOverrideProvider = !!this.openclawQuick.overrideProvider;
|
|
5511
|
+
const apiKey = (this.openclawQuick.apiKey || '').trim();
|
|
5512
|
+
const apiType = (this.openclawQuick.apiType || '').trim();
|
|
5513
|
+
const setProviderField = (key, value) => {
|
|
5514
|
+
if (!value) return;
|
|
5515
|
+
if (shouldOverrideProvider || provider[key] === undefined || provider[key] === null || provider[key] === '') {
|
|
5516
|
+
provider[key] = value;
|
|
5517
|
+
}
|
|
5518
|
+
};
|
|
5519
|
+
setProviderField('baseUrl', baseUrl);
|
|
5520
|
+
setProviderField('api', apiType);
|
|
5521
|
+
if (apiKey) {
|
|
5522
|
+
setProviderField('apiKey', apiKey);
|
|
5523
|
+
}
|
|
5524
|
+
|
|
5525
|
+
const modelName = (this.openclawQuick.modelName || '').trim() || modelId;
|
|
5526
|
+
const modelEntry = {
|
|
5527
|
+
id: modelId,
|
|
5528
|
+
name: modelName,
|
|
5529
|
+
reasoning: false,
|
|
5530
|
+
input: ['text'],
|
|
5531
|
+
cost: {
|
|
5532
|
+
input: 0,
|
|
5533
|
+
output: 0,
|
|
5534
|
+
cacheRead: 0,
|
|
5535
|
+
cacheWrite: 0
|
|
5536
|
+
}
|
|
5537
|
+
};
|
|
5538
|
+
if (contextWindow.value !== null) {
|
|
5539
|
+
modelEntry.contextWindow = contextWindow.value;
|
|
5540
|
+
}
|
|
5541
|
+
if (maxTokens.value !== null) {
|
|
5542
|
+
modelEntry.maxTokens = maxTokens.value;
|
|
5543
|
+
}
|
|
5544
|
+
|
|
5545
|
+
const existingModels = Array.isArray(provider.models) ? [...provider.models] : [];
|
|
5546
|
+
if (this.openclawQuick.overrideModels || existingModels.length === 0) {
|
|
5547
|
+
provider.models = [modelEntry];
|
|
5548
|
+
} else {
|
|
5549
|
+
const idx = existingModels.findIndex(item => item && item.id === modelId);
|
|
5550
|
+
if (idx >= 0) {
|
|
5551
|
+
existingModels[idx] = this.mergeOpenclawModelEntry(existingModels[idx], modelEntry, false);
|
|
5552
|
+
} else {
|
|
5553
|
+
existingModels.push(modelEntry);
|
|
5554
|
+
}
|
|
5555
|
+
provider.models = existingModels;
|
|
5556
|
+
}
|
|
5557
|
+
|
|
5558
|
+
providers[providerName] = provider;
|
|
5559
|
+
models.providers = providers;
|
|
5560
|
+
config.models = models;
|
|
5561
|
+
|
|
5562
|
+
if (this.openclawQuick.setPrimary) {
|
|
5563
|
+
const agents = ensureObject(config.agents);
|
|
5564
|
+
const defaults = ensureObject(agents.defaults);
|
|
5565
|
+
const modelConfig = defaults.model && typeof defaults.model === 'object' && !Array.isArray(defaults.model)
|
|
5566
|
+
? defaults.model
|
|
5567
|
+
: {};
|
|
5568
|
+
modelConfig.primary = `${providerName}/${modelId}`;
|
|
5569
|
+
defaults.model = modelConfig;
|
|
5570
|
+
agents.defaults = defaults;
|
|
5571
|
+
config.agents = agents;
|
|
5572
|
+
if (config.agent && typeof config.agent === 'object' && !Array.isArray(config.agent)) {
|
|
5573
|
+
config.agent.model = modelConfig.primary;
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5576
|
+
|
|
5577
|
+
this.openclawEditing.content = this.stringifyOpenclawConfig(config);
|
|
5578
|
+
this.fillOpenclawStructured(config);
|
|
5579
|
+
this.refreshOpenclawProviders(config);
|
|
5580
|
+
this.refreshOpenclawAgentsList(config);
|
|
5581
|
+
this.showMessage('快速配置已写入编辑器', 'success');
|
|
5582
|
+
},
|
|
5583
|
+
|
|
5584
|
+
addOpenclawFallback() {
|
|
5585
|
+
this.openclawStructured.agentFallbacks.push('');
|
|
5586
|
+
},
|
|
5587
|
+
|
|
5588
|
+
removeOpenclawFallback(index) {
|
|
5589
|
+
this.openclawStructured.agentFallbacks.splice(index, 1);
|
|
5590
|
+
if (this.openclawStructured.agentFallbacks.length === 0) {
|
|
5591
|
+
this.openclawStructured.agentFallbacks.push('');
|
|
5592
|
+
}
|
|
5593
|
+
},
|
|
5594
|
+
|
|
5595
|
+
addOpenclawEnvItem() {
|
|
5596
|
+
this.openclawStructured.envItems.push({ key: '', value: '', show: false });
|
|
5597
|
+
},
|
|
5598
|
+
|
|
5599
|
+
removeOpenclawEnvItem(index) {
|
|
5600
|
+
this.openclawStructured.envItems.splice(index, 1);
|
|
5601
|
+
if (this.openclawStructured.envItems.length === 0) {
|
|
5602
|
+
this.openclawStructured.envItems.push({ key: '', value: '', show: false });
|
|
5603
|
+
}
|
|
5604
|
+
},
|
|
5605
|
+
|
|
5606
|
+
toggleOpenclawEnvItem(index) {
|
|
5607
|
+
const item = this.openclawStructured.envItems[index];
|
|
5608
|
+
if (item) {
|
|
5609
|
+
item.show = !item.show;
|
|
5610
|
+
}
|
|
5611
|
+
},
|
|
5612
|
+
|
|
5613
|
+
addOpenclawToolsAllow() {
|
|
5614
|
+
this.openclawStructured.toolsAllow.push('');
|
|
5615
|
+
},
|
|
5616
|
+
|
|
5617
|
+
removeOpenclawToolsAllow(index) {
|
|
5618
|
+
this.openclawStructured.toolsAllow.splice(index, 1);
|
|
5619
|
+
if (this.openclawStructured.toolsAllow.length === 0) {
|
|
5620
|
+
this.openclawStructured.toolsAllow.push('');
|
|
5621
|
+
}
|
|
5622
|
+
},
|
|
5623
|
+
|
|
5624
|
+
addOpenclawToolsDeny() {
|
|
5625
|
+
this.openclawStructured.toolsDeny.push('');
|
|
5626
|
+
},
|
|
5627
|
+
|
|
5628
|
+
removeOpenclawToolsDeny(index) {
|
|
5629
|
+
this.openclawStructured.toolsDeny.splice(index, 1);
|
|
5630
|
+
if (this.openclawStructured.toolsDeny.length === 0) {
|
|
5631
|
+
this.openclawStructured.toolsDeny.push('');
|
|
5632
|
+
}
|
|
5633
|
+
},
|
|
5634
|
+
|
|
3783
5635
|
openclawHasContent(config) {
|
|
3784
5636
|
return !!(config && typeof config.content === 'string' && config.content.trim());
|
|
3785
5637
|
},
|
|
@@ -3800,23 +5652,24 @@
|
|
|
3800
5652
|
this.openclawEditorTitle = '添加 OpenClaw 配置';
|
|
3801
5653
|
this.openclawEditing = {
|
|
3802
5654
|
name: '',
|
|
3803
|
-
content:
|
|
5655
|
+
content: '',
|
|
3804
5656
|
lockName: false
|
|
3805
5657
|
};
|
|
3806
5658
|
this.openclawConfigPath = '';
|
|
3807
5659
|
this.openclawConfigExists = false;
|
|
3808
5660
|
this.openclawLineEnding = '\n';
|
|
5661
|
+
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
3809
5662
|
this.showOpenclawConfigModal = true;
|
|
3810
5663
|
},
|
|
3811
5664
|
|
|
3812
5665
|
openOpenclawEditModal(name) {
|
|
3813
|
-
const config = this.openclawConfigs[name];
|
|
3814
5666
|
this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
|
|
3815
5667
|
this.openclawEditing = {
|
|
3816
5668
|
name,
|
|
3817
|
-
content:
|
|
5669
|
+
content: '',
|
|
3818
5670
|
lockName: true
|
|
3819
5671
|
};
|
|
5672
|
+
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
3820
5673
|
this.showOpenclawConfigModal = true;
|
|
3821
5674
|
},
|
|
3822
5675
|
|
|
@@ -3825,27 +5678,41 @@
|
|
|
3825
5678
|
this.openclawEditing = { name: '', content: '', lockName: false };
|
|
3826
5679
|
this.openclawSaving = false;
|
|
3827
5680
|
this.openclawApplying = false;
|
|
5681
|
+
this.resetOpenclawStructured();
|
|
5682
|
+
this.resetOpenclawQuick();
|
|
3828
5683
|
},
|
|
3829
5684
|
|
|
3830
|
-
async loadOpenclawConfigFromFile() {
|
|
5685
|
+
async loadOpenclawConfigFromFile(options = {}) {
|
|
5686
|
+
const silent = !!options.silent;
|
|
5687
|
+
const force = !!options.force;
|
|
5688
|
+
const fallbackToTemplate = options.fallbackToTemplate !== false;
|
|
3831
5689
|
this.openclawFileLoading = true;
|
|
3832
5690
|
try {
|
|
3833
5691
|
const res = await api('get-openclaw-config');
|
|
3834
5692
|
if (res.error) {
|
|
3835
|
-
|
|
5693
|
+
if (!silent) {
|
|
5694
|
+
this.showMessage(res.error, 'error');
|
|
5695
|
+
}
|
|
3836
5696
|
return;
|
|
3837
5697
|
}
|
|
3838
5698
|
this.openclawConfigPath = res.path || '';
|
|
3839
5699
|
this.openclawConfigExists = !!res.exists;
|
|
3840
5700
|
this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3841
|
-
|
|
5701
|
+
const hasContent = !!(res.content && res.content.trim());
|
|
5702
|
+
const shouldOverride = force || !this.openclawEditing.content || !this.openclawEditing.content.trim();
|
|
5703
|
+
if (hasContent && shouldOverride) {
|
|
3842
5704
|
this.openclawEditing.content = res.content;
|
|
3843
|
-
} else if (!
|
|
5705
|
+
} else if (!hasContent && shouldOverride && fallbackToTemplate) {
|
|
3844
5706
|
this.openclawEditing.content = DEFAULT_OPENCLAW_TEMPLATE;
|
|
3845
5707
|
}
|
|
3846
|
-
this.
|
|
5708
|
+
this.syncOpenclawStructuredFromText({ silent: true });
|
|
5709
|
+
if (!silent) {
|
|
5710
|
+
this.showMessage('已加载当前 OpenClaw 配置', 'success');
|
|
5711
|
+
}
|
|
3847
5712
|
} catch (e) {
|
|
3848
|
-
|
|
5713
|
+
if (!silent) {
|
|
5714
|
+
this.showMessage('加载 OpenClaw 配置失败: ' + e.message, 'error');
|
|
5715
|
+
}
|
|
3849
5716
|
} finally {
|
|
3850
5717
|
this.openclawFileLoading = false;
|
|
3851
5718
|
}
|
|
@@ -3954,19 +5821,93 @@
|
|
|
3954
5821
|
return `${ms}ms`;
|
|
3955
5822
|
},
|
|
3956
5823
|
|
|
3957
|
-
|
|
3958
|
-
if (!name ||
|
|
5824
|
+
buildSpeedTestIssue(name, result) {
|
|
5825
|
+
if (!name || !result) return null;
|
|
5826
|
+
if (result.error) {
|
|
5827
|
+
const error = String(result.error || '');
|
|
5828
|
+
const errorLower = error.toLowerCase();
|
|
5829
|
+
if (error === 'Provider not found') {
|
|
5830
|
+
return {
|
|
5831
|
+
code: 'remote-speedtest-provider-missing',
|
|
5832
|
+
message: `提供商 ${name} 未找到,无法测速`,
|
|
5833
|
+
suggestion: '检查配置是否存在该 provider'
|
|
5834
|
+
};
|
|
5835
|
+
}
|
|
5836
|
+
if (error === 'Provider missing URL' || error === 'Missing name or url') {
|
|
5837
|
+
return {
|
|
5838
|
+
code: 'remote-speedtest-baseurl-missing',
|
|
5839
|
+
message: `提供商 ${name} 缺少 base_url`,
|
|
5840
|
+
suggestion: '补全 base_url 后重试'
|
|
5841
|
+
};
|
|
5842
|
+
}
|
|
5843
|
+
if (errorLower.includes('invalid url')) {
|
|
5844
|
+
return {
|
|
5845
|
+
code: 'remote-speedtest-invalid-url',
|
|
5846
|
+
message: `提供商 ${name} 的 base_url 无效`,
|
|
5847
|
+
suggestion: '请设置为 http/https 的完整 URL'
|
|
5848
|
+
};
|
|
5849
|
+
}
|
|
5850
|
+
if (errorLower.includes('timeout')) {
|
|
5851
|
+
return {
|
|
5852
|
+
code: 'remote-speedtest-timeout',
|
|
5853
|
+
message: `提供商 ${name} 远程测速超时`,
|
|
5854
|
+
suggestion: '检查网络或 base_url 是否可达'
|
|
5855
|
+
};
|
|
5856
|
+
}
|
|
5857
|
+
return {
|
|
5858
|
+
code: 'remote-speedtest-unreachable',
|
|
5859
|
+
message: `提供商 ${name} 远程测速失败:${error || '无法连接'}`,
|
|
5860
|
+
suggestion: '检查网络或 base_url 是否可用'
|
|
5861
|
+
};
|
|
5862
|
+
}
|
|
5863
|
+
|
|
5864
|
+
const status = typeof result.status === 'number' ? result.status : 0;
|
|
5865
|
+
if (status === 401 || status === 403) {
|
|
5866
|
+
return {
|
|
5867
|
+
code: 'remote-speedtest-auth-failed',
|
|
5868
|
+
message: `提供商 ${name} 远程测速鉴权失败(401/403)`,
|
|
5869
|
+
suggestion: '检查 API Key 或认证方式'
|
|
5870
|
+
};
|
|
5871
|
+
}
|
|
5872
|
+
if (status >= 400) {
|
|
5873
|
+
return {
|
|
5874
|
+
code: 'remote-speedtest-http-error',
|
|
5875
|
+
message: `提供商 ${name} 远程测速返回异常状态: ${status}`,
|
|
5876
|
+
suggestion: '检查 base_url 或服务状态'
|
|
5877
|
+
};
|
|
5878
|
+
}
|
|
5879
|
+
return null;
|
|
5880
|
+
},
|
|
5881
|
+
|
|
5882
|
+
async runSpeedTest(name, options = {}) {
|
|
5883
|
+
if (!name || this.speedLoading[name]) return null;
|
|
5884
|
+
const silent = !!options.silent;
|
|
3959
5885
|
this.speedLoading[name] = true;
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
5886
|
+
try {
|
|
5887
|
+
const res = await api('speed-test', { name });
|
|
5888
|
+
if (res.error) {
|
|
5889
|
+
this.speedResults[name] = { ok: false, error: res.error };
|
|
5890
|
+
if (!silent) {
|
|
5891
|
+
this.showMessage(res.error, 'error');
|
|
5892
|
+
}
|
|
5893
|
+
return { ok: false, error: res.error };
|
|
5894
|
+
}
|
|
3965
5895
|
this.speedResults[name] = res;
|
|
3966
|
-
|
|
3967
|
-
|
|
5896
|
+
if (!silent) {
|
|
5897
|
+
const status = res.status ? ` (${res.status})` : '';
|
|
5898
|
+
this.showMessage(`Speed ${name}: ${this.formatLatency(res)}${status}`, 'success');
|
|
5899
|
+
}
|
|
5900
|
+
return res;
|
|
5901
|
+
} catch (e) {
|
|
5902
|
+
const message = e && e.message ? e.message : 'Speed test failed';
|
|
5903
|
+
this.speedResults[name] = { ok: false, error: message };
|
|
5904
|
+
if (!silent) {
|
|
5905
|
+
this.showMessage(message, 'error');
|
|
5906
|
+
}
|
|
5907
|
+
return { ok: false, error: message };
|
|
5908
|
+
} finally {
|
|
5909
|
+
this.speedLoading[name] = false;
|
|
3968
5910
|
}
|
|
3969
|
-
this.speedLoading[name] = false;
|
|
3970
5911
|
},
|
|
3971
5912
|
|
|
3972
5913
|
showMessage(text, type) {
|