update apply diff

This commit is contained in:
duanfuxiang
2025-03-23 09:34:44 +08:00
parent 570e8d9564
commit 635db9babd
34 changed files with 3161 additions and 410 deletions

View File

@@ -17,6 +17,7 @@ import { v4 as uuidv4 } from 'uuid'
import { ApplyViewState } from '../../ApplyView'
import { APPLY_VIEW_TYPE } from '../../constants'
import { useApp } from '../../contexts/AppContext'
import { useDiffStrategy } from '../../contexts/DiffStrategyContext'
import { useLLM } from '../../contexts/LLMContext'
import { useRAG } from '../../contexts/RAGContext'
import { useSettings } from '../../contexts/SettingsContext'
@@ -93,6 +94,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
const app = useApp()
const { settings, setSettings } = useSettings()
const { getRAGEngine } = useRAG()
const diffStrategy = useDiffStrategy()
const {
createOrUpdateConversation,
@@ -104,8 +106,8 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
const { streamResponse, chatModel } = useLLM()
const promptGenerator = useMemo(() => {
return new PromptGenerator(getRAGEngine, app, settings)
}, [getRAGEngine, app, settings])
return new PromptGenerator(getRAGEngine, app, settings, diffStrategy)
}, [getRAGEngine, app, settings, diffStrategy])
const [inputMessage, setInputMessage] = useState<ChatUserMessage>(() => {
const newMessage = getNewInputMessage(app)
@@ -382,7 +384,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
if (!applyRes) {
throw new Error('Failed to apply edit changes')
}
// 返回一个Promise该Promise会在用户做出选择后解析
// return a Promise, which will be resolved after user makes a choice
return new Promise<{ type: string; applyMsgId: string; applyStatus: ApplyStatus; returnMsg?: ChatUserMessage }>((resolve) => {
app.workspace.getLeaf(true).setViewState({
type: APPLY_VIEW_TYPE,
@@ -419,7 +421,10 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
fileContent,
toolArgs.operations
)
// 返回一个Promise该Promise会在用户做出选择后解析
if (!applyRes) {
throw new Error('Failed to search_and_replace')
}
// return a Promise, which will be resolved after user makes a choice
return new Promise<{ type: string; applyMsgId: string; applyStatus: ApplyStatus; returnMsg?: ChatUserMessage }>((resolve) => {
app.workspace.getLeaf(true).setViewState({
type: APPLY_VIEW_TYPE,
@@ -449,6 +454,45 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
} satisfies ApplyViewState,
})
})
} else if (toolArgs.type === 'apply_diff') {
const diffResult = await diffStrategy.applyDiff(
activeFileContent,
toolArgs.diff
)
if (!diffResult.success) {
console.log(diffResult)
throw new Error(`Failed to apply_diff`)
}
// return a Promise, which will be resolved after user makes a choice
return new Promise<{ type: string; applyMsgId: string; applyStatus: ApplyStatus; returnMsg?: ChatUserMessage }>((resolve) => {
app.workspace.getLeaf(true).setViewState({
type: APPLY_VIEW_TYPE,
active: true,
state: {
file: activeFile,
originalContent: activeFileContent,
newContent: diffResult.content,
onClose: (applied: boolean) => {
const applyStatus = applied ? ApplyStatus.Applied : ApplyStatus.Rejected
const applyEditContent = applied ? 'Changes successfully applied'
: 'User rejected changes'
resolve({
type: 'apply_diff',
applyMsgId,
applyStatus,
returnMsg: {
role: 'user',
applyStatus: ApplyStatus.Idle,
content: null,
promptContent: `[apply_diff for '${toolArgs.filepath}'] Result:\n${applyEditContent}\n`,
id: uuidv4(),
mentionables: [],
}
});
}
} satisfies ApplyViewState,
})
})
} else if (toolArgs.type === 'read_file') {
const fileContent = activeFile.path === toolArgs.filepath ? activeFileContent : readFileContent(toolArgs.filepath)
const formattedContent = `[read_file for '${toolArgs.filepath}'] Result:\n${addLineNumbers(fileContent)}\n`;

View File

@@ -0,0 +1,93 @@
import { Check, Edit, Loader2, X } from 'lucide-react'
import { PropsWithChildren, useState } from 'react'
import { useDarkModeContext } from '../../contexts/DarkModeContext'
import { ApplyStatus, ToolArgs } from '../../types/apply'
import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper'
export default function MarkdownApplyDiffBlock({
mode,
applyStatus,
onApply,
path,
diff,
finish,
}: PropsWithChildren<{
mode: string
applyStatus: ApplyStatus
onApply: (args: ToolArgs) => void
path: string
diff: string
finish: boolean
}>) {
const [applying, setApplying] = useState(false)
const { isDarkMode } = useDarkModeContext()
console.log('MarkdownApplyDiffBlock', { mode, applyStatus, onApply, path, diff, finish })
const handleApply = async () => {
if (applyStatus !== ApplyStatus.Idle) {
return
}
setApplying(true)
onApply({
type: "apply_diff",
filepath: path,
diff,
finish,
})
}
return (
<div className={`infio-chat-code-block ${path ? 'has-filename' : ''} infio-reasoning-block`}>
<div className={'infio-chat-code-block-header'}>
{path && (
<div className={'infio-chat-code-block-header-filename'}>
<Edit size={10} className="infio-chat-code-block-header-icon" />
{mode}: {path}
</div>
)}
<div className={'infio-chat-code-block-header-button'}>
<button
onClick={handleApply}
style={{ color: '#008000' }}
disabled={applyStatus !== ApplyStatus.Idle || applying || !finish}
>
{
!finish ? (
<>
<Loader2 className="spinner" size={14} /> Loading...
</>
) : applyStatus === ApplyStatus.Idle ? (
applying ? (
<>
<Loader2 className="spinner" size={14} /> Applying...
</>
) : (
'Apply'
)
) : applyStatus === ApplyStatus.Applied ? (
<>
<Check size={14} /> Success
</>
) : (
<>
<X size={14} /> Failed
</>
)}
</button>
</div>
</div>
<div className="infio-reasoning-content-wrapper">
<MemoizedSyntaxHighlighterWrapper
isDarkMode={isDarkMode}
language="diff"
hasFilename={!!path}
wrapLines={true}
>
{diff}
</MemoizedSyntaxHighlighterWrapper>
</div>
</div>
)
}

View File

@@ -1,24 +1,29 @@
import { Check, Loader2, Replace, X } from 'lucide-react'
import React from 'react'
import React, { useMemo } from 'react'
import { useApp } from '../../contexts/AppContext'
import { ApplyStatus, SearchAndReplaceToolArgs } from '../../types/apply'
import { openMarkdownFile } from '../../utils/obsidian'
import { MemoizedSyntaxHighlighterWrapper } from './SyntaxHighlighterWrapper'
import { useDarkModeContext } from '../../contexts/DarkModeContext'
export default function MarkdownSearchAndReplace({
applyStatus,
onApply,
path,
content,
operations,
finish
}: {
applyStatus: ApplyStatus
onApply: (args: SearchAndReplaceToolArgs) => void
path: string,
content: string,
operations: SearchAndReplaceToolArgs['operations'],
finish: boolean
}) {
const app = useApp()
const { isDarkMode } = useDarkModeContext()
const [applying, setApplying] = React.useState(false)
@@ -51,9 +56,13 @@ export default function MarkdownSearchAndReplace({
<div className={'infio-chat-code-block-header-button'}>
<button
onClick={handleApply}
disabled={applyStatus !== ApplyStatus.Idle || applying}
disabled={applyStatus !== ApplyStatus.Idle || applying || !finish}
>
{applyStatus === ApplyStatus.Idle ? (
{!finish ? (
<>
<Loader2 className="spinner" size={14} />
</>
) : applyStatus === ApplyStatus.Idle ? (
applying ? (
<>
<Loader2 className="spinner" size={14} /> Applying...
@@ -73,6 +82,14 @@ export default function MarkdownSearchAndReplace({
</button>
</div>
</div>
<MemoizedSyntaxHighlighterWrapper
isDarkMode={isDarkMode}
language="markdown"
hasFilename={!!path}
wrapLines={true}
>
{content}
</MemoizedSyntaxHighlighterWrapper>
</div>
)
}

View File

@@ -7,6 +7,7 @@ import {
parseMsgBlocks,
} from '../../utils/parse-infio-block'
import MarkdownApplyDiffBlock from './MarkdownApplyDiffBlock'
import MarkdownEditFileBlock from './MarkdownEditFileBlock'
import MarkdownFetchUrlsContentBlock from './MarkdownFetchUrlsContentBlock'
import MarkdownListFilesBlock from './MarkdownListFilesBlock'
@@ -18,6 +19,7 @@ import MarkdownSearchWebBlock from './MarkdownSearchWebBlock'
import MarkdownSemanticSearchFilesBlock from './MarkdownSemanticSearchFilesBlock'
import MarkdownSwitchModeBlock from './MarkdownSwitchModeBlock'
import MarkdownWithIcons from './MarkdownWithIcon'
function ReactMarkdown({
applyStatus,
onApply,
@@ -27,6 +29,7 @@ function ReactMarkdown({
onApply: (toolArgs: ToolArgs) => void
children: string
}) {
const blocks: ParsedMsgBlock[] = useMemo(
() => parseMsgBlocks(children),
[children],
@@ -73,6 +76,7 @@ function ReactMarkdown({
applyStatus={applyStatus}
onApply={onApply}
path={block.path}
content={block.content}
operations={block.operations.map(op => ({
search: op.search,
replace: op.replace,
@@ -84,6 +88,16 @@ function ReactMarkdown({
}))}
finish={block.finish}
/>
) : block.type === 'apply_diff' ? (
<MarkdownApplyDiffBlock
key={"apply-diff-" + index}
applyStatus={applyStatus}
mode={block.type}
onApply={onApply}
path={block.path}
diff={block.diff}
finish={block.finish}
/>
) : block.type === 'read_file' ? (
<MarkdownReadFileBlock
key={"read-file-" + index}