mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-05-08 08:00:10 +00:00
update hello world
This commit is contained in:
@@ -59,12 +59,12 @@ import { editorStateToPlainText } from './chat-input/utils/editor-state-to-plain
|
||||
import { ChatHistory } from './ChatHistoryView'
|
||||
import CommandsView from './CommandsView'
|
||||
import CustomModeView from './CustomModeView'
|
||||
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
||||
import HelloInfo from './HelloInfo'
|
||||
import McpHubView from './McpHubView' // Moved after MarkdownReasoningBlock
|
||||
import QueryProgress, { QueryProgressState } from './QueryProgress'
|
||||
import ReactMarkdown from './ReactMarkdown'
|
||||
import ShortcutInfo from './ShortcutInfo'
|
||||
import SimilaritySearchResults from './SimilaritySearchResults'
|
||||
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
||||
|
||||
// Add an empty line here
|
||||
const getNewInputMessage = (app: App, defaultMention: string): ChatUserMessage => {
|
||||
@@ -609,7 +609,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
}
|
||||
} else if (toolArgs.type === 'regex_search_files') {
|
||||
// @ts-expect-error Obsidian API type mismatch
|
||||
const baseVaultPath = app.vault.adapter.getBasePath()
|
||||
const baseVaultPath = String(app.vault.adapter.getBasePath())
|
||||
const ripgrepPath = settings.ripgrepPath
|
||||
const absolutePath = path.join(baseVaultPath, toolArgs.filepath)
|
||||
const results = await regexSearchFiles(absolutePath, toolArgs.regex, ripgrepPath)
|
||||
@@ -730,7 +730,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
return item.text
|
||||
}
|
||||
if (item.type === "resource") {
|
||||
const { blob: _, ...rest } = item.resource
|
||||
const { blob: _blob, ...rest } = item.resource
|
||||
return JSON.stringify(rest, null, 2)
|
||||
}
|
||||
return ""
|
||||
@@ -776,7 +776,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
role: 'assistant',
|
||||
applyStatus: ApplyStatus.Idle,
|
||||
isToolResult: true,
|
||||
content: `<tool_result>${result.returnMsg.promptContent}</tool_result>`,
|
||||
content: `<tool_result>${typeof result.returnMsg.promptContent === 'string' ? result.returnMsg.promptContent : ''}</tool_result>`,
|
||||
reasoningContent: '',
|
||||
metadata: {
|
||||
usage: undefined,
|
||||
@@ -1037,7 +1037,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
||||
// If the chat is empty, show a message to start a new chat
|
||||
chatMessages.length === 0 && (
|
||||
<div className="infio-chat-empty-state">
|
||||
<ShortcutInfo />
|
||||
<HelloInfo onNavigate={(tab) => setTab(tab)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
144
src/components/chat-view/HelloInfo.tsx
Normal file
144
src/components/chat-view/HelloInfo.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import { NotebookPen, Server, SquareSlash } from 'lucide-react';
|
||||
import React from 'react';
|
||||
|
||||
import { t } from '../../lang/helpers';
|
||||
|
||||
interface HelloInfoProps {
|
||||
onNavigate: (tab: 'commands' | 'custom-mode' | 'mcp') => void;
|
||||
}
|
||||
|
||||
const HelloInfo: React.FC<HelloInfoProps> = ({ onNavigate }) => {
|
||||
const navigationItems = [
|
||||
{
|
||||
label: t('chat.navigation.commands'),
|
||||
description: t('chat.navigation.commandsDesc'),
|
||||
icon: <SquareSlash size={20} />,
|
||||
action: () => onNavigate('commands'),
|
||||
},
|
||||
{
|
||||
label: t('chat.navigation.customMode'),
|
||||
description: t('chat.navigation.customModeDesc'),
|
||||
icon: <NotebookPen size={20} />,
|
||||
action: () => onNavigate('custom-mode'),
|
||||
},
|
||||
{
|
||||
label: t('chat.navigation.mcp'),
|
||||
description: t('chat.navigation.mcpDesc'),
|
||||
icon: <Server size={20} />,
|
||||
action: () => onNavigate('mcp'),
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="infio-hello-info">
|
||||
{/* <div className="infio-hello-title">
|
||||
<h3>{t('chat.welcome.title')}</h3>
|
||||
<p>{t('chat.welcome.subtitle')}</p>
|
||||
</div> */}
|
||||
<div className="infio-navigation-cards">
|
||||
{navigationItems.map((item, index) => (
|
||||
<a
|
||||
key={index}
|
||||
className="infio-navigation-card"
|
||||
onClick={item.action}
|
||||
>
|
||||
<div className="infio-navigation-icon">
|
||||
{item.icon}
|
||||
</div>
|
||||
<div className="infio-navigation-content">
|
||||
<div className="infio-navigation-label">{item.label}</div>
|
||||
<div className="infio-navigation-description">{item.description}</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<style>
|
||||
{`
|
||||
/*
|
||||
* Hello Info and Navigation
|
||||
*/
|
||||
.infio-hello-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--size-4-8) var(--size-4-4);
|
||||
gap: var(--size-4-6);
|
||||
text-align: center;
|
||||
margin: var(--size-4-4);
|
||||
}
|
||||
|
||||
.infio-hello-title h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
margin: 0 0 var(--size-4-3) 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.infio-hello-title p {
|
||||
font-size: var(--font-ui-medium);
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
line-height: var(--line-height-normal);
|
||||
}
|
||||
|
||||
.infio-navigation-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.infio-navigation-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--size-4-4);
|
||||
padding: var(--size-4-5) var(--size-4-6);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.infio-navigation-card:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
border-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.infio-navigation-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-m);
|
||||
color: var(--text-accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.infio-navigation-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--size-2-2);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.infio-navigation-label {
|
||||
font-size: var(--font-ui-large);
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.infio-navigation-description {
|
||||
font-size: var(--font-ui-small);
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelloInfo;
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AlertTriangle, ChevronDown, ChevronRight, FileText, Folder, Power, RotateCcw, Trash2, Wrench } from 'lucide-react'
|
||||
import { Notice } from 'obsidian'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { useMcpHub } from '../../contexts/McpHubContext'
|
||||
@@ -13,6 +14,11 @@ const McpHubView = () => {
|
||||
const [expandedServers, setExpandedServers] = useState<Record<string, boolean>>({});
|
||||
const [activeServerDetailTab, setActiveServerDetailTab] = useState<Record<string, 'tools' | 'resources' | 'errors'>>({});
|
||||
|
||||
// 新增状态变量用于创建新服务器
|
||||
const [newServerName, setNewServerName] = useState('')
|
||||
const [newServerConfig, setNewServerConfig] = useState('')
|
||||
const [isCreateSectionExpanded, setIsCreateSectionExpanded] = useState(false)
|
||||
|
||||
const fetchServers = async () => {
|
||||
const hub = await getMcpHub()
|
||||
console.log('Fetching MCP Servers from hub:', hub)
|
||||
@@ -67,6 +73,42 @@ const McpHubView = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
// 验证输入
|
||||
if (newServerName.trim().length === 0) {
|
||||
new Notice("服务器名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
if (newServerConfig.trim().length === 0) {
|
||||
new Notice("配置不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// check config is valid json
|
||||
try {
|
||||
JSON.parse(newServerConfig)
|
||||
} catch (error) {
|
||||
new Notice("配置格式无效,请输入有效的 JSON 格式")
|
||||
return
|
||||
}
|
||||
|
||||
const hub = await getMcpHub();
|
||||
if (hub) {
|
||||
try {
|
||||
await hub.createServer(newServerName, newServerConfig, "global")
|
||||
const updatedServers = hub.getAllServers()
|
||||
setMcpServers(updatedServers)
|
||||
|
||||
// 清空表单
|
||||
setNewServerName('')
|
||||
setNewServerConfig('')
|
||||
new Notice(`服务器 "${newServerName}" 创建成功`)
|
||||
} catch (error) {
|
||||
new Notice(`创建服务器失败: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const toggleServerExpansion = (serverKey: string) => {
|
||||
setExpandedServers(prev => ({ ...prev, [serverKey]: !prev[serverKey] }));
|
||||
@@ -79,6 +121,10 @@ const McpHubView = () => {
|
||||
setActiveServerDetailTab(prev => ({ ...prev, [serverKey]: tab }));
|
||||
};
|
||||
|
||||
const toggleCreateSectionExpansion = () => {
|
||||
setIsCreateSectionExpanded(prev => !prev)
|
||||
}
|
||||
|
||||
const ToolRow = ({ tool }: { tool: McpTool }) => {
|
||||
return (
|
||||
<div className="infio-mcp-tool-row">
|
||||
@@ -168,11 +214,66 @@ const McpHubView = () => {
|
||||
<span className="infio-mcp-setting-text">启用 MCP 服务器</span>
|
||||
</label>
|
||||
<p className="infio-mcp-setting-description">
|
||||
开启后 Roo 可用已连接 MCP 服务器的工具,能力更强。不用这些工具时建议关闭,节省 API Token 费用。
|
||||
开启后可用已连接 MCP 服务器的工具,能力更强。不用这些工具时建议关闭,节省 API Token 费用。
|
||||
<a href="https://modelcontextprotocol.io/introduction" target="_blank" rel="noopener noreferrer">
|
||||
Learn more about MCP
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Create New Server Section */}
|
||||
{settings.mcpEnabled && (
|
||||
<div className="infio-mcp-create-section">
|
||||
<div className="infio-mcp-create-item">
|
||||
<div className="infio-mcp-create-item-header" onClick={toggleCreateSectionExpansion}>
|
||||
<div className="infio-mcp-create-item-info">
|
||||
<div className="infio-mcp-hub-expander">
|
||||
{isCreateSectionExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
</div>
|
||||
<h3 className="infio-mcp-create-title">+ 添加新的 MCP 服务器</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isCreateSectionExpanded && (
|
||||
<div className="infio-mcp-create-expanded">
|
||||
<div className="infio-mcp-create-label">服务器名称</div>
|
||||
<input
|
||||
type="text"
|
||||
value={newServerName}
|
||||
onChange={(e) => setNewServerName(e.target.value)}
|
||||
placeholder="输入服务器名称"
|
||||
className="infio-mcp-create-input"
|
||||
/>
|
||||
<div className="infio-mcp-create-label">配置 (JSON 格式)</div>
|
||||
<textarea
|
||||
value={newServerConfig}
|
||||
onChange={(e) => setNewServerConfig(e.target.value)}
|
||||
placeholder='example: {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"/Users/username/Desktop",
|
||||
"/path/to/other/allowed/dir"
|
||||
]
|
||||
}'
|
||||
className="infio-mcp-create-textarea"
|
||||
rows={4}
|
||||
/>
|
||||
<button
|
||||
onClick={handleCreate}
|
||||
className="infio-mcp-create-btn"
|
||||
disabled={!newServerName.trim() || !newServerConfig.trim()}
|
||||
>
|
||||
<span>创建服务器</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Servers List */}
|
||||
{settings.mcpEnabled && (
|
||||
<div className="infio-mcp-hub-list">
|
||||
@@ -300,8 +401,7 @@ const McpHubView = () => {
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
color: var(--text-normal);
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Header Styles */
|
||||
@@ -376,8 +476,7 @@ const McpHubView = () => {
|
||||
background-color: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.infio-mcp-hub-item-header {
|
||||
@@ -584,7 +683,21 @@ const McpHubView = () => {
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
background-color: var(--background-secondary);
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
padding-bottom: 16px;
|
||||
animation: expandContent 0.3s ease-out;
|
||||
border-bottom-left-radius: var(--radius-s);
|
||||
border-bottom-right-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
@keyframes expandContent {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.infio-mcp-tabs {
|
||||
@@ -747,6 +860,139 @@ const McpHubView = () => {
|
||||
padding: 40px 20px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Create New Server Section */
|
||||
.infio-mcp-create-section {
|
||||
background-color: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.infio-mcp-create-item {
|
||||
/* Remove background and padding since we're restructuring */
|
||||
}
|
||||
|
||||
.infio-mcp-create-item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.infio-mcp-create-item-header:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.infio-mcp-create-item-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.infio-mcp-create-title {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.infio-mcp-create-expanded {
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
background-color: var(--background-secondary);
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
animation: expandContent 0.3s ease-out;
|
||||
border-bottom-left-radius: var(--radius-s);
|
||||
border-bottom-right-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.infio-mcp-create-new {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.infio-mcp-create-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.infio-mcp-create-input {
|
||||
background-color: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
color: var(--text-normal);
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.infio-mcp-create-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.infio-mcp-create-textarea {
|
||||
background-color: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius-s);
|
||||
color: var(--text-normal);
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-family: var(--font-monospace);
|
||||
resize: vertical;
|
||||
min-height: 140px;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.infio-mcp-create-textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.infio-mcp-create-btn {
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: var(--radius-s);
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.infio-mcp-create-btn:hover:not(:disabled) {
|
||||
background-color: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.infio-mcp-create-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* Servers List */
|
||||
.infio-mcp-hub-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Platform } from 'obsidian';
|
||||
import React from 'react';
|
||||
|
||||
import { t } from '../../lang/helpers'
|
||||
|
||||
const ShortcutInfo: React.FC = () => {
|
||||
const modKey = Platform.isMacOS ? 'Cmd' : 'Ctrl';
|
||||
|
||||
const shortcuts = [
|
||||
{
|
||||
label: t('chat.shortcutInfo.editInline'),
|
||||
shortcut: `${modKey}+Shift+K`,
|
||||
},
|
||||
{
|
||||
label: t('chat.shortcutInfo.chatWithSelect'),
|
||||
shortcut: `${modKey}+Shift+L`,
|
||||
},
|
||||
{
|
||||
label: t('chat.shortcutInfo.submitWithVault'),
|
||||
shortcut: `${modKey}+Shift+Enter`,
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="infio-shortcut-info">
|
||||
<table className="infio-shortcut-table">
|
||||
<tbody>
|
||||
{shortcuts.map((item, index) => (
|
||||
<tr key={index} className="infio-shortcut-item">
|
||||
<td className="infio-shortcut-label">{item.label}</td>
|
||||
<td className="infio-shortcut-key"><kbd>{item.shortcut}</kbd></td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ShortcutInfo;
|
||||
Reference in New Issue
Block a user