mirror of
https://github.com/EthanMarti/infio-copilot.git
synced 2026-01-16 16:31:56 +00:00
update hello world
This commit is contained in:
parent
b1315aa6b1
commit
d83ea57fca
@ -59,12 +59,12 @@ import { editorStateToPlainText } from './chat-input/utils/editor-state-to-plain
|
|||||||
import { ChatHistory } from './ChatHistoryView'
|
import { ChatHistory } from './ChatHistoryView'
|
||||||
import CommandsView from './CommandsView'
|
import CommandsView from './CommandsView'
|
||||||
import CustomModeView from './CustomModeView'
|
import CustomModeView from './CustomModeView'
|
||||||
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
import HelloInfo from './HelloInfo'
|
||||||
import McpHubView from './McpHubView' // Moved after MarkdownReasoningBlock
|
import McpHubView from './McpHubView' // Moved after MarkdownReasoningBlock
|
||||||
import QueryProgress, { QueryProgressState } from './QueryProgress'
|
import QueryProgress, { QueryProgressState } from './QueryProgress'
|
||||||
import ReactMarkdown from './ReactMarkdown'
|
import ReactMarkdown from './ReactMarkdown'
|
||||||
import ShortcutInfo from './ShortcutInfo'
|
|
||||||
import SimilaritySearchResults from './SimilaritySearchResults'
|
import SimilaritySearchResults from './SimilaritySearchResults'
|
||||||
|
import MarkdownReasoningBlock from './Markdown/MarkdownReasoningBlock'
|
||||||
|
|
||||||
// Add an empty line here
|
// Add an empty line here
|
||||||
const getNewInputMessage = (app: App, defaultMention: string): ChatUserMessage => {
|
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') {
|
} else if (toolArgs.type === 'regex_search_files') {
|
||||||
// @ts-expect-error Obsidian API type mismatch
|
// @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 ripgrepPath = settings.ripgrepPath
|
||||||
const absolutePath = path.join(baseVaultPath, toolArgs.filepath)
|
const absolutePath = path.join(baseVaultPath, toolArgs.filepath)
|
||||||
const results = await regexSearchFiles(absolutePath, toolArgs.regex, ripgrepPath)
|
const results = await regexSearchFiles(absolutePath, toolArgs.regex, ripgrepPath)
|
||||||
@ -730,7 +730,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
|||||||
return item.text
|
return item.text
|
||||||
}
|
}
|
||||||
if (item.type === "resource") {
|
if (item.type === "resource") {
|
||||||
const { blob: _, ...rest } = item.resource
|
const { blob: _blob, ...rest } = item.resource
|
||||||
return JSON.stringify(rest, null, 2)
|
return JSON.stringify(rest, null, 2)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@ -776,7 +776,7 @@ const Chat = forwardRef<ChatRef, ChatProps>((props, ref) => {
|
|||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
applyStatus: ApplyStatus.Idle,
|
applyStatus: ApplyStatus.Idle,
|
||||||
isToolResult: true,
|
isToolResult: true,
|
||||||
content: `<tool_result>${result.returnMsg.promptContent}</tool_result>`,
|
content: `<tool_result>${typeof result.returnMsg.promptContent === 'string' ? result.returnMsg.promptContent : ''}</tool_result>`,
|
||||||
reasoningContent: '',
|
reasoningContent: '',
|
||||||
metadata: {
|
metadata: {
|
||||||
usage: undefined,
|
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
|
// If the chat is empty, show a message to start a new chat
|
||||||
chatMessages.length === 0 && (
|
chatMessages.length === 0 && (
|
||||||
<div className="infio-chat-empty-state">
|
<div className="infio-chat-empty-state">
|
||||||
<ShortcutInfo />
|
<HelloInfo onNavigate={(tab) => setTab(tab)} />
|
||||||
</div>
|
</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 { AlertTriangle, ChevronDown, ChevronRight, FileText, Folder, Power, RotateCcw, Trash2, Wrench } from 'lucide-react'
|
||||||
|
import { Notice } from 'obsidian'
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { useMcpHub } from '../../contexts/McpHubContext'
|
import { useMcpHub } from '../../contexts/McpHubContext'
|
||||||
@ -13,6 +14,11 @@ const McpHubView = () => {
|
|||||||
const [expandedServers, setExpandedServers] = useState<Record<string, boolean>>({});
|
const [expandedServers, setExpandedServers] = useState<Record<string, boolean>>({});
|
||||||
const [activeServerDetailTab, setActiveServerDetailTab] = useState<Record<string, 'tools' | 'resources' | 'errors'>>({});
|
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 fetchServers = async () => {
|
||||||
const hub = await getMcpHub()
|
const hub = await getMcpHub()
|
||||||
console.log('Fetching MCP Servers from hub:', hub)
|
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) => {
|
const toggleServerExpansion = (serverKey: string) => {
|
||||||
setExpandedServers(prev => ({ ...prev, [serverKey]: !prev[serverKey] }));
|
setExpandedServers(prev => ({ ...prev, [serverKey]: !prev[serverKey] }));
|
||||||
@ -79,6 +121,10 @@ const McpHubView = () => {
|
|||||||
setActiveServerDetailTab(prev => ({ ...prev, [serverKey]: tab }));
|
setActiveServerDetailTab(prev => ({ ...prev, [serverKey]: tab }));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleCreateSectionExpansion = () => {
|
||||||
|
setIsCreateSectionExpanded(prev => !prev)
|
||||||
|
}
|
||||||
|
|
||||||
const ToolRow = ({ tool }: { tool: McpTool }) => {
|
const ToolRow = ({ tool }: { tool: McpTool }) => {
|
||||||
return (
|
return (
|
||||||
<div className="infio-mcp-tool-row">
|
<div className="infio-mcp-tool-row">
|
||||||
@ -168,11 +214,66 @@ const McpHubView = () => {
|
|||||||
<span className="infio-mcp-setting-text">启用 MCP 服务器</span>
|
<span className="infio-mcp-setting-text">启用 MCP 服务器</span>
|
||||||
</label>
|
</label>
|
||||||
<p className="infio-mcp-setting-description">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Servers List */}
|
||||||
{settings.mcpEnabled && (
|
{settings.mcpEnabled && (
|
||||||
<div className="infio-mcp-hub-list">
|
<div className="infio-mcp-hub-list">
|
||||||
@ -300,8 +401,7 @@ const McpHubView = () => {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
height: 100%;
|
scroll-behavior: smooth;
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header Styles */
|
/* Header Styles */
|
||||||
@ -376,8 +476,7 @@ const McpHubView = () => {
|
|||||||
background-color: var(--background-primary);
|
background-color: var(--background-primary);
|
||||||
border: 1px solid var(--background-modifier-border);
|
border: 1px solid var(--background-modifier-border);
|
||||||
border-radius: var(--radius-s);
|
border-radius: var(--radius-s);
|
||||||
margin-bottom: 12px;
|
margin-bottom: 16px;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.infio-mcp-hub-item-header {
|
.infio-mcp-hub-item-header {
|
||||||
@ -584,7 +683,21 @@ const McpHubView = () => {
|
|||||||
border-top: 1px solid var(--background-modifier-border);
|
border-top: 1px solid var(--background-modifier-border);
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
padding-top: 8px;
|
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 {
|
.infio-mcp-tabs {
|
||||||
@ -747,6 +860,139 @@ const McpHubView = () => {
|
|||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
color: var(--text-muted);
|
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>
|
`}</style>
|
||||||
</div>
|
</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;
|
|
||||||
@ -1108,6 +1108,84 @@ export class McpHub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MCP server with the given name and configuration
|
||||||
|
* @param name The name of the server to create
|
||||||
|
* @param config JSON string containing the server configuration
|
||||||
|
* @param source Whether to create in global or project scope (defaults to global)
|
||||||
|
*/
|
||||||
|
public async createServer(
|
||||||
|
name: string,
|
||||||
|
config: string,
|
||||||
|
source: "global" | "project" = "global"
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
// Parse the JSON config string
|
||||||
|
let parsedConfig: unknown
|
||||||
|
try {
|
||||||
|
parsedConfig = JSON.parse(config)
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Invalid JSON format in config: ${error instanceof Error ? error.message : String(error)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the parsed config
|
||||||
|
const validatedConfig = this.validateServerConfig(parsedConfig, name)
|
||||||
|
|
||||||
|
// Determine which config file to update
|
||||||
|
let configPath: string
|
||||||
|
if (source === "project") {
|
||||||
|
const projectMcpPath = normalizePath(".infio_json_db/mcp/mcp.json")
|
||||||
|
if (!await this.app.vault.adapter.exists(projectMcpPath)) {
|
||||||
|
// Create project config file if it doesn't exist
|
||||||
|
await this.app.vault.adapter.write(
|
||||||
|
projectMcpPath,
|
||||||
|
JSON.stringify({ mcpServers: {} }, null, 2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
configPath = projectMcpPath
|
||||||
|
} else {
|
||||||
|
configPath = await this.getMcpSettingsFilePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read current config
|
||||||
|
const content = await this.app.vault.adapter.read(configPath)
|
||||||
|
const currentConfig = JSON.parse(content)
|
||||||
|
|
||||||
|
// Validate the config structure
|
||||||
|
if (!currentConfig || typeof currentConfig !== "object") {
|
||||||
|
throw new Error("Invalid config file structure")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure mcpServers object exists
|
||||||
|
if (!currentConfig.mcpServers || typeof currentConfig.mcpServers !== "object") {
|
||||||
|
currentConfig.mcpServers = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if server already exists
|
||||||
|
if (currentConfig.mcpServers[name]) {
|
||||||
|
throw new Error(`Server "${name}" already exists. Use updateServerConfig to modify existing servers.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new server to the config
|
||||||
|
currentConfig.mcpServers[name] = validatedConfig
|
||||||
|
|
||||||
|
// Write the updated config back to file
|
||||||
|
const updatedConfig = {
|
||||||
|
mcpServers: currentConfig.mcpServers,
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.app.vault.adapter.write(configPath, JSON.stringify(updatedConfig, null, 2))
|
||||||
|
|
||||||
|
// Update server connections to connect to the new server
|
||||||
|
await this.updateServerConnections(currentConfig.mcpServers, source)
|
||||||
|
|
||||||
|
console.log(`Successfully created and connected to MCP server: ${name}`)
|
||||||
|
} catch (error) {
|
||||||
|
this.showErrorMessage(`Failed to create MCP server "${name}"`, error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async readResource(serverName: string, uri: string, source: "global" | "project" = "global"): Promise<McpResourceResponse> {
|
async readResource(serverName: string, uri: string, source: "global" | "project" = "global"): Promise<McpResourceResponse> {
|
||||||
const connection = this.findConnection(serverName, source)
|
const connection = this.findConnection(serverName, source)
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
|
|||||||
@ -1,6 +1,18 @@
|
|||||||
export default {
|
export default {
|
||||||
chat: {
|
chat: {
|
||||||
stop: "Stop",
|
stop: "Stop",
|
||||||
|
welcome: {
|
||||||
|
title: "Welcome to Infio Copilot",
|
||||||
|
subtitle: "Explore different modes to enhance your productivity"
|
||||||
|
},
|
||||||
|
navigation: {
|
||||||
|
commands: "Commands",
|
||||||
|
commandsDesc: "Create and manage custom commands for quick actions",
|
||||||
|
customMode: "Custom Mode",
|
||||||
|
customModeDesc: "Define personalized AI modes with specific behaviors",
|
||||||
|
mcp: "MCP",
|
||||||
|
mcpDesc: "Manage Model Context Protocol integrations"
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
failedToLoadConversation: "Failed to load conversation",
|
failedToLoadConversation: "Failed to load conversation",
|
||||||
failedToSaveHistory: "Failed to save chat history",
|
failedToSaveHistory: "Failed to save chat history",
|
||||||
|
|||||||
@ -2,6 +2,18 @@
|
|||||||
export default {
|
export default {
|
||||||
chat: {
|
chat: {
|
||||||
stop: "停止",
|
stop: "停止",
|
||||||
|
welcome: {
|
||||||
|
title: "欢迎使用 Infio Copilot",
|
||||||
|
subtitle: "探索不同模式来提升您的生产力"
|
||||||
|
},
|
||||||
|
navigation: {
|
||||||
|
commands: "命令",
|
||||||
|
commandsDesc: "创建和管理用于快速操作的自定义命令",
|
||||||
|
customMode: "自定义模式",
|
||||||
|
customModeDesc: "定义具有特定行为的个性化 AI 模式",
|
||||||
|
mcp: "MCP",
|
||||||
|
mcpDesc: "管理模型上下文协议集成"
|
||||||
|
},
|
||||||
errors: {
|
errors: {
|
||||||
failedToLoadConversation: "加载对话失败",
|
failedToLoadConversation: "加载对话失败",
|
||||||
failedToSaveHistory: "保存聊天记录失败",
|
failedToSaveHistory: "保存聊天记录失败",
|
||||||
|
|||||||
@ -2242,6 +2242,8 @@ button.infio-chat-input-model-select {
|
|||||||
|
|
||||||
/* 禁用Obsidian模态框的滚动 */
|
/* 禁用Obsidian模态框的滚动 */
|
||||||
.modal.mod-image-selector .modal-content {
|
.modal.mod-image-selector .modal-content {
|
||||||
overflow: hidden !important;
|
max-width: 80vw;
|
||||||
|
max-height: 80vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user