Merge branch 'develop' into feature/model_zy

This commit is contained in:
zhaoying
2026-03-05 10:32:02 +08:00
36 changed files with 1182 additions and 179 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-03 16:58:03
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-03 13:46:22
* @Last Modified time: 2026-03-04 12:10:44
*/
/**
* Conversation Page
@@ -267,8 +267,8 @@ const Conversation: FC = () => {
currentConversationId = newId
break
case 'message':
const { content, chunk, conversation_id: curId } = item.data as { content: string; chunk: string; conversation_id: string; }
updateAssistantMessage(content ?? chunk)
const { content, conversation_id: curId } = item.data as { content: string; conversation_id: string; }
updateAssistantMessage(content)
if (curId) {
currentConversationId = curId;

View File

@@ -185,6 +185,7 @@ const OntologyClassExtractModal = forwardRef<OntologyClassExtractModalRef, Ontol
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 2000 },
{ pattern: /^(?!\s*$).+$/, message: t('common.notAllSpaces') },
]}
>
<Input.TextArea placeholder={t('ontology.scenarioPlaceholder')} />

View File

@@ -0,0 +1,315 @@
import React, { useState, useRef, type ReactNode } from 'react';
import { Input, Button, Spin, App } from 'antd';
import { SearchOutlined, SettingOutlined, GlobalOutlined, SyncOutlined } from '@ant-design/icons';
import { useTranslation } from 'react-i18next';
import MarketConfigModal, { type MarketConfigModalRef } from './components/MarketConfigModal';
interface MarketSource {
id: string;
name: string;
category: string;
icon: string;
url: string;
desc: string;
apiKey: string;
connected: boolean;
mcpCount: number;
}
interface MarketMcp {
id: string;
name: string;
provider: string;
type: string;
desc: string;
downloads?: string;
stars?: string;
icon: string;
configTemplate: any;
}
interface MarketCategory {
id: string;
name: string;
icon: string;
}
const Market: React.FC<{ getStatusTag?: (status: string) => ReactNode }> = () => {
const { t } = useTranslation();
const { message } = App.useApp();
const [loading, setLoading] = useState(false);
const [selectedSource, setSelectedSource] = useState<string | null>(null);
const marketConfigModalRef = useRef<MarketConfigModalRef>(null);
const [marketSources, setMarketSources] = useState<MarketSource[]>([
{ id: 'smithery', name: 'Smithery', category: 'official', icon: '🔧', url: 'https://mcp.smithery.ai', desc: '官方 MCP 服务市场,提供丰富的 MCP 服务', apiKey: '', connected: false, mcpCount: 2847 },
{ id: 'mcpmarket', name: 'MCP Market', category: 'official', icon: '🏪', url: 'https://mcpmarket.com', desc: '综合性 MCP 市场平台', apiKey: '', connected: false, mcpCount: 1523 },
{ id: 'glama', name: 'Glama.ai MCP', category: 'official', icon: '✨', url: 'https://glama.ai/mcp', desc: 'Glama AI 提供的 MCP 服务集合', apiKey: '', connected: false, mcpCount: 892 },
{ id: 'github-mcp', name: 'modelcontextprotocol/servers', category: 'official', icon: '🐙', url: 'https://github.com/modelcontextprotocol/servers', desc: 'GitHub 官方 MCP 服务器仓库', apiKey: '', connected: true, mcpCount: 156 },
{ id: 'aliyun-bailian', name: '阿里云百炼 MCP', category: 'china-cloud', icon: '☁️', url: 'https://bailian.console.aliyun.com/mcp', desc: '阿里云百炼平台 MCP 市场', apiKey: '', connected: false, mcpCount: 423 },
{ id: 'modelscope', name: '魔搭社区 MCP', category: 'china-cloud', icon: '🎭', url: 'https://modelscope.cn/mcp', desc: '阿里达摩院魔搭社区 MCP 市场', apiKey: '', connected: false, mcpCount: 312 },
]);
const [categories] = useState<MarketCategory[]>([
{ id: 'official', name: '官方/综合', icon: '🌐' },
{ id: 'china-cloud', name: '国内云', icon: '☁️' },
{ id: 'community', name: '社区/垂直', icon: '👥' }
]);
const [mcpCache, setMcpCache] = useState<Record<string, MarketMcp[]>>({
'github-mcp': [
{ id: 'gh-1', name: 'Fetch', provider: 'modelcontextprotocol', type: 'Hosted', desc: '使用浏览器模拟大型语言模型检索和处理网页内容', downloads: '203.7m', stars: '308.2k', icon: '🌐', configTemplate: {} },
{ id: 'gh-2', name: 'Filesystem', provider: 'modelcontextprotocol', type: 'Local', desc: '安全的文件系统操作,支持读写文件和目录管理', downloads: '156.2m', stars: '245.1k', icon: '📁', configTemplate: {} },
{ id: 'gh-3', name: 'GitHub', provider: 'modelcontextprotocol', type: 'Hosted', desc: 'GitHub API 集成支持仓库、Issue、PR 等操作', downloads: '89.4m', stars: '178.3k', icon: '🐙', configTemplate: {} },
]
});
const [searchKeyword, setSearchKeyword] = useState('');
const handleSelectSource = (sourceId: string) => {
setSelectedSource(sourceId);
};
const handleRefresh = (sourceId: string) => {
setLoading(true);
setTimeout(() => {
// 模拟刷新数据
const source = marketSources.find(s => s.id === sourceId);
if (source) {
message.success(`${source.name} 列表已刷新`);
}
setLoading(false);
}, 600);
};
const handleOpenConfig = (sourceId: string) => {
const source = marketSources.find(s => s.id === sourceId);
if (source) {
marketConfigModalRef.current?.handleOpen(source);
}
};
const handleConnect = (sourceId: string, apiKey: string) => {
// 更新市场源状态
setMarketSources(prev => prev.map(source => {
if (source.id === sourceId) {
return {
...source,
apiKey,
connected: true
};
}
return source;
}));
// 模拟获取MCP列表
setTimeout(() => {
const source = marketSources.find(s => s.id === sourceId);
if (source && !mcpCache[sourceId]) {
// 生成模拟数据
const mockData: MarketMcp[] = [
{ id: `${sourceId}-1`, name: `${source.name} 服务 1`, provider: source.name, type: 'Hosted', desc: `来自 ${source.name} 的 MCP 服务`, downloads: '10.2m', stars: '23.4k', icon: '🔧', configTemplate: {} },
{ id: `${sourceId}-2`, name: `${source.name} 服务 2`, provider: source.name, type: 'Local', desc: `来自 ${source.name} 的本地 MCP 服务`, downloads: '8.5m', stars: '18.7k', icon: '⚙️', configTemplate: {} }
];
setMcpCache(prev => ({
...prev,
[sourceId]: mockData
}));
}
message.success(`已连接 ${source?.name}`);
}, 800);
};
const renderSourceDetail = () => {
if (!selectedSource) {
return (
<div className="rb:flex rb:flex-col rb:items-center rb:justify-center rb:h-full rb:text-center">
<div className="rb:text-6xl rb:mb-4">🏪</div>
<h3 className="rb:text-lg rb:font-semibold rb:text-gray-900 rb:mb-2"> MCP </h3>
<p className="rb:text-sm rb:text-gray-600 rb:max-w-md"> MCP </p>
</div>
);
}
const source = marketSources.find(s => s.id === selectedSource);
if (!source) return null;
const mcpList = mcpCache[selectedSource] || [];
const filteredList = mcpList.filter(mcp =>
mcp.name.toLowerCase().includes(searchKeyword.toLowerCase()) ||
mcp.desc.toLowerCase().includes(searchKeyword.toLowerCase())
);
return (
<>
<div className="rb:flex rb:justify-between rb:items-start rb:pb-6 rb:border-b rb:border-gray-200 rb:mb-6">
<div className="rb:flex rb:gap-4">
<div className="rb:text-5xl rb:w-16 rb:h-16 rb:flex rb:items-center rb:justify-center rb:bg-gray-50 rb:rounded-xl rb:flex-shrink-0">
{source.icon}
</div>
<div className="rb:flex-1">
<h2 className="rb:text-xl rb:font-semibold rb:text-gray-900 rb:mb-2">{source.name}</h2>
<p className="rb:text-sm rb:text-gray-600 rb:leading-relaxed">{source.desc}</p>
</div>
</div>
<div className="rb:flex rb:gap-3">
<Button icon={<SettingOutlined />} onClick={() => handleOpenConfig(selectedSource)}>
</Button>
<Button type="primary" icon={<GlobalOutlined />} onClick={() => window.open(source.url, '_blank')}>
</Button>
</div>
</div>
<div className="rb:mt-6">
<div className="rb:flex rb:justify-between rb:items-center rb:mb-5">
<h3 className="rb:text-base rb:font-semibold rb:text-gray-900 rb:m-0">
MCP <span className="rb:text-gray-600 rb:font-normal">({mcpList.length})</span>
</h3>
<div className="rb:flex rb:gap-3 rb:items-center">
{source.connected && (
<Button size="small" icon={<SyncOutlined />} onClick={() => handleRefresh(selectedSource)}>
</Button>
)}
{mcpList.length > 0 && (
<Input
prefix={<SearchOutlined />}
placeholder="搜索服务..."
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
style={{ width: 200 }}
/>
)}
</div>
</div>
{mcpList.length > 0 ? (
<Spin spinning={loading}>
<div className="rb:grid rb:grid-cols-1 md:rb:grid-cols-2 lg:rb:grid-cols-3 rb:gap-4">
{filteredList.map(mcp => (
<div
key={mcp.id}
className="rb:bg-white rb:border rb:border-gray-200 rb:rounded-lg rb:p-4 rb:transition-all rb:duration-200 hover:rb:shadow-lg hover:rb:border-gray-300"
>
<div className="rb:flex rb:justify-between rb:items-center rb:mb-3">
<div className="rb:text-3xl rb:w-12 rb:h-12 rb:flex rb:items-center rb:justify-center rb:bg-gray-50 rb:rounded-lg">
{mcp.icon}
</div>
<span className={`rb:px-2 rb:py-1 rb:rounded rb:text-xs rb:font-medium ${
mcp.type === 'Hosted'
? 'rb:bg-blue-50 rb:text-blue-700'
: 'rb:bg-gray-100 rb:text-gray-600'
}`}>
{mcp.type}
</span>
</div>
<h3 className="rb:text-base rb:font-semibold rb:text-gray-900 rb:mb-1">{mcp.name}</h3>
{mcp.provider && (
<div className="rb:mb-2">
<span className="rb:text-xs rb:text-gray-500">@ {mcp.provider}</span>
</div>
)}
<p className="rb:text-sm rb:text-gray-600 rb:leading-relaxed rb:mb-3 rb:min-h-[42px]">{mcp.desc}</p>
<div className="rb:flex rb:gap-4 rb:mb-3 rb:pt-3 rb:border-t rb:border-gray-100">
{mcp.downloads && (
<span className="rb:flex rb:items-center rb:gap-1 rb:text-xs rb:text-gray-500">
<GlobalOutlined /> {mcp.downloads}
</span>
)}
{mcp.stars && (
<span className="rb:flex rb:items-center rb:gap-1 rb:text-xs rb:text-gray-500">
{mcp.stars}
</span>
)}
</div>
<div className="rb:flex rb:justify-end">
<Button type="primary" size="small">
+
</Button>
</div>
</div>
))}
</div>
</Spin>
) : (
<div className="rb:flex rb:flex-col rb:items-center rb:justify-center rb:py-16 rb:text-center">
<div className="rb:text-6xl rb:mb-4">{source.connected ? '📭' : '🔌'}</div>
<h4 className="rb:text-base rb:font-semibold rb:text-gray-900 rb:mb-2">
{source.connected ? '暂无可用的 MCP 服务' : '尚未连接此市场'}
</h4>
<p className="rb:text-sm rb:text-gray-600 rb:mb-4">
{source.connected ? '该市场暂时没有可用的服务' : '点击右上角"配置"按钮设置连接信息'}
</p>
{!source.connected && (
<Button type="primary" onClick={() => handleOpenConfig(selectedSource)}>
</Button>
)}
</div>
)}
</div>
</>
);
};
return (
<div className="rb:flex rb:gap-4 rb:h-[calc(100vh-178px)]">
{/* 左侧市场源列表 */}
<div className="rb:w-70 rb:bg-white rb:rounded-lg rb:border rb:border-gray-200 rb:overflow-y-auto rb:flex-shrink-0">
<div className="rb:p-4 rb:border-b rb:border-gray-200">
<span className="rb:text-base rb:font-semibold rb:text-gray-900">MCP </span>
</div>
{categories.map(cat => (
<div key={cat.id} className="rb:py-3 rb:border-b rb:border-gray-100 last:rb:border-b-0">
<div className="rb:flex rb:items-center rb:gap-2 rb:px-4 rb:py-2 rb:text-xs rb:font-medium rb:text-gray-500 rb:uppercase">
<span className="rb:text-sm">{cat.icon}</span>
<span>{cat.name}</span>
</div>
<div className="rb:px-2 rb:py-1">
{marketSources
.filter(s => s.category === cat.id)
.map(source => (
<div
key={source.id}
className={`rb:flex rb:items-center rb:gap-2 rb:px-3 rb:py-2.5 rb:rounded-md rb:cursor-pointer rb:transition-all rb:relative ${
selectedSource === source.id
? 'rb:bg-blue-50 rb:text-blue-600'
: 'hover:rb:bg-gray-50'
}`}
onClick={() => handleSelectSource(source.id)}
>
<span className="rb:text-lg rb:flex-shrink-0">{source.icon}</span>
<span className="rb:flex-1 rb:text-sm rb:font-medium rb:overflow-hidden rb:text-ellipsis rb:whitespace-nowrap">
{source.name}
</span>
<span className="rb:text-xs rb:text-gray-500 rb:px-1.5 rb:py-0.5 rb:bg-gray-100 rb:rounded-full">
{source.mcpCount}
</span>
{source.connected && (
<span className="rb:text-green-500 rb:text-[8px] rb:ml-1"></span>
)}
</div>
))}
</div>
</div>
))}
</div>
{/* 右侧内容区 */}
<div className="rb:flex-1 rb:bg-white rb:rounded-lg rb:border rb:border-gray-200 rb:overflow-hidden">
<div className="rb:h-full rb:overflow-y-auto rb:p-6">
{renderSourceDetail()}
</div>
</div>
{/* 配置弹窗 */}
<MarketConfigModal
ref={marketConfigModalRef}
onConnect={handleConnect}
/>
</div>
);
};
export default Market;

