fix inline edit can't up error

This commit is contained in:
duanfuxiang 2025-04-30 18:47:14 +08:00
parent dfdb21e832
commit 92b5c8fe61
6 changed files with 97 additions and 39 deletions

View File

@ -7,7 +7,7 @@ import { APPLY_VIEW_TYPE } from './constants'
import { AppProvider } from './contexts/AppContext' import { AppProvider } from './contexts/AppContext'
export type ApplyViewState = { export type ApplyViewState = {
file: TFile file: string
oldContent: string oldContent: string
newContent: string newContent: string
onClose: (applied: boolean) => void onClose: (applied: boolean) => void

View File

@ -53,8 +53,11 @@ export default function ApplyViewRoot({
} }
return result; return result;
}, '') }, '')
const file = app.vault.getFileByPath(state.file)
await app.vault.modify(state.file, newContent) if (!file) {
throw new Error('File not found')
}
await app.vault.modify(file, newContent)
if (state.onClose) { if (state.onClose) {
state.onClose(true) state.onClose(true)
} }

View File

@ -401,7 +401,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: opFile, file: opFile.path,
oldContent: '', oldContent: '',
newContent: toolArgs.content, newContent: toolArgs.content,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {
@ -452,7 +452,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: opFile, file: opFile.path,
oldContent: fileContent, oldContent: fileContent,
newContent: appliedFileContent, newContent: appliedFileContent,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {
@ -494,7 +494,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: opFile, file: opFile.path,
oldContent: fileContent, oldContent: fileContent,
newContent: appliedFileContent, newContent: appliedFileContent,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {
@ -536,7 +536,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: opFile, file: opFile.path,
oldContent: fileContent, oldContent: fileContent,
newContent: appliedResult.content, newContent: appliedResult.content,
onClose: (applied: boolean) => { onClose: (applied: boolean) => {

View File

@ -98,7 +98,7 @@ const CustomModeView = () => {
setCustomInstructions(customMode.customInstructions || ''); setCustomInstructions(customMode.customInstructions || '');
setSelectedTools(customMode.groups); setSelectedTools(customMode.groups);
} else { } else {
console.log("error, custom mode not found") console.error("custom mode not found")
} }
} }
}, [selectedMode, customModeList]); }, [selectedMode, customModeList]);

View File

