codexmate 0.0.4 → 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/ISSUE_TEMPLATE/bug_report.md +27 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/.github/workflows/ci.yml +26 -0
- package/CHANGELOG.md +14 -0
- package/CHANGELOG.zh-CN.md +14 -0
- package/README.md +117 -74
- package/README.zh-CN.md +124 -77
- package/cli.js +2697 -675
- package/package.json +12 -5
- package/tests/e2e/recent-health.e2e.js +136 -0
- package/tests/e2e/run.js +357 -0
- package/web-ui.html +3036 -656
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
|
|
|
@@ -523,6 +524,101 @@
|
|
|
523
524
|
gap: var(--spacing-xs);
|
|
524
525
|
}
|
|
525
526
|
|
|
527
|
+
.recent-list {
|
|
528
|
+
display: flex;
|
|
529
|
+
flex-wrap: wrap;
|
|
530
|
+
gap: 8px;
|
|
531
|
+
margin-top: 8px;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.recent-item {
|
|
535
|
+
border: 1px solid var(--color-border-soft);
|
|
536
|
+
background: var(--color-surface-elevated);
|
|
537
|
+
border-radius: var(--radius-md);
|
|
538
|
+
padding: 10px 12px;
|
|
539
|
+
min-width: 170px;
|
|
540
|
+
text-align: left;
|
|
541
|
+
cursor: pointer;
|
|
542
|
+
transition: all var(--transition-fast) var(--ease-spring);
|
|
543
|
+
box-shadow: 0 2px 6px rgba(27, 23, 20, 0.06);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.recent-item:hover {
|
|
547
|
+
transform: translateY(-1px);
|
|
548
|
+
box-shadow: 0 4px 12px rgba(27, 23, 20, 0.12);
|
|
549
|
+
border-color: var(--color-border);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.recent-item:disabled {
|
|
553
|
+
opacity: 0.6;
|
|
554
|
+
cursor: not-allowed;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.recent-provider {
|
|
558
|
+
font-size: var(--font-size-body);
|
|
559
|
+
font-weight: var(--font-weight-secondary);
|
|
560
|
+
color: var(--color-text-primary);
|
|
561
|
+
margin-bottom: 4px;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.recent-model {
|
|
565
|
+
font-size: var(--font-size-caption);
|
|
566
|
+
color: var(--color-text-tertiary);
|
|
567
|
+
line-height: 1.4;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.recent-empty {
|
|
571
|
+
font-size: var(--font-size-caption);
|
|
572
|
+
color: var(--color-text-tertiary);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.health-report {
|
|
576
|
+
margin-top: 10px;
|
|
577
|
+
padding: 10px 12px;
|
|
578
|
+
border-radius: var(--radius-md);
|
|
579
|
+
border: 1px solid var(--color-border-soft);
|
|
580
|
+
background: var(--color-surface-alt);
|
|
581
|
+
display: grid;
|
|
582
|
+
gap: 8px;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.health-remote-toggle {
|
|
586
|
+
display: inline-flex;
|
|
587
|
+
align-items: center;
|
|
588
|
+
gap: 8px;
|
|
589
|
+
font-size: var(--font-size-caption);
|
|
590
|
+
color: var(--color-text-secondary);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.health-remote-toggle input {
|
|
594
|
+
accent-color: var(--color-brand);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.health-ok {
|
|
598
|
+
color: var(--color-success);
|
|
599
|
+
font-weight: var(--font-weight-secondary);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.health-issue {
|
|
603
|
+
background: #fff6f5;
|
|
604
|
+
border-left: 3px solid var(--color-error);
|
|
605
|
+
padding: 8px 10px;
|
|
606
|
+
border-radius: 10px;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.health-issue-title {
|
|
610
|
+
font-size: var(--font-size-caption);
|
|
611
|
+
font-weight: var(--font-weight-secondary);
|
|
612
|
+
color: var(--color-text-primary);
|
|
613
|
+
margin-bottom: 4px;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.health-issue-suggestion {
|
|
617
|
+
font-size: var(--font-size-caption);
|
|
618
|
+
color: var(--color-text-secondary);
|
|
619
|
+
line-height: 1.4;
|
|
620
|
+
}
|
|
621
|
+
|
|
526
622
|
.btn-icon {
|
|
527
623
|
width: 28px;
|
|
528
624
|
height: 28px;
|
|
@@ -580,6 +676,31 @@
|
|
|
580
676
|
box-shadow: var(--shadow-input-focus);
|
|
581
677
|
}
|
|
582
678
|
|
|
679
|
+
.model-input {
|
|
680
|
+
width: 100%;
|
|
681
|
+
padding: 12px var(--spacing-sm);
|
|
682
|
+
border: 1px solid var(--color-border-soft);
|
|
683
|
+
border-radius: var(--radius-sm);
|
|
684
|
+
font-size: var(--font-size-body);
|
|
685
|
+
font-weight: var(--font-weight-body);
|
|
686
|
+
background-color: var(--color-surface-alt);
|
|
687
|
+
color: var(--color-text-primary);
|
|
688
|
+
outline: none;
|
|
689
|
+
transition: all var(--transition-fast) var(--ease-smooth);
|
|
690
|
+
box-shadow: inset 0 1px 2px rgba(31, 26, 23, 0.04);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.model-input:hover {
|
|
694
|
+
border-color: var(--color-border-strong);
|
|
695
|
+
background-color: var(--color-surface);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
.model-input:focus {
|
|
699
|
+
background-color: var(--color-surface);
|
|
700
|
+
border-color: var(--color-brand);
|
|
701
|
+
box-shadow: var(--shadow-input-focus);
|
|
702
|
+
}
|
|
703
|
+
|
|
583
704
|
.config-template-hint {
|
|
584
705
|
margin-top: 8px;
|
|
585
706
|
margin-bottom: 10px;
|
|
@@ -800,6 +921,51 @@
|
|
|
800
921
|
letter-spacing: -0.01em;
|
|
801
922
|
}
|
|
802
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
|
+
|
|
803
969
|
.btn-session-refresh {
|
|
804
970
|
border: 1px solid var(--color-border-soft);
|
|
805
971
|
border-radius: var(--radius-sm);
|
|
@@ -827,13 +993,39 @@
|
|
|
827
993
|
transform: none;
|
|
828
994
|
}
|
|
829
995
|
|
|
830
|
-
.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 {
|
|
831
1011
|
border-color: var(--color-brand);
|
|
832
1012
|
color: var(--color-brand);
|
|
833
1013
|
transform: translateY(-1px);
|
|
834
1014
|
}
|
|
835
1015
|
|
|
836
|
-
.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 {
|
|
837
1029
|
opacity: 0.5;
|
|
838
1030
|
cursor: not-allowed;
|
|
839
1031
|
transform: none;
|
|
@@ -870,11 +1062,38 @@
|
|
|
870
1062
|
min-height: 520px;
|
|
871
1063
|
}
|
|
872
1064
|
|
|
873
|
-
.session-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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;
|
|
878
1097
|
top: 12px;
|
|
879
1098
|
height: 100%;
|
|
880
1099
|
max-height: none;
|
|
@@ -988,6 +1207,21 @@
|
|
|
988
1207
|
transition: all var(--transition-fast) var(--ease-spring);
|
|
989
1208
|
}
|
|
990
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
|
+
|
|
991
1225
|
.session-item-copy:hover {
|
|
992
1226
|
border-color: rgba(70, 86, 110, 0.7);
|
|
993
1227
|
background: rgba(70, 86, 110, 0.16);
|
|
@@ -995,17 +1229,35 @@
|
|
|
995
1229
|
transform: translateY(-1px);
|
|
996
1230
|
}
|
|
997
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
|
+
|
|
998
1239
|
.session-item-copy:disabled {
|
|
999
1240
|
opacity: 0.5;
|
|
1000
1241
|
cursor: not-allowed;
|
|
1001
1242
|
transform: none;
|
|
1002
1243
|
}
|
|
1003
1244
|
|
|
1245
|
+
.session-item-delete:disabled {
|
|
1246
|
+
opacity: 0.5;
|
|
1247
|
+
cursor: not-allowed;
|
|
1248
|
+
transform: none;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1004
1251
|
.session-item-copy svg {
|
|
1005
1252
|
width: 16px;
|
|
1006
1253
|
height: 16px;
|
|
1007
1254
|
}
|
|
1008
1255
|
|
|
1256
|
+
.session-item-delete svg {
|
|
1257
|
+
width: 16px;
|
|
1258
|
+
height: 16px;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1009
1261
|
.session-item-sub {
|
|
1010
1262
|
font-size: var(--font-size-caption);
|
|
1011
1263
|
color: var(--color-text-tertiary);
|
|
@@ -1280,6 +1532,37 @@
|
|
|
1280
1532
|
}
|
|
1281
1533
|
}
|
|
1282
1534
|
|
|
1535
|
+
@media (max-width: 520px) {
|
|
1536
|
+
.session-item-header {
|
|
1537
|
+
flex-direction: column;
|
|
1538
|
+
align-items: stretch;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
.session-item-actions {
|
|
1542
|
+
justify-content: flex-end;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
.session-actions {
|
|
1546
|
+
width: 100%;
|
|
1547
|
+
flex-direction: column;
|
|
1548
|
+
align-items: stretch;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
.btn-session-refresh,
|
|
1552
|
+
.btn-session-export {
|
|
1553
|
+
width: 100%;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
.session-toolbar-group.session-toolbar-actions {
|
|
1557
|
+
flex-direction: column;
|
|
1558
|
+
align-items: stretch;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
.session-toolbar-group.session-toolbar-actions .btn-tool {
|
|
1562
|
+
width: 100%;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1283
1566
|
.btn[disabled] {
|
|
1284
1567
|
opacity: 0.5;
|
|
1285
1568
|
cursor: not-allowed;
|
|
@@ -1310,6 +1593,9 @@
|
|
|
1310
1593
|
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.98) 100%);
|
|
1311
1594
|
width: 90%;
|
|
1312
1595
|
max-width: 400px;
|
|
1596
|
+
max-height: 90vh;
|
|
1597
|
+
overflow-y: auto;
|
|
1598
|
+
overscroll-behavior: contain;
|
|
1313
1599
|
border-radius: var(--radius-lg);
|
|
1314
1600
|
padding: var(--spacing-md);
|
|
1315
1601
|
box-shadow: var(--shadow-modal);
|
|
@@ -1329,6 +1615,25 @@
|
|
|
1329
1615
|
letter-spacing: -0.01em;
|
|
1330
1616
|
}
|
|
1331
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
|
+
|
|
1332
1637
|
.form-group {
|
|
1333
1638
|
margin-bottom: var(--spacing-sm);
|
|
1334
1639
|
}
|
|
@@ -1404,39 +1709,313 @@
|
|
|
1404
1709
|
opacity: 0.8;
|
|
1405
1710
|
}
|
|
1406
1711
|
|
|
1407
|
-
.
|
|
1408
|
-
display: flex;
|
|
1409
|
-
gap: var(--spacing-sm);
|
|
1712
|
+
.quick-section {
|
|
1410
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));
|
|
1411
1718
|
}
|
|
1412
1719
|
|
|
1413
|
-
.
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
transition: all var(--transition-fast) var(--ease-spring);
|
|
1421
|
-
border: 1px solid var(--color-border-soft);
|
|
1422
|
-
background: linear-gradient(to bottom, var(--color-surface) 0%, rgba(255, 255, 255, 0.95) 100%);
|
|
1423
|
-
color: var(--color-text-secondary);
|
|
1424
|
-
box-shadow: var(--shadow-subtle);
|
|
1425
|
-
letter-spacing: -0.01em;
|
|
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);
|
|
1426
1727
|
}
|
|
1427
1728
|
|
|
1428
|
-
.
|
|
1429
|
-
|
|
1729
|
+
.quick-title {
|
|
1730
|
+
font-size: var(--font-size-secondary);
|
|
1731
|
+
font-weight: var(--font-weight-secondary);
|
|
1732
|
+
color: var(--color-text-secondary);
|
|
1430
1733
|
}
|
|
1431
1734
|
|
|
1432
|
-
.
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1735
|
+
.quick-actions {
|
|
1736
|
+
display: flex;
|
|
1737
|
+
flex-wrap: wrap;
|
|
1738
|
+
gap: var(--spacing-xs);
|
|
1436
1739
|
}
|
|
1437
1740
|
|
|
1438
|
-
.
|
|
1439
|
-
|
|
1741
|
+
.quick-steps {
|
|
1742
|
+
display: flex;
|
|
1743
|
+
flex-wrap: wrap;
|
|
1744
|
+
gap: var(--spacing-xs);
|
|
1745
|
+
margin-bottom: var(--spacing-sm);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
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);
|
|
1758
|
+
}
|
|
1759
|
+
|
|
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);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
.quick-grid {
|
|
1774
|
+
display: grid;
|
|
1775
|
+
gap: var(--spacing-sm);
|
|
1776
|
+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
1777
|
+
}
|
|
1778
|
+
|
|
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);
|
|
1785
|
+
}
|
|
1786
|
+
|
|
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;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
.quick-option input {
|
|
1797
|
+
accent-color: var(--color-brand);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
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);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
.structured-header {
|
|
1809
|
+
display: flex;
|
|
1810
|
+
flex-wrap: wrap;
|
|
1811
|
+
gap: var(--spacing-xs);
|
|
1812
|
+
align-items: baseline;
|
|
1813
|
+
justify-content: space-between;
|
|
1814
|
+
margin-bottom: var(--spacing-sm);
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
.structured-title {
|
|
1818
|
+
font-size: var(--font-size-secondary);
|
|
1819
|
+
font-weight: var(--font-weight-secondary);
|
|
1820
|
+
color: var(--color-text-secondary);
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
.structured-grid {
|
|
1824
|
+
display: grid;
|
|
1825
|
+
gap: var(--spacing-sm);
|
|
1826
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
1827
|
+
}
|
|
1828
|
+
|
|
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);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
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;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
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%);
|
|
1440
2019
|
}
|
|
1441
2020
|
|
|
1442
2021
|
.btn-confirm {
|
|
@@ -1662,27 +2241,27 @@
|
|
|
1662
2241
|
<body>
|
|
1663
2242
|
<div id="app" class="container" v-cloak>
|
|
1664
2243
|
<!-- 主标题 -->
|
|
1665
|
-
<h1 class="main-title">
|
|
2244
|
+
<h1 v-if="!sessionStandalone" class="main-title">
|
|
1666
2245
|
Codex<br>
|
|
1667
2246
|
<span class="accent">Mate.</span>
|
|
1668
2247
|
</h1>
|
|
1669
|
-
<p class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</p>
|
|
2248
|
+
<p v-if="!sessionStandalone" class="subtitle">本地配置中枢,统一管理 Codex / Claude Code / OpenClaw / 会话。</p>
|
|
1670
2249
|
|
|
1671
2250
|
<!-- 模式切换器 -->
|
|
1672
|
-
<div class="segmented-control">
|
|
1673
|
-
<button :class="['segment', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
|
|
1674
|
-
Codex 配置
|
|
1675
|
-
</button>
|
|
1676
|
-
<button :class="['segment', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
|
|
1677
|
-
Claude Code 配置
|
|
1678
|
-
</button>
|
|
1679
|
-
<button :class="['segment', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">
|
|
1680
|
-
OpenClaw 配置
|
|
1681
|
-
</button>
|
|
1682
|
-
<button :class="['segment', { active: configMode === 'sessions' }]" @click="switchConfigMode('sessions')">
|
|
1683
|
-
会话浏览
|
|
1684
|
-
</button>
|
|
1685
|
-
</div>
|
|
2251
|
+
<div v-if="!sessionStandalone" class="segmented-control">
|
|
2252
|
+
<button :class="['segment', { active: configMode === 'codex' }]" @click="switchConfigMode('codex')">
|
|
2253
|
+
Codex 配置
|
|
2254
|
+
</button>
|
|
2255
|
+
<button :class="['segment', { active: configMode === 'claude' }]" @click="switchConfigMode('claude')">
|
|
2256
|
+
Claude Code 配置
|
|
2257
|
+
</button>
|
|
2258
|
+
<button :class="['segment', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">
|
|
2259
|
+
OpenClaw 配置
|
|
2260
|
+
</button>
|
|
2261
|
+
<button :class="['segment', { active: configMode === 'sessions' }]" @click="switchConfigMode('sessions')">
|
|
2262
|
+
会话浏览
|
|
2263
|
+
</button>
|
|
2264
|
+
</div>
|
|
1686
2265
|
|
|
1687
2266
|
<!-- 内容包裹器 - 稳定布局 -->
|
|
1688
2267
|
<div class="content-wrapper">
|
|
@@ -1701,33 +2280,86 @@
|
|
|
1701
2280
|
<div class="selector-header">
|
|
1702
2281
|
<span class="selector-title">模型</span>
|
|
1703
2282
|
<div class="selector-actions">
|
|
1704
|
-
<button class="btn-icon" @click="showModelModal = true" title="添加模型">+</button>
|
|
1705
|
-
<button class="btn-icon" @click="showModelListModal = true" title="管理模型">≡</button>
|
|
2283
|
+
<button class="btn-icon" @click="showModelModal = true" title="添加模型" v-if="modelsSource === 'legacy'">+</button>
|
|
2284
|
+
<button class="btn-icon" @click="showModelListModal = true" title="管理模型" v-if="modelsSource === 'legacy'">≡</button>
|
|
1706
2285
|
</div>
|
|
1707
2286
|
</div>
|
|
1708
|
-
<select
|
|
1709
|
-
|
|
2287
|
+
<select
|
|
2288
|
+
v-if="codexModelsLoading || modelsSource === 'remote'"
|
|
2289
|
+
class="model-select"
|
|
2290
|
+
v-model="currentModel"
|
|
2291
|
+
@change="onModelChange"
|
|
2292
|
+
:disabled="codexModelsLoading"
|
|
2293
|
+
>
|
|
2294
|
+
<option v-if="codexModelsLoading" value="">加载中...</option>
|
|
2295
|
+
<option v-else v-for="model in models" :key="model" :value="model">{{ model }}</option>
|
|
1710
2296
|
</select>
|
|
2297
|
+
<input
|
|
2298
|
+
v-if="!codexModelsLoading && (modelsSource !== 'remote' || !modelsHasCurrent)"
|
|
2299
|
+
class="model-input"
|
|
2300
|
+
v-model="currentModel"
|
|
2301
|
+
@blur="onModelChange"
|
|
2302
|
+
placeholder="例如: gpt-5.3-codex"
|
|
2303
|
+
>
|
|
2304
|
+
<div class="config-template-hint" v-if="modelsSource === 'unlimited'">
|
|
2305
|
+
当前提供商未提供模型列表,视为不限。模型可手动输入。
|
|
2306
|
+
</div>
|
|
2307
|
+
<div class="config-template-hint" v-if="modelsSource === 'error'">
|
|
2308
|
+
模型列表获取失败,请检查接口或手动输入。
|
|
2309
|
+
</div>
|
|
2310
|
+
<div class="config-template-hint" v-if="modelsSource === 'remote' && !modelsHasCurrent">
|
|
2311
|
+
当前模型不在接口列表中,请手动输入或在模板中调整。
|
|
2312
|
+
</div>
|
|
1711
2313
|
<div class="config-template-hint">
|
|
1712
|
-
Codex
|
|
2314
|
+
Codex 配置需先改模板,再手动应用。
|
|
1713
2315
|
</div>
|
|
1714
2316
|
<button class="btn-tool btn-template-editor" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
|
|
1715
2317
|
打开 Config 模板编辑器
|
|
1716
2318
|
</button>
|
|
1717
2319
|
</div>
|
|
1718
2320
|
|
|
2321
|
+
<div class="selector-section">
|
|
2322
|
+
<div class="selector-header">
|
|
2323
|
+
<span class="selector-title">最近使用</span>
|
|
2324
|
+
<span v-if="recentLoading" class="selector-title">加载中...</span>
|
|
2325
|
+
</div>
|
|
2326
|
+
<div v-if="recentConfigs.length === 0" class="recent-empty">
|
|
2327
|
+
暂无记录
|
|
2328
|
+
</div>
|
|
2329
|
+
<div v-else class="recent-list">
|
|
2330
|
+
<button
|
|
2331
|
+
v-for="item in recentConfigs"
|
|
2332
|
+
:key="item.provider + '::' + item.model + '::' + (item.usedAt || '')"
|
|
2333
|
+
class="recent-item"
|
|
2334
|
+
@click="applyRecentConfig(item)"
|
|
2335
|
+
:disabled="loading || !!initError">
|
|
2336
|
+
<div class="recent-provider">{{ item.provider }}</div>
|
|
2337
|
+
<div class="recent-model">{{ item.model }}</div>
|
|
2338
|
+
</button>
|
|
2339
|
+
</div>
|
|
2340
|
+
</div>
|
|
2341
|
+
|
|
1719
2342
|
<div class="selector-section">
|
|
1720
2343
|
<div class="selector-header">
|
|
1721
2344
|
<span class="selector-title">AGENTS.md</span>
|
|
1722
2345
|
</div>
|
|
1723
2346
|
<div class="config-template-hint">
|
|
1724
|
-
|
|
2347
|
+
Codex 指令:<code>~/.codex/AGENTS.md</code>(同级 <code>config.toml</code>)。
|
|
1725
2348
|
</div>
|
|
1726
2349
|
<button class="btn-tool" @click="openAgentsEditor" :disabled="loading || !!initError || agentsLoading">
|
|
1727
2350
|
{{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
|
|
1728
2351
|
</button>
|
|
1729
2352
|
</div>
|
|
1730
2353
|
|
|
2354
|
+
<div class="selector-section">
|
|
2355
|
+
<div class="selector-header">
|
|
2356
|
+
<span class="selector-title">配置健康检查</span>
|
|
2357
|
+
</div>
|
|
2358
|
+
<button class="btn-tool" @click="runHealthCheck" :disabled="healthCheckLoading || loading || !!initError">
|
|
2359
|
+
{{ healthCheckLoading ? '检查中...' : '运行检查' }}
|
|
2360
|
+
</button>
|
|
2361
|
+
</div>
|
|
2362
|
+
|
|
1731
2363
|
<div v-if="!loading && !initError" class="card-list">
|
|
1732
2364
|
<div v-for="provider in providersList" :key="provider.name"
|
|
1733
2365
|
:class="['card', { active: currentProvider === provider.name }]"
|
|
@@ -1770,17 +2402,33 @@
|
|
|
1770
2402
|
</div>
|
|
1771
2403
|
</div>
|
|
1772
2404
|
|
|
1773
|
-
<!-- Claude Code 配置模式 -->
|
|
1774
|
-
<div v-show="configMode === 'claude'" class="mode-content">
|
|
1775
|
-
<!-- 添加提供商按钮 -->
|
|
1776
|
-
<button class="btn-add" @click="
|
|
2405
|
+
<!-- Claude Code 配置模式 -->
|
|
2406
|
+
<div v-show="configMode === 'claude'" class="mode-content">
|
|
2407
|
+
<!-- 添加提供商按钮 -->
|
|
2408
|
+
<button class="btn-add" @click="openClaudeConfigModal" v-if="!loading && !initError">
|
|
1777
2409
|
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
1778
2410
|
<path d="M10 4v12M4 10h12"/>
|
|
1779
2411
|
</svg>
|
|
1780
2412
|
添加提供商
|
|
1781
2413
|
</button>
|
|
1782
2414
|
<div class="config-template-hint">
|
|
1783
|
-
默认应用到 <code>~/.claude/settings.json</code
|
|
2415
|
+
默认应用到 <code>~/.claude/settings.json</code>。
|
|
2416
|
+
</div>
|
|
2417
|
+
|
|
2418
|
+
<div class="selector-section">
|
|
2419
|
+
<div class="selector-header">
|
|
2420
|
+
<span class="selector-title">模型</span>
|
|
2421
|
+
</div>
|
|
2422
|
+
<input
|
|
2423
|
+
class="model-input"
|
|
2424
|
+
v-model="currentClaudeModel"
|
|
2425
|
+
@blur="onClaudeModelChange"
|
|
2426
|
+
@keyup.enter="onClaudeModelChange"
|
|
2427
|
+
placeholder="例如: claude-3-7-sonnet"
|
|
2428
|
+
>
|
|
2429
|
+
<div class="config-template-hint">
|
|
2430
|
+
模型修改后会自动保存并应用到当前配置。
|
|
2431
|
+
</div>
|
|
1784
2432
|
</div>
|
|
1785
2433
|
|
|
1786
2434
|
<div class="card-list">
|
|
@@ -1814,73 +2462,106 @@
|
|
|
1814
2462
|
</div>
|
|
1815
2463
|
</div>
|
|
1816
2464
|
</div>
|
|
1817
|
-
</div>
|
|
1818
|
-
</div>
|
|
1819
|
-
|
|
1820
|
-
<!-- OpenClaw 配置模式 -->
|
|
1821
|
-
<div v-show="configMode === 'openclaw'" class="mode-content">
|
|
1822
|
-
<button class="btn-add" @click="openOpenclawAddModal" v-if="!loading && !initError">
|
|
1823
|
-
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
1824
|
-
<path d="M10 4v12M4 10h12"/>
|
|
1825
|
-
</svg>
|
|
1826
|
-
添加 OpenClaw 配置
|
|
1827
|
-
</button>
|
|
1828
|
-
<div class="config-template-hint">
|
|
1829
|
-
默认应用到 <code>~/.openclaw/openclaw.json</code
|
|
1830
|
-
</div>
|
|
1831
|
-
|
|
1832
|
-
<div class="selector-section">
|
|
1833
|
-
<div class="selector-header">
|
|
1834
|
-
<span class="selector-title">AGENTS.md</span>
|
|
1835
|
-
</div>
|
|
1836
|
-
<div class="config-template-hint">
|
|
1837
|
-
管理 OpenClaw Workspace 指令文件,默认读写 <code>~/.openclaw/workspace/AGENTS.md</code>。
|
|
1838
|
-
</div>
|
|
1839
|
-
<button class="btn-tool" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
|
|
1840
|
-
{{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
|
|
1841
|
-
</button>
|
|
1842
|
-
</div>
|
|
1843
|
-
|
|
1844
|
-
<div class="card-list">
|
|
1845
|
-
<div v-for="(config, name) in openclawConfigs" :key="name"
|
|
1846
|
-
:class="['card', { active: currentOpenclawConfig === name }]"
|
|
1847
|
-
@click="applyOpenclawConfig(name)">
|
|
1848
|
-
<div class="card-leading">
|
|
1849
|
-
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
|
|
1850
|
-
<div class="card-content">
|
|
1851
|
-
<div class="card-title">{{ name }}</div>
|
|
1852
|
-
<div class="card-subtitle">{{ openclawSubtitle(config) }}</div>
|
|
1853
|
-
</div>
|
|
1854
|
-
</div>
|
|
1855
|
-
<div class="card-trailing">
|
|
1856
|
-
<span :class="['pill', openclawHasContent(config) ? 'configured' : 'empty']">
|
|
1857
|
-
{{ openclawHasContent(config) ? '已配置' : '未配置' }}
|
|
1858
|
-
</span>
|
|
1859
|
-
<div class="card-actions" @click.stop>
|
|
1860
|
-
<button class="card-action-btn" @click="openOpenclawEditModal(name)" title="编辑">
|
|
1861
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1862
|
-
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
1863
|
-
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
1864
|
-
</svg>
|
|
1865
|
-
</button>
|
|
1866
|
-
<button class="card-action-btn delete" @click="deleteOpenclawConfig(name)" title="删除">
|
|
1867
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
1868
|
-
<path d="M3 6h18"/>
|
|
1869
|
-
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
1870
|
-
</svg>
|
|
1871
|
-
</button>
|
|
1872
|
-
</div>
|
|
1873
|
-
</div>
|
|
1874
|
-
</div>
|
|
1875
|
-
</div>
|
|
1876
|
-
</div>
|
|
1877
|
-
|
|
1878
|
-
<!-- 会话浏览模式 -->
|
|
1879
|
-
<div v-show="configMode === 'sessions'" class="mode-content">
|
|
2465
|
+
</div>
|
|
2466
|
+
</div>
|
|
2467
|
+
|
|
2468
|
+
<!-- OpenClaw 配置模式 -->
|
|
2469
|
+
<div v-show="configMode === 'openclaw'" class="mode-content">
|
|
2470
|
+
<button class="btn-add" @click="openOpenclawAddModal" v-if="!loading && !initError">
|
|
2471
|
+
<svg class="icon" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
|
|
2472
|
+
<path d="M10 4v12M4 10h12"/>
|
|
2473
|
+
</svg>
|
|
2474
|
+
添加 OpenClaw 配置
|
|
2475
|
+
</button>
|
|
2476
|
+
<div class="config-template-hint">
|
|
2477
|
+
默认应用到 <code>~/.openclaw/openclaw.json</code>。支持 JSON5(注释/尾逗号)。
|
|
2478
|
+
</div>
|
|
2479
|
+
|
|
2480
|
+
<div class="selector-section">
|
|
2481
|
+
<div class="selector-header">
|
|
2482
|
+
<span class="selector-title">AGENTS.md</span>
|
|
2483
|
+
</div>
|
|
2484
|
+
<div class="config-template-hint">
|
|
2485
|
+
管理 OpenClaw Workspace 指令文件,默认读写 <code>~/.openclaw/workspace/AGENTS.md</code>。
|
|
2486
|
+
</div>
|
|
2487
|
+
<button class="btn-tool" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
|
|
2488
|
+
{{ agentsLoading ? '加载中...' : '打开 AGENTS.md 编辑器' }}
|
|
2489
|
+
</button>
|
|
2490
|
+
</div>
|
|
2491
|
+
|
|
1880
2492
|
<div class="selector-section">
|
|
1881
2493
|
<div class="selector-header">
|
|
1882
|
-
<span class="selector-title"
|
|
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
|
+
|
|
2508
|
+
<div class="card-list">
|
|
2509
|
+
<div v-for="(config, name) in openclawConfigs" :key="name"
|
|
2510
|
+
:class="['card', { active: currentOpenclawConfig === name }]"
|
|
2511
|
+
@click="applyOpenclawConfig(name)">
|
|
2512
|
+
<div class="card-leading">
|
|
2513
|
+
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
|
|
2514
|
+
<div class="card-content">
|
|
2515
|
+
<div class="card-title">{{ name }}</div>
|
|
2516
|
+
<div class="card-subtitle">{{ openclawSubtitle(config) }}</div>
|
|
2517
|
+
</div>
|
|
2518
|
+
</div>
|
|
2519
|
+
<div class="card-trailing">
|
|
2520
|
+
<span :class="['pill', openclawHasContent(config) ? 'configured' : 'empty']">
|
|
2521
|
+
{{ openclawHasContent(config) ? '已配置' : '未配置' }}
|
|
2522
|
+
</span>
|
|
2523
|
+
<div class="card-actions" @click.stop>
|
|
2524
|
+
<button class="card-action-btn" @click="openOpenclawEditModal(name)" title="编辑">
|
|
2525
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2526
|
+
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
|
|
2527
|
+
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
|
|
2528
|
+
</svg>
|
|
2529
|
+
</button>
|
|
2530
|
+
<button class="card-action-btn delete" @click="deleteOpenclawConfig(name)" title="删除">
|
|
2531
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
2532
|
+
<path d="M3 6h18"/>
|
|
2533
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
|
2534
|
+
</svg>
|
|
2535
|
+
</button>
|
|
2536
|
+
</div>
|
|
2537
|
+
</div>
|
|
1883
2538
|
</div>
|
|
2539
|
+
</div>
|
|
2540
|
+
</div>
|
|
2541
|
+
|
|
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>
|
|
1884
2565
|
<div class="session-toolbar">
|
|
1885
2566
|
<div class="session-toolbar-group">
|
|
1886
2567
|
<select class="session-source-select" v-model="sessionFilterSource" @change="onSessionSourceChange" :disabled="sessionsLoading">
|
|
@@ -1902,8 +2583,8 @@
|
|
|
1902
2583
|
class="session-query-input"
|
|
1903
2584
|
v-model="sessionQuery"
|
|
1904
2585
|
@keyup.enter="loadSessions"
|
|
1905
|
-
disabled
|
|
1906
|
-
placeholder="
|
|
2586
|
+
:disabled="sessionsLoading || !isSessionQueryEnabled"
|
|
2587
|
+
:placeholder="sessionQueryPlaceholder">
|
|
1907
2588
|
</div>
|
|
1908
2589
|
<div class="session-toolbar-group">
|
|
1909
2590
|
<select
|
|
@@ -1937,20 +2618,22 @@
|
|
|
1937
2618
|
</div>
|
|
1938
2619
|
</div>
|
|
1939
2620
|
<div class="session-hint">
|
|
1940
|
-
|
|
2621
|
+
关键词检索仅 Codex 可用;<br>
|
|
2622
|
+
角色/时间筛选暂不可用;<br>
|
|
2623
|
+
仅支持来源与路径筛选,右侧仅查看/导出。
|
|
1941
2624
|
</div>
|
|
1942
2625
|
</div>
|
|
1943
2626
|
|
|
1944
|
-
<div v-if="sessionsLoading" class="state-message">
|
|
2627
|
+
<div v-if="!sessionStandalone && sessionsLoading" class="state-message">
|
|
1945
2628
|
会话加载中...
|
|
1946
2629
|
</div>
|
|
1947
2630
|
|
|
1948
|
-
<div v-else-if="sessionsList.length === 0" class="session-empty">
|
|
2631
|
+
<div v-else-if="!sessionStandalone && sessionsList.length === 0" class="session-empty">
|
|
1949
2632
|
暂无可用会话记录
|
|
1950
2633
|
</div>
|
|
1951
2634
|
|
|
1952
|
-
<div v-else class="session-layout">
|
|
1953
|
-
<div class="session-list">
|
|
2635
|
+
<div v-else :class="['session-layout', { 'session-standalone': sessionStandalone }]">
|
|
2636
|
+
<div v-if="!sessionStandalone" class="session-list">
|
|
1954
2637
|
<div
|
|
1955
2638
|
v-for="session in sessionsList"
|
|
1956
2639
|
:key="session.source + '-' + session.sessionId + '-' + session.filePath"
|
|
@@ -1978,6 +2661,21 @@
|
|
|
1978
2661
|
<path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"></path>
|
|
1979
2662
|
</svg>
|
|
1980
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>
|
|
1981
2679
|
</div>
|
|
1982
2680
|
</div>
|
|
1983
2681
|
<div class="session-item-meta">
|
|
@@ -2006,16 +2704,37 @@
|
|
|
2006
2704
|
<span class="session-preview-meta-item">{{ activeSession.cwd }}</span>
|
|
2007
2705
|
</div>
|
|
2008
2706
|
</div>
|
|
2009
|
-
<div class="session-actions">
|
|
2707
|
+
<div v-if="!sessionStandalone" class="session-actions">
|
|
2010
2708
|
<button class="btn-session-refresh" @click="loadActiveSessionDetail" :disabled="sessionDetailLoading || !activeSession">
|
|
2011
2709
|
{{ sessionDetailLoading ? '加载中...' : '刷新内容' }}
|
|
2012
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>
|
|
2013
2725
|
<button
|
|
2014
2726
|
class="btn-session-export"
|
|
2015
2727
|
@click="exportSession(activeSession)"
|
|
2016
2728
|
:disabled="!activeSession || sessionExporting[getSessionExportKey(activeSession)]">
|
|
2017
2729
|
{{ (activeSession && sessionExporting[getSessionExportKey(activeSession)]) ? '导出中...' : '导出记录' }}
|
|
2018
2730
|
</button>
|
|
2731
|
+
<button
|
|
2732
|
+
v-if="!sessionStandalone"
|
|
2733
|
+
class="btn-session-open"
|
|
2734
|
+
@click="openSessionStandalone(activeSession)"
|
|
2735
|
+
:disabled="!activeSession">
|
|
2736
|
+
新页查看
|
|
2737
|
+
</button>
|
|
2019
2738
|
</div>
|
|
2020
2739
|
</div>
|
|
2021
2740
|
|
|
@@ -2051,12 +2770,14 @@
|
|
|
2051
2770
|
</div>
|
|
2052
2771
|
</template>
|
|
2053
2772
|
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
</div>
|
|
2058
|
-
</div>
|
|
2059
|
-
|
|
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>
|
|
2060
2781
|
|
|
2061
2782
|
<!-- 加载状态 -->
|
|
2062
2783
|
<div v-if="loading" class="state-message">
|
|
@@ -2172,10 +2893,6 @@
|
|
|
2172
2893
|
<label class="form-label">Base URL</label>
|
|
2173
2894
|
<input v-model="newClaudeConfig.baseUrl" class="form-input" placeholder="https://open.bigmodel.cn/api/anthropic">
|
|
2174
2895
|
</div>
|
|
2175
|
-
<div class="form-group">
|
|
2176
|
-
<label class="form-label">模型</label>
|
|
2177
|
-
<input v-model="newClaudeConfig.model" class="form-input" placeholder="例如: claude-sonnet-4-20250514">
|
|
2178
|
-
</div>
|
|
2179
2896
|
|
|
2180
2897
|
<div class="btn-group">
|
|
2181
2898
|
<button class="btn btn-cancel" @click="closeClaudeConfigModal">取消</button>
|
|
@@ -2185,9 +2902,9 @@
|
|
|
2185
2902
|
</div>
|
|
2186
2903
|
|
|
2187
2904
|
<!-- 编辑Claude配置模态框 -->
|
|
2188
|
-
<div v-if="showEditConfigModal" class="modal-overlay" @click.self="closeEditConfigModal">
|
|
2189
|
-
<div class="modal">
|
|
2190
|
-
<div class="modal-title">编辑 Claude Code 配置</div>
|
|
2905
|
+
<div v-if="showEditConfigModal" class="modal-overlay" @click.self="closeEditConfigModal">
|
|
2906
|
+
<div class="modal">
|
|
2907
|
+
<div class="modal-title">编辑 Claude Code 配置</div>
|
|
2191
2908
|
|
|
2192
2909
|
<div class="form-group">
|
|
2193
2910
|
<label class="form-label">配置名称</label>
|
|
@@ -2201,71 +2918,292 @@
|
|
|
2201
2918
|
<label class="form-label">Base URL</label>
|
|
2202
2919
|
<input v-model="editingConfig.baseUrl" class="form-input" placeholder="https://open.bigmodel.cn/api/anthropic">
|
|
2203
2920
|
</div>
|
|
2921
|
+
|
|
2922
|
+
<div class="btn-group">
|
|
2923
|
+
<button class="btn btn-cancel" @click="closeEditConfigModal">取消</button>
|
|
2924
|
+
<button class="btn btn-confirm" @click="saveAndApplyConfig">保存并应用</button>
|
|
2925
|
+
</div>
|
|
2926
|
+
</div>
|
|
2927
|
+
</div>
|
|
2928
|
+
|
|
2929
|
+
<div v-if="showOpenclawConfigModal" class="modal-overlay" @click.self="closeOpenclawConfigModal">
|
|
2930
|
+
<div class="modal modal-wide">
|
|
2931
|
+
<div class="modal-title">{{ openclawEditorTitle }}</div>
|
|
2932
|
+
|
|
2933
|
+
<div class="form-group">
|
|
2934
|
+
<label class="form-label">配置名称</label>
|
|
2935
|
+
<input v-model="openclawEditing.name" class="form-input" :readonly="openclawEditing.lockName" placeholder="例如: 默认配置">
|
|
2936
|
+
</div>
|
|
2937
|
+
|
|
2938
|
+
<div class="form-group">
|
|
2939
|
+
<label class="form-label">目标文件</label>
|
|
2940
|
+
<div class="form-hint">
|
|
2941
|
+
{{ openclawConfigPath || '未加载' }}
|
|
2942
|
+
<span v-if="openclawConfigPath">
|
|
2943
|
+
({{ openclawConfigExists ? '已存在' : '不存在,将在应用时创建' }})
|
|
2944
|
+
</span>
|
|
2945
|
+
</div>
|
|
2946
|
+
<div class="btn-group" style="justify-content:flex-start;">
|
|
2947
|
+
<button class="btn btn-confirm secondary" @click="loadOpenclawConfigFromFile" :disabled="openclawFileLoading">
|
|
2948
|
+
{{ openclawFileLoading ? '加载中...' : '加载当前配置' }}
|
|
2949
|
+
</button>
|
|
2950
|
+
</div>
|
|
2951
|
+
</div>
|
|
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
|
+
|
|
2204
3180
|
<div class="form-group">
|
|
2205
|
-
<label class="form-label"
|
|
2206
|
-
<
|
|
3181
|
+
<label class="form-label">OpenClaw 配置(JSON5)</label>
|
|
3182
|
+
<textarea
|
|
3183
|
+
v-model="openclawEditing.content"
|
|
3184
|
+
class="form-input template-editor"
|
|
3185
|
+
spellcheck="false"
|
|
3186
|
+
placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
|
|
3187
|
+
<div class="template-editor-warning">
|
|
3188
|
+
保存仅写入本地配置库。点击“保存并应用”后会写入 openclaw.json。
|
|
3189
|
+
</div>
|
|
2207
3190
|
</div>
|
|
2208
3191
|
|
|
2209
3192
|
<div class="btn-group">
|
|
2210
|
-
<button class="btn btn-cancel" @click="
|
|
2211
|
-
<button class="btn btn-confirm" @click="
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
<input v-model="openclawEditing.name" class="form-input" :readonly="openclawEditing.lockName" placeholder="例如: 默认配置">
|
|
2225
|
-
</div>
|
|
2226
|
-
|
|
2227
|
-
<div class="form-group">
|
|
2228
|
-
<label class="form-label">目标文件</label>
|
|
2229
|
-
<div class="form-hint">
|
|
2230
|
-
{{ openclawConfigPath || '未加载' }}
|
|
2231
|
-
<span v-if="openclawConfigPath">
|
|
2232
|
-
({{ openclawConfigExists ? '已存在' : '不存在,将在应用时创建' }})
|
|
2233
|
-
</span>
|
|
2234
|
-
</div>
|
|
2235
|
-
<div class="btn-group" style="justify-content:flex-start;">
|
|
2236
|
-
<button class="btn btn-confirm secondary" @click="loadOpenclawConfigFromFile" :disabled="openclawFileLoading">
|
|
2237
|
-
{{ openclawFileLoading ? '加载中...' : '加载当前配置' }}
|
|
2238
|
-
</button>
|
|
2239
|
-
</div>
|
|
2240
|
-
</div>
|
|
2241
|
-
|
|
2242
|
-
<div class="form-group">
|
|
2243
|
-
<label class="form-label">OpenClaw 配置(JSON5)</label>
|
|
2244
|
-
<textarea
|
|
2245
|
-
v-model="openclawEditing.content"
|
|
2246
|
-
class="form-input template-editor"
|
|
2247
|
-
spellcheck="false"
|
|
2248
|
-
placeholder="在这里编辑 OpenClaw 配置(JSON5)"></textarea>
|
|
2249
|
-
<div class="template-editor-warning">
|
|
2250
|
-
保存仅写入本地配置库;点击“保存并应用”后会写入 openclaw.json。
|
|
2251
|
-
</div>
|
|
2252
|
-
</div>
|
|
2253
|
-
|
|
2254
|
-
<div class="btn-group">
|
|
2255
|
-
<button class="btn btn-cancel" @click="closeOpenclawConfigModal">取消</button>
|
|
2256
|
-
<button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving">
|
|
2257
|
-
{{ openclawSaving ? '保存中...' : '保存' }}
|
|
2258
|
-
</button>
|
|
2259
|
-
<button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawApplying">
|
|
2260
|
-
{{ openclawApplying ? '应用中...' : '保存并应用' }}
|
|
2261
|
-
</button>
|
|
2262
|
-
</div>
|
|
2263
|
-
</div>
|
|
2264
|
-
</div>
|
|
2265
|
-
|
|
2266
|
-
<div v-if="showConfigTemplateModal" class="modal-overlay" @click.self="closeConfigTemplateModal">
|
|
2267
|
-
<div class="modal modal-wide">
|
|
2268
|
-
<div class="modal-title">Config 模板编辑器(手动确认应用)</div>
|
|
3193
|
+
<button class="btn btn-cancel" @click="closeOpenclawConfigModal">取消</button>
|
|
3194
|
+
<button class="btn btn-confirm" @click="saveOpenclawConfig" :disabled="openclawSaving">
|
|
3195
|
+
{{ openclawSaving ? '保存中...' : '保存' }}
|
|
3196
|
+
</button>
|
|
3197
|
+
<button class="btn btn-confirm secondary" @click="saveAndApplyOpenclawConfig" :disabled="openclawApplying">
|
|
3198
|
+
{{ openclawApplying ? '应用中...' : '保存并应用' }}
|
|
3199
|
+
</button>
|
|
3200
|
+
</div>
|
|
3201
|
+
</div>
|
|
3202
|
+
</div>
|
|
3203
|
+
|
|
3204
|
+
<div v-if="showConfigTemplateModal" class="modal-overlay" @click.self="closeConfigTemplateModal">
|
|
3205
|
+
<div class="modal modal-wide">
|
|
3206
|
+
<div class="modal-title">Config 模板编辑器(手动确认应用)</div>
|
|
2269
3207
|
|
|
2270
3208
|
<div class="form-group">
|
|
2271
3209
|
<label class="form-label">config.toml 模板</label>
|
|
@@ -2275,7 +3213,7 @@
|
|
|
2275
3213
|
spellcheck="false"
|
|
2276
3214
|
placeholder="在这里编辑 config.toml 模板内容"></textarea>
|
|
2277
3215
|
<div class="template-editor-warning">
|
|
2278
|
-
工具不会自动改动 `config.toml
|
|
3216
|
+
工具不会自动改动 `config.toml`。只有点击“确认应用模板”后才写入。
|
|
2279
3217
|
</div>
|
|
2280
3218
|
</div>
|
|
2281
3219
|
|
|
@@ -2288,9 +3226,17 @@
|
|
|
2288
3226
|
</div>
|
|
2289
3227
|
</div>
|
|
2290
3228
|
|
|
2291
|
-
<div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
|
|
2292
|
-
<div class="modal modal-wide">
|
|
2293
|
-
<div class="modal-
|
|
3229
|
+
<div v-if="showAgentsModal" class="modal-overlay" @click.self="closeAgentsModal">
|
|
3230
|
+
<div class="modal modal-wide">
|
|
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>
|
|
2294
3240
|
|
|
2295
3241
|
<div class="form-group">
|
|
2296
3242
|
<label class="form-label">目标文件</label>
|
|
@@ -2304,16 +3250,16 @@
|
|
|
2304
3250
|
|
|
2305
3251
|
<div class="form-group">
|
|
2306
3252
|
<label class="form-label">AGENTS.md 内容</label>
|
|
2307
|
-
<textarea
|
|
2308
|
-
v-model="agentsContent"
|
|
2309
|
-
class="form-input template-editor"
|
|
2310
|
-
spellcheck="false"
|
|
2311
|
-
:readonly="agentsLoading"
|
|
2312
|
-
placeholder="在这里编辑 AGENTS.md 内容"></textarea>
|
|
2313
|
-
<div class="template-editor-warning">
|
|
2314
|
-
{{ agentsModalHint }}
|
|
2315
|
-
</div>
|
|
2316
|
-
</div>
|
|
3253
|
+
<textarea
|
|
3254
|
+
v-model="agentsContent"
|
|
3255
|
+
class="form-input template-editor"
|
|
3256
|
+
spellcheck="false"
|
|
3257
|
+
:readonly="agentsLoading"
|
|
3258
|
+
placeholder="在这里编辑 AGENTS.md 内容"></textarea>
|
|
3259
|
+
<div class="template-editor-warning">
|
|
3260
|
+
{{ agentsModalHint }}
|
|
3261
|
+
</div>
|
|
3262
|
+
</div>
|
|
2317
3263
|
|
|
2318
3264
|
<div class="btn-group">
|
|
2319
3265
|
<button class="btn btn-cancel" @click="closeAgentsModal">取消</button>
|
|
@@ -2330,22 +3276,22 @@
|
|
|
2330
3276
|
<div v-if="message" :class="['toast', messageType]">{{ message }}</div>
|
|
2331
3277
|
</div>
|
|
2332
3278
|
|
|
2333
|
-
<script>
|
|
2334
|
-
const { createApp } = Vue;
|
|
2335
|
-
const API_BASE = 'http://localhost:3737';
|
|
2336
|
-
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
2337
|
-
// OpenClaw config (JSON5)
|
|
2338
|
-
agent: {
|
|
2339
|
-
model: "gpt-4.1"
|
|
2340
|
-
},
|
|
2341
|
-
agents: {
|
|
2342
|
-
defaults: {
|
|
2343
|
-
workspace: "~/.openclaw/workspace"
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2346
|
-
}`;
|
|
2347
|
-
|
|
2348
|
-
async function api(action, params = {}) {
|
|
3279
|
+
<script>
|
|
3280
|
+
const { createApp } = Vue;
|
|
3281
|
+
const API_BASE = 'http://localhost:3737';
|
|
3282
|
+
const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
3283
|
+
// OpenClaw config (JSON5)
|
|
3284
|
+
agent: {
|
|
3285
|
+
model: "gpt-4.1"
|
|
3286
|
+
},
|
|
3287
|
+
agents: {
|
|
3288
|
+
defaults: {
|
|
3289
|
+
workspace: "~/.openclaw/workspace"
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
}`;
|
|
3293
|
+
|
|
3294
|
+
async function api(action, params = {}) {
|
|
2349
3295
|
const res = await fetch(`${API_BASE}/api`, {
|
|
2350
3296
|
method: 'POST',
|
|
2351
3297
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -2362,6 +3308,13 @@
|
|
|
2362
3308
|
currentModel: '',
|
|
2363
3309
|
providersList: [],
|
|
2364
3310
|
models: [],
|
|
3311
|
+
codexModelsLoading: false,
|
|
3312
|
+
modelsSource: 'remote',
|
|
3313
|
+
modelsHasCurrent: true,
|
|
3314
|
+
claudeModels: [],
|
|
3315
|
+
claudeModelsSource: 'idle',
|
|
3316
|
+
claudeModelsHasCurrent: true,
|
|
3317
|
+
claudeModelsLoading: false,
|
|
2365
3318
|
loading: true,
|
|
2366
3319
|
initError: '',
|
|
2367
3320
|
message: '',
|
|
@@ -2370,23 +3323,23 @@
|
|
|
2370
3323
|
showEditModal: false,
|
|
2371
3324
|
showModelModal: false,
|
|
2372
3325
|
showModelListModal: false,
|
|
2373
|
-
showClaudeConfigModal: false,
|
|
2374
|
-
showEditConfigModal: false,
|
|
2375
|
-
showOpenclawConfigModal: false,
|
|
2376
|
-
showConfigTemplateModal: false,
|
|
2377
|
-
showAgentsModal: false,
|
|
2378
|
-
configTemplateContent: '',
|
|
2379
|
-
configTemplateApplying: false,
|
|
2380
|
-
agentsContent: '',
|
|
2381
|
-
agentsPath: '',
|
|
2382
|
-
agentsExists: false,
|
|
2383
|
-
agentsLineEnding: '\n',
|
|
2384
|
-
agentsLoading: false,
|
|
2385
|
-
agentsSaving: false,
|
|
2386
|
-
agentsContext: 'codex',
|
|
2387
|
-
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
2388
|
-
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
2389
|
-
sessionsList: [],
|
|
3326
|
+
showClaudeConfigModal: false,
|
|
3327
|
+
showEditConfigModal: false,
|
|
3328
|
+
showOpenclawConfigModal: false,
|
|
3329
|
+
showConfigTemplateModal: false,
|
|
3330
|
+
showAgentsModal: false,
|
|
3331
|
+
configTemplateContent: '',
|
|
3332
|
+
configTemplateApplying: false,
|
|
3333
|
+
agentsContent: '',
|
|
3334
|
+
agentsPath: '',
|
|
3335
|
+
agentsExists: false,
|
|
3336
|
+
agentsLineEnding: '\n',
|
|
3337
|
+
agentsLoading: false,
|
|
3338
|
+
agentsSaving: false,
|
|
3339
|
+
agentsContext: 'codex',
|
|
3340
|
+
agentsModalTitle: 'AGENTS.md 编辑器',
|
|
3341
|
+
agentsModalHint: '保存后会写入目标 AGENTS.md(与 config.toml 同级)。',
|
|
3342
|
+
sessionsList: [],
|
|
2390
3343
|
sessionsLoading: false,
|
|
2391
3344
|
sessionFilterSource: 'all',
|
|
2392
3345
|
sessionPathFilter: '',
|
|
@@ -2407,20 +3360,30 @@
|
|
|
2407
3360
|
},
|
|
2408
3361
|
sessionPathRequestSeq: 0,
|
|
2409
3362
|
sessionExporting: {},
|
|
3363
|
+
sessionCloning: {},
|
|
3364
|
+
sessionDeleting: {},
|
|
2410
3365
|
activeSession: null,
|
|
2411
3366
|
activeSessionMessages: [],
|
|
2412
3367
|
activeSessionDetailError: '',
|
|
2413
3368
|
activeSessionDetailClipped: false,
|
|
2414
|
-
sessionDetailLoading: false,
|
|
2415
|
-
sessionDetailRequestSeq: 0,
|
|
2416
|
-
|
|
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: {},
|
|
2417
3379
|
speedLoading: {},
|
|
2418
3380
|
newProvider: { name: '', url: '', key: '' },
|
|
2419
3381
|
editingProvider: { name: '', url: '', key: '' },
|
|
2420
|
-
newModelName: '',
|
|
2421
|
-
currentClaudeConfig: '',
|
|
2422
|
-
|
|
2423
|
-
|
|
3382
|
+
newModelName: '',
|
|
3383
|
+
currentClaudeConfig: '',
|
|
3384
|
+
currentClaudeModel: '',
|
|
3385
|
+
editingConfig: { name: '', apiKey: '', baseUrl: '', model: '' },
|
|
3386
|
+
claudeConfigs: {
|
|
2424
3387
|
'智谱GLM': {
|
|
2425
3388
|
apiKey: '',
|
|
2426
3389
|
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
@@ -2428,33 +3391,70 @@
|
|
|
2428
3391
|
hasKey: false
|
|
2429
3392
|
}
|
|
2430
3393
|
},
|
|
2431
|
-
newClaudeConfig: {
|
|
2432
|
-
name: '',
|
|
2433
|
-
apiKey: '',
|
|
2434
|
-
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
2435
|
-
model: 'glm-4.7'
|
|
2436
|
-
},
|
|
2437
|
-
currentOpenclawConfig: '',
|
|
2438
|
-
openclawConfigs: {
|
|
2439
|
-
'默认配置': {
|
|
2440
|
-
content: DEFAULT_OPENCLAW_TEMPLATE
|
|
2441
|
-
}
|
|
2442
|
-
},
|
|
2443
|
-
openclawEditing: { name: '', content: '', lockName: false },
|
|
2444
|
-
openclawEditorTitle: '添加 OpenClaw 配置',
|
|
2445
|
-
openclawConfigPath: '',
|
|
2446
|
-
openclawConfigExists: false,
|
|
2447
|
-
openclawLineEnding: '\n',
|
|
2448
|
-
openclawFileLoading: false,
|
|
2449
|
-
openclawSaving: false,
|
|
2450
|
-
openclawApplying: false
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
3394
|
+
newClaudeConfig: {
|
|
3395
|
+
name: '',
|
|
3396
|
+
apiKey: '',
|
|
3397
|
+
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
3398
|
+
model: 'glm-4.7'
|
|
3399
|
+
},
|
|
3400
|
+
currentOpenclawConfig: '',
|
|
3401
|
+
openclawConfigs: {
|
|
3402
|
+
'默认配置': {
|
|
3403
|
+
content: DEFAULT_OPENCLAW_TEMPLATE
|
|
3404
|
+
}
|
|
3405
|
+
},
|
|
3406
|
+
openclawEditing: { name: '', content: '', lockName: false },
|
|
3407
|
+
openclawEditorTitle: '添加 OpenClaw 配置',
|
|
3408
|
+
openclawConfigPath: '',
|
|
3409
|
+
openclawConfigExists: false,
|
|
3410
|
+
openclawLineEnding: '\n',
|
|
3411
|
+
openclawFileLoading: false,
|
|
3412
|
+
openclawSaving: false,
|
|
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: [],
|
|
3445
|
+
recentConfigs: [],
|
|
3446
|
+
recentLoading: false,
|
|
3447
|
+
healthCheckLoading: false,
|
|
3448
|
+
healthCheckResult: null,
|
|
3449
|
+
healthCheckRemote: false
|
|
3450
|
+
}
|
|
3451
|
+
},
|
|
3452
|
+
mounted() {
|
|
3453
|
+
this.initSessionStandalone();
|
|
3454
|
+
const savedConfigs = localStorage.getItem('claudeConfigs');
|
|
3455
|
+
if (savedConfigs) {
|
|
3456
|
+
try {
|
|
3457
|
+
this.claudeConfigs = JSON.parse(savedConfigs);
|
|
2458
3458
|
for (const [name, config] of Object.entries(this.claudeConfigs)) {
|
|
2459
3459
|
if (config.apiKey && config.apiKey.includes('****')) {
|
|
2460
3460
|
config.apiKey = '';
|
|
@@ -2469,37 +3469,50 @@
|
|
|
2469
3469
|
}
|
|
2470
3470
|
} catch (e) {
|
|
2471
3471
|
console.error('加载 Claude 配置失败:', e);
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
this.
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
if (!this.currentClaudeConfig) {
|
|
3475
|
+
const configNames = Object.keys(this.claudeConfigs);
|
|
3476
|
+
if (configNames.length > 0) {
|
|
3477
|
+
this.currentClaudeConfig = configNames[0];
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
this.syncClaudeModelFromConfig();
|
|
3481
|
+
const savedOpenclawConfigs = localStorage.getItem('openclawConfigs');
|
|
3482
|
+
if (savedOpenclawConfigs) {
|
|
3483
|
+
try {
|
|
3484
|
+
this.openclawConfigs = JSON.parse(savedOpenclawConfigs);
|
|
3485
|
+
const configNames = Object.keys(this.openclawConfigs);
|
|
3486
|
+
if (configNames.length > 0) {
|
|
3487
|
+
this.currentOpenclawConfig = configNames[0];
|
|
3488
|
+
}
|
|
3489
|
+
} catch (e) {
|
|
3490
|
+
console.error('加载 OpenClaw 配置失败:', e);
|
|
3491
|
+
}
|
|
3492
|
+
} else {
|
|
3493
|
+
const configNames = Object.keys(this.openclawConfigs);
|
|
3494
|
+
if (configNames.length > 0) {
|
|
3495
|
+
this.currentOpenclawConfig = configNames[0];
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
this.loadAll();
|
|
3499
|
+
},
|
|
3500
|
+
|
|
3501
|
+
computed: {
|
|
3502
|
+
isSessionQueryEnabled() {
|
|
3503
|
+
return this.sessionFilterSource === 'codex';
|
|
3504
|
+
},
|
|
3505
|
+
sessionQueryPlaceholder() {
|
|
3506
|
+
return this.isSessionQueryEnabled ? '关键词检索' : '仅 Codex 支持关键词检索';
|
|
3507
|
+
}
|
|
3508
|
+
},
|
|
2493
3509
|
methods: {
|
|
2494
3510
|
async loadAll() {
|
|
2495
3511
|
this.loading = true;
|
|
2496
3512
|
this.initError = '';
|
|
2497
3513
|
try {
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
api('list'),
|
|
2501
|
-
api('models')
|
|
2502
|
-
]);
|
|
3514
|
+
const statusRes = await api('status');
|
|
3515
|
+
const listRes = await api('list');
|
|
2503
3516
|
|
|
2504
3517
|
if (statusRes.error) {
|
|
2505
3518
|
this.initError = statusRes.error;
|
|
@@ -2507,14 +3520,16 @@
|
|
|
2507
3520
|
this.currentProvider = statusRes.provider;
|
|
2508
3521
|
this.currentModel = statusRes.model;
|
|
2509
3522
|
this.providersList = listRes.providers;
|
|
2510
|
-
this.
|
|
3523
|
+
await this.loadModelsForProvider(this.currentProvider);
|
|
2511
3524
|
if (statusRes.configReady === false) {
|
|
2512
|
-
this.showMessage(statusRes.configNotice || '未检测到 config.toml
|
|
3525
|
+
this.showMessage(statusRes.configNotice || '未检测到 config.toml,已加载默认模板。请在模板编辑器确认后创建。', 'info');
|
|
2513
3526
|
}
|
|
2514
3527
|
if (statusRes.initNotice) {
|
|
2515
3528
|
this.showMessage(statusRes.initNotice, 'info');
|
|
2516
3529
|
}
|
|
3530
|
+
this.maybeShowStarPrompt();
|
|
2517
3531
|
}
|
|
3532
|
+
await this.loadRecentConfigs();
|
|
2518
3533
|
} catch (e) {
|
|
2519
3534
|
this.initError = '连接失败: ' + e.message;
|
|
2520
3535
|
} finally {
|
|
@@ -2522,15 +3537,230 @@
|
|
|
2522
3537
|
}
|
|
2523
3538
|
},
|
|
2524
3539
|
|
|
2525
|
-
|
|
2526
|
-
this.
|
|
2527
|
-
if (
|
|
2528
|
-
this.
|
|
3540
|
+
async loadModelsForProvider(providerName) {
|
|
3541
|
+
this.codexModelsLoading = true;
|
|
3542
|
+
if (!providerName) {
|
|
3543
|
+
this.models = [];
|
|
3544
|
+
this.modelsSource = 'unlimited';
|
|
3545
|
+
this.modelsHasCurrent = true;
|
|
3546
|
+
this.codexModelsLoading = false;
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
try {
|
|
3550
|
+
const res = await api('models', { provider: providerName });
|
|
3551
|
+
if (res.unlimited) {
|
|
3552
|
+
this.models = [];
|
|
3553
|
+
this.modelsSource = 'unlimited';
|
|
3554
|
+
this.modelsHasCurrent = true;
|
|
3555
|
+
return;
|
|
3556
|
+
}
|
|
3557
|
+
if (res.error) {
|
|
3558
|
+
this.showMessage('模型列表获取失败: ' + res.error, 'error');
|
|
3559
|
+
this.models = [];
|
|
3560
|
+
this.modelsSource = 'error';
|
|
3561
|
+
this.modelsHasCurrent = true;
|
|
3562
|
+
return;
|
|
3563
|
+
}
|
|
3564
|
+
const list = Array.isArray(res.models) ? res.models : [];
|
|
3565
|
+
this.models = list;
|
|
3566
|
+
this.modelsSource = res.source || 'remote';
|
|
3567
|
+
this.modelsHasCurrent = !!this.currentModel && list.includes(this.currentModel);
|
|
3568
|
+
} catch (e) {
|
|
3569
|
+
this.showMessage('模型列表获取失败: ' + e.message, 'error');
|
|
3570
|
+
this.models = [];
|
|
3571
|
+
this.modelsSource = 'error';
|
|
3572
|
+
this.modelsHasCurrent = true;
|
|
3573
|
+
} finally {
|
|
3574
|
+
this.codexModelsLoading = false;
|
|
2529
3575
|
}
|
|
2530
3576
|
},
|
|
2531
3577
|
|
|
2532
|
-
|
|
2533
|
-
|
|
3578
|
+
getCurrentClaudeConfig() {
|
|
3579
|
+
if (!this.currentClaudeConfig) return null;
|
|
3580
|
+
return this.claudeConfigs[this.currentClaudeConfig] || null;
|
|
3581
|
+
},
|
|
3582
|
+
|
|
3583
|
+
syncClaudeModelFromConfig() {
|
|
3584
|
+
const config = this.getCurrentClaudeConfig();
|
|
3585
|
+
this.currentClaudeModel = config && config.model ? config.model : '';
|
|
3586
|
+
},
|
|
3587
|
+
|
|
3588
|
+
refreshClaudeModelContext() {
|
|
3589
|
+
this.syncClaudeModelFromConfig();
|
|
3590
|
+
this.loadClaudeModels();
|
|
3591
|
+
},
|
|
3592
|
+
|
|
3593
|
+
resetClaudeModelsState() {
|
|
3594
|
+
this.claudeModels = [];
|
|
3595
|
+
this.claudeModelsSource = 'idle';
|
|
3596
|
+
this.claudeModelsHasCurrent = true;
|
|
3597
|
+
this.claudeModelsLoading = false;
|
|
3598
|
+
},
|
|
3599
|
+
|
|
3600
|
+
updateClaudeModelsCurrent() {
|
|
3601
|
+
const currentModel = (this.currentClaudeModel || '').trim();
|
|
3602
|
+
this.claudeModelsHasCurrent = !!currentModel && this.claudeModels.includes(currentModel);
|
|
3603
|
+
},
|
|
3604
|
+
|
|
3605
|
+
async loadClaudeModels() {
|
|
3606
|
+
const config = this.getCurrentClaudeConfig();
|
|
3607
|
+
const baseUrl = (config.baseUrl || '').trim();
|
|
3608
|
+
const apiKey = (config.apiKey || '').trim();
|
|
3609
|
+
|
|
3610
|
+
if (!baseUrl) {
|
|
3611
|
+
this.resetClaudeModelsState();
|
|
3612
|
+
return;
|
|
3613
|
+
}
|
|
3614
|
+
|
|
3615
|
+
this.claudeModelsLoading = true;
|
|
3616
|
+
try {
|
|
3617
|
+
const res = await api('models-by-url', { baseUrl, apiKey });
|
|
3618
|
+
if (res.unlimited) {
|
|
3619
|
+
this.claudeModels = [];
|
|
3620
|
+
this.claudeModelsSource = 'unlimited';
|
|
3621
|
+
this.claudeModelsHasCurrent = true;
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
if (res.error) {
|
|
3625
|
+
this.showMessage('模型列表获取失败: ' + res.error, 'error');
|
|
3626
|
+
this.claudeModels = [];
|
|
3627
|
+
this.claudeModelsSource = 'error';
|
|
3628
|
+
this.claudeModelsHasCurrent = true;
|
|
3629
|
+
return;
|
|
3630
|
+
}
|
|
3631
|
+
const list = Array.isArray(res.models) ? res.models : [];
|
|
3632
|
+
this.claudeModels = list;
|
|
3633
|
+
this.claudeModelsSource = res.source || 'remote';
|
|
3634
|
+
this.updateClaudeModelsCurrent();
|
|
3635
|
+
} catch (e) {
|
|
3636
|
+
this.showMessage('模型列表获取失败: ' + e.message, 'error');
|
|
3637
|
+
this.claudeModels = [];
|
|
3638
|
+
this.claudeModelsSource = 'error';
|
|
3639
|
+
this.claudeModelsHasCurrent = true;
|
|
3640
|
+
} finally {
|
|
3641
|
+
this.claudeModelsLoading = false;
|
|
3642
|
+
}
|
|
3643
|
+
},
|
|
3644
|
+
|
|
3645
|
+
openClaudeConfigModal() {
|
|
3646
|
+
this.showClaudeConfigModal = true;
|
|
3647
|
+
},
|
|
3648
|
+
|
|
3649
|
+
maybeShowStarPrompt() {
|
|
3650
|
+
const storageKey = 'codexmateStarPrompted';
|
|
3651
|
+
if (localStorage.getItem(storageKey)) {
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
this.showMessage('如果 Codex Mate 对你有帮助,欢迎到 GitHub 点个 Star。', 'info');
|
|
3655
|
+
localStorage.setItem(storageKey, '1');
|
|
3656
|
+
},
|
|
3657
|
+
|
|
3658
|
+
switchConfigMode(mode) {
|
|
3659
|
+
this.configMode = mode;
|
|
3660
|
+
if (mode === 'claude') {
|
|
3661
|
+
this.refreshClaudeModelContext();
|
|
3662
|
+
}
|
|
3663
|
+
if (mode === 'sessions' && this.sessionsList.length === 0) {
|
|
3664
|
+
this.loadSessions();
|
|
3665
|
+
}
|
|
3666
|
+
},
|
|
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
|
+
|
|
3762
|
+
getSessionExportKey(session) {
|
|
3763
|
+
return `${session.source || 'unknown'}:${session.sessionId || ''}:${session.filePath || ''}`;
|
|
2534
3764
|
},
|
|
2535
3765
|
|
|
2536
3766
|
isResumeCommandAvailable(session) {
|
|
@@ -2540,6 +3770,23 @@
|
|
|
2540
3770
|
return source === 'codex' && !!sessionId;
|
|
2541
3771
|
},
|
|
2542
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
|
+
|
|
2543
3790
|
buildResumeCommand(session) {
|
|
2544
3791
|
const sessionId = session && session.sessionId ? String(session.sessionId).trim() : '';
|
|
2545
3792
|
return `codex resume ${this.quoteResumeArg(sessionId)}`;
|
|
@@ -2576,6 +3823,20 @@
|
|
|
2576
3823
|
}
|
|
2577
3824
|
},
|
|
2578
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
|
+
|
|
2579
3840
|
async copyResumeCommand(session) {
|
|
2580
3841
|
if (!this.isResumeCommandAvailable(session)) {
|
|
2581
3842
|
this.showMessage('当前会话不支持生成恢复命令', 'error');
|
|
@@ -2599,6 +3860,71 @@
|
|
|
2599
3860
|
this.showMessage('复制失败,请手动复制命令', 'error');
|
|
2600
3861
|
},
|
|
2601
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
|
+
|
|
2602
3928
|
normalizeSessionPathValue(value) {
|
|
2603
3929
|
if (typeof value !== 'string') return '';
|
|
2604
3930
|
return value.trim();
|
|
@@ -2762,7 +4088,7 @@
|
|
|
2762
4088
|
if (this.sessionsLoading) return;
|
|
2763
4089
|
this.sessionsLoading = true;
|
|
2764
4090
|
this.activeSessionDetailError = '';
|
|
2765
|
-
const query = this.sessionQuery;
|
|
4091
|
+
const query = this.isSessionQueryEnabled ? this.sessionQuery : '';
|
|
2766
4092
|
try {
|
|
2767
4093
|
const res = await api('list-sessions', {
|
|
2768
4094
|
source: this.sessionFilterSource,
|
|
@@ -2770,7 +4096,7 @@
|
|
|
2770
4096
|
query,
|
|
2771
4097
|
queryMode: 'and',
|
|
2772
4098
|
queryScope: 'content',
|
|
2773
|
-
contentScanLimit:
|
|
4099
|
+
contentScanLimit: 50,
|
|
2774
4100
|
roleFilter: this.sessionRoleFilter,
|
|
2775
4101
|
timeRangePreset: this.sessionTimePreset,
|
|
2776
4102
|
limit: 200,
|
|
@@ -2812,21 +4138,66 @@
|
|
|
2812
4138
|
}
|
|
2813
4139
|
},
|
|
2814
4140
|
|
|
2815
|
-
async selectSession(session) {
|
|
2816
|
-
if (!session) return;
|
|
2817
|
-
if (this.activeSession && this.getSessionExportKey(this.activeSession) === this.getSessionExportKey(session)) return;
|
|
2818
|
-
this.activeSession = session;
|
|
2819
|
-
this.activeSessionMessages = [];
|
|
2820
|
-
this.activeSessionDetailError = '';
|
|
2821
|
-
this.activeSessionDetailClipped = false;
|
|
2822
|
-
await this.loadActiveSessionDetail();
|
|
2823
|
-
},
|
|
2824
|
-
|
|
2825
|
-
async
|
|
2826
|
-
if (!this.activeSession) {
|
|
2827
|
-
this.
|
|
2828
|
-
this.
|
|
2829
|
-
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;
|
|
2830
4201
|
return;
|
|
2831
4202
|
}
|
|
2832
4203
|
|
|
@@ -2854,6 +4225,20 @@
|
|
|
2854
4225
|
|
|
2855
4226
|
this.activeSessionMessages = Array.isArray(res.messages) ? res.messages : [];
|
|
2856
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
|
+
}
|
|
2857
4242
|
if (res.updatedAt) {
|
|
2858
4243
|
this.activeSession.updatedAt = res.updatedAt;
|
|
2859
4244
|
}
|
|
@@ -2898,23 +4283,29 @@
|
|
|
2898
4283
|
sessionId: session.sessionId,
|
|
2899
4284
|
filePath: session.filePath
|
|
2900
4285
|
});
|
|
2901
|
-
if (res.error) {
|
|
2902
|
-
this.showMessage(res.error, 'error');
|
|
2903
|
-
return;
|
|
2904
|
-
}
|
|
2905
|
-
|
|
2906
|
-
const fileName = res.fileName || `${session.source || 'session'}-${session.sessionId || Date.now()}.md`;
|
|
2907
|
-
this.downloadTextFile(fileName, res.content || '');
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
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 {
|
|
2912
4302
|
this.sessionExporting[key] = false;
|
|
2913
4303
|
}
|
|
2914
4304
|
},
|
|
2915
4305
|
|
|
2916
4306
|
async switchProvider(name) {
|
|
2917
4307
|
this.currentProvider = name;
|
|
4308
|
+
await this.loadModelsForProvider(name);
|
|
2918
4309
|
await this.openConfigTemplateEditor();
|
|
2919
4310
|
},
|
|
2920
4311
|
|
|
@@ -2922,6 +4313,90 @@
|
|
|
2922
4313
|
await this.openConfigTemplateEditor();
|
|
2923
4314
|
},
|
|
2924
4315
|
|
|
4316
|
+
async loadRecentConfigs() {
|
|
4317
|
+
this.recentLoading = true;
|
|
4318
|
+
try {
|
|
4319
|
+
const res = await api('get-recent-configs');
|
|
4320
|
+
if (res && Array.isArray(res.items)) {
|
|
4321
|
+
this.recentConfigs = res.items;
|
|
4322
|
+
} else {
|
|
4323
|
+
this.recentConfigs = [];
|
|
4324
|
+
}
|
|
4325
|
+
} catch (e) {
|
|
4326
|
+
this.recentConfigs = [];
|
|
4327
|
+
} finally {
|
|
4328
|
+
this.recentLoading = false;
|
|
4329
|
+
}
|
|
4330
|
+
},
|
|
4331
|
+
|
|
4332
|
+
async applyRecentConfig(item) {
|
|
4333
|
+
if (!item || !item.provider || !item.model) {
|
|
4334
|
+
this.showMessage('最近配置无效,无法应用', 'error');
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
this.currentProvider = item.provider;
|
|
4338
|
+
this.currentModel = item.model;
|
|
4339
|
+
await this.openConfigTemplateEditor({
|
|
4340
|
+
appendHint: '最近使用配置,确认后将写入 config.toml'
|
|
4341
|
+
});
|
|
4342
|
+
},
|
|
4343
|
+
|
|
4344
|
+
async runHealthCheck() {
|
|
4345
|
+
this.healthCheckLoading = true;
|
|
4346
|
+
this.healthCheckResult = null;
|
|
4347
|
+
try {
|
|
4348
|
+
const res = await api('config-health-check', {
|
|
4349
|
+
remote: false
|
|
4350
|
+
});
|
|
4351
|
+
if (res && typeof res === 'object') {
|
|
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) {
|
|
4386
|
+
this.showMessage('健康检查通过', 'success');
|
|
4387
|
+
}
|
|
4388
|
+
} else {
|
|
4389
|
+
this.healthCheckResult = null;
|
|
4390
|
+
this.showMessage('健康检查失败:返回数据异常', 'error');
|
|
4391
|
+
}
|
|
4392
|
+
} catch (e) {
|
|
4393
|
+
this.healthCheckResult = null;
|
|
4394
|
+
this.showMessage('健康检查失败: ' + e.message, 'error');
|
|
4395
|
+
} finally {
|
|
4396
|
+
this.healthCheckLoading = false;
|
|
4397
|
+
}
|
|
4398
|
+
},
|
|
4399
|
+
|
|
2925
4400
|
escapeTomlString(value) {
|
|
2926
4401
|
return String(value || '')
|
|
2927
4402
|
.replace(/\\/g, '\\\\')
|
|
@@ -2984,105 +4459,152 @@
|
|
|
2984
4459
|
}
|
|
2985
4460
|
},
|
|
2986
4461
|
|
|
2987
|
-
async openAgentsEditor() {
|
|
2988
|
-
this.setAgentsModalContext('codex');
|
|
2989
|
-
this.agentsLoading = true;
|
|
2990
|
-
try {
|
|
2991
|
-
const res = await api('get-agents-file');
|
|
2992
|
-
if (res.error) {
|
|
2993
|
-
this.showMessage(res.error, 'error');
|
|
4462
|
+
async openAgentsEditor() {
|
|
4463
|
+
this.setAgentsModalContext('codex');
|
|
4464
|
+
this.agentsLoading = true;
|
|
4465
|
+
try {
|
|
4466
|
+
const res = await api('get-agents-file');
|
|
4467
|
+
if (res.error) {
|
|
4468
|
+
this.showMessage(res.error, 'error');
|
|
2994
4469
|
return;
|
|
2995
4470
|
}
|
|
2996
4471
|
this.agentsContent = res.content || '';
|
|
2997
4472
|
this.agentsPath = res.path || '';
|
|
2998
4473
|
this.agentsExists = !!res.exists;
|
|
2999
|
-
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3000
|
-
this.showAgentsModal = true;
|
|
3001
|
-
} catch (e) {
|
|
3002
|
-
this.showMessage('加载 AGENTS.md 失败: ' + e.message, 'error');
|
|
3003
|
-
} finally {
|
|
3004
|
-
this.agentsLoading = false;
|
|
3005
|
-
}
|
|
3006
|
-
},
|
|
3007
|
-
|
|
3008
|
-
async openOpenclawAgentsEditor() {
|
|
3009
|
-
this.setAgentsModalContext('openclaw');
|
|
3010
|
-
this.agentsLoading = true;
|
|
3011
|
-
try {
|
|
3012
|
-
const res = await api('get-openclaw-agents-file');
|
|
3013
|
-
if (res.error) {
|
|
3014
|
-
this.showMessage(res.error, 'error');
|
|
3015
|
-
return;
|
|
3016
|
-
}
|
|
3017
|
-
if (res.configError) {
|
|
3018
|
-
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
3019
|
-
}
|
|
3020
|
-
this.agentsContent = res.content || '';
|
|
3021
|
-
this.agentsPath = res.path || '';
|
|
3022
|
-
this.agentsExists = !!res.exists;
|
|
3023
|
-
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
3024
|
-
this.showAgentsModal = true;
|
|
3025
|
-
} catch (e) {
|
|
3026
|
-
this.showMessage('加载 OpenClaw AGENTS.md 失败: ' + e.message, 'error');
|
|
3027
|
-
} finally {
|
|
3028
|
-
this.agentsLoading = false;
|
|
3029
|
-
}
|
|
3030
|
-
},
|
|
3031
|
-
|
|
3032
|
-
setAgentsModalContext(context) {
|
|
3033
|
-
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
3034
|
-
if (this.agentsContext === 'openclaw') {
|
|
3035
|
-
this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
|
|
3036
|
-
this.agentsModalHint = '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。';
|
|
3037
|
-
} else {
|
|
3038
|
-
this.agentsModalTitle = 'AGENTS.md 编辑器';
|
|
3039
|
-
this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
|
|
3040
|
-
}
|
|
3041
|
-
},
|
|
3042
|
-
|
|
3043
|
-
closeAgentsModal() {
|
|
3044
|
-
this.showAgentsModal = false;
|
|
3045
|
-
this.agentsContent = '';
|
|
3046
|
-
this.agentsPath = '';
|
|
3047
|
-
this.agentsExists = false;
|
|
3048
|
-
this.agentsLineEnding = '\n';
|
|
3049
|
-
this.agentsSaving = false;
|
|
3050
|
-
this.setAgentsModalContext('codex');
|
|
3051
|
-
},
|
|
3052
|
-
|
|
3053
|
-
async applyAgentsContent() {
|
|
3054
|
-
this.agentsSaving = true;
|
|
3055
|
-
try {
|
|
3056
|
-
const action = this.agentsContext === 'openclaw'
|
|
3057
|
-
? 'apply-openclaw-agents-file'
|
|
3058
|
-
: 'apply-agents-file';
|
|
3059
|
-
const res = await api(action, {
|
|
3060
|
-
content: this.agentsContent,
|
|
3061
|
-
lineEnding: this.agentsLineEnding
|
|
3062
|
-
});
|
|
3063
|
-
if (res.error) {
|
|
3064
|
-
this.showMessage(res.error, 'error');
|
|
4474
|
+
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
4475
|
+
this.showAgentsModal = true;
|
|
4476
|
+
} catch (e) {
|
|
4477
|
+
this.showMessage('加载 AGENTS.md 失败: ' + e.message, 'error');
|
|
4478
|
+
} finally {
|
|
4479
|
+
this.agentsLoading = false;
|
|
4480
|
+
}
|
|
4481
|
+
},
|
|
4482
|
+
|
|
4483
|
+
async openOpenclawAgentsEditor() {
|
|
4484
|
+
this.setAgentsModalContext('openclaw');
|
|
4485
|
+
this.agentsLoading = true;
|
|
4486
|
+
try {
|
|
4487
|
+
const res = await api('get-openclaw-agents-file');
|
|
4488
|
+
if (res.error) {
|
|
4489
|
+
this.showMessage(res.error, 'error');
|
|
3065
4490
|
return;
|
|
3066
4491
|
}
|
|
3067
|
-
|
|
3068
|
-
|
|
4492
|
+
if (res.configError) {
|
|
4493
|
+
this.showMessage(`OpenClaw 配置解析失败,已使用默认 Workspace:${res.configError}`, 'error');
|
|
4494
|
+
}
|
|
4495
|
+
this.agentsContent = res.content || '';
|
|
4496
|
+
this.agentsPath = res.path || '';
|
|
4497
|
+
this.agentsExists = !!res.exists;
|
|
4498
|
+
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
4499
|
+
this.showAgentsModal = true;
|
|
3069
4500
|
} catch (e) {
|
|
3070
|
-
this.showMessage('
|
|
4501
|
+
this.showMessage('加载 OpenClaw AGENTS.md 失败: ' + e.message, 'error');
|
|
3071
4502
|
} finally {
|
|
3072
|
-
this.
|
|
4503
|
+
this.agentsLoading = false;
|
|
3073
4504
|
}
|
|
3074
4505
|
},
|
|
3075
4506
|
|
|
3076
|
-
async
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
if (!name) {
|
|
3082
|
-
return this.showMessage('名称不能为空', 'error');
|
|
4507
|
+
async openOpenclawWorkspaceEditor() {
|
|
4508
|
+
const fileName = (this.openclawWorkspaceFileName || '').trim();
|
|
4509
|
+
if (!fileName) {
|
|
4510
|
+
this.showMessage('请输入工作区文件名', 'error');
|
|
4511
|
+
return;
|
|
3083
4512
|
}
|
|
3084
|
-
|
|
3085
|
-
|
|
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
|
+
}
|
|
4545
|
+
this.agentsContext = context === 'openclaw' ? 'openclaw' : 'codex';
|
|
4546
|
+
if (this.agentsContext === 'openclaw') {
|
|
4547
|
+
this.agentsModalTitle = 'OpenClaw AGENTS.md 编辑器';
|
|
4548
|
+
this.agentsModalHint = '保存后会写入 OpenClaw Workspace 下的 AGENTS.md。';
|
|
4549
|
+
} else {
|
|
4550
|
+
this.agentsModalTitle = 'AGENTS.md 编辑器';
|
|
4551
|
+
this.agentsModalHint = '保存后会写入目标 AGENTS.md(与 config.toml 同级)。';
|
|
4552
|
+
}
|
|
4553
|
+
this.agentsWorkspaceFileName = '';
|
|
4554
|
+
},
|
|
4555
|
+
|
|
4556
|
+
closeAgentsModal() {
|
|
4557
|
+
this.showAgentsModal = false;
|
|
4558
|
+
this.agentsContent = '';
|
|
4559
|
+
this.agentsPath = '';
|
|
4560
|
+
this.agentsExists = false;
|
|
4561
|
+
this.agentsLineEnding = '\n';
|
|
4562
|
+
this.agentsSaving = false;
|
|
4563
|
+
this.agentsWorkspaceFileName = '';
|
|
4564
|
+
this.setAgentsModalContext('codex');
|
|
4565
|
+
},
|
|
4566
|
+
|
|
4567
|
+
async applyAgentsContent() {
|
|
4568
|
+
this.agentsSaving = true;
|
|
4569
|
+
try {
|
|
4570
|
+
let action = 'apply-agents-file';
|
|
4571
|
+
const params = {
|
|
4572
|
+
content: this.agentsContent,
|
|
4573
|
+
lineEnding: this.agentsLineEnding
|
|
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);
|
|
4582
|
+
if (res.error) {
|
|
4583
|
+
this.showMessage(res.error, 'error');
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
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');
|
|
4590
|
+
this.closeAgentsModal();
|
|
4591
|
+
} catch (e) {
|
|
4592
|
+
this.showMessage('保存文件失败: ' + e.message, 'error');
|
|
4593
|
+
} finally {
|
|
4594
|
+
this.agentsSaving = false;
|
|
4595
|
+
}
|
|
4596
|
+
},
|
|
4597
|
+
|
|
4598
|
+
async addProvider() {
|
|
4599
|
+
if (!this.newProvider.name || !this.newProvider.url) {
|
|
4600
|
+
return this.showMessage('名称和URL必填', 'error');
|
|
4601
|
+
}
|
|
4602
|
+
const name = this.newProvider.name.trim();
|
|
4603
|
+
if (!name) {
|
|
4604
|
+
return this.showMessage('名称不能为空', 'error');
|
|
4605
|
+
}
|
|
4606
|
+
if (this.providersList.some(item => item.name === name)) {
|
|
4607
|
+
return this.showMessage('提供商已存在', 'error');
|
|
3086
4608
|
}
|
|
3087
4609
|
|
|
3088
4610
|
const safeName = this.escapeTomlString(name);
|
|
@@ -3186,6 +4708,35 @@
|
|
|
3186
4708
|
|
|
3187
4709
|
switchClaudeConfig(name) {
|
|
3188
4710
|
this.currentClaudeConfig = name;
|
|
4711
|
+
this.refreshClaudeModelContext();
|
|
4712
|
+
},
|
|
4713
|
+
|
|
4714
|
+
onClaudeModelChange() {
|
|
4715
|
+
const name = this.currentClaudeConfig;
|
|
4716
|
+
if (!name) {
|
|
4717
|
+
this.showMessage('请先选择配置', 'error');
|
|
4718
|
+
return;
|
|
4719
|
+
}
|
|
4720
|
+
const model = (this.currentClaudeModel || '').trim();
|
|
4721
|
+
if (!model) {
|
|
4722
|
+
this.showMessage('请输入模型', 'error');
|
|
4723
|
+
return;
|
|
4724
|
+
}
|
|
4725
|
+
const existing = this.claudeConfigs[name] || {};
|
|
4726
|
+
this.currentClaudeModel = model;
|
|
4727
|
+
this.claudeConfigs[name] = {
|
|
4728
|
+
apiKey: existing.apiKey || '',
|
|
4729
|
+
baseUrl: existing.baseUrl || '',
|
|
4730
|
+
model: model,
|
|
4731
|
+
hasKey: !!existing.apiKey
|
|
4732
|
+
};
|
|
4733
|
+
this.saveClaudeConfigs();
|
|
4734
|
+
this.updateClaudeModelsCurrent();
|
|
4735
|
+
if (!this.claudeConfigs[name].apiKey) {
|
|
4736
|
+
this.showMessage('该配置未设置 API Key,请先编辑', 'error');
|
|
4737
|
+
return;
|
|
4738
|
+
}
|
|
4739
|
+
this.applyClaudeConfig(name);
|
|
3189
4740
|
},
|
|
3190
4741
|
|
|
3191
4742
|
saveClaudeConfigs() {
|
|
@@ -3214,6 +4765,9 @@
|
|
|
3214
4765
|
this.saveClaudeConfigs();
|
|
3215
4766
|
this.showMessage('配置已更新', 'success');
|
|
3216
4767
|
this.closeEditConfigModal();
|
|
4768
|
+
if (name === this.currentClaudeConfig) {
|
|
4769
|
+
this.refreshClaudeModelContext();
|
|
4770
|
+
}
|
|
3217
4771
|
},
|
|
3218
4772
|
|
|
3219
4773
|
closeEditConfigModal() {
|
|
@@ -3233,7 +4787,12 @@
|
|
|
3233
4787
|
|
|
3234
4788
|
const config = this.claudeConfigs[name];
|
|
3235
4789
|
if (!config.apiKey) {
|
|
3236
|
-
|
|
4790
|
+
this.showMessage('已保存,未应用:请先输入 API Key', 'info');
|
|
4791
|
+
this.closeEditConfigModal();
|
|
4792
|
+
if (name === this.currentClaudeConfig) {
|
|
4793
|
+
this.refreshClaudeModelContext();
|
|
4794
|
+
}
|
|
4795
|
+
return;
|
|
3237
4796
|
}
|
|
3238
4797
|
|
|
3239
4798
|
const res = await api('apply-claude-config', { config });
|
|
@@ -3243,30 +4802,9 @@
|
|
|
3243
4802
|
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
3244
4803
|
this.showMessage(`已保存并应用到 Claude 配置${targetTip}`, 'success');
|
|
3245
4804
|
this.closeEditConfigModal();
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
async saveAndApplyEnvCompat() {
|
|
3250
|
-
const name = this.editingConfig.name;
|
|
3251
|
-
this.claudeConfigs[name] = {
|
|
3252
|
-
apiKey: this.editingConfig.apiKey,
|
|
3253
|
-
baseUrl: this.editingConfig.baseUrl,
|
|
3254
|
-
model: this.editingConfig.model,
|
|
3255
|
-
hasKey: !!this.editingConfig.apiKey
|
|
3256
|
-
};
|
|
3257
|
-
this.saveClaudeConfigs();
|
|
3258
|
-
|
|
3259
|
-
const config = this.claudeConfigs[name];
|
|
3260
|
-
if (!config.apiKey) {
|
|
3261
|
-
return this.showMessage('请先输入 API Key', 'error');
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
const res = await api('apply-env', { config });
|
|
3265
|
-
if (res.error || res.success === false) {
|
|
3266
|
-
this.showMessage(res.error || '应用环境变量失败', 'error');
|
|
3267
|
-
} else {
|
|
3268
|
-
this.showMessage('已保存并应用到系统环境变量(兼容模式)', 'success');
|
|
3269
|
-
this.closeEditConfigModal();
|
|
4805
|
+
if (name === this.currentClaudeConfig) {
|
|
4806
|
+
this.refreshClaudeModelContext();
|
|
4807
|
+
}
|
|
3270
4808
|
}
|
|
3271
4809
|
},
|
|
3272
4810
|
|
|
@@ -3290,6 +4828,7 @@
|
|
|
3290
4828
|
this.saveClaudeConfigs();
|
|
3291
4829
|
this.showMessage('配置已添加', 'success');
|
|
3292
4830
|
this.closeClaudeConfigModal();
|
|
4831
|
+
this.refreshClaudeModelContext();
|
|
3293
4832
|
},
|
|
3294
4833
|
|
|
3295
4834
|
deleteClaudeConfig(name) {
|
|
@@ -3305,10 +4844,12 @@
|
|
|
3305
4844
|
}
|
|
3306
4845
|
this.saveClaudeConfigs();
|
|
3307
4846
|
this.showMessage('配置已删除', 'success');
|
|
4847
|
+
this.refreshClaudeModelContext();
|
|
3308
4848
|
},
|
|
3309
4849
|
|
|
3310
4850
|
async applyClaudeConfig(name) {
|
|
3311
4851
|
this.currentClaudeConfig = name;
|
|
4852
|
+
this.refreshClaudeModelContext();
|
|
3312
4853
|
const config = this.claudeConfigs[name];
|
|
3313
4854
|
|
|
3314
4855
|
if (!config.apiKey) {
|
|
@@ -3324,203 +4865,1049 @@
|
|
|
3324
4865
|
}
|
|
3325
4866
|
},
|
|
3326
4867
|
|
|
3327
|
-
closeClaudeConfigModal() {
|
|
3328
|
-
this.showClaudeConfigModal = false;
|
|
3329
|
-
this.newClaudeConfig = {
|
|
3330
|
-
name: '',
|
|
3331
|
-
apiKey: '',
|
|
3332
|
-
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
3333
|
-
model: 'glm-4.7'
|
|
3334
|
-
};
|
|
3335
|
-
},
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
this.
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
}
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
if (
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
4868
|
+
closeClaudeConfigModal() {
|
|
4869
|
+
this.showClaudeConfigModal = false;
|
|
4870
|
+
this.newClaudeConfig = {
|
|
4871
|
+
name: '',
|
|
4872
|
+
apiKey: '',
|
|
4873
|
+
baseUrl: 'https://open.bigmodel.cn/api/anthropic',
|
|
4874
|
+
model: 'glm-4.7'
|
|
4875
|
+
};
|
|
4876
|
+
},
|
|
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
|
+
|
|
5635
|
+
openclawHasContent(config) {
|
|
5636
|
+
return !!(config && typeof config.content === 'string' && config.content.trim());
|
|
5637
|
+
},
|
|
5638
|
+
|
|
5639
|
+
openclawSubtitle(config) {
|
|
5640
|
+
if (!this.openclawHasContent(config)) {
|
|
5641
|
+
return '未设置配置';
|
|
5642
|
+
}
|
|
5643
|
+
const length = config.content.trim().length;
|
|
5644
|
+
return `已保存 ${length} 字符`;
|
|
5645
|
+
},
|
|
5646
|
+
|
|
5647
|
+
saveOpenclawConfigs() {
|
|
5648
|
+
localStorage.setItem('openclawConfigs', JSON.stringify(this.openclawConfigs));
|
|
5649
|
+
},
|
|
5650
|
+
|
|
5651
|
+
openOpenclawAddModal() {
|
|
5652
|
+
this.openclawEditorTitle = '添加 OpenClaw 配置';
|
|
5653
|
+
this.openclawEditing = {
|
|
5654
|
+
name: '',
|
|
5655
|
+
content: '',
|
|
5656
|
+
lockName: false
|
|
5657
|
+
};
|
|
5658
|
+
this.openclawConfigPath = '';
|
|
5659
|
+
this.openclawConfigExists = false;
|
|
5660
|
+
this.openclawLineEnding = '\n';
|
|
5661
|
+
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
5662
|
+
this.showOpenclawConfigModal = true;
|
|
5663
|
+
},
|
|
5664
|
+
|
|
5665
|
+
openOpenclawEditModal(name) {
|
|
5666
|
+
this.openclawEditorTitle = `编辑 OpenClaw 配置: ${name}`;
|
|
5667
|
+
this.openclawEditing = {
|
|
5668
|
+
name,
|
|
5669
|
+
content: '',
|
|
5670
|
+
lockName: true
|
|
5671
|
+
};
|
|
5672
|
+
void this.loadOpenclawConfigFromFile({ silent: true, force: true, fallbackToTemplate: true });
|
|
5673
|
+
this.showOpenclawConfigModal = true;
|
|
5674
|
+
},
|
|
5675
|
+
|
|
5676
|
+
closeOpenclawConfigModal() {
|
|
5677
|
+
this.showOpenclawConfigModal = false;
|
|
5678
|
+
this.openclawEditing = { name: '', content: '', lockName: false };
|
|
5679
|
+
this.openclawSaving = false;
|
|
5680
|
+
this.openclawApplying = false;
|
|
5681
|
+
this.resetOpenclawStructured();
|
|
5682
|
+
this.resetOpenclawQuick();
|
|
5683
|
+
},
|
|
5684
|
+
|
|
5685
|
+
async loadOpenclawConfigFromFile(options = {}) {
|
|
5686
|
+
const silent = !!options.silent;
|
|
5687
|
+
const force = !!options.force;
|
|
5688
|
+
const fallbackToTemplate = options.fallbackToTemplate !== false;
|
|
5689
|
+
this.openclawFileLoading = true;
|
|
5690
|
+
try {
|
|
5691
|
+
const res = await api('get-openclaw-config');
|
|
5692
|
+
if (res.error) {
|
|
5693
|
+
if (!silent) {
|
|
5694
|
+
this.showMessage(res.error, 'error');
|
|
5695
|
+
}
|
|
5696
|
+
return;
|
|
5697
|
+
}
|
|
5698
|
+
this.openclawConfigPath = res.path || '';
|
|
5699
|
+
this.openclawConfigExists = !!res.exists;
|
|
5700
|
+
this.openclawLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
5701
|
+
const hasContent = !!(res.content && res.content.trim());
|
|
5702
|
+
const shouldOverride = force || !this.openclawEditing.content || !this.openclawEditing.content.trim();
|
|
5703
|
+
if (hasContent && shouldOverride) {
|
|
5704
|
+
this.openclawEditing.content = res.content;
|
|
5705
|
+
} else if (!hasContent && shouldOverride && fallbackToTemplate) {
|
|
5706
|
+
this.openclawEditing.content = DEFAULT_OPENCLAW_TEMPLATE;
|
|
5707
|
+
}
|
|
5708
|
+
this.syncOpenclawStructuredFromText({ silent: true });
|
|
5709
|
+
if (!silent) {
|
|
5710
|
+
this.showMessage('已加载当前 OpenClaw 配置', 'success');
|
|
5711
|
+
}
|
|
5712
|
+
} catch (e) {
|
|
5713
|
+
if (!silent) {
|
|
5714
|
+
this.showMessage('加载 OpenClaw 配置失败: ' + e.message, 'error');
|
|
5715
|
+
}
|
|
5716
|
+
} finally {
|
|
5717
|
+
this.openclawFileLoading = false;
|
|
5718
|
+
}
|
|
5719
|
+
},
|
|
5720
|
+
|
|
5721
|
+
persistOpenclawConfig({ closeModal = true } = {}) {
|
|
5722
|
+
if (!this.openclawEditing.name || !this.openclawEditing.name.trim()) {
|
|
5723
|
+
this.showMessage('请输入配置名称', 'error');
|
|
5724
|
+
return '';
|
|
5725
|
+
}
|
|
5726
|
+
const name = this.openclawEditing.name.trim();
|
|
5727
|
+
if (!this.openclawEditing.lockName && this.openclawConfigs[name]) {
|
|
5728
|
+
this.showMessage('配置名称已存在', 'error');
|
|
5729
|
+
return '';
|
|
5730
|
+
}
|
|
5731
|
+
if (!this.openclawEditing.content || !this.openclawEditing.content.trim()) {
|
|
5732
|
+
this.showMessage('配置内容不能为空', 'error');
|
|
5733
|
+
return '';
|
|
5734
|
+
}
|
|
5735
|
+
|
|
5736
|
+
this.openclawConfigs[name] = {
|
|
5737
|
+
content: this.openclawEditing.content
|
|
5738
|
+
};
|
|
5739
|
+
this.currentOpenclawConfig = name;
|
|
5740
|
+
this.saveOpenclawConfigs();
|
|
5741
|
+
if (closeModal) {
|
|
5742
|
+
this.closeOpenclawConfigModal();
|
|
5743
|
+
}
|
|
5744
|
+
return name;
|
|
5745
|
+
},
|
|
5746
|
+
|
|
5747
|
+
async saveOpenclawConfig() {
|
|
5748
|
+
this.openclawSaving = true;
|
|
5749
|
+
try {
|
|
5750
|
+
const name = this.persistOpenclawConfig();
|
|
5751
|
+
if (!name) return;
|
|
5752
|
+
this.showMessage('OpenClaw 配置已保存', 'success');
|
|
5753
|
+
} finally {
|
|
5754
|
+
this.openclawSaving = false;
|
|
5755
|
+
}
|
|
5756
|
+
},
|
|
5757
|
+
|
|
5758
|
+
async saveAndApplyOpenclawConfig() {
|
|
5759
|
+
this.openclawApplying = true;
|
|
5760
|
+
try {
|
|
5761
|
+
const name = this.persistOpenclawConfig({ closeModal: false });
|
|
5762
|
+
if (!name) return;
|
|
5763
|
+
const config = this.openclawConfigs[name];
|
|
5764
|
+
const res = await api('apply-openclaw-config', {
|
|
5765
|
+
content: config.content,
|
|
5766
|
+
lineEnding: this.openclawLineEnding
|
|
5767
|
+
});
|
|
5768
|
+
if (res.error || res.success === false) {
|
|
5769
|
+
this.showMessage(res.error || '应用 OpenClaw 配置失败', 'error');
|
|
5770
|
+
return;
|
|
5771
|
+
}
|
|
5772
|
+
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
5773
|
+
this.openclawConfigExists = true;
|
|
5774
|
+
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
5775
|
+
this.showMessage(`已保存并应用 OpenClaw 配置${targetTip}`, 'success');
|
|
5776
|
+
this.closeOpenclawConfigModal();
|
|
5777
|
+
} catch (e) {
|
|
5778
|
+
this.showMessage('应用 OpenClaw 配置失败: ' + e.message, 'error');
|
|
5779
|
+
} finally {
|
|
5780
|
+
this.openclawApplying = false;
|
|
5781
|
+
}
|
|
5782
|
+
},
|
|
5783
|
+
|
|
5784
|
+
deleteOpenclawConfig(name) {
|
|
5785
|
+
if (Object.keys(this.openclawConfigs).length <= 1) {
|
|
5786
|
+
return this.showMessage('至少保留一个配置', 'error');
|
|
5787
|
+
}
|
|
5788
|
+
if (!confirm(`确定删除配置 "${name}"?`)) return;
|
|
5789
|
+
delete this.openclawConfigs[name];
|
|
5790
|
+
if (this.currentOpenclawConfig === name) {
|
|
5791
|
+
this.currentOpenclawConfig = Object.keys(this.openclawConfigs)[0];
|
|
5792
|
+
}
|
|
5793
|
+
this.saveOpenclawConfigs();
|
|
5794
|
+
this.showMessage('OpenClaw 配置已删除', 'success');
|
|
5795
|
+
},
|
|
5796
|
+
|
|
5797
|
+
async applyOpenclawConfig(name) {
|
|
5798
|
+
this.currentOpenclawConfig = name;
|
|
5799
|
+
const config = this.openclawConfigs[name];
|
|
5800
|
+
if (!this.openclawHasContent(config)) {
|
|
5801
|
+
return this.showMessage('该配置为空,请先编辑', 'error');
|
|
5802
|
+
}
|
|
5803
|
+
const res = await api('apply-openclaw-config', {
|
|
5804
|
+
content: config.content,
|
|
5805
|
+
lineEnding: this.openclawLineEnding
|
|
5806
|
+
});
|
|
5807
|
+
if (res.error || res.success === false) {
|
|
5808
|
+
this.showMessage(res.error || '应用 OpenClaw 配置失败', 'error');
|
|
5809
|
+
} else {
|
|
5810
|
+
this.openclawConfigPath = res.targetPath || this.openclawConfigPath;
|
|
5811
|
+
this.openclawConfigExists = true;
|
|
5812
|
+
const targetTip = res.targetPath ? `(${res.targetPath})` : '';
|
|
5813
|
+
this.showMessage(`已应用 OpenClaw 配置: ${name}${targetTip}`, 'success');
|
|
5814
|
+
}
|
|
5815
|
+
},
|
|
5816
|
+
|
|
5817
|
+
formatLatency(result) {
|
|
5818
|
+
if (!result) return '';
|
|
5819
|
+
if (!result.ok) return result.status ? `ERR ${result.status}` : 'ERR';
|
|
5820
|
+
const ms = typeof result.durationMs === 'number' ? result.durationMs : 0;
|
|
3508
5821
|
return `${ms}ms`;
|
|
3509
5822
|
},
|
|
3510
5823
|
|
|
3511
|
-
|
|
3512
|
-
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;
|
|
3513
5885
|
this.speedLoading[name] = true;
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
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
|
+
}
|
|
3519
5895
|
this.speedResults[name] = res;
|
|
3520
|
-
|
|
3521
|
-
|
|
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;
|
|
3522
5910
|
}
|
|
3523
|
-
this.speedLoading[name] = false;
|
|
3524
5911
|
},
|
|
3525
5912
|
|
|
3526
5913
|
showMessage(text, type) {
|
|
@@ -3537,10 +5924,3 @@
|
|
|
3537
5924
|
</script>
|
|
3538
5925
|
</body>
|
|
3539
5926
|
</html>
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|