Project-aware assist, link suggestions, project-scoped RAG, semantic search tool, SSE race fix
- Writing assistant: inject project notes as context (definition-tagged first), wikilink suggestions - Link suggestions: server-side endpoint finds unlinked term occurrences, NoteEditorView sidebar panel - Project-scoped RAG: ChatView ProjectSelector filters semantic+keyword search to selected project - Semantic search tool: LLM search_notes upgraded to hybrid semantic (0.40 threshold) + keyword merge - SSE race condition fix: drain remaining events after stream loop exits in chat.py and notes.py - RAG_AUTO_SNIPPET raised 800→4000; sidebar include uses full note body; MAX_BODY_CHARS 8000→24000 - Enter-to-submit on writing assistant instruction textareas (note and task editors) - DiffView: equal-line collapsing with 3-line context around changes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,9 +6,52 @@ const props = defineProps<{
|
||||
diff: DiffLine[];
|
||||
}>();
|
||||
|
||||
const CONTEXT_LINES = 3;
|
||||
const MIN_COLLAPSE = 6; // Only collapse if there are more than this many consecutive equal lines
|
||||
|
||||
type CollapseMarker = { type: 'collapse'; count: number };
|
||||
type DisplayItem = DiffLine | CollapseMarker;
|
||||
|
||||
const insertions = computed(() => props.diff.filter(l => l.type === 'insert').length);
|
||||
const deletions = computed(() => props.diff.filter(l => l.type === 'delete').length);
|
||||
|
||||
const displayItems = computed<DisplayItem[]>(() => {
|
||||
const d = props.diff;
|
||||
if (!d.length) return [];
|
||||
|
||||
// Mark which indices are within CONTEXT_LINES of a change
|
||||
const shown = new Set<number>();
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
if (d[i].type !== 'equal') {
|
||||
for (let c = Math.max(0, i - CONTEXT_LINES); c <= Math.min(d.length - 1, i + CONTEXT_LINES); c++) {
|
||||
shown.add(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing changed, show all
|
||||
if (shown.size === 0) return d;
|
||||
|
||||
const result: DisplayItem[] = [];
|
||||
let i = 0;
|
||||
while (i < d.length) {
|
||||
if (shown.has(i)) {
|
||||
result.push(d[i]);
|
||||
i++;
|
||||
} else {
|
||||
let count = 0;
|
||||
while (i < d.length && !shown.has(i)) { count++; i++; }
|
||||
if (count >= MIN_COLLAPSE) {
|
||||
result.push({ type: 'collapse', count });
|
||||
} else {
|
||||
// Too short to collapse — show as-is
|
||||
for (let k = i - count; k < i; k++) result.push(d[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
|
||||
function markerFor(type: DiffLine['type']): string {
|
||||
if (type === 'insert') return '+';
|
||||
if (type === 'delete') return '−';
|
||||
@@ -24,14 +67,19 @@ function markerFor(type: DiffLine['type']): string {
|
||||
</div>
|
||||
<div class="diff-scroll">
|
||||
<div v-if="!diff.length" class="diff-empty">No changes.</div>
|
||||
<div
|
||||
v-for="(line, i) in diff"
|
||||
:key="i"
|
||||
:class="['diff-line', `diff-${line.type}`]"
|
||||
>
|
||||
<span class="diff-marker">{{ markerFor(line.type) }}</span>
|
||||
<span class="diff-text">{{ line.text }}</span>
|
||||
</div>
|
||||
<template v-for="(item, i) in displayItems" :key="i">
|
||||
<div v-if="item.type === 'collapse'" class="diff-line diff-collapse">
|
||||
<span class="diff-marker">⋯</span>
|
||||
<span class="diff-text">{{ item.count }} unchanged line{{ item.count !== 1 ? 's' : '' }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
:class="['diff-line', `diff-${item.type}`]"
|
||||
>
|
||||
<span class="diff-marker">{{ markerFor(item.type) }}</span>
|
||||
<span class="diff-text">{{ item.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -102,6 +150,16 @@ function markerFor(type: DiffLine['type']): string {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.diff-collapse {
|
||||
color: var(--color-text-muted);
|
||||
opacity: 0.6;
|
||||
font-style: italic;
|
||||
border-top: 1px dashed var(--color-border);
|
||||
border-bottom: 1px dashed var(--color-border);
|
||||
padding-top: 0.2rem;
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.diff-marker {
|
||||
flex-shrink: 0;
|
||||
width: 1rem;
|
||||
|
||||
@@ -8,6 +8,7 @@ interface NoteVersion {
|
||||
id: number;
|
||||
note_id: number;
|
||||
title: string;
|
||||
tags: string[];
|
||||
body?: string;
|
||||
created_at: string;
|
||||
}
|
||||
@@ -18,7 +19,7 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'restore', body: string): void;
|
||||
(e: 'restore', body: string, tags: string[]): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
@@ -101,7 +102,7 @@ async function selectVersionItem(v: NoteVersion) {
|
||||
|
||||
function restore() {
|
||||
if (!selectedVersion.value?.body) return;
|
||||
emit('restore', selectedVersion.value.body);
|
||||
emit('restore', selectedVersion.value.body, selectedVersion.value.tags ?? []);
|
||||
}
|
||||
|
||||
onMounted(loadVersions);
|
||||
|
||||
Reference in New Issue
Block a user