View File

@@ -6,6 +6,7 @@ import type { CustomToolItem, CustomToolModalRef, ToolItem } from '../types'
import RbModal from '@/components/RbModal';
import { parseSchema, addTool, updateTool } from '@/api/tools';
import Table from '@/components/Table';
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
interface CustomToolModalProps {
@@ -134,7 +135,11 @@ const CustomToolModal = forwardRef<CustomToolModalRef, CustomToolModalProps>(({
<Form.Item
name="name"
label={t('tool.name')}
rules={[{ required: true, message: t('common.enterNamePlaceholder') }]}
rules={[
{ required: true, message: t('tool.enterNamePlaceholder') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('tool.enterNamePlaceholder')} />
</Form.Item>

View File

@@ -0,0 +1,173 @@
import { forwardRef, useImperativeHandle, useState } from 'react';
import { Form, Input, Button, App, Space } from 'antd';
import { useTranslation } from 'react-i18next';
import { CopyOutlined, EyeInvisibleOutlined, EyeOutlined } from '@ant-design/icons';
import RbModal from '@/components/RbModal';
const FormItem = Form.Item;
interface MarketSource {
id: string;
name: string;
icon: string;
url: string;
desc: string;
apiKey: string;
connected: boolean;
}
interface MarketConfigModalProps {
onConnect: (sourceId: string, apiKey: string) => void;
}
export interface MarketConfigModalRef {
handleOpen: (source: MarketSource) => void;
handleClose: () => void;
}
const MarketConfigModal = forwardRef<MarketConfigModalRef, MarketConfigModalProps>(({
onConnect
}, ref) => {
const { t } = useTranslation();
const { message } = App.useApp();
const [visible, setVisible] = useState(false);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [currentSource, setCurrentSource] = useState<MarketSource | null>(null);
const [showApiKey, setShowApiKey] = useState(false);
const handleClose = () => {
setVisible(false);
form.resetFields();
setLoading(false);
setCurrentSource(null);
setShowApiKey(false);
};
const handleOpen = (source: MarketSource) => {
setCurrentSource(source);
form.setFieldsValue({
url: source.url,
apiKey: source.apiKey,
});
setVisible(true);
};
const handleSave = () => {
form
.validateFields()
.then((values) => {
if (!currentSource) return;
setLoading(true);
// 模拟连接延迟
setTimeout(() => {
onConnect(currentSource.id, values.apiKey || '');
message.success(`正在连接 ${currentSource.name}...`);
setLoading(false);
handleClose();
}, 500);
})
.catch((err) => {
console.log('表单验证失败:', err);
});
};
const handleCopyUrl = () => {
if (currentSource?.url) {
navigator.clipboard.writeText(currentSource.url).then(() => {
message.success(t('common.copySuccess'));
});
}
};
useImperativeHandle(ref, () => ({
handleOpen,
handleClose
}));
if (!currentSource) return null;
return (
<RbModal
title={`配置 ${currentSource.name}`}
open={visible}
onCancel={handleClose}
okText="保存并连接"
onOk={handleSave}
confirmLoading={loading}
width={600}
>
<div>
{/* 市场源信息头部 */}
<div className="rb:flex rb:gap-4 rb:mb-6 rb:p-4 rb:bg-gray-50 rb:rounded-lg">
<div className="rb:text-4xl rb:w-16 rb:h-16 rb:flex rb:items-center rb:justify-center rb:bg-white rb:rounded-lg rb:flex-shrink-0">
{currentSource.icon}
</div>
<div className="rb:flex-1">
<h3 className="rb:text-base rb:font-semibold rb:mb-1 rb:text-gray-900">{currentSource.name}</h3>
<p className="rb:text-sm rb:text-gray-600 rb:leading-relaxed">{currentSource.desc}</p>
</div>
</div>
<Form
form={form}
layout="vertical"
>
{/* 市场地址 */}
<FormItem
name="url"
label="市场地址"
>
<Space.Compact style={{ width: '100%' }}>
<Input
readOnly
placeholder="市场地址"
/>
<Button
icon={<CopyOutlined />}
onClick={handleCopyUrl}
>
</Button>
</Space.Compact>
</FormItem>
{/* API Key */}
<FormItem
name="apiKey"
label={
<span>
API Key <span className="rb:text-gray-400 rb:font-normal">()</span>
</span>
}
extra="部分市场需要 API Key 才能获取完整的服务列表"
>
<Space.Compact style={{ width: '100%' }}>
<Input
type={showApiKey ? 'text' : 'password'}
placeholder="输入 API Key 以获取更多服务"
autoComplete="off"
/>
<Button
icon={showApiKey ? <EyeInvisibleOutlined /> : <EyeOutlined />}
onClick={() => setShowApiKey(!showApiKey)}
/>
</Space.Compact>
</FormItem>
{/* 连接状态 */}
<div className="rb:flex rb:items-center rb:gap-2 rb:p-3 rb:bg-gray-50 rb:rounded rb:text-sm">
<span className="rb:text-gray-600"></span>
<span className={`rb:font-medium ${currentSource.connected ? 'rb:text-green-600' : 'rb:text-gray-400'}`}>
{currentSource.connected ? '● 已连接' : '○ 未连接'}
</span>
</div>
</Form>
</div>
</RbModal>
);
});
export default MarketConfigModal;

View File

@@ -9,6 +9,7 @@ import RequestHeaderModal from './RequestHeaderModal';
import Table from '@/components/Table';
import { addTool, updateTool, testConnection } from '@/api/tools'
import type { McpServiceModalRef } from '../types'
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -168,14 +169,22 @@ const McpServiceModal = forwardRef<McpServiceModalRef, McpServiceModalProps>(({
name={['config', "server_url"]}
label={t('tool.serviceEndpoint')}
extra={t('tool.serviceEndpointExtra')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 500 },
{ pattern: /^https?:\/\/\S+$/, message: t('tool.serverUrlInvalid') },
]}
>
<Input placeholder={t('tool.serviceEndpointPlaceholder')} />
</FormItem>
<Form.Item
name="name"
label={t('tool.name')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ max: 50 },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
]}
>
<Input placeholder={t('tool.namePlaceholder')} />
</Form.Item>
@@ -201,6 +210,7 @@ const McpServiceModal = forwardRef<McpServiceModalRef, McpServiceModalProps>(({
<FormItem
name="description"
label={t('tool.description')}
rules={[{ max: 500 }]}
>
<Input.TextArea rows={3} placeholder={t('common.inputPlaceholder', { title: t('tool.description') })}/>
</FormItem>

View File

@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
import type { RequestHeader, RequestHeaderModalRef } from './McpServiceModal'
import RbModal from '@/components/RbModal'
import { stringRegExp } from '@/utils/validator';
const FormItem = Form.Item;
@@ -82,7 +83,11 @@ const RequestHeaderModal = forwardRef<RequestHeaderModalRef, RequestHeaderModalP
<FormItem
name="key"
label={t('tool.requestHeaderName')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ pattern: /^[a-zA-Z0-9][a-zA-Z0-9\-_]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/, message: t('tool.requestHeaderKeyInvalid') },
{ max: 100 }
]}
>
<Input placeholder={t('common.enter')} />
</FormItem>
@@ -90,7 +95,11 @@ const RequestHeaderModal = forwardRef<RequestHeaderModalRef, RequestHeaderModalP
<FormItem
name="value"
label={t('tool.requestHeaderValue')}
rules={[{ required: true, message: t('common.pleaseEnter') }]}
rules={[
{ required: true, message: t('common.pleaseEnter') },
{ pattern: stringRegExp, message: t('common.nameInvalid') },
{ max: 2000 }
]}
>
<Input placeholder={t('common.enter',)} />
</FormItem>

View File

@@ -1,3 +1,11 @@
/*
* @Description:
* @Version: 0.0.1
* @Author: yujiangping
* @Date: 2026-01-05 17:22:23
* @LastEditors: yujiangping
* @LastEditTime: 2026-03-04 15:12:48
*/
import React, { useState } from 'react';
import { Tabs } from 'antd';
import { useTranslation } from 'react-i18next';
@@ -5,9 +13,10 @@ import { useTranslation } from 'react-i18next';
import Mcp from './Mcp';
import Inner from './Inner';
import Custom from './Custom';
import Market from './Market';
import Tag from '@/components/Tag'
const tabKeys = ['mcp', 'inner', 'custom']
const tabKeys = ['mcp', 'inner', 'custom', 'market']
const ToolManagement: React.FC = () => {
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState('mcp');
@@ -45,6 +54,7 @@ const ToolManagement: React.FC = () => {
{activeTab === 'mcp' && <Mcp getStatusTag={getStatusTag} />}
{activeTab === 'inner' && <Inner getStatusTag={getStatusTag} />}
{activeTab === 'custom' && <Custom getStatusTag={getStatusTag} />}
{/* {activeTab === 'market' && <Market getStatusTag={getStatusTag} />} */}
</div>
);
};

View File

@@ -6,6 +6,7 @@ import {
getShortTerm,
} from '@/api/memory'
import Empty from '@/components/Empty'
import Markdown from '@/components/Markdown'
interface ShortTermItem {
retrieval: Array<{ query: string; retrieval: string[]; }>;
@@ -85,7 +86,9 @@ const ShortTermDetail: FC = () => {
))}
<div>
<div className="rb:font-medium rb:leading-5 rb:mb-1">{t('shortTermDetail.answer')}</div>
<div className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-3 rb:py-2.5 rb:leading-5">{vo.answer}</div>
<div className="rb:bg-[#FFFFFF] rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-3 rb:py-2.5 rb:leading-5">
<Markdown content={vo.answer} />
</div>
</div>
</Space>
</div>
@@ -103,7 +106,9 @@ const ShortTermDetail: FC = () => {
: data.long_term?.map((vo, voIdx) => (
<div key={voIdx} className="rb:leading-5 rb:shadow-[inset_3px_0px_0px_0px_#155EEF] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:px-6 rb:py-3">
<div className="rb:mb-1 rb:font-medium rb:leading-5.5">{vo.query}</div>
<div className="rb:mt-1 rb:leading-5 rb:text-[#5B6167] rb:text-[12px]">{vo.retrieval}</div>
<div className="rb:mt-1 rb:leading-5 rb:text-[#5B6167] rb:text-[12px]">
<Markdown content={vo.retrieval} />
</div>
</div>
))
}

View File

@@ -174,8 +174,8 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
*/
const handleStreamMessage = (data: SSEMessage[]) => {
data.forEach(item => {
const { chunk, conversation_id, node_id, cycle_id, cycle_idx, input, output, error, elapsed_time, status } = item.data as {
chunk: string;
const { content, conversation_id, node_id, cycle_id, cycle_idx, input, output, error, elapsed_time, status } = item.data as {
content: string;
conversation_id: string | null;
cycle_id: string;
cycle_idx: number;
@@ -202,7 +202,7 @@ const Chat = forwardRef<ChatRef, { appId: string; graphRef: GraphRef }>(({ appId
if (lastIndex >= 0) {
newList[lastIndex] = {
...newList[lastIndex],
content: newList[lastIndex].content + chunk
content: newList[lastIndex].content + content
}
}
return newList