feat: AI Assistant at the top of the main menu; warn-only conversation budget
This commit is contained in:
parent
7fdbcd51e6
commit
c8a9f934be
4 changed files with 9 additions and 14 deletions
|
|
@ -65,8 +65,7 @@
|
|||
|
||||
<form id="assistant-form" class="assistant-composer">
|
||||
<label for="assistant-input">Clinical question</label>
|
||||
<textarea id="assistant-input" rows="1" placeholder="Ask a focused clinical question..." autocomplete="off" aria-describedby="assistant-context-budget assistant-context-warning"></textarea>
|
||||
<p id="assistant-context-budget" class="assistant-muted" aria-live="polite">Loading conversation limit; history and draft are counted in UTF-16 code units. The server validates each request.</p>
|
||||
<textarea id="assistant-input" rows="1" placeholder="Ask a focused clinical question..." autocomplete="off" aria-describedby="assistant-context-warning"></textarea>
|
||||
<div id="assistant-context-warning" role="alert" hidden></div>
|
||||
<div id="assistant-attachments" class="assistant-attachments" aria-label="Attached images" hidden></div>
|
||||
<div class="assistant-composer-footer">
|
||||
|
|
|
|||
|
|
@ -211,6 +211,10 @@
|
|||
</button>
|
||||
</div>
|
||||
<div class="sidebar-nav">
|
||||
<button class="tab-btn" data-tab="assistant">
|
||||
<i class="fas fa-brain" style="color:var(--purple);"></i>
|
||||
<span>AI Assistant</span>
|
||||
</button>
|
||||
<span class="sidebar-section-label">Encounters</span>
|
||||
<button class="tab-btn active" data-tab="encounter">
|
||||
<i class="fas fa-comments"></i>
|
||||
|
|
@ -278,10 +282,6 @@
|
|||
<i class="fas fa-diagram-project"></i>
|
||||
<span>Diagrams</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="assistant">
|
||||
<i class="fas fa-brain" style="color:var(--purple);"></i>
|
||||
<span>AI Assistant</span>
|
||||
</button>
|
||||
<button class="tab-btn hidden" data-tab="cms" id="cms-tab-btn">
|
||||
<i class="fas fa-pen-to-square"></i>
|
||||
<span>Content Manager</span>
|
||||
|
|
|
|||
|
|
@ -363,9 +363,7 @@ test('UI sends images only with the question, clears them on success, persists t
|
|||
const input = ui.document.getElementById('assistant-input');
|
||||
input.value = 'x';
|
||||
input.dispatchEvent(new ui.dom.window.Event('input'));
|
||||
const label = ui.document.getElementById('assistant-context-budget');
|
||||
assert.equal(label.textContent, '1,000 / 1,000 conversation characters (UTF-16 code units).', 'budget label stays text-only');
|
||||
assert.doesNotMatch(label.textContent, /image/i);
|
||||
assert.equal(ui.document.getElementById('assistant-context-budget'), null, 'the constant character counter is gone; only the approaching-limit warning remains');
|
||||
|
||||
// Pending attachments stay input-only until the question is sent.
|
||||
ui.context.performAutosave();
|
||||
|
|
|
|||
|
|
@ -204,8 +204,8 @@ test('native conversation UI counts UTF-16 history + draft, warns at exactly 90%
|
|||
});
|
||||
await loadChat(ui);
|
||||
const warning = ui.document.getElementById('assistant-context-warning');
|
||||
const usage = ui.document.getElementById('assistant-context-budget');
|
||||
enter(ui, ''); assert.match(usage.textContent, /899 \/ 1,000.*UTF-16 code units/); assert.equal(warning.hidden, true);
|
||||
assert.equal(ui.document.getElementById('assistant-context-budget'), null, 'no constant counter — warnings only');
|
||||
enter(ui, ''); assert.equal(warning.hidden, true);
|
||||
enter(ui, 'x'); assert.equal(warning.hidden, false); assert.match(warning.textContent, /90%/);
|
||||
enter(ui, 'x'.repeat(101)); assert.match(warning.textContent, /At the conversation limit/);
|
||||
const input = enter(ui, 'x'.repeat(102));
|
||||
|
|
@ -266,12 +266,10 @@ test('missing/invalid metadata never fabricates a cap; authoritative refusal kee
|
|||
if (url.endsWith('/chat/stream')) return json({ error: 'Environment budget refused', budget: { limit: 1000 }, code: 'CONVERSATION_LIMIT' }, 413);
|
||||
});
|
||||
enter(ui, '😀'.repeat(501));
|
||||
assert.match(ui.document.getElementById('assistant-context-budget').textContent, /1,002.*Limit unavailable/);
|
||||
assert.doesNotMatch(ui.document.getElementById('assistant-context-budget').textContent, /120,000/);
|
||||
assert.equal(ui.document.getElementById('assistant-context-budget'), null, 'no fabricated counter when the limit is unavailable');
|
||||
await ask(ui);
|
||||
assert.equal(ui.document.getElementById('assistant-input').value, '😀'.repeat(501));
|
||||
assert.equal(ui.document.querySelectorAll('.assistant-msg').length, 0);
|
||||
assert.match(ui.document.getElementById('assistant-context-budget').textContent, /1,002 \/ 1,000/);
|
||||
assert.match(ui.document.getElementById('assistant-context-warning').textContent, /Sending is blocked/);
|
||||
await ask(ui);
|
||||
assert.equal(ui.calls.filter(c => c.url.endsWith('/chat/stream')).length, 1, 'subsequent refusal is local');
|
||||
|
|
|
|||
Loading…
Reference in a new issue