feat: implement CustomAIMessageChunk class and enhance reasoning handling in useMessageOption hook
This commit is contained in:
@@ -25,7 +25,7 @@ export const getAllOpenAIModels = async (baseUrl: string, apiKey?: string) => {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
// if Google API fails to return models, try another approach
|
||||
if (res.status === 401 && res.url == 'https://generativelanguage.googleapis.com/v1beta/openai/models') {
|
||||
if (res.url == 'https://generativelanguage.googleapis.com/v1beta/openai/models') {
|
||||
const urlGoogle = `https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`
|
||||
const resGoogle = await fetch(urlGoogle, {
|
||||
signal: controller.signal
|
||||
|
||||
@@ -1,109 +1,100 @@
|
||||
const tags = ["think", "reason", "reasoning", "thought"]
|
||||
export function parseReasoning(
|
||||
text: string
|
||||
): {
|
||||
type: "reasoning" | "text"
|
||||
content: string
|
||||
reasoning_running?: boolean
|
||||
export function parseReasoning(text: string): {
|
||||
type: "reasoning" | "text"
|
||||
content: string
|
||||
reasoning_running?: boolean
|
||||
}[] {
|
||||
try {
|
||||
const result: {
|
||||
type: "reasoning" | "text"
|
||||
content: string
|
||||
reasoning_running?: boolean
|
||||
}[] = []
|
||||
const tagPattern = new RegExp(`<(${tags.join("|")})>`, "i")
|
||||
const closeTagPattern = new RegExp(`</(${tags.join("|")})>`, "i")
|
||||
try {
|
||||
const result: {
|
||||
type: "reasoning" | "text"
|
||||
content: string
|
||||
reasoning_running?: boolean
|
||||
}[] = []
|
||||
const tagPattern = new RegExp(`<(${tags.join("|")})>`, "i")
|
||||
const closeTagPattern = new RegExp(`</(${tags.join("|")})>`, "i")
|
||||
|
||||
let currentIndex = 0
|
||||
let isReasoning = false
|
||||
let currentIndex = 0
|
||||
let isReasoning = false
|
||||
|
||||
while (currentIndex < text.length) {
|
||||
const openTagMatch = text.slice(currentIndex).match(tagPattern)
|
||||
const closeTagMatch = text.slice(currentIndex).match(closeTagPattern)
|
||||
while (currentIndex < text.length) {
|
||||
const openTagMatch = text.slice(currentIndex).match(tagPattern)
|
||||
const closeTagMatch = text.slice(currentIndex).match(closeTagPattern)
|
||||
|
||||
if (!isReasoning && openTagMatch) {
|
||||
const beforeText = text.slice(
|
||||
currentIndex,
|
||||
currentIndex + openTagMatch.index
|
||||
)
|
||||
if (beforeText.trim()) {
|
||||
result.push({ type: "text", content: beforeText.trim() })
|
||||
}
|
||||
|
||||
isReasoning = true
|
||||
currentIndex += openTagMatch.index! + openTagMatch[0].length
|
||||
continue
|
||||
}
|
||||
|
||||
if (isReasoning && closeTagMatch) {
|
||||
const reasoningContent = text.slice(
|
||||
currentIndex,
|
||||
currentIndex + closeTagMatch.index
|
||||
)
|
||||
if (reasoningContent.trim()) {
|
||||
result.push({ type: "reasoning", content: reasoningContent.trim() })
|
||||
}
|
||||
|
||||
isReasoning = false
|
||||
currentIndex += closeTagMatch.index! + closeTagMatch[0].length
|
||||
continue
|
||||
}
|
||||
|
||||
if (currentIndex < text.length) {
|
||||
const remainingText = text.slice(currentIndex)
|
||||
result.push({
|
||||
type: isReasoning ? "reasoning" : "text",
|
||||
content: remainingText.trim(),
|
||||
reasoning_running: isReasoning
|
||||
})
|
||||
break
|
||||
}
|
||||
if (!isReasoning && openTagMatch) {
|
||||
const beforeText = text.slice(
|
||||
currentIndex,
|
||||
currentIndex + openTagMatch.index
|
||||
)
|
||||
if (beforeText.trim()) {
|
||||
result.push({ type: "text", content: beforeText.trim() })
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error(`Error parsing reasoning: ${e}`)
|
||||
return [
|
||||
{
|
||||
type: "text",
|
||||
content: text
|
||||
}
|
||||
]
|
||||
isReasoning = true
|
||||
currentIndex += openTagMatch.index! + openTagMatch[0].length
|
||||
continue
|
||||
}
|
||||
|
||||
if (isReasoning && closeTagMatch) {
|
||||
const reasoningContent = text.slice(
|
||||
currentIndex,
|
||||
currentIndex + closeTagMatch.index
|
||||
)
|
||||
if (reasoningContent.trim()) {
|
||||
result.push({ type: "reasoning", content: reasoningContent.trim() })
|
||||
}
|
||||
|
||||
isReasoning = false
|
||||
currentIndex += closeTagMatch.index! + closeTagMatch[0].length
|
||||
continue
|
||||
}
|
||||
|
||||
if (currentIndex < text.length) {
|
||||
const remainingText = text.slice(currentIndex)
|
||||
result.push({
|
||||
type: isReasoning ? "reasoning" : "text",
|
||||
content: remainingText.trim(),
|
||||
reasoning_running: isReasoning
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error(`Error parsing reasoning: ${e}`)
|
||||
return [
|
||||
{
|
||||
type: "text",
|
||||
content: text
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function isReasoningStarted(text: string): boolean {
|
||||
const tagPattern = new RegExp(`<(${tags.join("|")})>`, "i")
|
||||
return tagPattern.test(text)
|
||||
const tagPattern = new RegExp(`<(${tags.join("|")})>`, "i")
|
||||
return tagPattern.test(text)
|
||||
}
|
||||
|
||||
export function isReasoningEnded(text: string): boolean {
|
||||
const closeTagPattern = new RegExp(`</(${tags.join("|")})>`, "i")
|
||||
return closeTagPattern.test(text)
|
||||
const closeTagPattern = new RegExp(`</(${tags.join("|")})>`, "i")
|
||||
return closeTagPattern.test(text)
|
||||
}
|
||||
|
||||
export function removeReasoning(text: string): string {
|
||||
const tagPattern = new RegExp(
|
||||
`<(${tags.join("|")})>.*?</(${tags.join("|")})>`,
|
||||
"gis"
|
||||
)
|
||||
return text.replace(tagPattern, "").trim()
|
||||
const tagPattern = new RegExp(
|
||||
`<(${tags.join("|")})>.*?</(${tags.join("|")})>`,
|
||||
"gis"
|
||||
)
|
||||
return text.replace(tagPattern, "").trim()
|
||||
}
|
||||
export function mergeReasoningContent(originalText: string, reasoning: string): string {
|
||||
const defaultReasoningTag = "think"
|
||||
const tagPattern = new RegExp(`<(${tags.join("|")})>(.*?)</(${tags.join("|")})>`, "is")
|
||||
const hasReasoningTag = tagPattern.test(originalText)
|
||||
export function mergeReasoningContent(
|
||||
originalText: string,
|
||||
reasoning: string
|
||||
): string {
|
||||
const reasoningTag = "<think>"
|
||||
|
||||
if (hasReasoningTag) {
|
||||
const match = originalText.match(tagPattern)
|
||||
if (match) {
|
||||
const [fullMatch, tag, existingContent] = match
|
||||
const remainingText = originalText.replace(fullMatch, '').trim()
|
||||
const newContent = `${existingContent.trim()}${reasoning}`
|
||||
return `<${tag}>${newContent}</${tag}> ${remainingText}`
|
||||
}
|
||||
}
|
||||
originalText = originalText.replace(reasoningTag, "")
|
||||
|
||||
return `<${defaultReasoningTag}>${reasoning}</${defaultReasoningTag}> ${originalText.trim()}`.trim()
|
||||
}
|
||||
return `${reasoningTag}${originalText + reasoning}`.trim()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user