Fix chat stream recovery and PDF library indexing (#468)
This commit is contained in:
@@ -29,7 +29,7 @@ class PersonalDocsConfig:
|
|||||||
"""Configuration for personal documents management."""
|
"""Configuration for personal documents management."""
|
||||||
CHUNK_SIZE: int = 1000
|
CHUNK_SIZE: int = 1000
|
||||||
CHUNK_OVERLAP: int = 200
|
CHUNK_OVERLAP: int = 200
|
||||||
DEFAULT_EXTENSIONS: Tuple[str, ...] = (".txt", ".md", ".json")
|
DEFAULT_EXTENSIONS: Tuple[str, ...] = (".txt", ".md", ".json", ".pdf")
|
||||||
DEFAULT_K: int = 5
|
DEFAULT_K: int = 5
|
||||||
STOP_WORDS: Set[str] = None
|
STOP_WORDS: Set[str] = None
|
||||||
|
|
||||||
@@ -85,7 +85,8 @@ def load_personal_index(
|
|||||||
if not any(name.lower().endswith(ext) for ext in extensions):
|
if not any(name.lower().endswith(ext) for ext in extensions):
|
||||||
continue
|
continue
|
||||||
size = os.path.getsize(p)
|
size = os.path.getsize(p)
|
||||||
text = read_text_file(p)
|
ext = os.path.splitext(name)[1].lower()
|
||||||
|
text = extract_pdf_text(p) if ext == ".pdf" else read_text_file(p)
|
||||||
chunks = split_chunks(text)
|
chunks = split_chunks(text)
|
||||||
display = os.path.relpath(p, personal_dir)
|
display = os.path.relpath(p, personal_dir)
|
||||||
files.append({"name": display, "path": p, "size": size, "chunks": chunks})
|
files.append({"name": display, "path": p, "size": size, "chunks": chunks})
|
||||||
|
|||||||
@@ -512,6 +512,9 @@ import createResearchSynapse from './researchSynapse.js';
|
|||||||
let timedOut = false;
|
let timedOut = false;
|
||||||
let processingProbeTimer = null;
|
let processingProbeTimer = null;
|
||||||
let processingProbeAbort = null;
|
let processingProbeAbort = null;
|
||||||
|
let _renderStream = () => {};
|
||||||
|
let _cancelThinkingTimer = () => {};
|
||||||
|
let _removeThinkingSpinner = () => {};
|
||||||
const clearProcessingProbe = () => {
|
const clearProcessingProbe = () => {
|
||||||
if (processingProbeTimer) {
|
if (processingProbeTimer) {
|
||||||
clearTimeout(processingProbeTimer);
|
clearTimeout(processingProbeTimer);
|
||||||
@@ -986,13 +989,13 @@ import createResearchSynapse from './researchSynapse.js';
|
|||||||
}
|
}
|
||||||
const esc = uiModule.esc;
|
const esc = uiModule.esc;
|
||||||
// Remove thinking spinner helper
|
// Remove thinking spinner helper
|
||||||
function _removeThinkingSpinner() {
|
_removeThinkingSpinner = () => {
|
||||||
const el = document.querySelector('.agent-thinking-dots');
|
const el = document.querySelector('.agent-thinking-dots');
|
||||||
if (el) {
|
if (el) {
|
||||||
if (el._spinner) el._spinner.destroy();
|
if (el._spinner) el._spinner.destroy();
|
||||||
el.remove();
|
el.remove();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Tool-aware thinking spinner
|
// Tool-aware thinking spinner
|
||||||
let _lastToolName = '';
|
let _lastToolName = '';
|
||||||
@@ -1056,9 +1059,9 @@ import createResearchSynapse from './researchSynapse.js';
|
|||||||
}
|
}
|
||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
function _cancelThinkingTimer() {
|
_cancelThinkingTimer = () => {
|
||||||
if (_textPauseTimer) { clearTimeout(_textPauseTimer); _textPauseTimer = null; }
|
if (_textPauseTimer) { clearTimeout(_textPauseTimer); _textPauseTimer = null; }
|
||||||
}
|
};
|
||||||
|
|
||||||
// Document streaming state (text-fence detection)
|
// Document streaming state (text-fence detection)
|
||||||
let _docFenceOpened = false;
|
let _docFenceOpened = false;
|
||||||
@@ -1085,7 +1088,7 @@ import createResearchSynapse from './researchSynapse.js';
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Direct render helper for streaming text
|
// Direct render helper for streaming text
|
||||||
function _renderStream() {
|
_renderStream = () => {
|
||||||
let dt = stripToolBlocks(roundText);
|
let dt = stripToolBlocks(roundText);
|
||||||
const bodyEl = roundHolder.querySelector('.body');
|
const bodyEl = roundHolder.querySelector('.body');
|
||||||
const contentEl = _ensureStreamLayout(bodyEl);
|
const contentEl = _ensureStreamLayout(bodyEl);
|
||||||
@@ -1184,7 +1187,7 @@ import createResearchSynapse from './researchSynapse.js';
|
|||||||
contentEl._prevTextLen = contentEl.textContent.length;
|
contentEl._prevTextLen = contentEl.textContent.length;
|
||||||
if (window.hljs) contentEl.querySelectorAll('pre code').forEach((b) => window.hljs.highlightElement(b));
|
if (window.hljs) contentEl.querySelectorAll('pre code').forEach((b) => window.hljs.highlightElement(b));
|
||||||
uiModule.scrollHistory();
|
uiModule.scrollHistory();
|
||||||
}
|
};
|
||||||
|
|
||||||
// Walk text nodes, skip past `prevLen` characters of old text,
|
// Walk text nodes, skip past `prevLen` characters of old text,
|
||||||
// wrap everything after that in <span class="token-new"> for fade-in
|
// wrap everything after that in <span class="token-new"> for fade-in
|
||||||
|
|||||||
19
tests/test_chat_stream_scope.py
Normal file
19
tests/test_chat_stream_scope.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_stream_render_helpers_are_visible_to_catch_block():
|
||||||
|
source = Path("static/js/chat.js").read_text(encoding="utf-8")
|
||||||
|
try_start = source.index(" try {\n // Re-enable auto-scroll")
|
||||||
|
catch_start = source.index(" } catch (err) {", try_start)
|
||||||
|
|
||||||
|
outer_scope = source[:try_start]
|
||||||
|
try_body = source[try_start:catch_start]
|
||||||
|
|
||||||
|
assert "let _renderStream = () => {};" in outer_scope
|
||||||
|
assert "let _cancelThinkingTimer = () => {};" in outer_scope
|
||||||
|
assert "let _removeThinkingSpinner = () => {};" in outer_scope
|
||||||
|
|
||||||
|
assert "_renderStream = () => {" in try_body
|
||||||
|
assert "_cancelThinkingTimer = () => {" in try_body
|
||||||
|
assert "_removeThinkingSpinner = () => {" in try_body
|
||||||
|
assert "function _renderStream()" not in try_body
|
||||||
24
tests/test_personal_docs_pdf_index.py
Normal file
24
tests/test_personal_docs_pdf_index.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src import personal_docs
|
||||||
|
|
||||||
|
|
||||||
|
def test_personal_index_includes_pdf_uploads(tmp_path, monkeypatch):
|
||||||
|
pdf_path = tmp_path / "notes.pdf"
|
||||||
|
pdf_path.write_bytes(b"%PDF-1.4 fake test pdf")
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
personal_docs,
|
||||||
|
"extract_pdf_text",
|
||||||
|
lambda path: "readable pdf text" if Path(path) == pdf_path else "",
|
||||||
|
)
|
||||||
|
|
||||||
|
files = personal_docs.load_personal_index(str(tmp_path))
|
||||||
|
|
||||||
|
assert [item["name"] for item in files] == ["notes.pdf"]
|
||||||
|
assert files[0]["path"] == str(pdf_path)
|
||||||
|
assert files[0]["chunks"] == ["readable pdf text"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_personal_index_default_extensions_advertise_pdf_support():
|
||||||
|
assert ".pdf" in personal_docs.config.DEFAULT_EXTENSIONS
|
||||||
Reference in New Issue
Block a user