@@ -5,7 +5,6 @@ const CONFIG = {
apiUrl : 'https://open.bigmodel.cn/api/paas/v4/chat/completions' ,
apiKey : '2259e33a1357460abe17919aaf81e73d.K44a8LPQTmFM5PKm' ,
model : 'glm-4.5-air' ,
thinkingModel : 'glm-4-flash-thinking' , // 思考模型
maxTokens : 2048
} ;
@@ -180,14 +179,24 @@ function openConversation(id) {
<!-- 功能开关栏 -->
<div class="feature-bar">
<button class="feature-btn thinking-btn ${ enableThinking ? 'active' : '' } " id="thinkingBtn ">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg >
<span>深度思考</span >
</butto n>
< button class="feature-btn search-btn ${ enableSearch ? 'active' : '' } " id="searchBtn" >
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg >
<span>联网搜索</span >
</butto n>
<div class="feature-left ">
<button class="feature-btn thinking-btn ${ enableThinking ? 'active' : '' } " id="thinkingBtn" >
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg >
<span>深度思考</spa n>
</ button>
<button class="feature-btn search-btn ${ enableSearch ? 'active' : '' } " id="searchBtn" >
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg >
<span>联网搜索</spa n>
</button>
</div>
<div class="feature-right">
<button class="feature-btn nav-btn" id="scrollTopBtn" title="回到顶部">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>
</button>
<button class="feature-btn nav-btn" id="scrollBottomBtn" title="回到底部">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>
</button>
</div>
</div>
<div class="input-area">
@@ -265,6 +274,32 @@ function openConversation(id) {
} ) ;
}
// 绑定置顶置底按钮事件
const scrollTopBtn = document . getElementById ( 'scrollTopBtn' ) ;
const scrollBottomBtn = document . getElementById ( 'scrollBottomBtn' ) ;
if ( scrollTopBtn ) {
scrollTopBtn . addEventListener ( 'click' , ( ) => {
if ( messagesContainer ) {
messagesContainer . scrollTo ( {
top : 0 ,
behavior : 'smooth'
} ) ;
}
} ) ;
}
if ( scrollBottomBtn ) {
scrollBottomBtn . addEventListener ( 'click' , ( ) => {
if ( messagesContainer ) {
messagesContainer . scrollTo ( {
top : messagesContainer . scrollHeight ,
behavior : 'smooth'
} ) ;
}
} ) ;
}
// 绑定输入事件
userInput . addEventListener ( 'keydown' , handleKeyDown ) ;
userInput . addEventListener ( 'input' , ( e ) => autoResize ( e . target ) ) ;
@@ -395,10 +430,12 @@ async function streamGenerate(userMsgIndex) {
sendBtn . disabled = true ;
const aiMessageIndex = currentConversation . messages . length ;
// 只有开启深度思考时才添加 thinking 字段
currentConversation . messages . push ( {
role : 'assistant' ,
content : '' ,
thinking : '' // 思考内容
... ( enableThinking ? { thinking : '' } : { } )
} ) ;
renderMessages ( ) ;
@@ -406,12 +443,29 @@ async function streamGenerate(userMsgIndex) {
const contentEl = lastMessageEl . querySelector ( '.message-content' ) ;
const thinkingEl = lastMessageEl . querySelector ( '.thinking-content' ) ;
// 深度思考模式:思考块默认展开
if ( enableThinking && thinkingEl ) {
const thinkingBlock = lastMessageEl . querySelector ( '.thinking-block' ) ;
if ( thinkingBlock ) thinkingBlock . classList . add ( 'expanded' ) ;
thinkingEl . innerHTML = '<span class="streaming-cursor">思考中...</span>' ;
}
contentEl . innerHTML = '<span class="streaming-cursor">▌</span>' ;
if ( thinkingEl ) thinkingEl . innerHTML = '<span class="streaming-cursor">▌</span>' ;
try {
// 根据开关选择模型
const model = enableThinking ? CONFIG . thinkingModel : CONFIG . model ;
// 构建请求体 - 统一使用 glm-4.5-air, 通过 thinking 参数控制
const requestBody = {
model : CONFIG . model ,
messages : currentConversation . messages . slice ( 0 , aiMessageIndex ) . map ( m => ( {
role : m . role ,
content : m . content
} ) ) ,
max _tokens : CONFIG . maxTokens ,
stream : true ,
thinking : {
type : enableThinking ? 'enabled' : 'disabled'
}
} ;
const response = await fetch ( CONFIG . apiUrl , {
method : 'POST' ,
@@ -419,15 +473,7 @@ async function streamGenerate(userMsgIndex) {
'Content-Type' : 'application/json' ,
'Authorization' : ` Bearer ${ CONFIG . apiKey } `
} ,
body : JSON . stringify ( {
model : model ,
messages : currentConversation . messages . slice ( 0 , aiMessageIndex ) . map ( m => ( {
role : m . role ,
content : m . content
} ) ) ,
max _tokens : CONFIG . maxTokens ,
stream : true
} )
body : JSON . stringify ( requestBody )
} ) ;
if ( ! response . ok ) {
@@ -437,7 +483,7 @@ async function streamGenerate(userMsgIndex) {
const reader = response . body . getReader ( ) ;
const decoder = new TextDecoder ( ) ;
let buffer = '' ;
let thinkingComple te = false ; // 思考是否完成
let thinkingOutputStar ted = false ; // 正式内容是否开始输出
while ( true ) {
const { done , value } = await reader . read ( ) ;
@@ -457,24 +503,21 @@ async function streamGenerate(userMsgIndex) {
const delta = data . choices ? . [ 0 ] ? . delta ;
if ( delta ) {
// 处理思考内容
if ( delta . reasoning _content || delta . thinking ) {
// 只有开启深度思考时才 处理思考内容
if ( enableThinking && ( delta. reasoning _content || delta . thinking ) ) {
const thinkingChunk = delta . reasoning _content || delta . thinking ;
currentConversation . messages [ aiMessageIndex ] . thinking += thinkingChunk ;
if ( thinkingEl ) {
thinkingEl . innerHTML = renderMarkdown ( currentConversation . messages [ aiMessageIndex ] . thinking ) + '<span class="streaming-cursor">▌</span>' ;
// 思考时展开
const thinkingBlock = lastMessageEl . querySelector ( '.thinking-block' ) ;
if ( thinkingBlock ) thinkingBlock . classList . add ( 'expanded' ) ;
}
scrollToBottom ( ) ;
}
// 处理正式回复内容
if ( delta . content ) {
// 如果开始输出正式内容,说明思考完成
if ( currentConversation . messages [ aiMessageIndex ] . thinking && ! thinkingComplete ) {
thinkingComple te = true ;
// 如果开启深度思考且开 始输出正式内容,折叠思考块
if ( enableThinking && ! thinkingOutputStarted && currentConversation. messages [ aiMessageIndex ] . thinking ) {
thinkingOutputStar ted = true ;
// 折叠思考内容
const thinkingBlock = lastMessageEl . querySelector ( '.thinking-block' ) ;
if ( thinkingBlock ) thinkingBlock . classList . remove ( 'expanded' ) ;
@@ -492,7 +535,7 @@ async function streamGenerate(userMsgIndex) {
}
// 最终渲染
if ( thinkingEl && currentConversation . messages [ aiMessageIndex ] . thinking ) {
if ( thinkingEl && enableThinking && currentConversation . messages [ aiMessageIndex ] . thinking ) {
thinkingEl . innerHTML = renderMarkdown ( currentConversation . messages [ aiMessageIndex ] . thinking ) ;
}
contentEl . innerHTML = renderMarkdown ( currentConversation . messages [ aiMessageIndex ] . content ) ;
@@ -507,6 +550,58 @@ async function streamGenerate(userMsgIndex) {
currentConversation . updatedAt = Date . now ( ) ;
saveConversations ( ) ;
renderMessages ( ) ;
// 自动总结标题: 第一次对话和每隔5次对话
const totalMessages = currentConversation . messages . length ;
if ( totalMessages === 1 || totalMessages % 5 === 0 ) {
await generateConversationTitle ( ) ;
}
}
}
// 生成对话标题
async function generateConversationTitle ( ) {
if ( ! currentConversation ) return ;
// 构建对话摘要
const conversationText = currentConversation . messages . map ( m =>
` ${ m . role === 'user' ? '用户' : 'AI' } : ${ m . content . slice ( 0 , 200 ) } `
) . join ( '\n' ) ;
const titlePrompt = ` 请用不超过10个字总结以下对话的主题, 只输出标题, 不要其他内容:
${ conversationText } ` ;
try {
const response = await fetch ( CONFIG . apiUrl , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
'Authorization' : ` Bearer ${ CONFIG . apiKey } `
} ,
body : JSON . stringify ( {
model : CONFIG . model ,
messages : [ { role : 'user' , content : titlePrompt } ] ,
max _tokens : 50
} )
} ) ;
if ( response . ok ) {
const data = await response . json ( ) ;
const newTitle = data . choices ? . [ 0 ] ? . message ? . content ? . trim ( ) ;
if ( newTitle && newTitle . length > 0 && newTitle . length <= 15 ) {
currentConversation . title = newTitle ;
currentConversation . updatedAt = Date . now ( ) ;
saveConversations ( ) ;
// 更新页面标题显示
const titleEl = document . querySelector ( '.header h1' ) ;
if ( titleEl ) {
titleEl . textContent = newTitle ;
}
}
}
} catch ( error ) {
console . error ( '生成标题失败:' , error ) ;
}
}