diff --git a/src/LLMProviders/chainRunner.ts b/src/LLMProviders/chainRunner.ts index b31a5ace..e5f44d28 100644 --- a/src/LLMProviders/chainRunner.ts +++ b/src/LLMProviders/chainRunner.ts @@ -11,6 +11,7 @@ import { getSystemPrompt } from "@/settings/model"; import { ChatMessage } from "@/sharedState"; import { ToolManager } from "@/tools/toolManager"; import { + err2String, extractChatHistory, extractUniqueTitlesFromDocs, extractYoutubeUrl, @@ -94,9 +95,10 @@ abstract class BaseChainRunner implements ChainRunner { addMessage?: (message: ChatMessage) => void, updateCurrentAiMessage?: (message: string) => void ) { - if (debug) console.error("Error during LLM invocation:", error); - const errorData = error?.response?.data?.error || error; - const errorCode = errorData?.code || error; + const msg = err2String(error); + if (debug) console.error("Error during LLM invocation:", msg); + const errorData = error?.response?.data?.error || msg; + const errorCode = errorData?.code || msg; let errorMessage = ""; // Check for specific error messages @@ -113,8 +115,23 @@ abstract class BaseChainRunner implements ChainRunner { if (addMessage && updateCurrentAiMessage) { updateCurrentAiMessage(""); + + // remove langchain troubleshooting URL from error message + const ignoreEndIndex = errorMessage.search("Troubleshooting URL"); + errorMessage = ignoreEndIndex !== -1 ? errorMessage.slice(0, ignoreEndIndex) : errorMessage; + + // add more user guide for invalid API key + if (msg.search(/401|invalid|not valid/gi) !== -1) { + errorMessage = + "Something went wrong. Please check if you have set your API key." + + "\nPath: Settings > copilot plugin > Basic Tab > Set Keys" + + "\nError Details: " + + errorMessage; + } + addMessage({ message: errorMessage, + isErrorMessage: true, sender: AI_SENDER, isVisible: true, timestamp: formatDateTime(new Date()), diff --git a/src/commands/index.ts b/src/commands/index.ts index 5040d33f..b83bce56 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -9,12 +9,11 @@ import { OramaSearchModal } from "@/components/modals/OramaSearchModal"; import { RemoveFromIndexModal } from "@/components/modals/RemoveFromIndexModal"; import { ToneModal } from "@/components/modals/ToneModal"; import { CustomPromptProcessor } from "@/customPromptProcessor"; -import { CustomError } from "@/error"; import CopilotPlugin from "@/main"; import { getAllQAMarkdownContent } from "@/search/searchUtils"; import { getSettings } from "@/settings/model"; import { ChatMessage } from "@/sharedState"; -import { formatDateTime } from "@/utils"; +import { formatDateTime, err2String } from "@/utils"; import { Editor, Notice, TFile } from "obsidian"; import { COMMAND_IDS, @@ -229,8 +228,9 @@ export function registerBuiltInCommands(plugin: CopilotPlugin) { await promptProcessor.savePrompt(title, prompt); new Notice("Custom prompt saved successfully."); } catch (e) { - new Notice("Error saving custom prompt. Please check if the title already exists."); - console.error(e); + const msg = "An error occurred while saving the custom prompt: " + err2String(e); + console.error(msg); + throw new Error(msg); } }).open(); }); @@ -318,12 +318,10 @@ export function registerBuiltInCommands(plugin: CopilotPlugin) { await promptProcessor.updatePrompt(promptTitle, title, newPrompt); new Notice(`Prompt "${title}" has been updated.`); } catch (err) { - console.error(err); - if (err instanceof CustomError) { - new Notice(err.message); - } else { - new Notice("An error occurred."); - } + const msg = + "An error occurred while updating the custom prompt: " + err2String(err); + console.error(msg); + throw new Error(msg); } }, prompt.title, diff --git a/src/components/chat-components/ChatSingleMessage.tsx b/src/components/chat-components/ChatSingleMessage.tsx index bb576daf..1f3984b2 100644 --- a/src/components/chat-components/ChatSingleMessage.tsx +++ b/src/components/chat-components/ChatSingleMessage.tsx @@ -207,7 +207,10 @@ const ChatSingleMessage: React.FC = ({ {message.message} ) : ( -
+
)} ); @@ -244,7 +247,7 @@ const ChatSingleMessage: React.FC = ({ {message.message} ) : ( -
+
); }; diff --git a/src/components/modals/AddPromptModal.tsx b/src/components/modals/AddPromptModal.tsx index 7c0e857d..1c1ec029 100644 --- a/src/components/modals/AddPromptModal.tsx +++ b/src/components/modals/AddPromptModal.tsx @@ -1,104 +1,200 @@ import { App, Modal, Notice } from "obsidian"; +import React, { useState } from "react"; +import { createRoot, Root } from "react-dom/client"; +import { err2String } from "@/utils"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; + +interface AddPromptModalContentProps { + initialTitle?: string; + initialPrompt?: string; + disabledTitle?: boolean; + onSave: (title: string, prompt: string) => Promise; + onCancel: () => void; +} + +function AddPromptModalContent({ + initialTitle = "", + initialPrompt = "", + disabledTitle = false, + onSave, + onCancel, +}: AddPromptModalContentProps) { + const [title, setTitle] = useState(initialTitle); + const [prompt, setPrompt] = useState(initialPrompt); + const [touched, setTouched] = useState({ title: false, prompt: false }); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Invalid characters for filename + // eslint-disable-next-line no-control-regex + const invalidChars = /[<>:"/\\|?*\x00-\x1F]/g; + const hasInvalidChars = title && invalidChars.test(title); + + const handleSave = async () => { + if (hasInvalidChars) { + new Notice("Title contains invalid characters. Please remove them before saving."); + return; + } + + if (title && prompt) { + try { + setIsSubmitting(true); + await onSave(title, prompt); + } catch (e) { + new Notice(err2String(e)); + } finally { + setIsSubmitting(false); + } + } else { + setTouched({ title: true, prompt: true }); + new Notice("Please fill in both fields: Title and Prompt."); + } + }; + + const showTitleError = touched.title && !title; + const showPromptError = touched.prompt && !prompt; + const isValid = title.trim() !== "" && prompt.trim() !== "" && !hasInvalidChars; + + return ( +
+
User Custom Prompt
+ +
+
+
Title
+ * +
+
+
The title of the prompt, must be unique.
+
+ Note: Title will be used as filename. Avoid using: {'< > : " / \\ | ? *'} +
+
+ { + setTitle(e.target.value); + if (!touched.title) setTouched((prev) => ({ ...prev, title: true })); + }} + onBlur={() => setTouched((prev) => ({ ...prev, title: true }))} + disabled={disabledTitle} + className={`w-full mt-1`} + required + /> + {showTitleError &&
Title is required
} + {hasInvalidChars && ( +
Title contains invalid characters
+ )} +
+ +
+
+
+
Prompt
+ * +
+
Use the following syntax in your prompt:
+
+
+ - {"{}"} represents the selected text (not required). + - {`{[[Note Title]]}`} represents a note. + - {`{activeNote}`} represents the active note. + - {`{FolderPath}`} represents a folder of notes. + + - {`{#tag1, #tag2}`} represents ALL notes with ANY of the specified tags in their + property (an OR operation).{" "} + +
+ + Tip: turn on debug mode to show the processed prompt in the chat window. + +
+
+ +