mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
* feat(relevant-notes): dedicated pane with redesigned populated view
Move Relevant Notes out of the chat into its own command-opened pane
("Open Relevant Notes") and redesign the populated view: a toolbar with
the active-note context line + Build index, color-graded relevance
meters/percentages, a "Best" tag, hover quick actions, a live indexing
overlay, and a restyled hover preview card. Add-to-Chat routes a wikilink
into the open chat/agent view via a new INSERT_TEXT_TO_CHAT event.
Rank relevant notes by semantic similarity only so the displayed
percentages stay monotonic — links no longer boost the score (link-only
notes still appear, without a meter). Remove the old in-chat Relevant
Notes block and its showRelevantNotes setting; update docs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refine(relevant-notes): linear meter, link badges, excluded state, preview fixes
- Meter width now maps 1:1 to the similarity score (70% → 70%).
- Add an excluded-file empty state when the active note is outside the
index inclusion/exclusion settings (takes priority over build/list).
- Replace the "Best" badge with Outgoing-link / Backlink badges; the
badge+percentage cluster swaps for the action buttons on hover.
- Move row action buttons into the flex flow so the title never overlaps
them and truncates with an ellipsis in both states.
- Render preview snippet line breaks (whitespace-pre-line).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(relevant-notes): route Add to Chat to the last-focused chat view
The Relevant Notes "Add to Chat" action hardcoded agent-first when picking
the target chat (getLeavesOfType(CHAT_AGENT_VIEWTYPE)[0] ?? legacy), so with
both chats open the wikilink landed in the wrong one. Reuse the existing
lastActiveChatViewType tracking (added for the add-to-context commands in
#2555) via a shared pickContextChatViewType() helper, so the chat we reveal
and the chat we type into always agree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refine(relevant-notes): show full note content in a scrollable preview
The hover-card preview truncated content two ways: the loaded text was
sliced to 1000 chars and the paragraph was clamped to 4 lines. Drop the
slice and replace line-clamp with a bounded, scrollable area (max-h-64 +
overflow-y-auto) so the full note is reachable in a slightly larger box.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refine(relevant-notes): replace mount-timing setTimeouts with reliable signals
Address PR review on the Relevant Notes work:
- insertTextIntoActiveChat: drop the 50ms guess. The chat views' event bus
(ChatViewEventTarget) now latches queued "insert text"; the view drains it
on mount and ChatInputContext buffers/flushes it when the Lexical editor
registers, so delivery no longer depends on mount timing.
- RelevantNotesView: drop the 50ms initial dispatch. useActiveFile now seeds
from the current active file on mount, so the pane populates immediately and
still updates via the active-leaf-change listener.
- RelevanceMeter: route the score-driven width/color through CSS variables
(.copilot-relevance-meter-fill) instead of an inline style.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
98 lines
3 KiB
TypeScript
98 lines
3 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useCallback,
|
|
useEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from "react";
|
|
import { INSERT_TEXT_WITH_PILLS_COMMAND } from "@/components/chat-components/utils/lexicalTextUtils";
|
|
import { LexicalEditor } from "lexical";
|
|
|
|
interface ChatInputContextType {
|
|
insertTextWithPills: (text: string, enableURLPills?: boolean) => void;
|
|
focusInput: () => void;
|
|
registerEditor: (editor: LexicalEditor) => void;
|
|
registerFocusHandler: (handler: () => void) => void;
|
|
}
|
|
|
|
const ChatInputContext = createContext<ChatInputContextType | undefined>(undefined);
|
|
|
|
/**
|
|
* Hook to access chat input functionality
|
|
*/
|
|
export function useChatInput(): ChatInputContextType {
|
|
const context = useContext(ChatInputContext);
|
|
if (context === undefined) {
|
|
throw new Error("useChatInput must be used within a ChatInputProvider");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
interface ChatInputProviderProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Provider component that manages chat input functionality without requiring refs
|
|
*/
|
|
export function ChatInputProvider({ children }: ChatInputProviderProps): JSX.Element {
|
|
const [editor, setEditor] = useState<LexicalEditor | null>(null);
|
|
const [focusHandler, setFocusHandler] = useState<(() => void) | null>(null);
|
|
// Text requested before the Lexical editor has mounted (e.g. routed in while
|
|
// the chat view is still opening). Held here and flushed once the editor
|
|
// registers, so insertion never depends on mount timing.
|
|
const pendingInsertRef = useRef<{ text: string; enableURLPills: boolean } | null>(null);
|
|
|
|
const registerEditor = useCallback((editorInstance: LexicalEditor) => {
|
|
setEditor(editorInstance);
|
|
}, []);
|
|
|
|
const registerFocusHandler = useCallback((handler: () => void) => {
|
|
setFocusHandler(() => handler);
|
|
}, []);
|
|
|
|
const insertTextWithPills = useCallback(
|
|
(text: string, enableURLPills = false) => {
|
|
if (editor) {
|
|
editor.dispatchCommand(INSERT_TEXT_WITH_PILLS_COMMAND, {
|
|
text,
|
|
options: { enableURLPills, insertAtSelection: true },
|
|
});
|
|
} else {
|
|
pendingInsertRef.current = { text, enableURLPills };
|
|
}
|
|
},
|
|
[editor]
|
|
);
|
|
|
|
// Flush any text buffered before the editor was ready.
|
|
useEffect(() => {
|
|
if (!editor || !pendingInsertRef.current) return;
|
|
const { text, enableURLPills } = pendingInsertRef.current;
|
|
pendingInsertRef.current = null;
|
|
editor.dispatchCommand(INSERT_TEXT_WITH_PILLS_COMMAND, {
|
|
text,
|
|
options: { enableURLPills, insertAtSelection: true },
|
|
});
|
|
}, [editor]);
|
|
|
|
const focusInput = useCallback(() => {
|
|
if (focusHandler) {
|
|
focusHandler();
|
|
}
|
|
}, [focusHandler]);
|
|
|
|
const contextValue = useMemo<ChatInputContextType>(
|
|
() => ({
|
|
insertTextWithPills,
|
|
focusInput,
|
|
registerEditor,
|
|
registerFocusHandler,
|
|
}),
|
|
[insertTextWithPills, focusInput, registerEditor, registerFocusHandler]
|
|
);
|
|
|
|
return <ChatInputContext.Provider value={contextValue}>{children}</ChatInputContext.Provider>;
|
|
}
|