fix(ai+editeur): BUG-046 erreur lisible et vault réel pour l'ajout IA au document; BUG-045 fin de la double scrollbar
BUG-046: 'Ajouter du texte au document courant' échouait avec 'Échec de l'action: [object Object]'. Trois causes corrigées: - continuation de confirmation sans payload -> second Appliquer POSTait sans 'message' (422); le payload d'origine est désormais transmis - detail 422 FastAPI (tableau d'objets) stringifié tel quel -> nouvelle _responseError() qui aplatit en message lisible - prompt documents/dossier muet sur le vault -> le modèle inventait 'vault: test' et append_to_file échouait en silence; build_system_prompt nomme le vault et enjoint les outils d'écriture de l'utiliser exactement BUG-045: double barre de défilement dans l'éditeur Editer (fichiers longs): #editor-body overflow:auto + cm-editor height:100% sous la barre IA -> corps et scroller CodeMirror défilant tous les deux. Flex column, seul le scroller défile; override global .cm-scroller retiré. SW v20. Vérifié: pytest 1038, ruff backend 0, tests frontend AI 84/84, editor-inline 25/25, validate-imports 38 modules; Playwright sur l'instance de test (un seul conteneur scrollable) et flux agent complet append_to_file ok=true sur fichier réel
This commit is contained in:
+27
-4
@@ -2397,6 +2397,27 @@ class BooksLM {
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a readable Error from a non-OK /chat or /agent response. BUG-046:
|
||||
* a 422 validation error carries `detail` as an ARRAY of objects (FastAPI),
|
||||
* which `new Error(detail)` stringified to "[object Object]" in the
|
||||
* "Échec de l'action" toast. Flatten arrays / objects into their messages.
|
||||
*/
|
||||
async _responseError(resp) {
|
||||
let detail = `HTTP ${resp.status}`;
|
||||
try {
|
||||
const body = await resp.json();
|
||||
const d = body && body.detail;
|
||||
if (typeof d === 'string' && d) detail = d;
|
||||
else if (Array.isArray(d) && d.length) {
|
||||
detail = d.map((item) => (item && (item.msg || item.message)) || JSON.stringify(item)).join(' · ');
|
||||
} else if (d && typeof d === 'object') {
|
||||
detail = d.msg || d.message || JSON.stringify(d);
|
||||
}
|
||||
} catch { /* no json body */ }
|
||||
return new Error(detail);
|
||||
}
|
||||
|
||||
async _applyConfirmation(msg) {
|
||||
const conf = msg.confirmation;
|
||||
if (!conf || conf._applying) return;
|
||||
@@ -2413,7 +2434,11 @@ class BooksLM {
|
||||
const sendBtn = this._panel && this._panel.querySelector('.bookslm-btn-send');
|
||||
if (sendBtn) sendBtn.disabled = true;
|
||||
|
||||
const continuation = { role: 'assistant', content: '', sources: [], toolCalls: [], confirmation: null, payload: null };
|
||||
// BUG-046: carry the original request payload over to the continuation.
|
||||
// When an applied tool failed and the model re-proposed a confirmation,
|
||||
// the continuation used to have `payload: null`: the second "Appliquer"
|
||||
// then resumed without `message` → 422 → "[object Object]" toast.
|
||||
const continuation = { role: 'assistant', content: '', sources: [], toolCalls: [], confirmation: null, payload: msg.payload ? { ...msg.payload } : null };
|
||||
try {
|
||||
let resp = await this._postChat(payload);
|
||||
if (resp.status === 401 && AuthManager._authEnabled) {
|
||||
@@ -2421,9 +2446,7 @@ class BooksLM {
|
||||
resp = await this._postChat(payload);
|
||||
}
|
||||
if (!resp.ok) {
|
||||
let detail = `HTTP ${resp.status}`;
|
||||
try { detail = (await resp.json()).detail || detail; } catch { /* */ }
|
||||
throw new Error(detail);
|
||||
throw await this._responseError(resp);
|
||||
}
|
||||
// The confirmation is resolved: drop the card and show the continuation.
|
||||
msg.confirmation = null;
|
||||
|
||||
+14
-7
@@ -3003,8 +3003,16 @@ select {
|
||||
@keyframes editor-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
||||
.editor-save-dot.saving { animation: editor-pulse 0.8s infinite; }
|
||||
.editor-save-dot.err { color: var(--err); } .editor-save-dot.err .dot { background: var(--err); }
|
||||
.editor-body-cm { flex: 1; overflow: auto; }
|
||||
.editor-body-cm .cm-editor { height: 100%; }
|
||||
/* BUG-045: only the CodeMirror scroller scrolls. #editor-body used to keep
|
||||
overflow:auto AND host a 100%-tall .cm-editor below the AI toolbar row,
|
||||
so the body (toolbar + editor) and the scroller both overflowed → two
|
||||
stacked scrollbars. Flex column: toolbar fixed, editor fills, scroller
|
||||
scrolls. Combined selector on purpose: `.editor-body` (defined later with
|
||||
equal specificity) must not re-introduce overflow-y:auto. */
|
||||
.editor-body.editor-body-cm { flex: 1; min-height: 0; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.editor-body.editor-body-cm #ai-toolbar-container { flex: 0 0 auto; }
|
||||
.editor-body.editor-body-cm .cm-editor { flex: 1 1 auto; height: auto; min-height: 0; overflow: hidden; }
|
||||
.editor-body.editor-body-cm .fallback-editor { flex: 1 1 auto; height: auto; min-height: 0; resize: none; overflow: auto; }
|
||||
.editor-body-cm .cm-editor .cm-scroller { font-family: var(--font-mono); font-size: 13.5px; line-height: 1.75; }
|
||||
.editor-footer { height: 26px; min-height: 26px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; background: var(--bg-secondary); border-top: 1px solid var(--border); font-size: 11px; color: var(--text-muted); flex-shrink: 0; }
|
||||
.editor-stat-file { color: var(--text-secondary); max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
@@ -3555,17 +3563,16 @@ select {
|
||||
.help-tip-card {
|
||||
padding: 16px;
|
||||
}
|
||||
/* BUG-045: inside the editor, the CodeMirror scroller is the ONLY scroll
|
||||
surface. `#editor-body` stays `overflow:hidden` (BUG-020 rule below) and
|
||||
the scroller fills it — otherwise the body and the scroller scroll
|
||||
together and render two stacked scrollbars. */
|
||||
.cm-editor {
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.cm-scroller {
|
||||
font-family: "JetBrains Mono", monospace;
|
||||
overflow-y: auto !important;
|
||||
overflow-x: auto !important;
|
||||
height: auto;
|
||||
min-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
.fallback-editor {
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
* cache or Cloudflare does NOT clear the Service Worker Cache Storage, which is
|
||||
* a separate store. Bumping SW_VERSION invalidates it on every release.
|
||||
*/
|
||||
const SW_VERSION = 'v19';
|
||||
const SW_VERSION = 'v20';
|
||||
const CODE_CACHE = `obsigate-code-${SW_VERSION}`;
|
||||
const RUNTIME_CACHE = `obsigate-runtime-${SW_VERSION}`;
|
||||
const API_CACHE = `obsigate-api-${SW_VERSION}`;
|
||||
|
||||
Reference in New Issue
Block a user