perf(llm): route background tasks to dedicated model to preserve KV cache
Background tasks (title generation, tag suggestions, project summaries, RSS classification) were using qwen3:8b and wiping its KV cache after every response, preventing prefix cache hits on subsequent user messages. Adds OLLAMA_BACKGROUND_MODEL (default: qwen2.5:0.5b) config var and routes all background LLM calls to it, keeping qwen3:8b's KV cache warm between user messages for consistent sub-second TTFT. Also adds infinite scroll to KnowledgeView (replaces load-more button) and bakes spaCy en_core_web_sm into the Docker image to eliminate the pip install on every startup. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from "vue";
|
||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick, watchEffect } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { apiGet, apiPatch, transcribeAudio } from "@/api/client";
|
||||
import { fmtCompact } from "@/utils/dateFormat";
|
||||
@@ -320,17 +320,45 @@ function formatDate(iso: string): string {
|
||||
return d.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
|
||||
}
|
||||
|
||||
// ─── Infinite scroll ──────────────────────────────────────────────────────────
|
||||
|
||||
const sentinelEl = ref<HTMLElement | null>(null);
|
||||
let scrollObserver: IntersectionObserver | null = null;
|
||||
|
||||
function setupScrollObserver() {
|
||||
if (scrollObserver) scrollObserver.disconnect();
|
||||
if (!sentinelEl.value) return;
|
||||
scrollObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting && !loading.value && items.value.length < total.value) {
|
||||
page.value++;
|
||||
fetchItems();
|
||||
}
|
||||
},
|
||||
{ rootMargin: "200px" }
|
||||
);
|
||||
scrollObserver.observe(sentinelEl.value);
|
||||
}
|
||||
|
||||
// ─── Lifecycle ────────────────────────────────────────────────────────────────
|
||||
|
||||
onMounted(() => {
|
||||
fetchItems(true);
|
||||
fetchTags();
|
||||
fetchTodayBar();
|
||||
nextTick(() => chatInputEl.value?.focus());
|
||||
nextTick(() => {
|
||||
chatInputEl.value?.focus();
|
||||
setupScrollObserver();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (searchDebounce) clearTimeout(searchDebounce);
|
||||
scrollObserver?.disconnect();
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
if (sentinelEl.value) setupScrollObserver();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -499,11 +527,9 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Load more -->
|
||||
<div v-if="items.length < total && !loading" class="load-more">
|
||||
<button class="btn-load-more" @click="() => { page++; fetchItems() }">
|
||||
Load more ({{ total - items.length }} remaining)
|
||||
</button>
|
||||
<!-- Infinite scroll sentinel -->
|
||||
<div ref="sentinelEl" class="scroll-sentinel">
|
||||
<span v-if="loading && items.length > 0" class="sentinel-loading">Loading…</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -975,23 +1001,18 @@ onUnmounted(() => {
|
||||
}
|
||||
.empty-hint { font-size: 0.85rem; opacity: 0.7; }
|
||||
|
||||
/* ── Load more ───────────────────────────────────────────── */
|
||||
.load-more {
|
||||
padding: 16px 20px;
|
||||
text-align: center;
|
||||
/* ── Infinite scroll sentinel ────────────────────────────── */
|
||||
.scroll-sentinel {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.btn-load-more {
|
||||
padding: 8px 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--color-border, rgba(255,255,255,0.1));
|
||||
background: transparent;
|
||||
.sentinel-loading {
|
||||
font-size: 0.8rem;
|
||||
color: var(--color-muted);
|
||||
cursor: pointer;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.btn-load-more:hover { color: var(--color-text); border-color: rgba(255,255,255,0.2); }
|
||||
|
||||
/* ── Graph panel ─────────────────────────────────────────── */
|
||||
.graph-panel {
|
||||
|
||||
Reference in New Issue
Block a user