Skip to content

Commit

Permalink
Find similar notes button and command (#704)
Browse files Browse the repository at this point in the history
* Update Vault QA language

* Remove Send Notes to Prompt button

* Add command and button for finding similar notes
  • Loading branch information
logancyang authored Oct 6, 2024
1 parent 97ea286 commit d51ab56
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 268 deletions.
131 changes: 3 additions & 128 deletions src/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,13 @@ import {
emojifyPrompt,
fixGrammarSpellingSelectionPrompt,
formatDateTime,
getFileContent,
getFileName,
getNotesFromPath,
getNotesFromTags,
getSendChatContextNotesPrompt,
getTagsFromNote,
glossaryPrompt,
removeUrlsFromSelectionPrompt,
rewriteLongerSelectionPrompt,
rewritePressReleaseSelectionPrompt,
rewriteShorterSelectionPrompt,
rewriteTweetSelectionPrompt,
rewriteTweetThreadSelectionPrompt,
sendNotesContentPrompt,
simplifyPrompt,
summarizePrompt,
tocPrompt,
Expand Down Expand Up @@ -243,125 +236,6 @@ ${chatContent}`;
}
};

const handleSendActiveNoteToPrompt = async () => {
if (!app) {
console.error("App instance is not available.");
return;
}

let noteFiles: TFile[] = [];
if (debug) {
console.log("Chat note context path:", settings.chatNoteContextPath);
console.log("Chat note context tags:", settings.chatNoteContextTags);
}
if (settings.chatNoteContextPath) {
// Recursively get all note TFiles in the path
noteFiles = await getNotesFromPath(app.vault, settings.chatNoteContextPath);
}
if (settings.chatNoteContextTags?.length > 0) {
// Get all notes with the specified tags
// If path is provided, get all notes with the specified tags in the path
// If path is not provided, get all notes with the specified tags
noteFiles = await getNotesFromTags(app.vault, settings.chatNoteContextTags, noteFiles);
}
const file = app.workspace.getActiveFile();
// If no note context provided, default to the active note
if (noteFiles.length === 0) {
if (!file) {
new Notice("No active note found.");
console.error("No active note found.");
return;
}
new Notice("No valid Chat context provided. Defaulting to the active note.");
noteFiles = [file];
}

const notes = [];
for (const file of noteFiles) {
// Get the content of the note
const content = await getFileContent(file, app.vault);
const tags = await getTagsFromNote(file, app.vault);
if (content) {
notes.push({ name: getFileName(file), content, tags });
}
}

// Send the content of the note to AI
const promptMessageHidden: ChatMessage = {
message: sendNotesContentPrompt(notes),
sender: USER_SENDER,
isVisible: false,
timestamp: formatDateTime(new Date()),
};

// Visible user message that is not sent to AI
// const sendNoteContentUserMessage = `Please read the following notes [[${activeNoteContent}]] and be ready to answer questions about it.`;
const sendNoteContentUserMessage = getSendChatContextNotesPrompt(
notes,
settings.chatNoteContextPath,
settings.chatNoteContextTags
);
const promptMessageVisible: ChatMessage = {
message: sendNoteContentUserMessage,
sender: USER_SENDER,
isVisible: true,
timestamp: formatDateTime(new Date()),
};

addMessage(promptMessageVisible);
addMessage(promptMessageHidden);

setLoading(true);
await getAIResponse(
promptMessageHidden,
chainManager,
addMessage,
setCurrentAiMessage,
setAbortController,
{ debug }
);
setLoading(false);
};

const forceRebuildActiveNoteContext = async () => {
if (!app) {
console.error("App instance is not available.");
return;
}

const file = app.workspace.getActiveFile();
if (!file) {
new Notice("No active note found.");
console.error("No active note found.");
return;
}
const noteContent = await getFileContent(file, app.vault);
const noteName = getFileName(file);
if (!noteContent) {
new Notice("No note content found.");
console.error("No note content found.");
return;
}

const fileMetadata = app.metadataCache.getFileCache(file);
const noteFile = {
path: file.path,
basename: file.basename,
mtime: file.stat.mtime,
content: noteContent,
metadata: fileMetadata?.frontmatter ?? {},
};
await chainManager.indexFile(noteFile);
const activeNoteOnMessage: ChatMessage = {
sender: AI_SENDER,
message: `Indexing [[${noteName}]]...\n\n Please switch to "QA" in Mode Selection to ask questions about it.`,
isVisible: true,
timestamp: formatDateTime(new Date()),
};

addMessage(activeNoteOnMessage);
};

const refreshVaultContext = async () => {
if (!app) {
console.error("App instance is not available.");
Expand Down Expand Up @@ -676,9 +550,10 @@ ${chatContent}`;
clearCurrentAiMessage();
}}
onSaveAsNote={() => handleSaveAsNote(true)}
onSendActiveNoteToPrompt={handleSendActiveNoteToPrompt}
onForceRebuildActiveNoteContext={forceRebuildActiveNoteContext}
onRefreshVaultContext={refreshVaultContext}
onFindSimilarNotes={(content, activeFilePath) =>
plugin.findSimilarNotes(content, activeFilePath)
}
addMessage={addMessage}
settings={settings}
vault={app.vault}
Expand Down
61 changes: 32 additions & 29 deletions src/components/ChatComponents/ChatIcons.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CustomModel, SetChainOptions } from "@/aiParams";
import { SimilarNotesModal } from "@/components/SimilarNotesModal";
import { AI_SENDER, VAULT_VECTOR_STORE_STRATEGY } from "@/constants";
import { CustomError } from "@/error";
import { CopilotSettings } from "@/settings/SettingsPage";
Expand All @@ -9,9 +10,9 @@ import React, { useEffect, useState } from "react";

