mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-05-06 06:56:29 +00:00
update buildin mcp tools
This commit is contained in:
@@ -64,6 +64,8 @@ import McpHubView from './McpHubView' // Moved after MarkdownReasoningBlock
|
||||
import QueryProgress, { QueryProgressState } from './QueryProgress'
|
||||
import ReactMarkdown from './ReactMarkdown'
|
||||
import SimilaritySearchResults from './SimilaritySearchResults'
|
||||
import FileReadResults from './FileReadResults'
|
||||
import WebsiteReadResults from './WebsiteReadResults'
|
||||
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
||||
|
||||
// Add an empty line here
|
||||
@@ -1079,6 +1081,18 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{message.fileReadResults && (
|
||||
<FileReadResults
|
||||
key={"file-read-" + message.id}
|
||||
fileContents={message.fileReadResults}
|
||||
/>
|
||||
)}
|
||||
{message.websiteReadResults && (
|
||||
<WebsiteReadResults
|
||||
key={"website-read-" + message.id}
|
||||
websiteContents={message.websiteReadResults}
|
||||
/>
|
||||
)}
|
||||
{message.similaritySearchResults && (
|
||||
<SimilaritySearchResults
|
||||
key={"similarity-search-" + message.id}
|
||||
|
||||
82
src/components/chat-view/FileReadResults.tsx
Normal file
82
src/components/chat-view/FileReadResults.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import path from 'path'
|
||||
|
||||
import { ChevronDown, ChevronRight, FileText } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useApp } from '../../contexts/AppContext'
|
||||
import { t } from '../../lang/helpers'
|
||||
import { openMarkdownFile } from '../../utils/obsidian'
|
||||
|
||||
function FileReadItem({
|
||||
fileResult,
|
||||
}: {
|
||||
fileResult: { path: string, content: string }
|
||||
}) {
|
||||
const app = useApp()
|
||||
|
||||
const handleClick = () => {
|
||||
openMarkdownFile(app, fileResult.path)
|
||||
}
|
||||
|
||||
const getFileSize = (content: string) => {
|
||||
const bytes = new Blob([content]).size
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={handleClick} className="infio-file-read-item">
|
||||
<div className="infio-file-read-item__icon">
|
||||
<FileText size={16} />
|
||||
</div>
|
||||
<div className="infio-file-read-item__info">
|
||||
<div className="infio-file-read-item__name">
|
||||
{path.basename(fileResult.path)}
|
||||
</div>
|
||||
<div className="infio-file-read-item__path">
|
||||
{fileResult.path}
|
||||
</div>
|
||||
</div>
|
||||
<div className="infio-file-read-item__size">
|
||||
{getFileSize(fileResult.content)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FileReadResults({
|
||||
fileContents,
|
||||
}: {
|
||||
fileContents: Array<{ path: string, content: string }>
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="infio-file-read-results">
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen)
|
||||
}}
|
||||
className="infio-file-read-results__trigger"
|
||||
>
|
||||
{isOpen ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
<div>
|
||||
{t('chat.fileResults.showReadFiles')} ({fileContents.length})
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{fileContents.map((fileResult, index) => (
|
||||
<FileReadItem key={`${fileResult.path}-${index}`} fileResult={fileResult} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,26 @@ export type QueryProgressState =
|
||||
| {
|
||||
type: 'reading-mentionables'
|
||||
}
|
||||
| {
|
||||
type: 'reading-files'
|
||||
currentFile?: string
|
||||
totalFiles?: number
|
||||
completedFiles?: number
|
||||
}
|
||||
| {
|
||||
type: 'reading-files-done'
|
||||
fileContents: Array<{ path: string, content: string }>
|
||||
}
|
||||
| {
|
||||
type: 'reading-websites'
|
||||
currentUrl?: string
|
||||
totalUrls?: number
|
||||
completedUrls?: number
|
||||
}
|
||||
| {
|
||||
type: 'reading-websites-done'
|
||||
websiteContents: Array<{ url: string, content: string }>
|
||||
}
|
||||
| {
|
||||
type: 'indexing'
|
||||
indexProgress: IndexProgress
|
||||
@@ -43,6 +63,62 @@ export default function QueryProgress({
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
case 'reading-files':
|
||||
return (
|
||||
<div className="infio-query-progress">
|
||||
<p>
|
||||
{t('chat.queryProgress.readingFiles')}
|
||||
<DotLoader />
|
||||
</p>
|
||||
{state.currentFile && (
|
||||
<p className="infio-query-progress-detail">
|
||||
{state.currentFile}
|
||||
{state.totalFiles && state.completedFiles !== undefined && (
|
||||
<span> ({state.completedFiles}/{state.totalFiles})</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
case 'reading-files-done':
|
||||
return (
|
||||
<div className="infio-query-progress">
|
||||
<p>
|
||||
{t('chat.queryProgress.readingFilesDone')}
|
||||
</p>
|
||||
<p className="infio-query-progress-detail">
|
||||
{t('chat.queryProgress.filesLoaded', { count: state.fileContents.length })}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
case 'reading-websites':
|
||||
return (
|
||||
<div className="infio-query-progress">
|
||||
<p>
|
||||
{t('chat.queryProgress.readingWebsites')}
|
||||
<DotLoader />
|
||||
</p>
|
||||
{state.currentUrl && (
|
||||
<p className="infio-query-progress-detail">
|
||||
{state.currentUrl}
|
||||
{state.totalUrls && state.completedUrls !== undefined && (
|
||||
<span> ({state.completedUrls}/{state.totalUrls})</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
case 'reading-websites-done':
|
||||
return (
|
||||
<div className="infio-query-progress">
|
||||
<p>
|
||||
{t('chat.queryProgress.readingWebsitesDone')}
|
||||
</p>
|
||||
<p className="infio-query-progress-detail">
|
||||
{t('chat.queryProgress.websitesLoaded', { count: state.websiteContents.length })}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
case 'indexing':
|
||||
return (
|
||||
<div className="infio-query-progress">
|
||||
|
||||
95
src/components/chat-view/WebsiteReadResults.tsx
Normal file
95
src/components/chat-view/WebsiteReadResults.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { ChevronDown, ChevronRight, FileText, Globe } from 'lucide-react'
|
||||
import { TFile } from 'obsidian'
|
||||
import { useState } from 'react'
|
||||
|
||||
import { useApp } from '../../contexts/AppContext'
|
||||
import { t } from '../../lang/helpers'
|
||||
|
||||
function WebsiteReadItem({
|
||||
websiteResult,
|
||||
}: {
|
||||
websiteResult: { url: string, content: string }
|
||||
}) {
|
||||
const app = useApp()
|
||||
|
||||
const handleClick = () => {
|
||||
// 现在url字段实际上是markdown文件路径,直接在Obsidian中打开
|
||||
const file = app.vault.getAbstractFileByPath(websiteResult.url)
|
||||
if (file instanceof TFile) {
|
||||
app.workspace.getLeaf('tab').openFile(file)
|
||||
}
|
||||
}
|
||||
|
||||
const getContentSize = (content: string) => {
|
||||
const bytes = new Blob([content]).size
|
||||
if (bytes < 1024) return `${bytes} B`
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
||||
}
|
||||
|
||||
const getFileBaseName = (filePath: string) => {
|
||||
return filePath.split('/').pop()?.replace('.md', '') || 'website'
|
||||
}
|
||||
|
||||
const truncatePath = (filePath: string, maxLength: number = 60) => {
|
||||
if (filePath.length <= maxLength) return filePath
|
||||
return '...' + filePath.substring(filePath.length - maxLength)
|
||||
}
|
||||
|
||||
return (
|
||||
<div onClick={handleClick} className="infio-website-read-item">
|
||||
<div className="infio-website-read-item__icon">
|
||||
<FileText size={16} />
|
||||
</div>
|
||||
<div className="infio-website-read-item__info">
|
||||
<div className="infio-website-read-item__domain">
|
||||
{getFileBaseName(websiteResult.url)}
|
||||
</div>
|
||||
<div className="infio-website-read-item__url">
|
||||
{truncatePath(websiteResult.url)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="infio-website-read-item__actions">
|
||||
<div className="infio-website-read-item__size">
|
||||
{getContentSize(websiteResult.content)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function WebsiteReadResults({
|
||||
websiteContents,
|
||||
}: {
|
||||
websiteContents: Array<{ url: string, content: string }>
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="infio-website-read-results">
|
||||
<div
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen)
|
||||
}}
|
||||
className="infio-website-read-results__trigger"
|
||||
>
|
||||
{isOpen ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
<div>
|
||||
{t('chat.websiteResults.showReadWebsites')} ({websiteContents.length})
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
{websiteContents.map((websiteResult, index) => (
|
||||
<WebsiteReadItem key={`${websiteResult.url}-${index}`} websiteResult={websiteResult} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user