|
|
@@ -4,40 +4,21 @@
|
|
|
<pstm-uri>/tasks/{task_id}/terminal</pstm-uri>
|
|
|
<pstm-template>main</pstm-template>
|
|
|
<script>
|
|
|
- // Live terminal: one EventSource to /terminal-stream/<session_id>
|
|
|
- // renders every entry (initial backlog + new arrivals) as a typed
|
|
|
- // card with collapsible output. Text highlighting inside the log
|
|
|
- // offers the shared comment popover (see main.html).
|
|
|
if (window.statum) {
|
|
|
(function () {
|
|
|
'use strict';
|
|
|
|
|
|
- var TERMINAL_TYPES = {
|
|
|
- 'command': 'bash',
|
|
|
- 'comment': 'llm-comment',
|
|
|
- 'block': 'block',
|
|
|
- 'user-comment': 'user-comment',
|
|
|
- 'progress': 'progress',
|
|
|
- 'review': 'review',
|
|
|
- 'plan': 'plan',
|
|
|
- 'shell-reset': 'Shell was reset',
|
|
|
- 'failed': 'failure',
|
|
|
- 'query': 'query'
|
|
|
- };
|
|
|
-
|
|
|
var connected = false;
|
|
|
- var commentAction = null;
|
|
|
|
|
|
statum.addStateListener('terminal', function () {
|
|
|
var state = statum.read('terminal');
|
|
|
if (!connected && state && state.exists && state.session_id > 0) {
|
|
|
- commentAction = state.comment;
|
|
|
if (window.initQuoteComments) {
|
|
|
window.initQuoteComments({
|
|
|
matches: function (node) {
|
|
|
return node.closest && node.closest('#term-log') != null;
|
|
|
},
|
|
|
- action: function () { return commentAction; }
|
|
|
+ action: function () { return state.comment; }
|
|
|
});
|
|
|
}
|
|
|
connect(state.session_id);
|
|
|
@@ -58,58 +39,44 @@
|
|
|
var placeholder = log.querySelector('.term-placeholder');
|
|
|
if (placeholder) placeholder.remove();
|
|
|
var nearBottom = log.scrollHeight - log.scrollTop - log.clientHeight < 48;
|
|
|
- log.appendChild(renderEntry(entry));
|
|
|
+ appendEntry(log, entry);
|
|
|
if (nearBottom) log.scrollTop = log.scrollHeight;
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- function renderEntry(entry) {
|
|
|
- var type = TERMINAL_TYPES[entry.type] || entry.type || 'command';
|
|
|
- var card = document.createElement('div');
|
|
|
- card.className = 'term-entry type-' + (entry.type || 'command');
|
|
|
+ function appendEntry(log, entry) {
|
|
|
+ var type = entry.type || 'command';
|
|
|
|
|
|
- var head = document.createElement('div');
|
|
|
- head.className = 'term-head';
|
|
|
- var badge = document.createElement('span');
|
|
|
- badge.className = 'badge badge-' + (entry.type || 'command');
|
|
|
- badge.textContent = type;
|
|
|
- head.appendChild(badge);
|
|
|
- if (entry.id) {
|
|
|
- var idLabel = document.createElement('span');
|
|
|
- idLabel.textContent = '#' + entry.id;
|
|
|
- head.appendChild(idLabel);
|
|
|
+ if (type === 'llm-comment' || (entry.command && entry.command.charAt(0) === '#')) {
|
|
|
+ var c = document.createElement('span');
|
|
|
+ c.className = 'term-comment';
|
|
|
+ c.textContent = (entry.command || entry.output || '') + '\n';
|
|
|
+ log.appendChild(c);
|
|
|
+ return;
|
|
|
}
|
|
|
- card.appendChild(head);
|
|
|
|
|
|
if (entry.command && entry.command.length > 0) {
|
|
|
- var command = document.createElement('div');
|
|
|
- command.className = 'term-command';
|
|
|
- command.textContent = entry.command;
|
|
|
- card.appendChild(command);
|
|
|
+ var cmd = document.createElement('span');
|
|
|
+ cmd.className = 'term-cmd';
|
|
|
+ cmd.textContent = '$ ' + entry.command + '\n';
|
|
|
+ log.appendChild(cmd);
|
|
|
}
|
|
|
|
|
|
if (entry.input && entry.input.length > 0) {
|
|
|
- var input = document.createElement('pre');
|
|
|
- input.className = 'term-input';
|
|
|
- input.textContent = entry.input;
|
|
|
- if (entry.input.split('\n').length > 5) input.classList.add('collapsed');
|
|
|
- input.addEventListener('click', function () {
|
|
|
- input.classList.toggle('collapsed');
|
|
|
- });
|
|
|
- card.appendChild(input);
|
|
|
+ var inp = document.createElement('span');
|
|
|
+ inp.className = 'term-input-text';
|
|
|
+ inp.textContent = entry.input + '\n';
|
|
|
+ log.appendChild(inp);
|
|
|
}
|
|
|
|
|
|
if (entry.output && entry.output.length > 0) {
|
|
|
- var output = document.createElement('pre');
|
|
|
- output.className = 'term-output';
|
|
|
- output.textContent = entry.output;
|
|
|
- if (entry.output.split('\n').length > 5) output.classList.add('collapsed');
|
|
|
- output.addEventListener('click', function () {
|
|
|
- output.classList.toggle('collapsed');
|
|
|
- });
|
|
|
- card.appendChild(output);
|
|
|
+ var out = document.createElement('span');
|
|
|
+ out.className = 'term-out';
|
|
|
+ out.textContent = entry.output + '\n';
|
|
|
+ log.appendChild(out);
|
|
|
}
|
|
|
- return card;
|
|
|
+
|
|
|
+ log.appendChild(document.createTextNode('\n'));
|
|
|
}
|
|
|
})();
|
|
|
}
|
|
|
@@ -135,10 +102,22 @@
|
|
|
</span>
|
|
|
</p>
|
|
|
|
|
|
- <div class="terminal" stm-if="terminal.sessions.length > 0" id="term-log">
|
|
|
- <p class="muted term-placeholder" stm-if="terminal.sessions.length > 0">Connecting to the live stream…</p>
|
|
|
+ <div class="terminal-plain" stm-if="terminal.sessions.length > 0" id="term-log">
|
|
|
+ <span class="muted term-placeholder">Connecting…</span>
|
|
|
</div>
|
|
|
<p stm-if="terminal.sessions.length == 0" class="muted">No sessions recorded for this task yet.</p>
|
|
|
+
|
|
|
+ <dialog id="quote-comment-dialog">
|
|
|
+ <h3>Comment on selected text</h3>
|
|
|
+ <blockquote id="quote-text" class="term-comment-quote"></blockquote>
|
|
|
+ <form stm-action="terminal.comment">
|
|
|
+ <input type="hidden" name="quote">
|
|
|
+ <input type="hidden" name="return_to">
|
|
|
+ <textarea name="body" rows="4" placeholder="Your comment…" style="width:100%; margin:0.5em 0;"></textarea>
|
|
|
+ <button type="submit" class="primary">Send comment</button>
|
|
|
+ <button type="button" onclick="document.getElementById('quote-comment-dialog').close()">Cancel</button>
|
|
|
+ </form>
|
|
|
+ </dialog>
|
|
|
</div>
|
|
|
</section>
|
|
|
</body>
|