import { ChainType } from "@/chainFactory";
import {
ConnectionIcon,
RefreshIcon,
SaveAsNoteIcon,
SendActiveNoteToPromptIcon,
UseActiveNoteAsContextIcon,
} from "@/components/Icons";
import { stringToChainType } from "@/utils";
Expand All @@ -23,9 +24,8 @@ interface ChatIconsProps {
setCurrentChain: (chain: ChainType, options?: SetChainOptions) => void;
onNewChat: (openNote: boolean) => void;
onSaveAsNote: () => void;
onSendActiveNoteToPrompt: () => void;
onForceRebuildActiveNoteContext: () => void;
onRefreshVaultContext: () => void;
onFindSimilarNotes: (content: string, activeFilePath: string) => Promise<any>;

Check warning on line 28 in src/components/ChatComponents/ChatIcons.tsx

View workflow job for this annotation

GitHub Actions / build (20.x)

Unexpected any. Specify a different type
addMessage: (message: ChatMessage) => void;
settings: CopilotSettings;
vault: Vault;
Expand All @@ -40,9 +40,8 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
setCurrentChain,
onNewChat,
onSaveAsNote,
onSendActiveNoteToPrompt,
onForceRebuildActiveNoteContext,
onRefreshVaultContext,
onFindSimilarNotes,
addMessage,
settings,
vault,
Expand Down Expand Up @@ -75,7 +74,7 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
}
const activeNoteOnMessage: ChatMessage = {
sender: AI_SENDER,
message: `OK Feel free to ask me questions about your vault: **${app.vault.getName()}**. \n\nIf you have *NEVER* as your auto-index strategy, you must click the *Refresh Index* button below, or run Copilot command: *Index vault for QA* first before you proceed!\n\nPlease note that this is a retrieval-based QA. Specific questions are encouraged. For generic questions like 'give me a summary', 'brainstorm based on the content', Chat mode with *Send Note to Prompt* button used with a *long context model* is a more suitable choice.`,
message: `OK Feel free to ask me questions about your vault: **${app.vault.getName()}**. \n\nIf you have *NEVER* as your auto-index strategy, you must click the *Refresh Index* button below, or run Copilot command: *Index vault for QA* first before you proceed!\n\nPlease note that this is a retrieval-based QA. Specific questions are encouraged. For generic questions like 'give me a summary', 'brainstorm based on the content', Chat mode with direct \`[[note title]]\` mention is a more suitable choice.`,
isVisible: true,
timestamp: formatDateTime(new Date()),
};
Expand All @@ -100,6 +99,18 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
handleChainSelection();
}, [selectedChain]);

const handleFindSimilarNotes = async () => {
const activeFile = app.workspace.getActiveFile();
if (!activeFile) {
new Notice("No active file");
return;
}

const activeNoteContent = await app.vault.cachedRead(activeFile);
const similarChunks = await onFindSimilarNotes(activeNoteContent, activeFile.path);
new SimilarNotesModal(app, similarChunks).open();
};

return (
<div className="chat-icons-container">
<div className="chat-icon-selection-tooltip">
Expand Down Expand Up @@ -142,34 +153,26 @@ const ChatIcons: React.FC<ChatIconsProps> = ({
onChange={handleChainChange}
>
<option value="llm_chain">Chat</option>
<option value="vault_qa">Vault QA (BETA)</option>
<option value="vault_qa">Vault QA (Basic)</option>
</select>
<span className="tooltip-text">Mode Selection</span>
</div>
</div>
{selectedChain === "llm_chain" && (
<button className="chat-icon-button clickable-icon" onClick={onSendActiveNoteToPrompt}>
<SendActiveNoteToPromptIcon className="icon-scaler" />
<span className="tooltip-text">
Send Note(s) to Prompt
<br />
(Set with Copilot command: <br />
set note context <br />
in Chat mode.
<br />
Default is active note)
</span>
</button>
)}
{selectedChain === "vault_qa" && (
<button className="chat-icon-button clickable-icon" onClick={onRefreshVaultContext}>
<UseActiveNoteAsContextIcon className="icon-scaler" />
<span className="tooltip-text">
Refresh Index
<br />
for Vault
</span>
</button>
<>
<button className="chat-icon-button clickable-icon" onClick={onRefreshVaultContext}>
<UseActiveNoteAsContextIcon className="icon-scaler" />
<span className="tooltip-text">
Refresh Index
<br />
for Vault
</span>
</button>
<button className="chat-icon-button clickable-icon" onClick={handleFindSimilarNotes}>
<ConnectionIcon className="icon-scaler" />
<span className="tooltip-text">Find Similar Notes for Active Note</span>
</button>
</>
)}
</div>
);
Expand Down
87 changes: 0 additions & 87 deletions src/components/ChatNoteContextModal.tsx

This file was deleted.

21 changes: 21 additions & 0 deletions src/components/Icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,27 @@ export const DeleteIcon = () => (
</svg>
);

export const ConnectionIcon: React.FC<IconProps> = ({ className }) => (
<svg
className={className}
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<circle cx="18" cy="5" r="3" />
<circle cx="6" cy="12" r="3" />
<circle cx="18" cy="19" r="3" />
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49" />
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49" />
</svg>
);

export const InsertIcon: React.FC<IconProps> = ({ className }) => (
<svg
className={className}
Expand Down
Loading

0 comments on commit d51ab56

Please sign in to comment.