mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-01-16 16:31:56 +00:00
update manage files
This commit is contained in:
parent
89bc10d16d
commit
fea5b382cf
@ -3,7 +3,7 @@ import * as path from 'path'
|
|||||||
import { BaseSerializedNode } from '@lexical/clipboard/clipboard'
|
import { BaseSerializedNode } from '@lexical/clipboard/clipboard'
|
||||||
import { useMutation } from '@tanstack/react-query'
|
import { useMutation } from '@tanstack/react-query'
|
||||||
import { Box, CircleStop, History, NotebookPen, Plus, Search, Server, SquareSlash, Undo } from 'lucide-react'
|
import { Box, CircleStop, History, NotebookPen, Plus, Search, Server, SquareSlash, Undo } from 'lucide-react'
|
||||||
import { App, Notice, TFile, WorkspaceLeaf } from 'obsidian'
|
import { App, Notice, TFile, TFolder, WorkspaceLeaf } from 'obsidian'
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -902,6 +902,150 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
} else if (toolArgs.type === 'manage_files') {
|
||||||
|
try {
|
||||||
|
const results: string[] = [];
|
||||||
|
|
||||||
|
// 处理每个文件操作
|
||||||
|
for (const operation of toolArgs.operations) {
|
||||||
|
switch (operation.action) {
|
||||||
|
case 'create_folder':
|
||||||
|
if (operation.path) {
|
||||||
|
const folderExists = await app.vault.adapter.exists(operation.path);
|
||||||
|
if (!folderExists) {
|
||||||
|
await app.vault.adapter.mkdir(operation.path);
|
||||||
|
results.push(`✅ 成功创建文件夹: ${operation.path}`);
|
||||||
|
} else {
|
||||||
|
results.push(`⚠️ 文件夹已存在: ${operation.path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'move':
|
||||||
|
if (operation.source_path && operation.destination_path) {
|
||||||
|
// 使用 getAbstractFileByPath 而不是 getFileByPath,这样可以获取文件和文件夹
|
||||||
|
const sourceFile = app.vault.getAbstractFileByPath(operation.source_path);
|
||||||
|
if (sourceFile) {
|
||||||
|
// 确保目标目录存在
|
||||||
|
const destDir = path.dirname(operation.destination_path);
|
||||||
|
if (destDir && destDir !== '.' && destDir !== '/') {
|
||||||
|
const dirExists = await app.vault.adapter.exists(destDir);
|
||||||
|
if (!dirExists) {
|
||||||
|
await app.vault.adapter.mkdir(destDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await app.vault.rename(sourceFile, operation.destination_path);
|
||||||
|
const itemType = sourceFile instanceof TFile ? '文件' : '文件夹';
|
||||||
|
results.push(`✅ 成功移动${itemType}: ${operation.source_path} → ${operation.destination_path}`);
|
||||||
|
} else {
|
||||||
|
results.push(`❌ 源文件或文件夹不存在: ${operation.source_path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
if (operation.path) {
|
||||||
|
// 使用 getAbstractFileByPath 而不是 getFileByPath
|
||||||
|
const fileOrFolder = app.vault.getAbstractFileByPath(operation.path);
|
||||||
|
if (fileOrFolder) {
|
||||||
|
try {
|
||||||
|
const isFolder = fileOrFolder instanceof TFolder;
|
||||||
|
// 使用 trash 方法将文件/文件夹移到回收站,更安全
|
||||||
|
// system: true 尝试使用系统回收站,失败则使用 Obsidian 本地回收站
|
||||||
|
await app.vault.trash(fileOrFolder, true);
|
||||||
|
const itemType = isFolder ? '文件夹' : '文件';
|
||||||
|
results.push(`✅ 成功将${itemType}移到回收站: ${operation.path}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('删除失败:', error);
|
||||||
|
results.push(`❌ 删除失败: ${operation.path} - ${error.message}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.push(`❌ 文件或文件夹不存在: ${operation.path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'copy':
|
||||||
|
if (operation.source_path && operation.destination_path) {
|
||||||
|
// 文件夹复制比较复杂,需要递归处理
|
||||||
|
const sourceFile = app.vault.getAbstractFileByPath(operation.source_path);
|
||||||
|
if (sourceFile) {
|
||||||
|
if (sourceFile instanceof TFile) {
|
||||||
|
// 文件复制
|
||||||
|
const destDir = path.dirname(operation.destination_path);
|
||||||
|
if (destDir && destDir !== '.' && destDir !== '/') {
|
||||||
|
const dirExists = await app.vault.adapter.exists(destDir);
|
||||||
|
if (!dirExists) {
|
||||||
|
await app.vault.adapter.mkdir(destDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const content = await app.vault.read(sourceFile);
|
||||||
|
await app.vault.create(operation.destination_path, content);
|
||||||
|
results.push(`✅ 成功复制文件: ${operation.source_path} → ${operation.destination_path}`);
|
||||||
|
} else if (sourceFile instanceof TFolder) {
|
||||||
|
// 文件夹复制需要递归处理
|
||||||
|
results.push(`❌ 文件夹复制功能暂未实现: ${operation.source_path}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.push(`❌ 源文件或文件夹不存在: ${operation.source_path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'rename':
|
||||||
|
if (operation.path && operation.new_name) {
|
||||||
|
// 使用 getAbstractFileByPath 而不是 getFileByPath
|
||||||
|
const file = app.vault.getAbstractFileByPath(operation.path);
|
||||||
|
if (file) {
|
||||||
|
const newPath = path.join(path.dirname(operation.path), operation.new_name);
|
||||||
|
await app.vault.rename(file, newPath);
|
||||||
|
const itemType = file instanceof TFile ? '文件' : '文件夹';
|
||||||
|
results.push(`✅ 成功重命名${itemType}: ${operation.path} → ${newPath}`);
|
||||||
|
} else {
|
||||||
|
results.push(`❌ 文件或文件夹不存在: ${operation.path}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
results.push(`❌ 不支持的操作类型: ${operation.action}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedContent = `[manage_files] 文件管理操作结果:\n${results.join('\n')}`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'manage_files',
|
||||||
|
applyMsgId,
|
||||||
|
applyStatus: ApplyStatus.Applied,
|
||||||
|
returnMsg: {
|
||||||
|
role: 'user',
|
||||||
|
applyStatus: ApplyStatus.Idle,
|
||||||
|
content: null,
|
||||||
|
promptContent: formattedContent,
|
||||||
|
id: uuidv4(),
|
||||||
|
mentionables: [],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('文件管理操作失败:', error);
|
||||||
|
return {
|
||||||
|
type: 'manage_files',
|
||||||
|
applyMsgId,
|
||||||
|
applyStatus: ApplyStatus.Failed,
|
||||||
|
returnMsg: {
|
||||||
|
role: 'user',
|
||||||
|
applyStatus: ApplyStatus.Idle,
|
||||||
|
content: null,
|
||||||
|
promptContent: `[manage_files] 文件管理操作失败: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
id: uuidv4(),
|
||||||
|
mentionables: [],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 处理未知的工具类型
|
||||||
|
throw new Error(`Unsupported tool type: ${toolArgs.type}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to apply changes', error)
|
console.error('Failed to apply changes', error)
|
||||||
|
|||||||
124
src/components/chat-view/Markdown/MarkdownManageFilesBlock.tsx
Normal file
124
src/components/chat-view/Markdown/MarkdownManageFilesBlock.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { Check, Copy, FileIcon, FolderPlus, Loader2, Move, Trash2, X } from 'lucide-react'
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
|
||||||
|
import { ApplyStatus, ManageFilesToolArgs } from "../../../types/apply"
|
||||||
|
|
||||||
|
interface ManageFilesOperation {
|
||||||
|
action: 'create_folder' | 'move' | 'delete' | 'copy' | 'rename'
|
||||||
|
path?: string
|
||||||
|
source_path?: string
|
||||||
|
destination_path?: string
|
||||||
|
new_name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MarkdownManageFilesBlock({
|
||||||
|
applyStatus,
|
||||||
|
onApply,
|
||||||
|
operations,
|
||||||
|
finish
|
||||||
|
}: {
|
||||||
|
applyStatus: ApplyStatus
|
||||||
|
onApply: (args: ManageFilesToolArgs) => void
|
||||||
|
operations: ManageFilesOperation[]
|
||||||
|
finish: boolean
|
||||||
|
}) {
|
||||||
|
const [applying, setApplying] = useState(false)
|
||||||
|
|
||||||
|
const getOperationIcon = (action: string) => {
|
||||||
|
switch (action) {
|
||||||
|
case 'create_folder':
|
||||||
|
return <FolderPlus size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
case 'move':
|
||||||
|
return <Move size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
case 'delete':
|
||||||
|
return <Trash2 size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
case 'copy':
|
||||||
|
return <Copy size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
case 'rename':
|
||||||
|
return <FileIcon size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
default:
|
||||||
|
return <FileIcon size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getOperationDescription = (operation: ManageFilesOperation) => {
|
||||||
|
switch (operation.action) {
|
||||||
|
case 'create_folder':
|
||||||
|
return `创建文件夹:${operation.path}`
|
||||||
|
case 'move':
|
||||||
|
return `移动文件:${operation.source_path} → ${operation.destination_path}`
|
||||||
|
case 'delete':
|
||||||
|
return `删除:${operation.path}`
|
||||||
|
case 'copy':
|
||||||
|
return `复制:${operation.source_path} → ${operation.destination_path}`
|
||||||
|
case 'rename':
|
||||||
|
return `重命名:${operation.path} → ${operation.new_name}`
|
||||||
|
default:
|
||||||
|
return `未知操作`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleApply = async () => {
|
||||||
|
if (applyStatus !== ApplyStatus.Idle) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setApplying(true)
|
||||||
|
onApply({
|
||||||
|
type: 'manage_files',
|
||||||
|
operations: operations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`infio-chat-code-block has-filename`}>
|
||||||
|
<div className={'infio-chat-code-block-header'}>
|
||||||
|
<div className={'infio-chat-code-block-header-filename'}>
|
||||||
|
<FolderPlus size={14} className="infio-chat-code-block-header-icon" />
|
||||||
|
文件管理操作 ({operations.length} 个操作)
|
||||||
|
</div>
|
||||||
|
<div className={'infio-chat-code-block-header-button'}>
|
||||||
|
<button
|
||||||
|
onClick={handleApply}
|
||||||
|
className="infio-apply-button"
|
||||||
|
disabled={applyStatus !== ApplyStatus.Idle || applying || !finish}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
!finish ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="spinner" size={14} /> 准备执行
|
||||||
|
</>
|
||||||
|
) : applyStatus === ApplyStatus.Idle ? (
|
||||||
|
applying ? (
|
||||||
|
<>
|
||||||
|
<Loader2 className="spinner" size={14} /> 执行中
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
'执行操作'
|
||||||
|
)
|
||||||
|
) : applyStatus === ApplyStatus.Applied ? (
|
||||||
|
<>
|
||||||
|
<Check size={14} /> 已完成
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<X size={14} /> 执行失败
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="infio-chat-code-block-content">
|
||||||
|
{operations.map((operation, index) => (
|
||||||
|
<div key={index} className="manage-files-operation">
|
||||||
|
<div className="operation-item">
|
||||||
|
{getOperationIcon(operation.action)}
|
||||||
|
<span className="operation-description">
|
||||||
|
{getOperationDescription(operation)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -11,8 +11,8 @@ import MarkdownDataviewQueryBlock from './Markdown/MarkdownDataviewQueryBlock'
|
|||||||
import MarkdownEditFileBlock from './Markdown/MarkdownEditFileBlock'
|
import MarkdownEditFileBlock from './Markdown/MarkdownEditFileBlock'
|
||||||
import MarkdownFetchUrlsContentBlock from './Markdown/MarkdownFetchUrlsContentBlock'
|
import MarkdownFetchUrlsContentBlock from './Markdown/MarkdownFetchUrlsContentBlock'
|
||||||
import MarkdownListFilesBlock from './Markdown/MarkdownListFilesBlock'
|
import MarkdownListFilesBlock from './Markdown/MarkdownListFilesBlock'
|
||||||
|
import MarkdownManageFilesBlock from './Markdown/MarkdownManageFilesBlock'
|
||||||
import MarkdownMatchSearchFilesBlock from './Markdown/MarkdownMatchSearchFilesBlock'
|
import MarkdownMatchSearchFilesBlock from './Markdown/MarkdownMatchSearchFilesBlock'
|
||||||
import MarkdownPlanBlock from './Markdown/MarkdownPlanBlock'
|
|
||||||
import MarkdownReadFileBlock from './Markdown/MarkdownReadFileBlock'
|
import MarkdownReadFileBlock from './Markdown/MarkdownReadFileBlock'
|
||||||
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
||||||
import MarkdownRegexSearchFilesBlock from './Markdown/MarkdownRegexSearchFilesBlock'
|
import MarkdownRegexSearchFilesBlock from './Markdown/MarkdownRegexSearchFilesBlock'
|
||||||
@ -44,21 +44,15 @@ function ReactMarkdown({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{blocks.map((block, index) =>
|
{blocks.map((block, index) =>
|
||||||
block.type === 'communication' ? (
|
block.type === 'think' ? (
|
||||||
<RawMarkdownBlock
|
|
||||||
key={"markdown-" + index}
|
|
||||||
content={block.content}
|
|
||||||
className="infio-markdown"
|
|
||||||
/>
|
|
||||||
) : block.type === 'think' ? (
|
|
||||||
<MarkdownReasoningBlock
|
<MarkdownReasoningBlock
|
||||||
key={"reasoning-" + index}
|
key={"reasoning-" + index}
|
||||||
reasoningContent={block.content}
|
reasoningContent={block.content}
|
||||||
/>
|
/>
|
||||||
) : block.type === 'thinking' ? (
|
) : block.type === 'thinking' ? (
|
||||||
<MarkdownPlanBlock
|
<RawMarkdownBlock
|
||||||
key={"plan-" + index}
|
key={"plan-" + index}
|
||||||
planContent={block.content}
|
content={block.content}
|
||||||
/>
|
/>
|
||||||
) : block.type === 'write_to_file' ? (
|
) : block.type === 'write_to_file' ? (
|
||||||
<MarkdownEditFileBlock
|
<MarkdownEditFileBlock
|
||||||
@ -229,6 +223,14 @@ function ReactMarkdown({
|
|||||||
transformation={block.transformation}
|
transformation={block.transformation}
|
||||||
finish={block.finish}
|
finish={block.finish}
|
||||||
/>
|
/>
|
||||||
|
) : block.type === 'manage_files' ? (
|
||||||
|
<MarkdownManageFilesBlock
|
||||||
|
key={"manage-files-" + index}
|
||||||
|
applyStatus={applyStatus}
|
||||||
|
onApply={onApply}
|
||||||
|
operations={block.operations}
|
||||||
|
finish={block.finish}
|
||||||
|
/>
|
||||||
) : block.type === 'tool_result' ? (
|
) : block.type === 'tool_result' ? (
|
||||||
<MarkdownToolResult
|
<MarkdownToolResult
|
||||||
key={"tool-result-" + index}
|
key={"tool-result-" + index}
|
||||||
|
|||||||
@ -119,4 +119,16 @@ export type CallTransformationsToolArgs = {
|
|||||||
finish?: boolean;
|
finish?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ToolArgs = ReadFileToolArgs | WriteToFileToolArgs | InsertContentToolArgs | SearchAndReplaceToolArgs | ListFilesToolArgs | MatchSearchFilesToolArgs | RegexSearchFilesToolArgs | SemanticSearchFilesToolArgs | SearchWebToolArgs | FetchUrlsContentToolArgs | SwitchModeToolArgs | ApplyDiffToolArgs | UseMcpToolArgs | DataviewQueryToolArgs | CallTransformationsToolArgs;
|
export type ManageFilesToolArgs = {
|
||||||
|
type: 'manage_files';
|
||||||
|
operations: Array<{
|
||||||
|
action: 'create_folder' | 'move' | 'delete' | 'copy' | 'rename';
|
||||||
|
path?: string;
|
||||||
|
source_path?: string;
|
||||||
|
destination_path?: string;
|
||||||
|
new_name?: string;
|
||||||
|
}>;
|
||||||
|
finish?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolArgs = ReadFileToolArgs | WriteToFileToolArgs | InsertContentToolArgs | SearchAndReplaceToolArgs | ListFilesToolArgs | MatchSearchFilesToolArgs | RegexSearchFilesToolArgs | SemanticSearchFilesToolArgs | SearchWebToolArgs | FetchUrlsContentToolArgs | SwitchModeToolArgs | ApplyDiffToolArgs | UseMcpToolArgs | DataviewQueryToolArgs | CallTransformationsToolArgs | ManageFilesToolArgs;
|
||||||
|
|||||||
@ -105,6 +105,16 @@ export type ParsedMsgBlock =
|
|||||||
path: string
|
path: string
|
||||||
transformation: string
|
transformation: string
|
||||||
finish: boolean
|
finish: boolean
|
||||||
|
} | {
|
||||||
|
type: 'manage_files'
|
||||||
|
operations: Array<{
|
||||||
|
action: 'create_folder' | 'move' | 'delete' | 'copy' | 'rename'
|
||||||
|
path?: string
|
||||||
|
source_path?: string
|
||||||
|
destination_path?: string
|
||||||
|
new_name?: string
|
||||||
|
}>
|
||||||
|
finish: boolean
|
||||||
} | {
|
} | {
|
||||||
type: 'tool_result'
|
type: 'tool_result'
|
||||||
content: string
|
content: string
|
||||||
@ -817,6 +827,76 @@ export function parseMsgBlocks(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
lastEndOffset = endOffset
|
lastEndOffset = endOffset
|
||||||
|
} else if (node.nodeName === 'manage_files') {
|
||||||
|
if (!node.sourceCodeLocation) {
|
||||||
|
throw new Error('sourceCodeLocation is undefined')
|
||||||
|
}
|
||||||
|
const startOffset = node.sourceCodeLocation.startOffset
|
||||||
|
const endOffset = node.sourceCodeLocation.endOffset
|
||||||
|
if (startOffset > lastEndOffset) {
|
||||||
|
parsedResult.push({
|
||||||
|
type: 'string',
|
||||||
|
content: input.slice(lastEndOffset, startOffset),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let operations: Array<{
|
||||||
|
action: 'create_folder' | 'move' | 'delete' | 'copy' | 'rename'
|
||||||
|
path?: string
|
||||||
|
source_path?: string
|
||||||
|
destination_path?: string
|
||||||
|
new_name?: string
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
// 检查是否有 operations 子标签
|
||||||
|
for (const childNode of node.childNodes) {
|
||||||
|
if (childNode.nodeName === 'operations' && childNode.childNodes.length > 0) {
|
||||||
|
try {
|
||||||
|
// 获取 operations 标签内的内容
|
||||||
|
const operationsChildren = childNode.childNodes
|
||||||
|
if (operationsChildren.length > 0) {
|
||||||
|
const innerContentStartOffset = operationsChildren[0].sourceCodeLocation?.startOffset
|
||||||
|
const innerContentEndOffset = operationsChildren[operationsChildren.length - 1].sourceCodeLocation?.endOffset
|
||||||
|
|
||||||
|
if (innerContentStartOffset && innerContentEndOffset) {
|
||||||
|
const jsonContent = input.slice(innerContentStartOffset, innerContentEndOffset).trim()
|
||||||
|
operations = JSON5.parse(jsonContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse operations JSON', error)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有找到 operations 子标签,尝试直接解析标签内容
|
||||||
|
if (operations.length === 0) {
|
||||||
|
const children = node.childNodes
|
||||||
|
if (children.length > 0) {
|
||||||
|
try {
|
||||||
|
const innerContentStartOffset = children[0].sourceCodeLocation?.startOffset
|
||||||
|
const innerContentEndOffset = children[children.length - 1].sourceCodeLocation?.endOffset
|
||||||
|
|
||||||
|
if (innerContentStartOffset && innerContentEndOffset) {
|
||||||
|
const jsonContent = input.slice(innerContentStartOffset, innerContentEndOffset).trim()
|
||||||
|
// 检查内容是否以 [ 开头(纯 JSON 数组)
|
||||||
|
if (jsonContent.startsWith('[')) {
|
||||||
|
operations = JSON5.parse(jsonContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to parse manage_files JSON', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedResult.push({
|
||||||
|
type: 'manage_files',
|
||||||
|
operations,
|
||||||
|
finish: node.sourceCodeLocation.endTag !== undefined
|
||||||
|
})
|
||||||
|
lastEndOffset = endOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
30
styles.css
30
styles.css
@ -2068,6 +2068,36 @@ button.infio-chat-input-model-select {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Manage Files Block Styles
|
||||||
|
*/
|
||||||
|
.manage-files-operation {
|
||||||
|
margin-bottom: var(--size-2-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--size-2-1);
|
||||||
|
padding: var(--size-2-1) var(--size-4-1);
|
||||||
|
border-radius: var(--radius-s);
|
||||||
|
font-size: var(--font-ui-small);
|
||||||
|
color: var(--text-normal);
|
||||||
|
background-color: var(--background-secondary);
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-item:hover {
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-description {
|
||||||
|
flex: 1;
|
||||||
|
word-break: break-word;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
.infio-chat-code-block-status-button {
|
.infio-chat-code-block-status-button {
|
||||||
color: var(--color-green); /* 替换原来的 #008000 */
|
color: var(--color-green); /* 替换原来的 #008000 */
|
||||||
background: none;
|
background: none;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user