diff --git a/src/pages/chatConversation/ChatConversation.less b/src/pages/chatConversation/ChatConversation.less
index af8983a..ee02736 100644
--- a/src/pages/chatConversation/ChatConversation.less
+++ b/src/pages/chatConversation/ChatConversation.less
@@ -1,4 +1,3 @@
-
// // 滚动条样式
// .chat-list::-webkit-scrollbar {
// width: 6px;
@@ -193,6 +192,14 @@
align-items: center;
gap: 10px;
}
+
+ // 流式输出样式
+ &.streaming {
+ .ds-message-content {
+ background: #f8f9fa;
+ border: 1px solid #e3e8ef;
+ }
+ }
}
.ds-avatar {
@@ -208,8 +215,8 @@
color: #355070;
&.user {
- background: #e9eef5; // 用户头像也用这个颜色
- color: #4f5fa3;
+ background: #e9eef5; // 用户头像也用这个颜色
+ color: #4f5fa3;
}
}
@@ -253,9 +260,10 @@
}
.ds-message-content {
- display: inline-block;
- max-width: 100%;
- min-width: 48px;
+ // display: inline-block;
+ display: block;
+ max-width: 100%;
+ min-width: 48px;
background: #fff;
border: 1px solid #e3e8ef;
border-radius: 14px;
@@ -263,7 +271,8 @@
font-size: 17px;
color: #1f2329;
position: relative;
- overflow: hidden;
+ // overflow: hidden;
+ overflow: visible;
word-break: break-word;
box-shadow: 0 2px 4px rgba(0, 0, 0, .04);
@@ -300,6 +309,66 @@
}
}
+// 思考内容样式 - 浅灰色
+.ds-thought-content {
+ color: #999;
+ font-style: italic;
+ margin-bottom: 12px;
+ padding: 8px 12px;
+ background: #f5f5f5;
+ border-radius: 8px;
+ border-left: 3px solid #ddd;
+
+ p {
+ margin: 0 0 4px;
+ color: #999;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+// 回复内容样式 - 正常颜色
+.ds-response-content {
+ color: #1f2329;
+
+ p {
+ margin: 0 0 8px;
+ color: #1f2329;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+}
+
+// 流式输出指示器
+.ds-streaming-indicator {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 8px;
+ padding: 4px 8px;
+ background: #f0f0f0;
+ border-radius: 6px;
+ font-size: 12px;
+ color: #666;
+
+ .streaming-text {
+ font-style: italic;
+ }
+}
+
+// 流式输入光标
+.ds-typing-cursor {
+ display: inline-block;
+ animation: blink 1s infinite;
+ color: #3478f6;
+ font-weight: bold;
+ margin-left: 2px;
+}
+
.typing-dots {
display: inline-flex;
gap: 4px;
@@ -339,7 +408,7 @@
}
.ds-input-bar {
- // display: flex;
+ // display: flex;
// align-items: flex-end;
// position: fixed;
left: 0;
@@ -449,6 +518,7 @@
opacity: .3;
}
}
+
.ds-select,
.ds-select .ant-select-selector,
.ds-select .ant-select-selection-item,
@@ -456,6 +526,7 @@
font-size: 12px !important;
color: #808791 !important;
}
+
.ds-select-dropdown .ant-select-item {
font-size: 12px !important;
color: #808791 !important;
diff --git a/src/pages/config/api.js b/src/pages/config/api.js
deleted file mode 100644
index ec4c2d1..0000000
--- a/src/pages/config/api.js
+++ /dev/null
@@ -1,183 +0,0 @@
-// API配置文件
-export const API_CONFIG = {
- // DeepSeek API配置
- DEEPSEEK: {
- BASE_URL: 'https://api.deepseek.com/v1',
- MODEL: 'deepseek-chat',
- API_KEY:
- process.env.UMI_APP_DEEPSEEK_API_KEY ||
- process.env.REACT_APP_DEEPSEEK_API_KEY ||
- 'your-api-key-here',
- MAX_TOKENS: 2000,
- TEMPERATURE: 0.7,
- SYSTEM_PROMPT: '你是一个智能AI助手,请用简洁、专业、友好的方式回答用户的问题。'
- },
-
- // 其他API配置
- OPENAI: {
- BASE_URL: 'https://api.openai.com/v1',
- MODEL: 'gpt-3.5-turbo',
- API_KEY: process.env.UMI_APP_OPENAI_API_KEY || '',
- MAX_TOKENS: 2000,
- TEMPERATURE: 0.7
- }
-}
-
-// 调试:开发阶段临时查看是否拿到
-if (process.env.NODE_ENV === 'development') {
- // 只显示是否存在,不打印具体 key
- // eslint-disable-next-line no-console
- console.log(
- 'DeepSeek key loaded =',
- !!process.env.UMI_APP_DEEPSEEK_API_KEY
- )
-}
-
-// DeepSeek 调用
-export const callDeepSeekAPI = async (messages, options = {}) => {
- const cfg = API_CONFIG.DEEPSEEK
- const apiKey = cfg.API_KEY
- if (!apiKey || apiKey === 'your-api-key-here') {
- throw new Error('NO_API_KEY')
- }
-
- // 组装消息(如果历史里已经含 system 就不重复)
- const hasSystem = messages.some(m => m.role === 'system')
- const finalMessages = hasSystem
- ? messages
- : [{ role: 'system', content: cfg.SYSTEM_PROMPT }, ...messages]
-
- const payload = {
- model: cfg.MODEL,
- messages: finalMessages.map(m => ({ role: m.role, content: m.content })),
- temperature: options.temperature ?? cfg.TEMPERATURE,
- max_tokens: options.maxTokens ?? cfg.MAX_TOKENS,
- stream: false
- }
-
- const resp = await fetch(`${cfg.BASE_URL}/chat/completions`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- Authorization: `Bearer ${apiKey}`
- },
- body: JSON.stringify(payload)
- })
-
- const data = await resp.json().catch(() => ({}))
-
- if (!resp.ok) {
- // 统一抛出,交给 handleAPIError
- const msg = data?.error?.message || resp.statusText || 'API_ERROR'
- const err = new Error(msg)
- err.status = resp.status
- err.code = data?.error?.code
- throw err
- }
-
- const content = data?.choices?.[0]?.message?.content?.trim()
- if (!content) throw new Error('EMPTY_RESPONSE')
- return content
-}
-
-// 本地服务调用(调试版:打印详细日志,方便逐步排查)
-export const callLocalChatAPI = async (prompt) => {
- const base = '/api/chat/stream'
- const url = `${base}?prompt=${encodeURIComponent(prompt)}`
-
- // 逐步调试日志
- // eslint-disable-next-line no-console
- console.debug('[callLocalChatAPI] url:', url)
- try {
- // eslint-disable-next-line no-console
- console.debug('[callLocalChatAPI] fetch start')
- const resp = await fetch(url, { method: 'GET' })
- // eslint-disable-next-line no-console
- console.debug('[callLocalChatAPI] fetch done', {
- ok: resp.ok,
- status: resp.status,
- statusText: resp.statusText,
- headers: Object.fromEntries(resp.headers.entries())
- })
-
- if (!resp.ok) {
- const txt = await resp.text().catch(() => null)
- // eslint-disable-next-line no-console
- console.error('[callLocalChatAPI] non-OK response body:', txt)
- const msg = txt || resp.statusText || `HTTP_${resp.status}`
- const err = new Error(msg)
- err.status = resp.status
- throw err
- }
-
- // 最简单:一次性读取完整响应文本(非流式),然后从 SSE 格式中提取第一个 event:message 的 data
- const raw = await resp.text().catch(e => {
- // eslint-disable-next-line no-console
- console.error('[callLocalChatAPI] text read error:', e)
- return ''
- })
- // eslint-disable-next-line no-console
- console.debug('[callLocalChatAPI] raw length:', raw?.length || 0, 'preview:', (raw || '').slice(0, 500))
- if (!raw) throw new Error('EMPTY_RESPONSE')
-
- // 按空行分块,优先找到 event:message 的 data
- const blocks = raw.split(/\r?\n\r?\n/).map(b => b.trim()).filter(Boolean)
- for (const block of blocks) {
- const lines = block.split(/\r?\n/).map(l => l.trim())
- let eventType = ''
- const dataLines = []
- for (const line of lines) {
- if (/^event\s*:/i.test(line)) {
- eventType = line.split(':').slice(1).join(':').trim()
- } else if (/^data\s*:/i.test(line)) {
- dataLines.push(line.split(':').slice(1).join(':'))
- }
- }
- if (eventType === 'message' && dataLines.length > 0) {
- const message = dataLines.join('\n').trim()
- // eslint-disable-next-line no-console
- console.debug('[callLocalChatAPI] parsed message data preview:', message.slice(0, 300))
- if (!message) throw new Error('EMPTY_RESPONSE')
- return message
- }
- }
-
- // 回退:取第一个 data: 行的内容
- const firstDataMatch = raw.match(/^[ \t]*data\s*:(.*)$/im)
- if (firstDataMatch && firstDataMatch[1]) {
- const fallback = firstDataMatch[1].trim()
- // eslint-disable-next-line no-console
- console.debug('[callLocalChatAPI] fallback data:', fallback.slice(0, 300))
- return fallback
- }
-
- // 最后回退:返回原始文本
- return raw
- } catch (err) {
- // eslint-disable-next-line no-console
- console.error('[callLocalChatAPI] ERROR:', err)
- throw err
- }
-}
-
-// 错误处理函数
-export const handleAPIError = (error) => {
- const raw = error?.message || ''
- const status = error?.status
- const code = error?.code
-
- if (raw === 'NO_API_KEY') return '未配置 API Key,请在 .env 里加 UMI_APP_DEEPSEEK_API_KEY 并重启。'
- if (raw === 'EMPTY_RESPONSE') return '接口返回空结果,请重试。'
-
- // 典型鉴权问题
- if (status === 401 || /unauthorized|invalid api key/i.test(raw) || code === 'invalid_api_key') {
- return 'API密钥无效,请检查是否正确。'
- }
-
- if (status === 429 || /rate limit/i.test(raw)) return '请求过多,被限流。'
- if (status === 500) return '服务端错误,请稍后再试。'
- if (/timeout/i.test(raw)) return '请求超时,请检查网络。'
- if (/certificate|SSL/i.test(raw)) return '证书/网络问题,请更换网络或代理。'
-
- return `调用失败:${raw || '未知错误'}`
-}
diff --git a/src/pages/nav_system_content/SystemContentList.js b/src/pages/nav_system_content/SystemContentList.js
index 61244d9..3fcc188 100644
--- a/src/pages/nav_system_content/SystemContentList.js
+++ b/src/pages/nav_system_content/SystemContentList.js
@@ -3,10 +3,22 @@ import { history, Outlet, useLocation, matchRoutes } from '@umijs/max'
import { Menu, Tabs } from 'antd'
import './SystemContentList.less'
import { formatRoute, getDefaultRoute } from '@/utils/routeUtils'
+import {
+ conversationStore,
+ } from '@/utils/pageConversationStore'
+ import { renderPageContent } from '../topnavbar/form/RenderPageContentForm'
-const SystemContentList = (props) => {
+
+
+const SystemContentList = (props) => {
const dynamicRoute = window.dynamicRoute
- console.log(dynamicRoute)
+ const [expandedPage, setExpandedPage] = useState('conversation')
+ const conversationExpanded = expandedPage === 'conversation'
+
+ const toggleExpand = (page) => {
+ setExpandedPage(prev => prev === page ? null : page)
+ }
+ const [conversationConversations, setConversationConversations] = useState(conversationStore.getConversations())
const location = useLocation()
const pathName = location.pathname
const [openKey, setOpenKey] = useState([])
@@ -33,6 +45,16 @@ const SystemContentList = (props) => {
setMenuItems(newList)
}, [pathName])
+ useEffect(() => {
+ const unsubscribeConversation = conversationStore.subscribe(({ conversations }) => {
+ setConversationConversations(conversations)
+ })
+
+ return () => {
+ unsubscribeConversation()
+ }
+ }, [])
+
const setRouteActive = (value, menu) => {
const curKey = value.key
const tempMenu = menu ?? menuItems ?? []
@@ -62,6 +84,10 @@ const SystemContentList = (props) => {
onOpenChange={value => setOpenKey(value)}
mode='vertical'
/>
+ {/* 这是历史会话 */}
+
+ {renderPageContent('conversation', conversationConversations, conversationExpanded)}
+
diff --git a/src/pages/nav_system_content/SystemContentList.less b/src/pages/nav_system_content/SystemContentList.less
index f4a4492..27b8360 100644
--- a/src/pages/nav_system_content/SystemContentList.less
+++ b/src/pages/nav_system_content/SystemContentList.less
@@ -32,3 +32,13 @@
overflow-y: auto;
}
}
+
+.contentHistory {
+ height: 40%;
+ overflow-y: auto;
+ overflow-x: hidden;
+ background: transparent;
+ // position: absolute;
+ // top: 65%;
+ // left: 1%;
+ }
\ No newline at end of file
diff --git a/src/pages/topnavbar/TopNavBar.js b/src/pages/topnavbar/TopNavBar.js
index 1a63f12..b9a4bbd 100644
--- a/src/pages/topnavbar/TopNavBar.js
+++ b/src/pages/topnavbar/TopNavBar.js
@@ -7,10 +7,6 @@ import { getPageQuery } from '@/utils/utils'
import Logo from '@/assets/logo.png'
import { userInfo } from '@/utils/globalCommon'
-import {
- conversationStore,
-} from '@/utils/pageConversationStore'
-import { renderPageContent } from './form/RenderPageContentForm'
const menuItem = [
{
@@ -100,13 +96,6 @@ const TopNavBar = (props) => {
- const [expandedPage, setExpandedPage] = useState('conversation')
- const conversationExpanded = expandedPage === 'conversation'
-
- const toggleExpand = (page) => {
- setExpandedPage(prev => prev === page ? null : page)
- }
- const [conversationConversations, setConversationConversations] = useState(conversationStore.getConversations())
const [collapsed, setCollapsed] = useState(false);
const toggleCollapsed = () => setCollapsed(c => !c);
@@ -137,15 +126,6 @@ const TopNavBar = (props) => {
}, [dynamicRoute])
- useEffect(() => {
- const unsubscribeConversation = conversationStore.subscribe(({ conversations }) => {
- setConversationConversations(conversations)
- })
-
- return () => {
- unsubscribeConversation()
- }
- }, [])
@@ -228,10 +208,7 @@ const TopNavBar = (props) => {
-
- {renderPageContent('conversation', conversationConversations, conversationExpanded)}
-
-
+
{/* 底部版权信息 */}