@ -11,7 +11,7 @@ import { removeAITags } from '../../utils/content-filter';
import { PromptGenerator } from '../../utils/prompt-generator'; import { PromptGenerator } from '../../utils/prompt-generator';
type InlineEditProps = { type InlineEditProps = {
source: string; source?: string;
secStartLine: number; secStartLine: number;
secEndLine: number; secEndLine: number;
plugin: Plugin; plugin: Plugin;
@ -173,20 +173,14 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
}; };
const parseSmartComposeBlock = (content: string) => { const parseSmartComposeBlock = (content: string) => {
const match = /<infio_block[^>]*>([\s\S]*?)<\/infio_block>/.exec(content); const match = /<response>([\s\S]*?)<\/response>/.exec(content);
if (!match) { if (!match) {
return null; return null;
} }
const blockContent = match[1].trim(); const blockContent = match[1].trim();
const attributes = /startLine="(\d+)"/.exec(match[0]);
const startLine = attributes ? parseInt(attributes[1]) : undefined;
const endLineMatch = /endLine="(\d+)"/.exec(match[0]);
const endLine = endLineMatch ? parseInt(endLineMatch[1]) : undefined;
return { return {
startLine,
endLine,
content: blockContent, content: blockContent,
}; };
}; };
@ -196,6 +190,7 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
try { try {
const { activeFile, editor, selection } = await getActiveContext(); const { activeFile, editor, selection } = await getActiveContext();
if (!activeFile || !editor || !selection) { if (!activeFile || !editor || !selection) {
console.error("No active file, editor, or selection");
setIsSubmitting(false); setIsSubmitting(false);
return; return;
} }
@ -237,14 +232,26 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
response.choices[0].message.content response.choices[0].message.content
); );
const finalContent = parsedBlock?.content || response.choices[0].message.content; const finalContent = parsedBlock?.content || response.choices[0].message.content;
const startLine = parsedBlock?.startLine || defaultStartLine;
const endLine = parsedBlock?.endLine || defaultEndLine; if (!activeFile || !(activeFile.path && typeof activeFile.path === 'string')) {
setIsSubmitting(false);
throw new Error("Invalid active file");
}
let fileContent: string;
try {
fileContent = await plugin.app.vault.cachedRead(activeFile);
} catch (error) {
console.error("Failed to read file:", error);
setIsSubmitting(false);
return;
}
const updatedContent = await ApplyEditToFile( const updatedContent = await ApplyEditToFile(
await plugin.app.vault.cachedRead(activeFile), fileContent,
finalContent, finalContent,
startLine, defaultStartLine,
endLine defaultEndLine
); );
if (!updatedContent) { if (!updatedContent) {
@ -258,7 +265,7 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
type: APPLY_VIEW_TYPE, type: APPLY_VIEW_TYPE,
active: true, active: true,
state: { state: {
file: activeFile, file: activeFile.path,
oldContent: removeAITags(oldContent), oldContent: removeAITags(oldContent),
newContent: removeAITags(updatedContent), newContent: removeAITags(updatedContent),
}, },
@ -277,8 +284,8 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
<InputArea value={instruction} onChange={setInstruction} handleSubmit={handleSubmit} handleClose={handleClose} /> <InputArea value={instruction} onChange={setInstruction} handleSubmit={handleSubmit} handleClose={handleClose} />
<button className="infio-ai-block-close-button" onClick={handleClose}> <button className="infio-ai-block-close-button" onClick={handleClose}>
<svg <svg
width="14" width="16"
height="14" height="16"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"

View File

@ -491,18 +491,29 @@ export class PromptGenerator {
} }
private getSystemMessage(shouldUseRAG: boolean, type?: string): RequestMessage { private getSystemMessage(shouldUseRAG: boolean, type?: string): RequestMessage {
const systemPromptEdit = `You are an intelligent assistant to help edit text content based on user instructions. You will be given the current text content and the user's instruction for how to modify it. const systemPromptEdit = `You are an expert text editor assistant. Your task is to modify the selected content precisely according to the user's instruction, while preserving the original formatting and ensuring consistency with the surrounding context.
1. Your response should contain the modified text content wrapped in <infio_block> tags with appropriate attributes: You will receive:
<infio_block filename="path/to/file.md" language="markdown" startLine="10" endLine="20" type="edit"> - <task>: The specific editing instruction
[modified content here] - <selected_content>: The text to be modified
</infio_block> - <surrounding_context>: The surrounding file context (may be truncated)
2. Preserve the original formatting, indentation and line breaks unless specifically instructed otherwise. When performing the edit:
- Make only the minimal changes necessary to fulfill the instruction
- Preserve original formatting (indentation, line breaks, spacing) unless the instruction explicitly requires changing it
- Use the context to ensure the edit maintains consistency with the surrounding content
- Match the style, terminology, and conventions of the original document
- Handle special content types appropriately:
- Code: Maintain syntax correctness and follow existing code style
- Lists: Preserve formatting and hierarchy
- Tables: Keep alignment and structure
- Markdown/formatting: Respect existing markup
3. Make minimal changes necessary to fulfill the user's instruction. Do not modify parts of the text that don't need to change. Your edit response must be wrapped in <response> tags:
<response>
4. If the instruction is unclear or cannot be fulfilled, respond with "ERROR: " followed by a brief explanation.` [modified content here]
</response>
`
const systemPrompt = `You are an intelligent assistant to help answer any questions that the user has, particularly about editing and organizing markdown files in Obsidian. const systemPrompt = `You are an intelligent assistant to help answer any questions that the user has, particularly about editing and organizing markdown files in Obsidian.
@ -604,6 +615,30 @@ ${fileContent}
} }
} }
private async getContextForEdit(
currentFile: TFile,
startLine: number,
endLine: number
): Promise<string | null> {
// 如果选中内容超过500行则不提供上下文
if (endLine - startLine + 1 > 500) {
return null;
}
const fileContent = await readTFileContent(currentFile, this.app.vault);
const lines = fileContent.split('\n');
// 计算上下文范围,并处理边界情况
const contextStartLine = Math.max(1, startLine - 20);
const contextEndLine = Math.min(lines.length, endLine + 20);
// 提取上下文行
const contextLines = lines.slice(contextStartLine - 1, contextEndLine);
// 返回带行号的上下文内容
return addLineNumbers(contextLines.join('\n'), contextStartLine);
}
public async generateEditMessages({ public async generateEditMessages({
currentFile, currentFile,
selectedContent, selectedContent,
@ -617,14 +652,27 @@ ${fileContent}
startLine: number startLine: number
endLine: number endLine: number
}): Promise<RequestMessage[]> { }): Promise<RequestMessage[]> {
const systemMessage = this.getSystemMessage(false, 'edit') const systemMessage = this.getSystemMessage(false, 'edit');
const currentFileMessage = await this.getCurrentFileMessage(currentFile)
const userMessage: RequestMessage = { // 获取适当大小的上下文
role: 'user', const context = await this.getContextForEdit(currentFile, startLine, endLine);
content: `Selected text (lines ${startLine}-${endLine}):\n${selectedContent}\n\nInstruction:\n${instruction}`,
let userPrompt = `<task>\n${instruction}\n</task>\n\n
<selected_content location="${currentFile.path}#L${startLine}-${endLine}">\n${selectedContent}\n</selected_content>`;
// 只有当上下文不为null时才添加
if (context !== null) {
userPrompt += `\n\n<surrounding_context location="${currentFile.path}">\n${context}\n</surrounding_context>`;
} else {
userPrompt += `\n\n<surrounding_context location="${currentFile.path}">\n(No relevant context found)\n</surrounding_context>`;
} }
return [systemMessage, currentFileMessage, userMessage] const userMessage: RequestMessage = {
role: 'user',
content: userPrompt,
};
return [systemMessage, userMessage];
} }
private getRagInstructionMessage(): RequestMessage { private getRagInstructionMessage(): RequestMessage {