mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-01-16 08:21:55 +00:00
fix inline edit can't up error
This commit is contained in:
parent
dfdb21e832
commit
92b5c8fe61
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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]);
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user