mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
Improve custom command (#1942)
* Persist quick command configs * upgrade langchain and fix gpt-5 in custom command * Handle thinking in custom command * code clean up * Address cursor feedback
This commit is contained in:
parent
3ccbcda9ec
commit
3269943cc3
19 changed files with 916 additions and 664 deletions
|
|
@ -2,11 +2,19 @@ import esbuild from "esbuild";
|
|||
import svgPlugin from "esbuild-plugin-svg";
|
||||
import process from "process";
|
||||
import wasmPlugin from "./wasmPlugin.mjs";
|
||||
import nodeModuleShim from "./nodeModuleShim.mjs";
|
||||
|
||||
const banner = `/*
|
||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
|
||||
if you want to view the source, please visit the github repository of this plugin
|
||||
*/
|
||||
|
||||
// Polyfill for import.meta in CommonJS context
|
||||
if (typeof import_meta === 'undefined') {
|
||||
var import_meta = {
|
||||
url: typeof __filename !== 'undefined' ? 'file://' + __filename : 'file:///obsidian-plugin'
|
||||
};
|
||||
}
|
||||
`;
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
|
|
@ -31,6 +39,14 @@ const context = await esbuild.context({
|
|||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
// Node.js built-in modules (available in Electron) - except node:module which we shim
|
||||
"node:fs",
|
||||
"node:path",
|
||||
"node:url",
|
||||
"node:buffer",
|
||||
"node:stream",
|
||||
"node:crypto",
|
||||
"node:async_hooks",
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2020",
|
||||
|
|
@ -38,10 +54,11 @@ const context = await esbuild.context({
|
|||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
plugins: [svgPlugin(), wasmPlugin],
|
||||
plugins: [nodeModuleShim, svgPlugin(), wasmPlugin],
|
||||
define: {
|
||||
global: "window",
|
||||
"process.env.NODE_ENV": prod ? '"production"' : '"development"',
|
||||
"import.meta.url": "import_meta.url",
|
||||
},
|
||||
minify: prod,
|
||||
});
|
||||
|
|
|
|||
37
nodeModuleShim.mjs
Normal file
37
nodeModuleShim.mjs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Plugin to provide a shim for node:module in browser/Electron renderer context
|
||||
const nodeModuleShim = {
|
||||
name: "node-module-shim",
|
||||
setup(build) {
|
||||
// Intercept node:module imports and provide a shim
|
||||
build.onResolve({ filter: /^node:module$/ }, (args) => {
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: "node-module-shim",
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad({ filter: /.*/, namespace: "node-module-shim" }, () => {
|
||||
return {
|
||||
contents: `
|
||||
// Shim for node:module in Electron/Obsidian environment (CommonJS format)
|
||||
module.exports = {
|
||||
createRequire: function(filename) {
|
||||
// In Electron renderer, we can use the global require
|
||||
// Note: filename parameter is ignored (may be undefined from @langchain/community v1.0.0)
|
||||
if (typeof require !== 'undefined') {
|
||||
return require;
|
||||
}
|
||||
// Fallback: return a function that throws a helpful error
|
||||
return function shimmedRequire(id) {
|
||||
throw new Error('Dynamic require of "' + id + '" is not supported in this environment');
|
||||
};
|
||||
}
|
||||
};
|
||||
`,
|
||||
loader: "js",
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default nodeModuleShim;
|
||||
1315
package-lock.json
generated
1315
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
|
@ -35,7 +35,7 @@
|
|||
},
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@langchain/ollama": "^0.2.0",
|
||||
"@langchain/ollama": "^1.0.0",
|
||||
"@tailwindcss/container-queries": "^0.1.1",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
|
|
@ -81,18 +81,19 @@
|
|||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@huggingface/inference": "^2.6.4",
|
||||
"@huggingface/inference": "^4.11.3",
|
||||
"@koa/cors": "^5.0.0",
|
||||
"@langchain/anthropic": "^0.3.30",
|
||||
"@langchain/cohere": "^0.3.0",
|
||||
"@langchain/community": "^0.3.22",
|
||||
"@langchain/core": "^0.3.45",
|
||||
"@langchain/deepseek": "^0.0.1",
|
||||
"@langchain/google-genai": "^0.1.6",
|
||||
"@langchain/groq": "^0.1.2",
|
||||
"@langchain/mistralai": "^0.2.0",
|
||||
"@langchain/openai": "^0.6.6",
|
||||
"@langchain/xai": "^0.0.2",
|
||||
"@langchain/anthropic": "^1.0.0",
|
||||
"@langchain/cohere": "^1.0.0",
|
||||
"@langchain/community": "^1.0.0",
|
||||
"@langchain/core": "^1.0.1",
|
||||
"@langchain/deepseek": "^1.0.0",
|
||||
"@langchain/google-genai": "^1.0.0",
|
||||
"@langchain/groq": "^1.0.0",
|
||||
"@langchain/mistralai": "^1.0.0",
|
||||
"@langchain/openai": "^1.0.0",
|
||||
"@langchain/textsplitters": "^1.0.0",
|
||||
"@langchain/xai": "^1.0.0",
|
||||
"@lexical/plain-text": "^0.34.0",
|
||||
"@lexical/react": "^0.34.0",
|
||||
"@lexical/selection": "^0.34.0",
|
||||
|
|
@ -131,7 +132,7 @@
|
|||
"jotai": "^2.10.3",
|
||||
"koa": "^2.14.2",
|
||||
"koa-proxies": "^0.12.3",
|
||||
"langchain": "^0.3.2",
|
||||
"langchain": "^1.0.1",
|
||||
"lexical": "^0.34.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lucide-react": "^0.462.0",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { deriveReadNoteDisplayName, ToolResultFormatter } from "@/tools/ToolResu
|
|||
import { ChatMessage, ResponseMetadata, StreamingResult } from "@/types/message";
|
||||
import { getMessageRole, withSuppressedTokenWarnings } from "@/utils";
|
||||
import { processToolResults } from "@/utils/toolResultUtils";
|
||||
import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
|
||||
import { CopilotPlusChainRunner } from "./CopilotPlusChainRunner";
|
||||
import { addChatHistoryToMessages } from "./utils/chatHistoryUtils";
|
||||
import {
|
||||
|
|
@ -773,8 +774,17 @@ ${params}
|
|||
|
||||
while (retryCount <= maxRetries) {
|
||||
try {
|
||||
// Convert ConversationMessage to LangChain BaseMessage format
|
||||
const langchainMessages: BaseMessage[] = messages.map((msg) => {
|
||||
if (msg.role === "user") {
|
||||
return new HumanMessage(msg.content);
|
||||
} else {
|
||||
return new AIMessage(msg.content);
|
||||
}
|
||||
});
|
||||
|
||||
const chatStream = await withSuppressedTokenWarnings(() =>
|
||||
this.chainManager.chatModelManager.getChatModel().stream(messages, {
|
||||
this.chainManager.chatModelManager.getChatModel().stream(langchainMessages, {
|
||||
signal: abortController.signal,
|
||||
})
|
||||
);
|
||||
|
|
|
|||
|
|
@ -16,17 +16,24 @@ export class ThinkBlockStreamer {
|
|||
|
||||
constructor(
|
||||
private updateCurrentAiMessage: (message: string) => void,
|
||||
private modelAdapter?: ModelAdapter
|
||||
private modelAdapter?: ModelAdapter,
|
||||
private excludeThinking: boolean = false
|
||||
) {}
|
||||
|
||||
private handleClaude37Chunk(content: any[]) {
|
||||
let textContent = "";
|
||||
let hasThinkingContent = false;
|
||||
for (const item of content) {
|
||||
switch (item.type) {
|
||||
case "text":
|
||||
textContent += item.text;
|
||||
break;
|
||||
case "thinking":
|
||||
hasThinkingContent = true;
|
||||
// Skip thinking content if excludeThinking is enabled
|
||||
if (this.excludeThinking) {
|
||||
break;
|
||||
}
|
||||
if (!this.hasOpenThinkBlock) {
|
||||
this.fullResponse += "\n<think>";
|
||||
this.hasOpenThinkBlock = true;
|
||||
|
|
@ -36,13 +43,13 @@ export class ThinkBlockStreamer {
|
|||
this.fullResponse += item.thinking;
|
||||
}
|
||||
this.updateCurrentAiMessage(this.fullResponse);
|
||||
return true; // Indicate we handled a thinking chunk
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (textContent) {
|
||||
this.fullResponse += textContent;
|
||||
}
|
||||
return false; // No thinking chunk handled
|
||||
return hasThinkingContent;
|
||||
}
|
||||
|
||||
private handleDeepseekChunk(chunk: any) {
|
||||
|
|
@ -53,6 +60,10 @@ export class ThinkBlockStreamer {
|
|||
|
||||
// Handle deepseek reasoning/thinking content
|
||||
if (chunk.additional_kwargs?.reasoning_content) {
|
||||
// Skip thinking content if excludeThinking is enabled
|
||||
if (this.excludeThinking) {
|
||||
return true; // Indicate we handled (but skipped) a thinking chunk
|
||||
}
|
||||
if (!this.hasOpenThinkBlock) {
|
||||
this.fullResponse += "\n<think>";
|
||||
this.hasOpenThinkBlock = true;
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ export default class ChatModelManager {
|
|||
},
|
||||
[ChatModelProviders.GOOGLE]: {
|
||||
apiKey: await getDecryptedKey(customModel.apiKey || settings.googleApiKey),
|
||||
modelName: modelName,
|
||||
model: modelName,
|
||||
safetySettings: [
|
||||
{
|
||||
category: HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
|
||||
|
|
@ -246,7 +246,7 @@ export default class ChatModelManager {
|
|||
},
|
||||
[ChatModelProviders.GROQ]: {
|
||||
apiKey: await getDecryptedKey(customModel.apiKey || settings.groqApiKey),
|
||||
modelName: modelName,
|
||||
model: modelName,
|
||||
},
|
||||
[ChatModelProviders.OLLAMA]: {
|
||||
// ChatOllama has `model` instead of `modelName`!!
|
||||
|
|
@ -475,6 +475,23 @@ export default class ChatModelManager {
|
|||
return ChatModelManager.chatModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* langchain 1.0 TypeScript doesn't support temperature override in BaseChatModelCallOptions,
|
||||
* so we need to create a new model instance with the specified temperature.
|
||||
*/
|
||||
async getChatModelWithTemperature(temperature: number): Promise<BaseChatModel> {
|
||||
const settings = getSettings();
|
||||
const currentModel = settings.activeModels[0];
|
||||
|
||||
// Create a temporary model config with overridden temperature
|
||||
const modelWithTempOverride: CustomModel = {
|
||||
...currentModel,
|
||||
temperature,
|
||||
};
|
||||
|
||||
return await this.createModelInstance(modelWithTempOverride);
|
||||
}
|
||||
|
||||
async setChatModel(model: CustomModel): Promise<void> {
|
||||
const modelKey = getModelKeyFromModel(model);
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { getSettings, subscribeToSettingsChange } from "@/settings/model";
|
||||
import { BaseChatMemory, BufferWindowMemory } from "langchain/memory";
|
||||
import { BaseChatMemory, BufferWindowMemory } from "@langchain/classic/memory";
|
||||
import { BaseChatMessageHistory } from "@langchain/core/chat_history";
|
||||
|
||||
export default class MemoryManager {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { StringOutputParser } from "@langchain/core/output_parsers";
|
|||
import { ChatPromptTemplate, PromptTemplate } from "@langchain/core/prompts";
|
||||
import { BaseRetriever } from "@langchain/core/retrievers";
|
||||
import { RunnablePassthrough, RunnableSequence } from "@langchain/core/runnables";
|
||||
import { BaseChatMemory } from "langchain/memory";
|
||||
import { formatDocumentsAsString } from "langchain/util/document";
|
||||
import { BaseChatMemory } from "@langchain/classic/memory";
|
||||
import { formatDocumentsAsString } from "@langchain/classic/util/document";
|
||||
import { removeThinkTags } from "./utils";
|
||||
|
||||
export interface LLMChainInput {
|
||||
|
|
@ -72,7 +72,7 @@ class ChainFactory {
|
|||
public static createNewLLMChain(args: LLMChainInput): RunnableSequence {
|
||||
const { llm, memory, prompt, abortController } = args;
|
||||
|
||||
const model = llm.bind({ signal: abortController?.signal });
|
||||
const model = llm.withConfig({ signal: abortController?.signal });
|
||||
const instance = RunnableSequence.from([
|
||||
{
|
||||
input: (initialInput) => initialInput.input,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import ProjectManager from "@/LLMProviders/projectManager";
|
||||
import { ChatHistoryEntry, removeThinkTags, withSuppressedTokenWarnings } from "@/utils";
|
||||
import { BaseChatModelCallOptions } from "@langchain/core/language_models/chat_models";
|
||||
|
||||
export async function getStandaloneQuestion(
|
||||
question: string,
|
||||
|
|
@ -24,10 +23,11 @@ export async function getStandaloneQuestion(
|
|||
|
||||
// Wrap the model call with token warning suppression
|
||||
return await withSuppressedTokenWarnings(async () => {
|
||||
const chatModel = ProjectManager.instance
|
||||
// Use temperature=0 for deterministic question condensation
|
||||
const chatModel = await ProjectManager.instance
|
||||
.getCurrentChainManager()
|
||||
.chatModelManager.getChatModel()
|
||||
.bind({ temperature: 0 } as BaseChatModelCallOptions);
|
||||
.chatModelManager.getChatModelWithTemperature(0);
|
||||
|
||||
const response = await chatModel.invoke([
|
||||
{
|
||||
role: "user",
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { processCommandPrompt } from "@/commands/customCommandUtils";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { getModelDisplayText } from "@/components/ui/model-display";
|
||||
import ChatModelManager from "@/LLMProviders/chatModelManager";
|
||||
import { ThinkBlockStreamer } from "@/LLMProviders/chainRunner/utils/ThinkBlockStreamer";
|
||||
import { logError } from "@/logger";
|
||||
import { findCustomModel, insertIntoEditor } from "@/utils";
|
||||
import {
|
||||
|
|
@ -12,7 +13,7 @@ import {
|
|||
SystemMessagePromptTemplate,
|
||||
} from "@langchain/core/prompts";
|
||||
import { RunnableSequence } from "@langchain/core/runnables";
|
||||
import { BaseChatMemory, BufferMemory } from "langchain/memory";
|
||||
import { BaseChatMemory, BufferMemory } from "@langchain/classic/memory";
|
||||
import { ArrowBigUp, Bot, Command, Copy, CornerDownLeft, PenLine } from "lucide-react";
|
||||
import { App, Modal, Notice, Platform } from "obsidian";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
|
@ -94,6 +95,29 @@ function CustomCommandChatModalContent({
|
|||
|
||||
const commandTitle = command.title;
|
||||
|
||||
/**
|
||||
* Compute the display value for the textarea
|
||||
*/
|
||||
const displayValue = useMemo(() => {
|
||||
// If we have a processed message, show that
|
||||
if (processedMessage) {
|
||||
return processedMessage;
|
||||
}
|
||||
|
||||
// If not generating, show loading
|
||||
if (!generating) {
|
||||
return "loading...";
|
||||
}
|
||||
|
||||
// If generating but no content yet, show loading
|
||||
if (!aiCurrentMessage || aiCurrentMessage.trim() === "") {
|
||||
return "loading...";
|
||||
}
|
||||
|
||||
// Otherwise show the streaming content
|
||||
return aiCurrentMessage;
|
||||
}, [processedMessage, generating, aiCurrentMessage]);
|
||||
|
||||
// Reusable function to handle streaming responses wrapped in useCallback
|
||||
const streamResponse = useCallback(
|
||||
async (input: string, abortController: AbortController) => {
|
||||
|
|
@ -108,21 +132,31 @@ function CustomCommandChatModalContent({
|
|||
setAiCurrentMessage(null);
|
||||
setProcessedMessage(null);
|
||||
setGenerating(true);
|
||||
let fullResponse = "";
|
||||
|
||||
const chainWithSignal = chatChain.bind({ signal: abortController.signal });
|
||||
const chainWithSignal = chatChain.withConfig({ signal: abortController.signal });
|
||||
|
||||
const stream = await chainWithSignal.stream({ input });
|
||||
|
||||
// Initialize ThinkBlockStreamer to handle reasoning content from Claude and Deepseek
|
||||
// excludeThinking=true means thinking tokens are not included in the response
|
||||
const thinkStreamer = new ThinkBlockStreamer(
|
||||
(message: string) => {
|
||||
setAiCurrentMessage(message);
|
||||
},
|
||||
undefined, // no model adapter
|
||||
true // excludeThinking
|
||||
);
|
||||
|
||||
for await (const chunk of stream) {
|
||||
if (abortController.signal.aborted) break;
|
||||
const chunkContent = typeof chunk.content === "string" ? chunk.content : "";
|
||||
fullResponse += chunkContent;
|
||||
setAiCurrentMessage(fullResponse);
|
||||
thinkStreamer.processChunk(chunk);
|
||||
}
|
||||
|
||||
// Close the streamer to finalize the response and close any open think blocks
|
||||
const result = thinkStreamer.close();
|
||||
|
||||
if (!abortController.signal.aborted) {
|
||||
const trimmedResponse = fullResponse.trim();
|
||||
const trimmedResponse = result.content.trim();
|
||||
setProcessedMessage(trimmedResponse);
|
||||
setGenerating(false);
|
||||
|
||||
|
|
@ -262,7 +296,7 @@ function CustomCommandChatModalContent({
|
|||
<textarea
|
||||
ref={textareaRef}
|
||||
className="tw-peer tw-h-60 tw-w-full tw-text-text"
|
||||
value={processedMessage ?? aiCurrentMessage ?? "loading..."}
|
||||
value={displayValue}
|
||||
disabled={processedMessage == null}
|
||||
onChange={(e) => setProcessedMessage(e.target.value)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { useModelKey } from "@/aiParams";
|
|||
import { CustomCommand } from "@/commands/type";
|
||||
import { removeQuickCommandBlocks } from "@/commands/customCommandUtils";
|
||||
import CopilotPlugin from "@/main";
|
||||
import { updateSetting, useSettingsValue } from "@/settings/model";
|
||||
|
||||
interface QuickCommandProps {
|
||||
plugin: CopilotPlugin;
|
||||
|
|
@ -18,12 +19,14 @@ interface QuickCommandProps {
|
|||
|
||||
export function QuickCommand({ plugin, onRemove }: QuickCommandProps) {
|
||||
const [prompt, setPrompt] = useState("");
|
||||
const [includeActiveNote, setIncludeActiveNote] = useState(true);
|
||||
const settings = useSettingsValue();
|
||||
const [selectedText, setSelectedText] = useState("");
|
||||
const [globalModelKey] = useModelKey();
|
||||
const [selectedModelKey, setSelectedModelKey] = useState(globalModelKey);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
const selectedModelKey = settings.quickCommandModelKey ?? globalModelKey;
|
||||
const includeActiveNote = settings.quickCommandIncludeNoteContext;
|
||||
|
||||
// Get the currently selected text from the editor
|
||||
useEffect(() => {
|
||||
const activeView = plugin.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
|
|
@ -119,6 +122,20 @@ Response format: Match the format implied by the user's request (e.g., if they a
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle model selection change and persist to settings
|
||||
*/
|
||||
const handleModelChange = (modelKey: string) => {
|
||||
updateSetting("quickCommandModelKey", modelKey);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle include note context checkbox change and persist to settings
|
||||
*/
|
||||
const handleIncludeNoteContextChange = (checked: boolean) => {
|
||||
updateSetting("quickCommandIncludeNoteContext", checked);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="tw-rounded-lg tw-border tw-border-solid tw-border-border tw-bg-primary tw-p-4"
|
||||
|
|
@ -140,14 +157,14 @@ Response format: Match the format implied by the user's request (e.g., if they a
|
|||
size="sm"
|
||||
variant="ghost"
|
||||
value={selectedModelKey}
|
||||
onChange={setSelectedModelKey}
|
||||
onChange={handleModelChange}
|
||||
/>
|
||||
|
||||
<div className="tw-flex tw-items-center tw-gap-2">
|
||||
<Checkbox
|
||||
id="includeActiveNote"
|
||||
checked={includeActiveNote}
|
||||
onCheckedChange={(checked) => setIncludeActiveNote(!!checked)}
|
||||
onCheckedChange={(checked) => handleIncludeNoteContextChange(!!checked)}
|
||||
/>
|
||||
<label
|
||||
htmlFor="includeActiveNote"
|
||||
|
|
|
|||
|
|
@ -805,6 +805,8 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
|
|||
maxRecentConversations: 30,
|
||||
enableSavedMemory: true,
|
||||
enableInlineCitations: true,
|
||||
quickCommandModelKey: undefined,
|
||||
quickCommandIncludeNoteContext: true,
|
||||
};
|
||||
|
||||
export const EVENT_NAMES = {
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ export interface ImageContent {
|
|||
image_url: {
|
||||
url: string;
|
||||
};
|
||||
[key: string]: unknown; // Index signature for LangChain compatibility
|
||||
}
|
||||
|
||||
export interface TextContent {
|
||||
type: "text";
|
||||
text: string;
|
||||
[key: string]: unknown; // Index signature for LangChain compatibility
|
||||
}
|
||||
|
||||
export interface ImageProcessingResult {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import {
|
|||
} from "@/utils";
|
||||
import { BaseCallbackConfig } from "@langchain/core/callbacks/manager";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { BaseChatModelCallOptions } from "@langchain/core/language_models/chat_models";
|
||||
import { ChatPromptTemplate } from "@langchain/core/prompts";
|
||||
import { BaseRetriever } from "@langchain/core/retrievers";
|
||||
import { search } from "@orama/orama";
|
||||
|
|
@ -143,11 +142,11 @@ export class HybridRetriever extends BaseRetriever {
|
|||
const promptResult = await this.queryRewritePrompt.format({ question: query });
|
||||
|
||||
// Execute model invocation with warnings suppressed
|
||||
const rewrittenQueryObject = await withSuppressedTokenWarnings(() => {
|
||||
const chatModel = ProjectManager.instance
|
||||
const rewrittenQueryObject = await withSuppressedTokenWarnings(async () => {
|
||||
// Use temperature=0 for deterministic HyDE query rewriting
|
||||
const chatModel = await ProjectManager.instance
|
||||
.getCurrentChainManager()
|
||||
.chatModelManager.getChatModel()
|
||||
.bind({ temperature: 0 } as BaseChatModelCallOptions);
|
||||
.chatModelManager.getChatModelWithTemperature(0);
|
||||
|
||||
return chatModel.invoke(promptResult);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { RateLimiter } from "@/rateLimiter";
|
|||
import { getSettings, subscribeToSettingsChange } from "@/settings/model";
|
||||
import { formatDateTime } from "@/utils";
|
||||
import { MD5 } from "crypto-js";
|
||||
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
|
||||
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
|
||||
import { App, Notice, TFile } from "obsidian";
|
||||
import { DBOperations } from "./dbOperations";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { logInfo, logWarn } from "@/logger";
|
||||
import { CHUNK_SIZE } from "@/constants";
|
||||
import { App, TFile } from "obsidian";
|
||||
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
|
||||
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
|
||||
|
||||
/**
|
||||
* Chunk interface for unified search system
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ export interface CopilotSettings {
|
|||
enableSavedMemory: boolean;
|
||||
/** Enable inline citations in AI responses with footnote-style references */
|
||||
enableInlineCitations: boolean;
|
||||
/** Last selected model for quick command */
|
||||
quickCommandModelKey: string | undefined;
|
||||
/** Last checkbox state for including note context in quick command */
|
||||
quickCommandIncludeNoteContext: boolean;
|
||||
}
|
||||
|
||||
export const settingsStore = createStore();
|
||||
|
|
@ -402,6 +406,20 @@ export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
|
|||
sanitizedSettings.autosaveChat = DEFAULT_SETTINGS.autosaveChat;
|
||||
}
|
||||
|
||||
// Ensure quickCommandIncludeNoteContext has a default value
|
||||
if (typeof sanitizedSettings.quickCommandIncludeNoteContext !== "boolean") {
|
||||
sanitizedSettings.quickCommandIncludeNoteContext =
|
||||
DEFAULT_SETTINGS.quickCommandIncludeNoteContext;
|
||||
}
|
||||
|
||||
// Ensure quickCommandModelKey is either undefined or a string
|
||||
if (
|
||||
settingsToSanitize.quickCommandModelKey !== undefined &&
|
||||
typeof settingsToSanitize.quickCommandModelKey !== "string"
|
||||
) {
|
||||
sanitizedSettings.quickCommandModelKey = DEFAULT_SETTINGS.quickCommandModelKey;
|
||||
}
|
||||
|
||||
// Ensure folder settings fall back to defaults when empty/whitespace
|
||||
const saveFolder = (settingsToSanitize.defaultSaveFolder || "").trim();
|
||||
sanitizedSettings.defaultSaveFolder =
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { ChatMessage } from "@/types/message";
|
|||
import { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
||||
import { MemoryVariables } from "@langchain/core/memory";
|
||||
import { RunnableSequence } from "@langchain/core/runnables";
|
||||
import { BaseChain, RetrievalQAChain } from "langchain/chains";
|
||||
import { BaseChain, RetrievalQAChain } from "@langchain/classic/chains";
|
||||
import moment from "moment";
|
||||
import { MarkdownView, Notice, TFile, Vault, normalizePath, requestUrl } from "obsidian";
|
||||
import { CustomModel } from "./aiParams";
|
||||
|
|
@ -204,12 +204,12 @@ export const stringToChainType = (chain: string): ChainType => {
|
|||
// Remove after confirming chainManager no longer uses them
|
||||
export const isLLMChain = (chain: RunnableSequence): chain is RunnableSequence => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (chain as any).last.bound.modelName || (chain as any).last.bound.model;
|
||||
return (chain as any).last?.modelName || (chain as any).last?.model;
|
||||
};
|
||||
|
||||
export const isRetrievalQAChain = (chain: BaseChain): chain is RetrievalQAChain => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (chain as any).last.bound.retriever !== undefined;
|
||||
return (chain as any).last?.retriever !== undefined;
|
||||
};
|
||||
|
||||
export const isSupportedChain = (chain: RunnableSequence): chain is RunnableSequence => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue