psyclaw 0.29.9 → 0.29.11
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/README.md +4 -4
- package/dist/apps/panel/index.html +355 -86
- package/dist/src/adapters/pi/extension.js +130 -0
- package/dist/src/adapters/pi/extension.js.map +1 -1
- package/dist/src/branding.d.ts +3 -3
- package/dist/src/branding.js +7 -4
- package/dist/src/branding.js.map +1 -1
- package/dist/src/chat.d.ts +2 -0
- package/dist/src/chat.js +7 -2
- package/dist/src/chat.js.map +1 -1
- package/dist/src/network-routing.js +6 -3
- package/dist/src/network-routing.js.map +1 -1
- package/dist/src/panel/extension.js +41 -3
- package/dist/src/panel/extension.js.map +1 -1
- package/dist/src/panel/hub.d.ts +81 -0
- package/dist/src/panel/hub.js +122 -0
- package/dist/src/panel/hub.js.map +1 -0
- package/dist/src/panel/server.d.ts +3 -0
- package/dist/src/panel/server.js +77 -1
- package/dist/src/panel/server.js.map +1 -1
- package/dist/src/panel/workbench.js +8 -1
- package/dist/src/panel/workbench.js.map +1 -1
- package/dist/src/session/help.js +2 -1
- package/dist/src/session/help.js.map +1 -1
- package/dist/src/tui/wake-options.d.ts +33 -0
- package/dist/src/tui/wake-options.js +131 -0
- package/dist/src/tui/wake-options.js.map +1 -0
- package/dist/src/wake-options/runtime.d.ts +30 -0
- package/dist/src/wake-options/runtime.js +70 -0
- package/dist/src/wake-options/runtime.js.map +1 -0
- package/package.json +1 -1
- package/scripts/psyclaw.mjs +6 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<title>PsyClaw Panel ·
|
|
6
|
+
<title>PsyClaw Panel · 科研工作台</title>
|
|
7
7
|
<style>
|
|
8
8
|
:root {
|
|
9
9
|
color-scheme: light;
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
|
61
61
|
--font-mono: ui-monospace, SFMono-Regular, "Cascadia Code", Menlo, Monaco, Consolas, monospace;
|
|
62
62
|
--font-serif: "Charter", "Bitstream Charter", Cambria, Georgia, serif;
|
|
63
|
+
--accent: var(--brand-primary);
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -938,7 +939,7 @@
|
|
|
938
939
|
background: var(--brand-primary-hover);
|
|
939
940
|
}
|
|
940
941
|
|
|
941
|
-
/*
|
|
942
|
+
/* Chat drawer — light app chrome (not terminal) */
|
|
942
943
|
.console-fab {
|
|
943
944
|
position: fixed;
|
|
944
945
|
bottom: 24px;
|
|
@@ -948,20 +949,20 @@
|
|
|
948
949
|
align-items: center;
|
|
949
950
|
gap: 8px;
|
|
950
951
|
padding: 10px 18px;
|
|
951
|
-
background:
|
|
952
|
-
color:
|
|
953
|
-
border: 1px solid
|
|
954
|
-
border-radius:
|
|
952
|
+
background: var(--bg-surface);
|
|
953
|
+
color: var(--brand-primary);
|
|
954
|
+
border: 1px solid var(--brand-primary-border);
|
|
955
|
+
border-radius: var(--radius-lg);
|
|
955
956
|
font-size: 13px;
|
|
956
957
|
font-weight: 600;
|
|
957
958
|
cursor: pointer;
|
|
958
|
-
box-shadow:
|
|
959
|
-
transition:
|
|
959
|
+
box-shadow: var(--shadow-md);
|
|
960
|
+
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
|
960
961
|
}
|
|
961
962
|
.console-fab:hover {
|
|
962
|
-
background:
|
|
963
|
-
|
|
964
|
-
|
|
963
|
+
background: var(--brand-primary-light);
|
|
964
|
+
border-color: var(--brand-primary);
|
|
965
|
+
transform: translateY(-1px);
|
|
965
966
|
}
|
|
966
967
|
.console-drawer {
|
|
967
968
|
position: fixed;
|
|
@@ -971,34 +972,44 @@
|
|
|
971
972
|
max-width: calc(100vw - 40px);
|
|
972
973
|
height: 560px;
|
|
973
974
|
max-height: calc(100vh - 80px);
|
|
974
|
-
background:
|
|
975
|
-
color:
|
|
976
|
-
border: 1px solid
|
|
975
|
+
background: var(--bg-surface);
|
|
976
|
+
color: var(--text-main);
|
|
977
|
+
border: 1px solid var(--border-subtle);
|
|
977
978
|
border-bottom: 0;
|
|
978
|
-
border-radius:
|
|
979
|
-
box-shadow:
|
|
979
|
+
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
980
|
+
box-shadow: var(--shadow-md);
|
|
980
981
|
display: flex;
|
|
981
982
|
flex-direction: column;
|
|
982
983
|
z-index: 1000;
|
|
983
984
|
transform: translateY(105%);
|
|
984
985
|
transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
|
|
986
|
+
font-family: var(--font-sans);
|
|
985
987
|
}
|
|
986
988
|
.console-drawer.open {
|
|
987
989
|
transform: translateY(0);
|
|
988
990
|
}
|
|
991
|
+
.console-drawer.embedded {
|
|
992
|
+
border: none;
|
|
993
|
+
border-radius: 0;
|
|
994
|
+
box-shadow: none;
|
|
995
|
+
transform: none;
|
|
996
|
+
}
|
|
989
997
|
.console-header {
|
|
990
998
|
display: flex;
|
|
991
999
|
align-items: center;
|
|
992
1000
|
justify-content: space-between;
|
|
993
1001
|
padding: 12px 16px;
|
|
994
|
-
border-bottom: 1px solid
|
|
995
|
-
background:
|
|
996
|
-
border-radius:
|
|
1002
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
1003
|
+
background: var(--bg-surface);
|
|
1004
|
+
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
|
1005
|
+
}
|
|
1006
|
+
.console-drawer.embedded .console-header {
|
|
1007
|
+
border-radius: 0;
|
|
997
1008
|
}
|
|
998
1009
|
.console-title {
|
|
999
1010
|
font-size: 13px;
|
|
1000
1011
|
font-weight: 650;
|
|
1001
|
-
color:
|
|
1012
|
+
color: var(--text-main);
|
|
1002
1013
|
display: flex;
|
|
1003
1014
|
align-items: center;
|
|
1004
1015
|
gap: 8px;
|
|
@@ -1007,10 +1018,10 @@
|
|
|
1007
1018
|
font-size: 10px;
|
|
1008
1019
|
font-weight: 600;
|
|
1009
1020
|
padding: 1px 6px;
|
|
1010
|
-
border-radius:
|
|
1011
|
-
background:
|
|
1012
|
-
color:
|
|
1013
|
-
border: 1px solid
|
|
1021
|
+
border-radius: var(--radius-xs);
|
|
1022
|
+
background: var(--ok-bg);
|
|
1023
|
+
color: var(--ok-text);
|
|
1024
|
+
border: 1px solid var(--ok-border);
|
|
1014
1025
|
}
|
|
1015
1026
|
.console-actions {
|
|
1016
1027
|
display: flex;
|
|
@@ -1020,103 +1031,126 @@
|
|
|
1020
1031
|
.console-btn-icon {
|
|
1021
1032
|
background: transparent;
|
|
1022
1033
|
border: none;
|
|
1023
|
-
color:
|
|
1034
|
+
color: var(--text-muted);
|
|
1024
1035
|
cursor: pointer;
|
|
1025
1036
|
padding: 4px;
|
|
1026
|
-
border-radius:
|
|
1037
|
+
border-radius: var(--radius-sm);
|
|
1027
1038
|
display: flex;
|
|
1028
1039
|
}
|
|
1029
1040
|
.console-btn-icon:hover {
|
|
1030
|
-
color:
|
|
1031
|
-
background:
|
|
1041
|
+
color: var(--text-main);
|
|
1042
|
+
background: var(--bg-surface-subtle);
|
|
1032
1043
|
}
|
|
1033
1044
|
.console-quick-chips {
|
|
1034
1045
|
display: flex;
|
|
1035
1046
|
align-items: center;
|
|
1036
1047
|
gap: 6px;
|
|
1037
1048
|
padding: 8px 14px;
|
|
1038
|
-
background:
|
|
1039
|
-
border-bottom: 1px solid
|
|
1049
|
+
background: var(--bg-surface-subtle);
|
|
1050
|
+
border-bottom: 1px solid var(--border-subtle);
|
|
1040
1051
|
overflow-x: auto;
|
|
1041
1052
|
}
|
|
1042
1053
|
.chip {
|
|
1043
1054
|
font-family: var(--font-mono);
|
|
1044
1055
|
font-size: 11px;
|
|
1045
|
-
background:
|
|
1046
|
-
color:
|
|
1047
|
-
border: 1px solid
|
|
1056
|
+
background: var(--bg-surface);
|
|
1057
|
+
color: var(--text-secondary);
|
|
1058
|
+
border: 1px solid var(--border-default);
|
|
1048
1059
|
padding: 3px 8px;
|
|
1049
|
-
border-radius:
|
|
1060
|
+
border-radius: var(--radius-sm);
|
|
1050
1061
|
cursor: pointer;
|
|
1051
1062
|
white-space: nowrap;
|
|
1052
|
-
transition:
|
|
1063
|
+
transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
|
|
1053
1064
|
}
|
|
1054
1065
|
.chip:hover {
|
|
1055
|
-
background:
|
|
1056
|
-
color:
|
|
1066
|
+
background: var(--brand-primary-light);
|
|
1067
|
+
border-color: var(--brand-primary-border);
|
|
1068
|
+
color: var(--brand-primary);
|
|
1057
1069
|
}
|
|
1058
1070
|
.console-log {
|
|
1059
1071
|
flex: 1;
|
|
1060
1072
|
overflow-y: auto;
|
|
1061
|
-
padding:
|
|
1062
|
-
font-family: var(--font-
|
|
1063
|
-
font-size:
|
|
1064
|
-
line-height: 1.
|
|
1073
|
+
padding: 16px;
|
|
1074
|
+
font-family: var(--font-sans);
|
|
1075
|
+
font-size: 13px;
|
|
1076
|
+
line-height: 1.55;
|
|
1065
1077
|
display: flex;
|
|
1066
1078
|
flex-direction: column;
|
|
1067
1079
|
gap: 12px;
|
|
1080
|
+
background: var(--bg-app);
|
|
1068
1081
|
}
|
|
1069
1082
|
.log-entry {
|
|
1070
1083
|
display: flex;
|
|
1071
1084
|
flex-direction: column;
|
|
1072
1085
|
gap: 4px;
|
|
1086
|
+
max-width: 92%;
|
|
1087
|
+
}
|
|
1088
|
+
.log-entry.user {
|
|
1089
|
+
align-self: flex-end;
|
|
1090
|
+
align-items: flex-end;
|
|
1091
|
+
}
|
|
1092
|
+
.log-entry.agent,
|
|
1093
|
+
.log-entry.sys {
|
|
1094
|
+
align-self: flex-start;
|
|
1095
|
+
align-items: flex-start;
|
|
1073
1096
|
}
|
|
1074
1097
|
.log-user {
|
|
1075
|
-
color: #
|
|
1076
|
-
font-weight:
|
|
1098
|
+
color: #fff;
|
|
1099
|
+
font-weight: 500;
|
|
1100
|
+
background: var(--brand-primary);
|
|
1101
|
+
padding: 10px 12px;
|
|
1102
|
+
border-radius: 12px 12px 4px 12px;
|
|
1103
|
+
white-space: pre-wrap;
|
|
1104
|
+
word-break: break-word;
|
|
1105
|
+
box-shadow: var(--shadow-xs);
|
|
1077
1106
|
}
|
|
1078
1107
|
.log-agent {
|
|
1079
|
-
color:
|
|
1080
|
-
background:
|
|
1108
|
+
color: var(--text-main);
|
|
1109
|
+
background: var(--bg-surface);
|
|
1081
1110
|
padding: 10px 12px;
|
|
1082
|
-
border-radius:
|
|
1083
|
-
border
|
|
1111
|
+
border-radius: 12px 12px 12px 4px;
|
|
1112
|
+
border: 1px solid var(--border-subtle);
|
|
1084
1113
|
white-space: pre-wrap;
|
|
1085
1114
|
word-break: break-word;
|
|
1115
|
+
box-shadow: var(--shadow-xs);
|
|
1086
1116
|
}
|
|
1087
1117
|
.log-sys {
|
|
1088
|
-
color:
|
|
1089
|
-
font-style:
|
|
1090
|
-
font-size:
|
|
1118
|
+
color: var(--text-muted);
|
|
1119
|
+
font-style: normal;
|
|
1120
|
+
font-size: 12px;
|
|
1121
|
+
background: transparent;
|
|
1122
|
+
padding: 2px 0;
|
|
1091
1123
|
}
|
|
1092
1124
|
.console-input-box {
|
|
1093
|
-
padding: 12px;
|
|
1094
|
-
background:
|
|
1095
|
-
border-top: 1px solid
|
|
1125
|
+
padding: 12px 14px;
|
|
1126
|
+
background: var(--bg-surface);
|
|
1127
|
+
border-top: 1px solid var(--border-subtle);
|
|
1096
1128
|
display: flex;
|
|
1097
1129
|
gap: 8px;
|
|
1098
1130
|
}
|
|
1099
1131
|
.console-input {
|
|
1100
1132
|
flex: 1;
|
|
1101
|
-
background:
|
|
1102
|
-
border: 1px solid
|
|
1103
|
-
color:
|
|
1104
|
-
padding:
|
|
1105
|
-
font-size:
|
|
1106
|
-
border-radius:
|
|
1133
|
+
background: var(--bg-surface-subtle);
|
|
1134
|
+
border: 1px solid var(--border-default);
|
|
1135
|
+
color: var(--text-main);
|
|
1136
|
+
padding: 10px 12px;
|
|
1137
|
+
font-size: 13px;
|
|
1138
|
+
border-radius: var(--radius-md);
|
|
1107
1139
|
outline: none;
|
|
1108
|
-
font-family: var(--font-
|
|
1140
|
+
font-family: var(--font-sans);
|
|
1109
1141
|
}
|
|
1110
1142
|
.console-input:focus {
|
|
1111
|
-
border-color:
|
|
1143
|
+
border-color: var(--brand-primary);
|
|
1144
|
+
background: var(--bg-surface);
|
|
1145
|
+
box-shadow: 0 0 0 3px var(--brand-primary-border);
|
|
1112
1146
|
}
|
|
1113
1147
|
.console-send-btn {
|
|
1114
1148
|
background: var(--brand-primary);
|
|
1115
1149
|
color: #fff;
|
|
1116
1150
|
border: none;
|
|
1117
1151
|
padding: 8px 14px;
|
|
1118
|
-
border-radius:
|
|
1119
|
-
font-size:
|
|
1152
|
+
border-radius: var(--radius-md);
|
|
1153
|
+
font-size: 13px;
|
|
1120
1154
|
font-weight: 600;
|
|
1121
1155
|
cursor: pointer;
|
|
1122
1156
|
display: flex;
|
|
@@ -1126,6 +1160,54 @@
|
|
|
1126
1160
|
.console-send-btn:hover {
|
|
1127
1161
|
background: var(--brand-primary-hover);
|
|
1128
1162
|
}
|
|
1163
|
+
.log-agent.streaming::after {
|
|
1164
|
+
content: "▍";
|
|
1165
|
+
animation: blink 1s steps(1) infinite;
|
|
1166
|
+
color: var(--brand-primary);
|
|
1167
|
+
margin-left: 2px;
|
|
1168
|
+
}
|
|
1169
|
+
@keyframes blink { 50% { opacity: 0; } }
|
|
1170
|
+
.wake-option-list {
|
|
1171
|
+
display: grid;
|
|
1172
|
+
gap: 8px;
|
|
1173
|
+
max-height: 360px;
|
|
1174
|
+
overflow-y: auto;
|
|
1175
|
+
}
|
|
1176
|
+
.wake-option {
|
|
1177
|
+
display: flex;
|
|
1178
|
+
align-items: flex-start;
|
|
1179
|
+
gap: 10px;
|
|
1180
|
+
padding: 10px 12px;
|
|
1181
|
+
border: 1px solid var(--border-subtle);
|
|
1182
|
+
border-radius: var(--radius-md);
|
|
1183
|
+
background: var(--bg-surface);
|
|
1184
|
+
cursor: pointer;
|
|
1185
|
+
text-align: left;
|
|
1186
|
+
}
|
|
1187
|
+
.wake-option:hover {
|
|
1188
|
+
border-color: var(--brand-primary-border);
|
|
1189
|
+
background: var(--brand-primary-light);
|
|
1190
|
+
}
|
|
1191
|
+
.wake-option.selected {
|
|
1192
|
+
border-color: var(--brand-primary);
|
|
1193
|
+
background: var(--brand-primary-light);
|
|
1194
|
+
box-shadow: 0 0 0 2px var(--brand-primary-border);
|
|
1195
|
+
}
|
|
1196
|
+
.wake-option .wake-mark {
|
|
1197
|
+
font-family: var(--font-mono);
|
|
1198
|
+
color: var(--brand-primary);
|
|
1199
|
+
font-weight: 700;
|
|
1200
|
+
min-width: 1.5rem;
|
|
1201
|
+
}
|
|
1202
|
+
.wake-option .wake-label {
|
|
1203
|
+
font-weight: 600;
|
|
1204
|
+
color: var(--text-main);
|
|
1205
|
+
}
|
|
1206
|
+
.wake-option .wake-desc {
|
|
1207
|
+
font-size: 12px;
|
|
1208
|
+
color: var(--text-muted);
|
|
1209
|
+
margin-top: 2px;
|
|
1210
|
+
}
|
|
1129
1211
|
|
|
1130
1212
|
/* Command Palette Modal */
|
|
1131
1213
|
.modal-overlay {
|
|
@@ -1514,7 +1596,7 @@
|
|
|
1514
1596
|
<div class="page-head">
|
|
1515
1597
|
<div>
|
|
1516
1598
|
<h1>交互对话</h1>
|
|
1517
|
-
<p
|
|
1599
|
+
<p>提问会转入当前 PsyClaw 会话;助手回复经 SSE 同步到此处。模型可调用「唤醒选项」弹窗。</p>
|
|
1518
1600
|
</div>
|
|
1519
1601
|
</div>
|
|
1520
1602
|
<div id="chat-console-host" class="panel section" style="min-height:62vh;padding:0;overflow:hidden;display:flex;flex-direction:column"></div>
|
|
@@ -1523,22 +1605,22 @@
|
|
|
1523
1605
|
</div>
|
|
1524
1606
|
</div>
|
|
1525
1607
|
|
|
1526
|
-
<!--
|
|
1527
|
-
<button id="console-fab" class="console-fab" title="
|
|
1528
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><
|
|
1529
|
-
<span
|
|
1608
|
+
<!-- 悬浮对话入口 -->
|
|
1609
|
+
<button id="console-fab" class="console-fab" title="打开/折叠对话">
|
|
1610
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
1611
|
+
<span>对话</span>
|
|
1530
1612
|
</button>
|
|
1531
1613
|
|
|
1532
|
-
<!--
|
|
1614
|
+
<!-- 抽屉式对话 -->
|
|
1533
1615
|
<div id="console-drawer" class="console-drawer">
|
|
1534
1616
|
<div class="console-header">
|
|
1535
1617
|
<div class="console-title">
|
|
1536
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color
|
|
1537
|
-
<span
|
|
1538
|
-
<span id="console-status-badge" class="console-status-tag"
|
|
1618
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="color:var(--brand-primary)"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
|
|
1619
|
+
<span>对话</span>
|
|
1620
|
+
<span id="console-status-badge" class="console-status-tag">已连接</span>
|
|
1539
1621
|
</div>
|
|
1540
1622
|
<div class="console-actions">
|
|
1541
|
-
<button id="console-clear-btn" class="console-btn-icon" title="
|
|
1623
|
+
<button id="console-clear-btn" class="console-btn-icon" title="清空对话">
|
|
1542
1624
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><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"/></svg>
|
|
1543
1625
|
</button>
|
|
1544
1626
|
<button id="console-close-btn" class="console-btn-icon" title="最小化">
|
|
@@ -1547,7 +1629,6 @@
|
|
|
1547
1629
|
</div>
|
|
1548
1630
|
</div>
|
|
1549
1631
|
|
|
1550
|
-
<!-- 快捷指令快速贴入栏 -->
|
|
1551
1632
|
<div class="console-quick-chips">
|
|
1552
1633
|
<span class="chip" data-cmd="/plan status">/plan status</span>
|
|
1553
1634
|
<span class="chip" data-cmd="/verify list">/verify list</span>
|
|
@@ -1556,16 +1637,14 @@
|
|
|
1556
1637
|
<span class="chip" data-cmd="/agents">/agents 角色</span>
|
|
1557
1638
|
</div>
|
|
1558
1639
|
|
|
1559
|
-
<!-- 终端流式日志输出区域 -->
|
|
1560
1640
|
<div id="console-log" class="console-log">
|
|
1561
|
-
<div class="log-entry">
|
|
1562
|
-
<div class="log-sys"
|
|
1641
|
+
<div class="log-entry sys">
|
|
1642
|
+
<div class="log-sys">对话已就绪(SSE)。提问转入当前会话,助手回复会流式同步到此处。支持 /init、/plan、/grill 等指令;模型可弹出「唤醒选项」。</div>
|
|
1563
1643
|
</div>
|
|
1564
1644
|
</div>
|
|
1565
1645
|
|
|
1566
|
-
<!-- 输入与触发栏 -->
|
|
1567
1646
|
<div class="console-input-box">
|
|
1568
|
-
<input type="text" id="console-input" class="console-input" placeholder="
|
|
1647
|
+
<input type="text" id="console-input" class="console-input" placeholder="输入研究问题或指令,Enter 发送…" autocomplete="off" />
|
|
1569
1648
|
<button id="console-send-btn" class="console-send-btn">
|
|
1570
1649
|
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
1571
1650
|
发送
|
|
@@ -1573,6 +1652,26 @@
|
|
|
1573
1652
|
</div>
|
|
1574
1653
|
</div>
|
|
1575
1654
|
|
|
1655
|
+
<!-- 唤醒选项弹窗 -->
|
|
1656
|
+
<div id="wake-modal" class="modal-overlay hidden">
|
|
1657
|
+
<div class="modal-box">
|
|
1658
|
+
<div class="modal-head">
|
|
1659
|
+
<h3 id="wake-modal-title">唤醒选项</h3>
|
|
1660
|
+
<button id="wake-modal-close" class="console-btn-icon" style="color:var(--text-muted)" title="取消">
|
|
1661
|
+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
1662
|
+
</button>
|
|
1663
|
+
</div>
|
|
1664
|
+
<div class="modal-body">
|
|
1665
|
+
<p id="wake-modal-prompt" style="color:var(--text-secondary);margin:0"></p>
|
|
1666
|
+
<div id="wake-option-list" class="wake-option-list"></div>
|
|
1667
|
+
</div>
|
|
1668
|
+
<div class="modal-foot">
|
|
1669
|
+
<button id="wake-cancel-btn" class="button">取消</button>
|
|
1670
|
+
<button id="wake-submit-btn" class="button primary">确认</button>
|
|
1671
|
+
</div>
|
|
1672
|
+
</div>
|
|
1673
|
+
</div>
|
|
1674
|
+
|
|
1576
1675
|
<!-- /init 项目向导模态框 -->
|
|
1577
1676
|
<div id="init-modal" class="modal-overlay hidden">
|
|
1578
1677
|
<div class="modal-box">
|
|
@@ -1681,7 +1780,7 @@ function mountConsoleInChat() {
|
|
|
1681
1780
|
const drawer = $("console-drawer");
|
|
1682
1781
|
if (!host || !drawer) return;
|
|
1683
1782
|
host.appendChild(drawer);
|
|
1684
|
-
drawer.classList.add("open");
|
|
1783
|
+
drawer.classList.add("open", "embedded");
|
|
1685
1784
|
drawer.style.position = "relative";
|
|
1686
1785
|
drawer.style.inset = "auto";
|
|
1687
1786
|
drawer.style.width = "100%";
|
|
@@ -1689,12 +1788,14 @@ function mountConsoleInChat() {
|
|
|
1689
1788
|
drawer.style.minHeight = "58vh";
|
|
1690
1789
|
drawer.style.boxShadow = "none";
|
|
1691
1790
|
drawer.style.borderRadius = "0";
|
|
1791
|
+
drawer.style.transform = "none";
|
|
1692
1792
|
}
|
|
1693
1793
|
|
|
1694
1794
|
function restoreConsoleDrawer() {
|
|
1695
1795
|
const drawer = $("console-drawer");
|
|
1696
1796
|
if (!drawer || drawer.parentElement === document.body) return;
|
|
1697
1797
|
document.body.appendChild(drawer);
|
|
1798
|
+
drawer.classList.remove("embedded");
|
|
1698
1799
|
drawer.style.position = "";
|
|
1699
1800
|
drawer.style.inset = "";
|
|
1700
1801
|
drawer.style.width = "";
|
|
@@ -1702,6 +1803,7 @@ function restoreConsoleDrawer() {
|
|
|
1702
1803
|
drawer.style.minHeight = "";
|
|
1703
1804
|
drawer.style.boxShadow = "";
|
|
1704
1805
|
drawer.style.borderRadius = "";
|
|
1806
|
+
drawer.style.transform = "";
|
|
1705
1807
|
}
|
|
1706
1808
|
|
|
1707
1809
|
async function loadChecklist() {
|
|
@@ -2240,11 +2342,14 @@ document.querySelectorAll(".step-item").forEach(item => {
|
|
|
2240
2342
|
});
|
|
2241
2343
|
});
|
|
2242
2344
|
|
|
2243
|
-
/* ---
|
|
2345
|
+
/* --- 交互对话 + SSE + 唤醒选项 --- */
|
|
2244
2346
|
const consoleDrawer = $("console-drawer");
|
|
2245
2347
|
const consoleFab = $("console-fab");
|
|
2246
2348
|
const consoleLog = $("console-log");
|
|
2247
2349
|
const consoleInput = $("console-input");
|
|
2350
|
+
const streamBubbles = new Map();
|
|
2351
|
+
let lastLocalUserText = "";
|
|
2352
|
+
let wakeState = null;
|
|
2248
2353
|
|
|
2249
2354
|
function openConsole() {
|
|
2250
2355
|
consoleDrawer.classList.add("open");
|
|
@@ -2262,31 +2367,192 @@ function openConsoleWithMessage(text) {
|
|
|
2262
2367
|
|
|
2263
2368
|
function appendLog(role, text) {
|
|
2264
2369
|
const entry = document.createElement("div");
|
|
2265
|
-
entry.className =
|
|
2370
|
+
entry.className = `log-entry ${role}`;
|
|
2266
2371
|
if (role === "user") {
|
|
2267
|
-
entry.innerHTML = `<div class="log-user"
|
|
2372
|
+
entry.innerHTML = `<div class="log-user">${esc(text)}</div>`;
|
|
2268
2373
|
} else if (role === "agent") {
|
|
2269
2374
|
entry.innerHTML = `<div class="log-agent">${renderMarkdown(text)}</div>`;
|
|
2270
2375
|
} else {
|
|
2271
|
-
entry.innerHTML = `<div class="log-sys"
|
|
2376
|
+
entry.innerHTML = `<div class="log-sys">${esc(text)}</div>`;
|
|
2272
2377
|
}
|
|
2273
2378
|
consoleLog.appendChild(entry);
|
|
2274
2379
|
consoleLog.scrollTop = consoleLog.scrollHeight;
|
|
2380
|
+
return entry;
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
function ensureStreamBubble(messageId) {
|
|
2384
|
+
let entry = streamBubbles.get(messageId);
|
|
2385
|
+
if (entry) return entry;
|
|
2386
|
+
entry = document.createElement("div");
|
|
2387
|
+
entry.className = "log-entry agent";
|
|
2388
|
+
entry.dataset.messageId = messageId;
|
|
2389
|
+
entry.innerHTML = `<div class="log-agent streaming"></div>`;
|
|
2390
|
+
consoleLog.appendChild(entry);
|
|
2391
|
+
streamBubbles.set(messageId, entry);
|
|
2392
|
+
consoleLog.scrollTop = consoleLog.scrollHeight;
|
|
2393
|
+
return entry;
|
|
2394
|
+
}
|
|
2395
|
+
|
|
2396
|
+
function updateStreamBubble(messageId, text, done) {
|
|
2397
|
+
const entry = ensureStreamBubble(messageId);
|
|
2398
|
+
const bubble = entry.querySelector(".log-agent");
|
|
2399
|
+
if (!bubble) return;
|
|
2400
|
+
bubble.classList.toggle("streaming", !done);
|
|
2401
|
+
bubble.innerHTML = renderMarkdown(text || (done ? "" : "…"));
|
|
2402
|
+
consoleLog.scrollTop = consoleLog.scrollHeight;
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
function setConsoleStatus(text, ok = true) {
|
|
2406
|
+
const badge = $("console-status-badge");
|
|
2407
|
+
if (!badge) return;
|
|
2408
|
+
badge.textContent = text;
|
|
2409
|
+
badge.style.background = ok ? "var(--ok-bg)" : "var(--warn-bg)";
|
|
2410
|
+
badge.style.color = ok ? "var(--ok-text)" : "var(--warn-text)";
|
|
2411
|
+
badge.style.borderColor = ok ? "var(--ok-border)" : "var(--warn-border)";
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
function connectAssistantStream() {
|
|
2415
|
+
if (typeof EventSource === "undefined") return;
|
|
2416
|
+
const es = new EventSource("/api/assistant/stream");
|
|
2417
|
+
es.addEventListener("ready", () => setConsoleStatus("已连接", true));
|
|
2418
|
+
es.addEventListener("ping", () => setConsoleStatus("已连接", true));
|
|
2419
|
+
es.addEventListener("user_echo", (ev) => {
|
|
2420
|
+
try {
|
|
2421
|
+
const data = JSON.parse(ev.data);
|
|
2422
|
+
if (data.text && data.text !== lastLocalUserText) appendLog("user", data.text);
|
|
2423
|
+
} catch { /* ignore */ }
|
|
2424
|
+
});
|
|
2425
|
+
es.addEventListener("assistant_start", (ev) => {
|
|
2426
|
+
try {
|
|
2427
|
+
const data = JSON.parse(ev.data);
|
|
2428
|
+
ensureStreamBubble(data.messageId || "assistant");
|
|
2429
|
+
} catch { /* ignore */ }
|
|
2430
|
+
});
|
|
2431
|
+
es.addEventListener("assistant_delta", (ev) => {
|
|
2432
|
+
try {
|
|
2433
|
+
const data = JSON.parse(ev.data);
|
|
2434
|
+
updateStreamBubble(data.messageId || "assistant", data.text || "", false);
|
|
2435
|
+
} catch { /* ignore */ }
|
|
2436
|
+
});
|
|
2437
|
+
es.addEventListener("assistant_end", (ev) => {
|
|
2438
|
+
try {
|
|
2439
|
+
const data = JSON.parse(ev.data);
|
|
2440
|
+
updateStreamBubble(data.messageId || "assistant", data.text || "", true);
|
|
2441
|
+
streamBubbles.delete(data.messageId || "assistant");
|
|
2442
|
+
} catch { /* ignore */ }
|
|
2443
|
+
});
|
|
2444
|
+
es.addEventListener("agent_settled", () => {
|
|
2445
|
+
for (const [id, entry] of streamBubbles) {
|
|
2446
|
+
entry.querySelector(".log-agent")?.classList.remove("streaming");
|
|
2447
|
+
streamBubbles.delete(id);
|
|
2448
|
+
}
|
|
2449
|
+
});
|
|
2450
|
+
es.addEventListener("wake_options", (ev) => {
|
|
2451
|
+
try {
|
|
2452
|
+
const data = JSON.parse(ev.data);
|
|
2453
|
+
if (data.prompt) showWakeModal(data.prompt);
|
|
2454
|
+
} catch { /* ignore */ }
|
|
2455
|
+
});
|
|
2456
|
+
es.addEventListener("wake_options_resolved", (ev) => {
|
|
2457
|
+
try {
|
|
2458
|
+
const data = JSON.parse(ev.data);
|
|
2459
|
+
if (wakeState && wakeState.id === data.promptId) hideWakeModal();
|
|
2460
|
+
appendLog("sys", `唤醒选项已关闭(${data.answer?.source || "resolved"})`);
|
|
2461
|
+
if (data.answer?.source === "panel" || data.answer?.selectedIds?.length) {
|
|
2462
|
+
loadChecklist().catch(() => {});
|
|
2463
|
+
}
|
|
2464
|
+
} catch { /* ignore */ }
|
|
2465
|
+
});
|
|
2466
|
+
es.onerror = () => setConsoleStatus("重连中", false);
|
|
2275
2467
|
}
|
|
2276
2468
|
|
|
2469
|
+
function showWakeModal(prompt, { switchChecklist = true } = {}) {
|
|
2470
|
+
const firstOpen = !wakeState || wakeState.id !== prompt.id;
|
|
2471
|
+
wakeState = {
|
|
2472
|
+
id: prompt.id,
|
|
2473
|
+
mode: prompt.mode,
|
|
2474
|
+
allowMultiple: Boolean(prompt.allowMultiple),
|
|
2475
|
+
selected: wakeState && wakeState.id === prompt.id
|
|
2476
|
+
? wakeState.selected
|
|
2477
|
+
: new Set((prompt.options || []).filter(o => o.checked).map(o => o.id)),
|
|
2478
|
+
options: prompt.options || [],
|
|
2479
|
+
};
|
|
2480
|
+
if (wakeState.mode === "choice" && wakeState.selected.size === 0 && wakeState.options[0]) {
|
|
2481
|
+
wakeState.selected.add(wakeState.options[0].id);
|
|
2482
|
+
}
|
|
2483
|
+
$("wake-modal-title").textContent = prompt.title || "唤醒选项";
|
|
2484
|
+
$("wake-modal-prompt").textContent = prompt.prompt || (prompt.mode === "checklist" ? "勾选需要确认的项目后提交。" : "请选择一项。");
|
|
2485
|
+
const list = $("wake-option-list");
|
|
2486
|
+
list.innerHTML = "";
|
|
2487
|
+
for (const option of wakeState.options) {
|
|
2488
|
+
const btn = document.createElement("button");
|
|
2489
|
+
btn.type = "button";
|
|
2490
|
+
btn.className = "wake-option" + (wakeState.selected.has(option.id) ? " selected" : "");
|
|
2491
|
+
btn.dataset.id = option.id;
|
|
2492
|
+
const mark = wakeState.mode === "checklist" || wakeState.allowMultiple
|
|
2493
|
+
? (wakeState.selected.has(option.id) ? "[x]" : "[ ]")
|
|
2494
|
+
: (wakeState.selected.has(option.id) ? "(•)" : "( )");
|
|
2495
|
+
btn.innerHTML = `<span class="wake-mark">${mark}</span><span><div class="wake-label">${esc(option.label)}</div>${option.description ? `<div class="wake-desc">${esc(option.description)}</div>` : ""}</span>`;
|
|
2496
|
+
btn.addEventListener("click", () => {
|
|
2497
|
+
if (wakeState.mode === "choice" && !wakeState.allowMultiple) {
|
|
2498
|
+
wakeState.selected.clear();
|
|
2499
|
+
wakeState.selected.add(option.id);
|
|
2500
|
+
} else if (wakeState.selected.has(option.id)) {
|
|
2501
|
+
wakeState.selected.delete(option.id);
|
|
2502
|
+
} else {
|
|
2503
|
+
wakeState.selected.add(option.id);
|
|
2504
|
+
}
|
|
2505
|
+
showWakeModal(prompt, { switchChecklist: false });
|
|
2506
|
+
});
|
|
2507
|
+
list.appendChild(btn);
|
|
2508
|
+
}
|
|
2509
|
+
$("wake-modal").classList.remove("hidden");
|
|
2510
|
+
openConsole();
|
|
2511
|
+
if (firstOpen && switchChecklist && prompt.mode === "checklist") setView("checklist");
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
function hideWakeModal() {
|
|
2515
|
+
$("wake-modal").classList.add("hidden");
|
|
2516
|
+
wakeState = null;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
async function submitWake(cancel = false) {
|
|
2520
|
+
if (!wakeState) return;
|
|
2521
|
+
const promptId = wakeState.id;
|
|
2522
|
+
const selectedIds = cancel ? [] : [...wakeState.selected];
|
|
2523
|
+
try {
|
|
2524
|
+
await json("/api/wake-options/respond", {
|
|
2525
|
+
method: "POST",
|
|
2526
|
+
headers: { "content-type": "application/json" },
|
|
2527
|
+
body: JSON.stringify({ promptId, selectedIds, cancel: Boolean(cancel) }),
|
|
2528
|
+
});
|
|
2529
|
+
hideWakeModal();
|
|
2530
|
+
appendLog("sys", cancel ? "已取消唤醒选项" : `已提交选择:${selectedIds.join(", ") || "(空)"}`);
|
|
2531
|
+
await loadChecklist().catch(() => {});
|
|
2532
|
+
} catch (err) {
|
|
2533
|
+
appendLog("sys", `[唤醒选项] ${err.message}`);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
$("wake-modal-close")?.addEventListener("click", () => submitWake(true));
|
|
2538
|
+
$("wake-cancel-btn")?.addEventListener("click", () => submitWake(true));
|
|
2539
|
+
$("wake-submit-btn")?.addEventListener("click", () => submitWake(false));
|
|
2540
|
+
|
|
2277
2541
|
consoleFab.addEventListener("click", () => {
|
|
2278
2542
|
if (consoleDrawer.classList.contains("open")) closeConsole();
|
|
2279
2543
|
else openConsole();
|
|
2280
2544
|
});
|
|
2281
2545
|
$("console-close-btn").addEventListener("click", closeConsole);
|
|
2282
2546
|
$("console-clear-btn").addEventListener("click", () => {
|
|
2283
|
-
consoleLog.innerHTML = `<div class="log-entry"><div class="log-sys"
|
|
2547
|
+
consoleLog.innerHTML = `<div class="log-entry sys"><div class="log-sys">对话已清空。输入问题或 /help 即可继续。</div></div>`;
|
|
2548
|
+
streamBubbles.clear();
|
|
2284
2549
|
});
|
|
2285
2550
|
|
|
2286
2551
|
async function sendConsoleMessage(msg) {
|
|
2287
2552
|
const text = (msg || consoleInput.value).trim();
|
|
2288
2553
|
if (!text) return;
|
|
2289
2554
|
consoleInput.value = "";
|
|
2555
|
+
lastLocalUserText = text;
|
|
2290
2556
|
appendLog("user", text);
|
|
2291
2557
|
|
|
2292
2558
|
try {
|
|
@@ -2295,12 +2561,15 @@ async function sendConsoleMessage(msg) {
|
|
|
2295
2561
|
headers: { "content-type": "application/json" },
|
|
2296
2562
|
body: JSON.stringify({ message: text })
|
|
2297
2563
|
});
|
|
2298
|
-
|
|
2564
|
+
// Stub ack only — real assistant text arrives via SSE.
|
|
2565
|
+
if (res.text) appendLog("sys", res.text);
|
|
2299
2566
|
} catch (err) {
|
|
2300
2567
|
appendLog("sys", `[提示] ${err.message}`);
|
|
2301
2568
|
}
|
|
2302
2569
|
}
|
|
2303
2570
|
|
|
2571
|
+
connectAssistantStream();
|
|
2572
|
+
|
|
2304
2573
|
$("console-send-btn").addEventListener("click", () => sendConsoleMessage());
|
|
2305
2574
|
consoleInput.addEventListener("keydown", (e) => {
|
|
2306
2575
|
if (e.key === "Enter" && !e.shiftKey) {
|