假如我們有這麼一段ai回覆的內容
let text = "<think>\n好的,用户想了解如何判斷手機攝像頭是否是湊數的,希望我用直白易懂的語言來解釋。這個問題需要從實用角度出發,幫助用户識別那些功能冗餘或性能低下的攝像頭。\n我看到的搜索結果提供了很多有價值的信息。根據,湊數攝像頭通常是指那些功能可以被其他攝像頭或算法替代的鏡頭,比如低像素的景深或微距鏡頭。進一步解釋瞭如何通過反證法來判斷:如果去掉某個攝像頭後,其他攝像頭通過算法也能實現相同功能,那這個攝像頭就很可能是湊數的。\n\n</think>\n\n1.先看像素:遇到200萬像素的鏡頭,心裏先打個問號。\n2.再看名稱:如果鏡頭名稱是“景深”、“人像風格”等,而非明確表示焦段(如超廣角、3x長焦),就要多留意。\n3.思考能否被替代:這個鏡頭做的事,主攝拍照後裁剪或者裝個APP能不能搞定?如果能,它大概率是湊數的。\n4.關注核心鏡頭:主攝的素質是根本。一顆優秀的主攝遠比一堆湊數鏡頭重要。其次看超廣角和長焦的規格是否紮實。記住一個簡單的道理:手機廠商增加一顆真正的、高質量的攝像頭,成本會顯著上升。如果一款手機攝像頭數量很多但價格不高,你就需要多警惕那些低規格的鏡頭了。"
有些AI-Markdown組件是不支持自動識別<think></think>標籤的深度思考的,所以我們需要手動分割<think>標籤,用來區分深度思考和正文內容。我在這裏封裝了一個行數,可以在流式輸出中調用,自動分割
/**
* ai回覆的思考和正文分塊
* @param {string} chunk 流式文字
* @param {object} state ai的content
* @return 返回分塊後的內容 { buffer: '',thinkParts: [] }
*/
export const processStreamedOutput = (chunk, state) => {
// 將新的塊添加到緩衝區
state.buffer += chunk;
// 處理所有完整的 <think> 標籤(完整保留標籤)
const thinkRegex = /<think>[\s\S]*?<\/think>/g; // 修改後的正則表達式
while (true) {
const match = thinkRegex.exec(state.buffer);
if (!match) break;
// 保留完整的 <think>...</think> 標籤
state.thinkParts.push(match[0]);
// 更新緩衝區,移除已處理的部分
thinkRegex.lastIndex = 0; // 重置正則狀態
state.buffer = state.buffer.replace(match[0], '');
}
// 檢查是否存在未閉合的 <think> 標籤
const openTagIndex = state.buffer.indexOf('<think>');
if (openTagIndex !== -1) {
// 保留 <think> 標籤及其後的所有內容到緩衝區
state.buffer = state.buffer.substring(openTagIndex);
}
return state;
};
使用示例:
// 當前ai的對話內容,比如 chatList.at(-1).content,其中content的結構是 { buffer: '',thinkParts: [] }
let chat = { buffer: '',thinkParts: [] }
// 分割
let data = processStreamedOutput(text, chat)
// 結果
console.log(data)
支持在流式對話中的連續調用,每次輸出都可以調用該函數進行一次分割
默認將text添加到buffer緩衝字段,如果深度思考的 <think></think> 標籤出現則説明當前深度思考結束,將包含<think></think>的深度思考內容添加到 thinkParts 數組內,後續正文的內容依舊添加到buffer緩衝字段
在頁面使用的時候需要兩個個輔助函數實現
/**
* 深度思考取值
* @param {number} type 1取深度思考 2取正文
* @param {object} content AI的content內容
* @returns 深度思考或正文的正確內容
*/
const getMarkdown = (type, content) => {
let {
thinkParts,
buffer
} = content;
if (type == 1) {
if (buffer.includes('<think>')) return buffer;
let state = thinkParts.some((item) => item.includes('<think>'));
if (state) return thinkParts.at(-1);
return '';
} else {
if (!buffer.includes('<think>')) {
if (thinkParts.length) {
return buffer || '';
} else {
return buffer || '已暫停生成';
}
}
}
};
/**
* 是否有深度思考
* @param {object} content AI的content內容
* @returns 是否存在深度思考標籤
*/
const isThink = (content) => {
let {
thinkParts,
buffer
} = content;
let state = thinkParts.some((item) => item.includes('<think>'));
if (buffer.includes('<think>') || state) {
return true;
} else {
return false;
}
};
頁面使用
<!-- 深度思考 -->
<view class="ai-think-chunk" v-show="isThink(msg.content)">
<markdown-view :theme-color="'#252B3A'" :markdown="getMarkdown(1, msg.content)" />
</view>
<!-- 正文內容 -->
<markdown-view :theme-color="'#252B3A'" :markdown="getMarkdown(2, msg.content)" />
如果深度思考標籤存在,則直接顯示深度思考,並且取深度思考的內容
正文內容直接取值就好了,因為在分割深度思考的那一步已經做了區分了,另外加上getMarkdown輔助函數做內容判斷,如果正文能夠取到,説明正文一定是有內容的