update hello world

This commit is contained in:
duanfuxiang 2025-06-02 23:00:56 +08:00
parent b1315aa6b1
commit d83ea57fca
8 changed files with 507 additions and 53 deletions

View File

@ -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>
)
}

View 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;

View File

@ -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>
)

View File

@ -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;

View File

@ -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> {
const connection = this.findConnection(serverName, source)
if (!connection) {

View File

@ -1,6 +1,18 @@
export default {
chat: {
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: {
failedToLoadConversation: "Failed to load conversation",
failedToSaveHistory: "Failed to save chat history",

View File

@ -2,6 +2,18 @@
export default {
chat: {
stop: "停止",
welcome: {
title: "欢迎使用 Infio Copilot",
subtitle: "探索不同模式来提升您的生产力"
},
navigation: {
commands: "命令",
commandsDesc: "创建和管理用于快速操作的自定义命令",
customMode: "自定义模式",
customModeDesc: "定义具有特定行为的个性化 AI 模式",
mcp: "MCP",
mcpDesc: "管理模型上下文协议集成"
},
errors: {
failedToLoadConversation: "加载对话失败",
failedToSaveHistory: "保存聊天记录失败",

View File

@ -2242,6 +2242,8 @@ button.infio-chat-input-model-select {
/* 禁用Obsidian模态框的滚动 */
.modal.mod-image-selector .modal-content {
overflow: hidden !important;
max-width: 80vw;
max-height: 80vh;
}