@agent-link/server 0.1.169 → 0.1.171
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/package.json +1 -1
- package/web/app.js +71 -6
- package/web/css/btw.css +148 -0
- package/web/css/chat.css +1 -0
- package/web/index.html +1 -0
- package/web/locales/en.json +9 -2
- package/web/locales/zh.json +9 -2
- package/web/modules/connection.js +22 -0
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -27,6 +27,7 @@ import { createI18n } from './modules/i18n.js';
|
|
|
27
27
|
|
|
28
28
|
// ── Slash commands ──────────────────────────────────────────────────────────
|
|
29
29
|
const SLASH_COMMANDS = [
|
|
30
|
+
{ command: '/btw', descKey: 'slash.btw', isPrefix: true },
|
|
30
31
|
{ command: '/cost', descKey: 'slash.cost' },
|
|
31
32
|
{ command: '/context', descKey: 'slash.context' },
|
|
32
33
|
{ command: '/compact', descKey: 'slash.compact' },
|
|
@@ -69,6 +70,10 @@ const App = {
|
|
|
69
70
|
const slashMenuIndex = ref(0);
|
|
70
71
|
const slashMenuOpen = ref(false);
|
|
71
72
|
|
|
73
|
+
// Side question (/btw) state
|
|
74
|
+
const btwState = ref(null);
|
|
75
|
+
const btwPending = ref(false);
|
|
76
|
+
|
|
72
77
|
// Sidebar state
|
|
73
78
|
const sidebarOpen = ref(window.innerWidth > 768);
|
|
74
79
|
const historySessions = ref([]);
|
|
@@ -357,6 +362,8 @@ const App = {
|
|
|
357
362
|
switchConversation,
|
|
358
363
|
// Memory management
|
|
359
364
|
memoryFiles, memoryDir, memoryLoading, memoryEditing, memoryEditContent, memorySaving, memoryPanelOpen,
|
|
365
|
+
// Side question (/btw)
|
|
366
|
+
btwState, btwPending,
|
|
360
367
|
// i18n
|
|
361
368
|
t,
|
|
362
369
|
});
|
|
@@ -448,9 +455,23 @@ const App = {
|
|
|
448
455
|
|
|
449
456
|
// ── Send message ──
|
|
450
457
|
function sendMessage() {
|
|
458
|
+
const text = inputText.value.trim();
|
|
459
|
+
|
|
460
|
+
// Side question — /btw <question> (allowed even during compaction)
|
|
461
|
+
if (text === '/btw' || text.startsWith('/btw ')) {
|
|
462
|
+
if (status.value !== 'Connected') return;
|
|
463
|
+
const question = text.startsWith('/btw ') ? text.slice(5).trim() : '';
|
|
464
|
+
if (!question) return;
|
|
465
|
+
btwState.value = { question, answer: '', done: false, error: null };
|
|
466
|
+
btwPending.value = true;
|
|
467
|
+
inputText.value = '';
|
|
468
|
+
if (inputRef.value) inputRef.value.style.height = 'auto';
|
|
469
|
+
wsSend({ type: 'btw_question', question, conversationId: currentConversationId.value, claudeSessionId: currentClaudeSessionId.value });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
451
473
|
if (!canSend.value) return;
|
|
452
474
|
|
|
453
|
-
const text = inputText.value.trim();
|
|
454
475
|
const files = attachments.value.slice();
|
|
455
476
|
inputText.value = '';
|
|
456
477
|
if (inputRef.value) inputRef.value.style.height = 'auto';
|
|
@@ -504,6 +525,11 @@ const App = {
|
|
|
504
525
|
wsSend(cancelPayload);
|
|
505
526
|
}
|
|
506
527
|
|
|
528
|
+
function dismissBtw() {
|
|
529
|
+
btwState.value = null;
|
|
530
|
+
btwPending.value = false;
|
|
531
|
+
}
|
|
532
|
+
|
|
507
533
|
function dequeueNext() {
|
|
508
534
|
if (queuedMessages.value.length === 0) return;
|
|
509
535
|
const queued = queuedMessages.value.shift();
|
|
@@ -528,8 +554,13 @@ const App = {
|
|
|
528
554
|
|
|
529
555
|
function selectSlashCommand(cmd) {
|
|
530
556
|
slashMenuOpen.value = false;
|
|
531
|
-
|
|
532
|
-
|
|
557
|
+
if (cmd.isPrefix) {
|
|
558
|
+
inputText.value = cmd.command + ' ';
|
|
559
|
+
nextTick(() => inputRef.value?.focus());
|
|
560
|
+
} else {
|
|
561
|
+
inputText.value = cmd.command;
|
|
562
|
+
sendMessage();
|
|
563
|
+
}
|
|
533
564
|
}
|
|
534
565
|
|
|
535
566
|
function openSlashMenu() {
|
|
@@ -545,7 +576,7 @@ const App = {
|
|
|
545
576
|
document.addEventListener('click', _slashMenuClickOutside);
|
|
546
577
|
|
|
547
578
|
function handleKeydown(e) {
|
|
548
|
-
// Slash menu key handling
|
|
579
|
+
// Slash menu key handling (must come before btw overlay so Escape closes menu first)
|
|
549
580
|
if (slashMenuVisible.value && filteredSlashCommands.value.length > 0 && !e.isComposing) {
|
|
550
581
|
const len = filteredSlashCommands.value.length;
|
|
551
582
|
if (e.key === 'ArrowDown') {
|
|
@@ -575,6 +606,12 @@ const App = {
|
|
|
575
606
|
return;
|
|
576
607
|
}
|
|
577
608
|
}
|
|
609
|
+
// Btw overlay dismiss (after slash menu so menu Escape takes priority)
|
|
610
|
+
if (e.key === 'Escape' && btwState.value) {
|
|
611
|
+
dismissBtw();
|
|
612
|
+
e.preventDefault();
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
578
615
|
|
|
579
616
|
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
|
|
580
617
|
e.preventDefault();
|
|
@@ -645,6 +682,8 @@ const App = {
|
|
|
645
682
|
inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages, usageStats,
|
|
646
683
|
slashMenuVisible, filteredSlashCommands, slashMenuIndex, slashMenuOpen, selectSlashCommand, openSlashMenu,
|
|
647
684
|
sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
|
|
685
|
+
// Side question (/btw)
|
|
686
|
+
btwState, btwPending, dismissBtw, renderMarkdown,
|
|
648
687
|
getRenderedContent, copyMessage, toggleTool,
|
|
649
688
|
isPrevAssistant: _isPrevAssistant,
|
|
650
689
|
toggleContextSummary, formatTimestamp, formatUsage: (u) => formatUsage(u, t),
|
|
@@ -2440,6 +2479,32 @@ const App = {
|
|
|
2440
2479
|
</div>
|
|
2441
2480
|
</template>
|
|
2442
2481
|
|
|
2482
|
+
<!-- ══ Side question overlay ══ -->
|
|
2483
|
+
<Transition name="fade">
|
|
2484
|
+
<div v-if="btwState" class="btw-overlay" @click.self="dismissBtw">
|
|
2485
|
+
<div class="btw-panel">
|
|
2486
|
+
<div class="btw-header">
|
|
2487
|
+
<span class="btw-title">{{ t('btw.title') }}</span>
|
|
2488
|
+
<button class="btw-close" @click="dismissBtw" :aria-label="t('btw.dismiss')">✕</button>
|
|
2489
|
+
</div>
|
|
2490
|
+
<div class="btw-body">
|
|
2491
|
+
<div class="btw-question">{{ btwState.question }}</div>
|
|
2492
|
+
<div v-if="btwState.error" class="btw-error">{{ btwState.error }}</div>
|
|
2493
|
+
<template v-else>
|
|
2494
|
+
<div v-if="btwState.answer" class="btw-answer markdown-body" v-html="renderMarkdown(btwState.answer)"></div>
|
|
2495
|
+
<div v-if="!btwState.done" class="btw-loading">
|
|
2496
|
+
<span class="btw-loading-dots"><span></span><span></span><span></span></span>
|
|
2497
|
+
<span v-if="!btwState.answer" class="btw-loading-text">{{ t('btw.thinking') }}</span>
|
|
2498
|
+
</div>
|
|
2499
|
+
</template>
|
|
2500
|
+
</div>
|
|
2501
|
+
<div v-if="btwState.done && !btwState.error" class="btw-hint">
|
|
2502
|
+
{{ isMobile ? t('btw.tapDismiss') : t('btw.escDismiss') }}
|
|
2503
|
+
</div>
|
|
2504
|
+
</div>
|
|
2505
|
+
</div>
|
|
2506
|
+
</Transition>
|
|
2507
|
+
|
|
2443
2508
|
<!-- Input area (shown in both chat and team create mode) -->
|
|
2444
2509
|
<div class="input-area" v-if="viewMode === 'chat'">
|
|
2445
2510
|
<input
|
|
@@ -2483,7 +2548,7 @@ const App = {
|
|
|
2483
2548
|
@keydown="handleKeydown"
|
|
2484
2549
|
@input="autoResize"
|
|
2485
2550
|
@paste="handlePaste"
|
|
2486
|
-
:disabled="status !== 'Connected'
|
|
2551
|
+
:disabled="status !== 'Connected'"
|
|
2487
2552
|
:placeholder="isCompacting ? t('input.compacting') : t('input.placeholder')"
|
|
2488
2553
|
rows="1"
|
|
2489
2554
|
></textarea>
|
|
@@ -2505,7 +2570,7 @@ const App = {
|
|
|
2505
2570
|
<button class="attach-btn" @click="triggerFileInput" :disabled="status !== 'Connected' || isCompacting || attachments.length >= 5" :title="t('input.attachFiles')">
|
|
2506
2571
|
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
|
|
2507
2572
|
</button>
|
|
2508
|
-
<button class="slash-btn" @click="openSlashMenu" :disabled="status !== 'Connected'
|
|
2573
|
+
<button class="slash-btn" @click="openSlashMenu" :disabled="status !== 'Connected'" :title="t('input.slashCommands')">
|
|
2509
2574
|
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 21 11 3h2L9 21H7Z"/></svg>
|
|
2510
2575
|
</button>
|
|
2511
2576
|
</div>
|
package/web/css/btw.css
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* ── Side question (/btw) overlay ─────────────────────────────────────────── */
|
|
2
|
+
|
|
3
|
+
.btw-overlay {
|
|
4
|
+
position: absolute;
|
|
5
|
+
inset: 0;
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: center;
|
|
9
|
+
background: var(--overlay-bg, rgba(0, 0, 0, 0.3));
|
|
10
|
+
z-index: 500;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.btw-panel {
|
|
14
|
+
background: var(--bg-primary);
|
|
15
|
+
border: 1px solid var(--border);
|
|
16
|
+
border-radius: 12px;
|
|
17
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
18
|
+
width: 90%;
|
|
19
|
+
max-width: 560px;
|
|
20
|
+
max-height: 60vh;
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.btw-header {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
justify-content: space-between;
|
|
30
|
+
padding: 12px 16px;
|
|
31
|
+
border-bottom: 1px solid var(--border);
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.btw-title {
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
font-size: 14px;
|
|
38
|
+
color: var(--text-primary);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.btw-close {
|
|
42
|
+
background: none;
|
|
43
|
+
border: none;
|
|
44
|
+
font-size: 16px;
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
color: var(--text-secondary);
|
|
47
|
+
padding: 4px 8px;
|
|
48
|
+
border-radius: 4px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.btw-close:hover {
|
|
52
|
+
background: var(--bg-hover);
|
|
53
|
+
color: var(--text-primary);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.btw-body {
|
|
57
|
+
padding: 16px;
|
|
58
|
+
overflow-y: auto;
|
|
59
|
+
flex: 1;
|
|
60
|
+
min-height: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.btw-question {
|
|
64
|
+
font-size: 13px;
|
|
65
|
+
color: var(--text-secondary);
|
|
66
|
+
margin-bottom: 12px;
|
|
67
|
+
padding-bottom: 12px;
|
|
68
|
+
border-bottom: 1px solid var(--border);
|
|
69
|
+
font-style: italic;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.btw-answer {
|
|
73
|
+
font-size: 14px;
|
|
74
|
+
color: var(--text-primary);
|
|
75
|
+
line-height: 1.6;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.btw-error {
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
color: var(--danger, #e53e3e);
|
|
81
|
+
background: var(--danger-bg, rgba(229, 62, 62, 0.08));
|
|
82
|
+
padding: 10px 12px;
|
|
83
|
+
border-radius: 6px;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.btw-loading {
|
|
87
|
+
display: flex;
|
|
88
|
+
flex-direction: column;
|
|
89
|
+
align-items: center;
|
|
90
|
+
justify-content: center;
|
|
91
|
+
padding: 24px 0;
|
|
92
|
+
gap: 12px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.btw-loading-dots {
|
|
96
|
+
display: flex;
|
|
97
|
+
align-items: center;
|
|
98
|
+
gap: 6px;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.btw-loading-dots span {
|
|
102
|
+
width: 8px;
|
|
103
|
+
height: 8px;
|
|
104
|
+
border-radius: 50%;
|
|
105
|
+
background: var(--accent, #6366f1);
|
|
106
|
+
animation: btw-bounce 1.4s infinite ease-in-out both;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.btw-loading-dots span:nth-child(1) { animation-delay: -0.32s; }
|
|
110
|
+
.btw-loading-dots span:nth-child(2) { animation-delay: -0.16s; }
|
|
111
|
+
.btw-loading-dots span:nth-child(3) { animation-delay: 0s; }
|
|
112
|
+
|
|
113
|
+
@keyframes btw-bounce {
|
|
114
|
+
0%, 80%, 100% { transform: scale(0.4); opacity: 0.4; }
|
|
115
|
+
40% { transform: scale(1); opacity: 1; }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.btw-loading-text {
|
|
119
|
+
font-size: 13px;
|
|
120
|
+
color: var(--text-secondary);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.btw-hint {
|
|
124
|
+
text-align: center;
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
color: var(--text-tertiary);
|
|
127
|
+
padding: 8px 16px 12px;
|
|
128
|
+
flex-shrink: 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ── Mobile (< 768px) ──────────────────────────────────────────────────── */
|
|
132
|
+
|
|
133
|
+
@media (max-width: 768px) {
|
|
134
|
+
.btw-panel {
|
|
135
|
+
width: calc(100% - 24px);
|
|
136
|
+
max-width: none;
|
|
137
|
+
max-height: 50vh;
|
|
138
|
+
margin: 0 12px;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.btw-header {
|
|
142
|
+
padding: 10px 14px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.btw-body {
|
|
146
|
+
padding: 14px;
|
|
147
|
+
}
|
|
148
|
+
}
|
package/web/css/chat.css
CHANGED
package/web/index.html
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
<link rel="stylesheet" href="/css/team.css">
|
|
17
17
|
<link rel="stylesheet" href="/css/responsive.css">
|
|
18
18
|
<link rel="stylesheet" href="/css/loop.css">
|
|
19
|
+
<link rel="stylesheet" href="/css/btw.css">
|
|
19
20
|
<link id="hljs-theme" rel="stylesheet" href="/vendor/github.min.css">
|
|
20
21
|
<script>
|
|
21
22
|
// Apply saved theme immediately to prevent flash
|
package/web/locales/en.json
CHANGED
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"chat.customResponse": "Or type a custom response...",
|
|
172
172
|
|
|
173
173
|
"input.placeholder": "Send a message · Enter to send",
|
|
174
|
-
"input.compacting": "
|
|
174
|
+
"input.compacting": "Compacting context... (type /btw to ask a side question)",
|
|
175
175
|
"input.removeFromQueue": "Remove from queue",
|
|
176
176
|
"input.attachFiles": "Attach files",
|
|
177
177
|
"input.stopGeneration": "Stop generation",
|
|
@@ -264,5 +264,12 @@
|
|
|
264
264
|
|
|
265
265
|
"slash.cost": "Show token usage and cost",
|
|
266
266
|
"slash.context": "Show context usage",
|
|
267
|
-
"slash.compact": "Compact context"
|
|
267
|
+
"slash.compact": "Compact context",
|
|
268
|
+
|
|
269
|
+
"slash.btw": "Ask a side question (won't affect conversation)",
|
|
270
|
+
"btw.title": "Side Question",
|
|
271
|
+
"btw.dismiss": "Dismiss",
|
|
272
|
+
"btw.thinking": "Thinking...",
|
|
273
|
+
"btw.escDismiss": "Press Esc to dismiss",
|
|
274
|
+
"btw.tapDismiss": "Tap to dismiss"
|
|
268
275
|
}
|
package/web/locales/zh.json
CHANGED
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"chat.customResponse": "或输入自定义回复...",
|
|
172
172
|
|
|
173
173
|
"input.placeholder": "发送消息 · 按 Enter 发送",
|
|
174
|
-
"input.compacting": "上下文压缩中...",
|
|
174
|
+
"input.compacting": "上下文压缩中... (输入 /btw 可提旁问)",
|
|
175
175
|
"input.removeFromQueue": "从队列中移除",
|
|
176
176
|
"input.attachFiles": "附加文件",
|
|
177
177
|
"input.stopGeneration": "停止生成",
|
|
@@ -264,5 +264,12 @@
|
|
|
264
264
|
|
|
265
265
|
"slash.cost": "显示 Token 用量和费用",
|
|
266
266
|
"slash.context": "显示上下文用量",
|
|
267
|
-
"slash.compact": "压缩上下文"
|
|
267
|
+
"slash.compact": "压缩上下文",
|
|
268
|
+
|
|
269
|
+
"slash.btw": "快速旁问(不影响对话)",
|
|
270
|
+
"btw.title": "旁问",
|
|
271
|
+
"btw.dismiss": "关闭",
|
|
272
|
+
"btw.thinking": "思考中...",
|
|
273
|
+
"btw.escDismiss": "按 Esc 关闭",
|
|
274
|
+
"btw.tapDismiss": "点击关闭"
|
|
268
275
|
}
|
|
@@ -27,6 +27,8 @@ export function createConnection(deps) {
|
|
|
27
27
|
switchConversation,
|
|
28
28
|
// Memory management
|
|
29
29
|
memoryFiles, memoryDir, memoryLoading, memoryEditing, memoryEditContent, memorySaving, memoryPanelOpen,
|
|
30
|
+
// Side question (/btw)
|
|
31
|
+
btwState, btwPending,
|
|
30
32
|
// i18n
|
|
31
33
|
t,
|
|
32
34
|
} = deps;
|
|
@@ -360,6 +362,7 @@ export function createConnection(deps) {
|
|
|
360
362
|
}
|
|
361
363
|
|
|
362
364
|
// Clear foreground
|
|
365
|
+
const wasForegroundProcessing = isProcessing.value;
|
|
363
366
|
if (!activeSet.has(currentConversationId && currentConversationId.value)) {
|
|
364
367
|
isProcessing.value = false;
|
|
365
368
|
isCompacting.value = false;
|
|
@@ -406,7 +409,18 @@ export function createConnection(deps) {
|
|
|
406
409
|
team.handleActiveTeamRestore(msg.activeTeam, workDir.value);
|
|
407
410
|
}
|
|
408
411
|
resetIdleCheck();
|
|
412
|
+
// If foreground was processing but no longer is, dequeue pending messages
|
|
413
|
+
if (wasForegroundProcessing && !isProcessing.value) _dequeueNext();
|
|
409
414
|
} else if (msg.type === 'error') {
|
|
415
|
+
// Route btw-related errors to the overlay instead of the message list
|
|
416
|
+
if (btwPending && btwPending.value && msg.message && msg.message.includes('btw_question')) {
|
|
417
|
+
btwPending.value = false;
|
|
418
|
+
if (btwState && btwState.value) {
|
|
419
|
+
btwState.value.error = msg.message;
|
|
420
|
+
btwState.value.done = true;
|
|
421
|
+
}
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
410
424
|
streaming.flushReveal();
|
|
411
425
|
finalizeStreamingMsg(scheduleHighlight);
|
|
412
426
|
messages.value.push({
|
|
@@ -582,6 +596,14 @@ export function createConnection(deps) {
|
|
|
582
596
|
// Close preview if open (might be showing the deleted file)
|
|
583
597
|
if (filePreview) filePreview.closePreview();
|
|
584
598
|
}
|
|
599
|
+
} else if (msg.type === 'btw_answer') {
|
|
600
|
+
if (btwPending) btwPending.value = false;
|
|
601
|
+
if (btwState && btwState.value) {
|
|
602
|
+
btwState.value.answer += msg.delta;
|
|
603
|
+
if (msg.done) {
|
|
604
|
+
btwState.value.done = true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
585
607
|
} else if (msg.type === 'workdir_changed') {
|
|
586
608
|
workdirSwitching.value = false;
|
|
587
609
|
workDir.value = msg.workDir;
|