mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
* fix: prevent agent ReAct loop from getting stuck with small local models Small local models (e.g. qwen3.5-4b-mlx) get stuck calling localSearch repeatedly with near-identical queries and never synthesizing an answer. - Remove QueryExpander from agent loop to avoid model contention on single-connection local models (retriever still expands internally) - Add query deduplication that pre-filters tool call batches using Jaccard word-overlap similarity before execution - Force synthesis via raw model (without tools) after 2 consecutive iterations where all tool calls are duplicates - Add agent-specific system prompt guidance for search limits - Strip leaked chat template role tokens from intermediate content - Add intermediate model output logging for debugging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: make inline citation numbers clickable and restore Max Sources setting Inline citation numbers like [1], [2] in chat responses are now clickable links that open the corresponding source note. Also restores the Max Sources slider in QA settings, reverting the hardcoded DEFAULT_MAX_SOURCE_CHUNKS. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: remove excessive 32px bottom padding from chat view on desktop The padding-bottom on .view-content used max(safe-area + 4px, 32px), forcing a 32px minimum even on desktop where safe-area-inset-bottom is 0. Simplified to safe-area + 4px so desktop gets minimal padding while devices with safe areas still get proper spacing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(agent): track dedup queries post-execution to keep failed searches retryable Move previousSearchQueries tracking from before to after each localSearch tool call executes. Previously, queries were marked as seen before any tool ran, so if a call was aborted or the loop threw early, those queries were still blocked from retrying on the next iteration. Now each query is added to previousSearchQueries immediately after its tool call returns, keeping the dedup effective for completed calls while leaving transient failures retryable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(citations): preserve Obsidian internal-link attributes on inline citation anchors When building clickable inline citation links (e.g. [1], [2]), the previous code only copied the href from the source section anchor. Obsidian vault-note links have additional attributes such as data-href and class="internal-link" that are required for in-app note navigation. Without them, clicking an inline citation resolves the link as a plain relative URL instead of opening the corresponding note. Fix: store the source anchor element rather than just the href, then copy all attributes onto the generated citation anchor before setting the citation-specific class and aria-label overrides. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(agent): only mark localSearch queries as seen after successful execution Change the dedup tracking condition from always tracking queries post-execution to only tracking when the tool call succeeds (result.success is true). This ensures transient failures (e.g. temporary index unavailability) leave the query retryable: if a localSearch fails, the model can issue the same query again on the next iteration. Previously, even a failed execution would mark the query as seen, blocking recovery. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: deduplicate inline citations after source consolidation When multiple chunks come from the same note, the LLM cites them separately. After consolidation maps them to the same source number, adjacent duplicates like [1][1] or [1] and [1] now collapse to [1]. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: update GPT-5.2 to GPT-5.4 in builtin models Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add dynamic status bar clearance for desktop chat view The chat input was hidden behind Obsidian's status bar on desktop. Adds ChatViewLayout which measures the geometric overlap between the view-content and the status bar, setting a CSS variable to provide the exact padding needed. Adapts to any theme: no clearance for auto-hide themes (opacity: 0) or themes that already account for the status bar in their layout; correct clearance for themes where the bar is visible and overlapping. Re-checks on theme switches via debounced css-change listener. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: suppress thinking tokens during streaming when reasoning is disabled When a local model (e.g. Qwen 3.5 via LM Studio) emits text-level <think> tags but the user has not enabled the reasoning capability, the thinking content was visibly rendered during streaming and only stripped once the </think> tag arrived. Now tracks the position of excluded think blocks across chunks and truncates fullResponse to the safe prefix on each chunk, so thinking content never appears. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
import { Platform, Workspace } from "obsidian";
|
|
|
|
/** Wait for CSS transitions to settle before re-measuring after a theme switch. */
|
|
const CSS_CHANGE_DEBOUNCE_MS = 600;
|
|
|
|
/**
|
|
* Manages layout concerns for the Copilot chat view, such as status bar
|
|
* clearance and (in the future) chat input collapse state.
|
|
*
|
|
* Instantiated once per CopilotView and tied to its lifecycle.
|
|
*/
|
|
export class ChatViewLayout {
|
|
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
private cssChangeRef: ReturnType<Workspace["on"]> | null = null;
|
|
|
|
constructor(
|
|
private containerEl: HTMLElement,
|
|
private workspace: Workspace
|
|
) {
|
|
this.setupStatusBarClearance();
|
|
}
|
|
|
|
/**
|
|
* Tear down observers and timers. Call from CopilotView.onClose().
|
|
*/
|
|
destroy(): void {
|
|
if (this.debounceTimer) {
|
|
clearTimeout(this.debounceTimer);
|
|
this.debounceTimer = null;
|
|
}
|
|
if (this.cssChangeRef) {
|
|
this.workspace.offref(this.cssChangeRef);
|
|
this.cssChangeRef = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Measure how much the status bar overlaps the view-content and expose
|
|
* the overlap as a CSS variable so padding adapts to any theme.
|
|
*
|
|
* Works by temporarily zeroing the clearance, measuring the geometric
|
|
* overlap, then setting the correct value -- all within one synchronous
|
|
* reflow so nothing flickers. Themes that already position content above
|
|
* the status bar (no overlap) get 0 clearance automatically. Auto-hide
|
|
* themes (opacity: 0) also get 0 since the bar is transparent.
|
|
*/
|
|
private setupStatusBarClearance(): void {
|
|
if (Platform.isMobile) return;
|
|
|
|
const syncClearance = () => {
|
|
// Re-query each time to avoid stale references after theme reloads.
|
|
const statusBar = document.querySelector(".status-bar") as HTMLElement | null;
|
|
const viewContent = this.containerEl.querySelector(".view-content") as HTMLElement | null;
|
|
if (!statusBar || !viewContent) return;
|
|
|
|
// Zero out clearance and force reflow to measure natural overlap.
|
|
viewContent.style.setProperty("--copilot-status-bar-clearance", "0px");
|
|
const overlap =
|
|
viewContent.getBoundingClientRect().bottom - statusBar.getBoundingClientRect().top;
|
|
|
|
if (overlap <= 0) {
|
|
// Theme layout already clears the status bar.
|
|
return;
|
|
}
|
|
|
|
// Overlap exists -- only add clearance if the bar is actually visible.
|
|
const s = getComputedStyle(statusBar);
|
|
const hidden =
|
|
s.display === "none" || s.visibility === "hidden" || parseFloat(s.opacity) === 0;
|
|
viewContent.style.setProperty(
|
|
"--copilot-status-bar-clearance",
|
|
`${hidden ? 0 : Math.ceil(overlap)}px`
|
|
);
|
|
};
|
|
|
|
syncClearance();
|
|
|
|
this.cssChangeRef = this.workspace.on("css-change", () => {
|
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
this.debounceTimer = setTimeout(syncClearance, CSS_CHANGE_DEBOUNCE_MS);
|
|
});
|
|
}
|
|
}
|