feat: add optional export of thinking/reasoning process

Add a new "Export Thinking Process" setting that includes the model's
thinking/reasoning content in Markdown and HTML exports. Supports
multiple thinking formats across model generations (gpt-5-2, gpt-5-4,
gpt-5-5) including thought content, search activities, and duration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
pionxzh
2026-05-10 04:57:04 +08:00
parent 84605316a9
commit 4df6676826
16 changed files with 246 additions and 14 deletions

View File

@@ -125,6 +125,10 @@ interface MessageMeta {
content_references?: ContentReference[]
/** Whether this message is hidden in the UI (e.g., internal system prompts) */
is_visually_hidden_from_conversation?: boolean
/** Duration of reasoning in seconds (from reasoning_recap messages) */
finished_duration_sec?: number
/** Reasoning activity title shown in the thinking panel */
reasoning_title?: string
}
export type AuthorRole = 'system' | 'assistant' | 'user' | 'tool'
@@ -258,16 +262,24 @@ export interface ConversationNodeMessage {
id: string
metadata?: MessageMeta
recipient: 'all' | 'browser' | 'python' | 'dalle.text2im' & (string & {})
channel?: string | null
status: string
end_turn?: boolean
weight: number
}
export interface ThinkingContent {
thoughts: Array<{ summary: string; content: string }>
activities?: string[]
durationSeconds?: number
}
export interface ConversationNode {
children: string[]
id: string
message?: ConversationNodeMessage
parent?: string
thinking?: ThinkingContent
}
export interface ApiConversation {
@@ -882,7 +894,11 @@ const ModelMapping: { [key in ModelSlug]: string } & { [key: string]: string } =
'text-davinci-002': 'GPT-3.5',
}
export function processConversation(conversation: ApiConversationWithId): ConversationResult {
export interface ProcessConversationOptions {
enableThinking?: boolean
}
export function processConversation(conversation: ApiConversationWithId, options?: ProcessConversationOptions): ConversationResult {
const title = conversation.title || 'ChatGPT Conversation'
const createTime = conversation.create_time
const updateTime = conversation.update_time
@@ -895,6 +911,10 @@ export function processConversation(conversation: ApiConversationWithId): Conver
const conversationNodes = extractConversationResult(conversation.mapping, startNodeId)
const mergedConversationNodes = mergeContinuationNodes(conversationNodes)
if (options?.enableThinking) {
attachThinkingToNodes(conversation.mapping, mergedConversationNodes, startNodeId)
}
return {
id: conversation.id,
title,
@@ -974,8 +994,8 @@ function mergeContinuationNodes(nodes: ConversationNode[]): ConversationNode[] {
&& prevNode.message.recipient === 'all' && node.message.recipient === 'all'
&& prevNode.message.content.content_type === 'text' && node.message.content.content_type === 'text'
) {
// the last part of the previous node should directly concat to the first part of the current node
prevNode.message.content.parts[prevNode.message.content.parts.length - 1] += node.message.content.parts[0]
const separator = prevNode.message.channel !== node.message.channel ? '\n\n' : ''
prevNode.message.content.parts[prevNode.message.content.parts.length - 1] += separator + node.message.content.parts[0]
prevNode.message.content.parts.push(...node.message.content.parts.slice(1))
}
else {
@@ -984,3 +1004,86 @@ function mergeContinuationNodes(nodes: ConversationNode[]): ConversationNode[] {
}
return result
}
/**
* Walk the raw conversation mapping and attach thinking/reasoning content
* to the corresponding assistant response nodes.
*/
function hasThinkingContent(thinking: ThinkingContent): boolean {
return thinking.thoughts.length > 0
|| (thinking.activities != null && thinking.activities.length > 0)
}
function attachThinkingToNodes(
mapping: Record<string, ConversationNode>,
resultNodes: ConversationNode[],
startNodeId: string,
): void {
const resultNodeIds = new Set(resultNodes.map(n => n.id))
let currentNodeId: string | undefined = startNodeId
let targetNodeId: string | null = null
let thinking: ThinkingContent = { thoughts: [] }
while (currentNodeId) {
const node: ConversationNode = mapping[currentNodeId]
if (!node || node.parent === undefined) break
const message = node.message
if (message?.content) {
const ct = message.content.content_type
if (resultNodeIds.has(node.id) && message.author.role !== 'user') {
if (hasThinkingContent(thinking)) {
const saveToId = targetNodeId ?? node.id
const target = resultNodes.find(n => n.id === saveToId)
if (target) target.thinking = thinking
}
targetNodeId = node.id
thinking = { thoughts: [] }
}
else if (ct === 'reasoning_recap') {
const duration = message.metadata?.finished_duration_sec
if (typeof duration === 'number') {
thinking.durationSeconds = duration
}
else {
const match = message.content.content.match(/(\d+)/)
if (match) thinking.durationSeconds = Number.parseInt(match[1])
}
}
else if (ct === 'thoughts') {
for (const thought of message.content.thoughts) {
if (thought.content || thought.summary) {
thinking.thoughts.unshift({
summary: thought.summary,
content: thought.content,
})
}
}
}
else if (message.metadata?.reasoning_title) {
if (!thinking.activities) thinking.activities = []
const title = message.metadata.reasoning_title
if (!thinking.activities.includes(title)) {
thinking.activities.unshift(title)
}
}
else if (message.author.role === 'user' && !message.metadata?.is_visually_hidden_from_conversation) {
if (targetNodeId && hasThinkingContent(thinking)) {
const target = resultNodes.find(n => n.id === targetNodeId)
if (target) target.thinking = thinking
}
targetNodeId = null
thinking = { thoughts: [] }
}
}
currentNodeId = node.parent
}
if (targetNodeId && hasThinkingContent(thinking)) {
const target = resultNodes.find(n => n.id === targetNodeId)
if (target) target.thinking = thinking
}
}

View File

@@ -17,6 +17,7 @@ export const KEY_TIMESTAMP_MARKDOWN = 'exporter:timestamp_markdown'
export const KEY_TIMESTAMP_HTML = 'exporter:timestamp_html'
export const KEY_META_ENABLED = 'exporter:enable_meta'
export const KEY_META_LIST = 'exporter:meta_list'
export const KEY_THINKING_ENABLED = 'exporter:enable_thinking'
export const KEY_EXPORT_ALL_LIMIT = 'exporter:export_all_limit'
export const KEY_OAI_LOCALE = 'oai/apps/locale'

View File

@@ -1,6 +1,6 @@
import JSZip from 'jszip'
import { fetchConversation, getCurrentChatId, processConversation, shouldSkipMessageInExport } from '../api'
import { KEY_TIMESTAMP_24H, KEY_TIMESTAMP_ENABLED, KEY_TIMESTAMP_HTML, baseUrl } from '../constants'
import { KEY_THINKING_ENABLED, KEY_TIMESTAMP_24H, KEY_TIMESTAMP_ENABLED, KEY_TIMESTAMP_HTML, baseUrl } from '../constants'
import i18n from '../i18n'
import { checkIfConversationStarted, getUserAvatar } from '../page'
import templateHtml from '../template.html?raw'
@@ -9,7 +9,7 @@ import { fromMarkdown, toHtml } from '../utils/markdown'
import { ScriptStorage } from '../utils/storage'
import { standardizeLineBreaks } from '../utils/text'
import { dateStr, getColorScheme, timestamp, unixTimestampToISOString } from '../utils/utils'
import type { ApiConversationWithId, ConversationNodeMessage, ConversationResult } from '../api'
import type { ApiConversationWithId, ConversationNodeMessage, ConversationResult, ThinkingContent } from '../api'
import type { ExportMeta } from '../ui/SettingContext'
import type { PartInfo } from '../utils/download'
@@ -23,7 +23,8 @@ export async function exportToHtml(fileNameFormat: string, metaList: ExportMeta[
const chatId = await getCurrentChatId()
const rawConversation = await fetchConversation(chatId, true)
const conversation = processConversation(rawConversation)
const enableThinking = ScriptStorage.get<boolean>(KEY_THINKING_ENABLED) ?? false
const conversation = processConversation(rawConversation, { enableThinking })
const html = conversationToHtml(conversation, userAvatar, metaList)
const fileName = getFileNameWithFormat(fileNameFormat, 'html', { title: conversation.title, chatId, createTime: conversation.createTime, updateTime: conversation.updateTime })
@@ -37,7 +38,8 @@ export async function exportAllToHtml(fileNameFormat: string, apiConversations:
const zip = new JSZip()
const filenameMap = new Map<string, number>()
const conversations = apiConversations.map(x => processConversation(x))
const enableThinking = ScriptStorage.get<boolean>(KEY_THINKING_ENABLED) ?? false
const conversations = apiConversations.map(x => processConversation(x, { enableThinking }))
conversations.forEach((conversation) => {
let fileName = getFileNameWithFormat(fileNameFormat, 'html', {
title: conversation.title,
@@ -81,7 +83,7 @@ function conversationToHtml(conversation: ConversationResult, avatar: string, me
const LatexRegex = /(\s\$\$.+?\$\$\s|\s\$.+?\$\s|\\\[.+?\\\]|\\\(.+?\\\))|(^\$$[\S\s]+?^\$$)|(^\$\$[\S\s]+?^\$\$\$)/gm
const conversationHtml = conversationNodes.map(({ message }) => {
const conversationHtml = conversationNodes.map(({ message, thinking }) => {
if (!message || !message.content) return null
if (shouldSkipMessageInExport(message)) return null
@@ -149,12 +151,15 @@ function conversationToHtml(conversation: ConversationResult, avatar: string, me
timestampHtml = `<time class="time" datetime="${date.toISOString()}" title="${date.toLocaleString()}">${conversationTime}</time>`
}
const thinkingBlock = thinking ? formatThinkingHtml(thinking) : ''
return `
<div class="conversation-item">
<div class="author ${authorType}">
${avatarEl}
</div>
<div class="conversation-content-wrapper">
${thinkingBlock}
<div class="conversation-content">
${content}
</div>
@@ -340,6 +345,32 @@ function transformContent(
}
}
function formatThinkingHtml(thinking: ThinkingContent): string {
const durationLabel = thinking.durationSeconds != null
? `Thought for ${thinking.durationSeconds} seconds`
: 'Thinking'
const parts: string[] = []
if (thinking.activities?.length) {
const items = thinking.activities.map(a => `<li>${escapeHtml(a)}</li>`).join('')
parts.push(`<ul>${items}</ul>`)
}
const thoughts = thinking.thoughts
.map(t => t.content || t.summary)
.filter(Boolean)
.map(text => `<p>${escapeHtml(text)}</p>`)
.join('\n')
if (thoughts) parts.push(thoughts)
const body = parts.join('\n')
if (!body) return ''
return `<details class="thinking"><summary>${escapeHtml(durationLabel)}</summary>${body}</details>`
}
function escapeHtml(html: string) {
return html
.replace(/&/g, '&amp;')

View File

@@ -1,6 +1,6 @@
import JSZip from 'jszip'
import { fetchConversation, getCurrentChatId, processConversation, shouldSkipMessageInExport } from '../api'
import { KEY_TIMESTAMP_24H, KEY_TIMESTAMP_ENABLED, KEY_TIMESTAMP_MARKDOWN, baseUrl } from '../constants'
import { KEY_THINKING_ENABLED, KEY_TIMESTAMP_24H, KEY_TIMESTAMP_ENABLED, KEY_TIMESTAMP_MARKDOWN, baseUrl } from '../constants'
import i18n from '../i18n'
import { checkIfConversationStarted } from '../page'
import { buildZipFileName, downloadFile, getFileNameWithFormat } from '../utils/download'
@@ -8,7 +8,7 @@ import { fromMarkdown, toMarkdown } from '../utils/markdown'
import { ScriptStorage } from '../utils/storage'
import { standardizeLineBreaks } from '../utils/text'
import { dateStr, timestamp, unixTimestampToISOString } from '../utils/utils'
import type { ApiConversationWithId, Citation, ConversationNodeMessage, ConversationResult } from '../api'
import type { ApiConversationWithId, Citation, ConversationNodeMessage, ConversationResult, ThinkingContent } from '../api'
import type { ExportMeta } from '../ui/SettingContext'
import type { PartInfo } from '../utils/download'
@@ -20,7 +20,8 @@ export async function exportToMarkdown(fileNameFormat: string, metaList: ExportM
const chatId = await getCurrentChatId()
const rawConversation = await fetchConversation(chatId, true)
const conversation = processConversation(rawConversation)
const enableThinking = ScriptStorage.get<boolean>(KEY_THINKING_ENABLED) ?? false
const conversation = processConversation(rawConversation, { enableThinking })
const markdown = conversationToMarkdown(conversation, metaList)
const fileName = getFileNameWithFormat(fileNameFormat, 'md', { title: conversation.title, chatId, createTime: conversation.createTime, updateTime: conversation.updateTime })
@@ -32,7 +33,8 @@ export async function exportToMarkdown(fileNameFormat: string, metaList: ExportM
export async function exportAllToMarkdown(fileNameFormat: string, apiConversations: ApiConversationWithId[], metaList?: ExportMeta[], projectName?: string, partIndex?: number, totalParts?: number) {
const zip = new JSZip()
const filenameMap = new Map<string, number>()
const conversations = apiConversations.map(x => processConversation(x))
const enableThinking = ScriptStorage.get<boolean>(KEY_THINKING_ENABLED) ?? false
const conversations = apiConversations.map(x => processConversation(x, { enableThinking }))
conversations.forEach((conversation) => {
let fileName = getFileNameWithFormat(fileNameFormat, 'md', {
title: conversation.title,
@@ -97,7 +99,7 @@ function conversationToMarkdown(conversation: ConversationResult, metaList?: Exp
const timeStampMarkdown = ScriptStorage.get<boolean>(KEY_TIMESTAMP_MARKDOWN) ?? false
const timeStamp24H = ScriptStorage.get<boolean>(KEY_TIMESTAMP_24H) ?? false
const content = conversationNodes.map(({ message }) => {
const content = conversationNodes.map(({ message, thinking }) => {
if (!message || !message.content) return null
if (shouldSkipMessageInExport(message)) return null
@@ -113,6 +115,7 @@ function conversationToMarkdown(conversation: ConversationResult, metaList?: Exp
}
const author = transformAuthor(message.author)
const thinkingBlock = thinking ? formatThinkingMarkdown(thinking) : ''
const postSteps: Array<(input: string) => string> = []
if (message.author.role === 'assistant') {
@@ -157,7 +160,7 @@ function conversationToMarkdown(conversation: ConversationResult, metaList?: Exp
const postProcess = (input: string) => postSteps.reduce((acc, fn) => fn(acc), input)
const content = transformContent(message.content, message.metadata, postProcess)
return `#### ${author}:\n${timestampHtml}${content}`
return `#### ${author}:\n${timestampHtml}${thinkingBlock}${content}`
}).filter(Boolean).join('\n\n')
const markdown = `${frontMatter}# ${title}\n\n${content}`
@@ -314,3 +317,27 @@ function transformContent(
return postProcess(`[Unsupported Content: ${content.content_type}]`)
}
}
function formatThinkingMarkdown(thinking: ThinkingContent): string {
const durationLabel = thinking.durationSeconds != null
? `Thought for ${thinking.durationSeconds} seconds`
: 'Thinking'
const parts: string[] = []
if (thinking.activities?.length) {
parts.push(thinking.activities.map(a => `- ${a}`).join('\n'))
}
const thoughts = thinking.thoughts
.map(t => t.content || t.summary)
.filter(Boolean)
.join('\n\n')
if (thoughts) parts.push(thoughts)
const body = parts.join('\n\n')
if (!body) return ''
return `<details>\n<summary>${durationLabel}</summary>\n\n${body}\n\n</details>\n\n`
}

View File

@@ -33,6 +33,8 @@
"Export Format": "Export Format",
"Export Metadata": "Export Metadata",
"Export Metadata Description": "Add metadata to exported Markdown and HTML files.",
"Export Thinking Process": "Export Thinking Process",
"Export Thinking Process Description": "Include the model's thinking/reasoning process in exported Markdown and HTML files.",
"OpenAI Official Format": "OpenAI Official Format",
"Conversation Archive Alert": "Are you sure you want to archive all selected conversations?",
"Conversation Archived Message": "All selected conversations have been archived. Please refresh the page to see the changes.",

View File

@@ -33,6 +33,8 @@
"Export Format": "Formato de Exportación",
"Export Metadata": "Exportar Metadatos",
"Export Metadata Description": "Añadir Metadatos a los archivos Markdown y HTML exportados.",
"Export Thinking Process": "Exportar Proceso de Pensamiento",
"Export Thinking Process Description": "Incluir el proceso de pensamiento/razonamiento del modelo en los archivos Markdown y HTML exportados.",
"OpenAI Official Format": "Formato Oficial de OpenAI",
"Conversation Archive Alert": "¿Estás seguro que quieres archivar todas las conversaciones seleccionadas?",
"Conversation Archived Message": "Todos las conversaciones seleccionadas se han archivado. Por favor refresca la página para ver los cambios.",

View File

@@ -33,6 +33,8 @@
"Export Format": "Format d'exportation",
"Export Metadata": "Exporter les métadonnées",
"Export Metadata Description": "Ajouter des métadonnées aux fichiers Markdown et HTML exportés.",
"Export Thinking Process": "Exporter le processus de réflexion",
"Export Thinking Process Description": "Inclure le processus de réflexion/raisonnement du modèle dans les fichiers Markdown et HTML exportés.",
"OpenAI Official Format": "Format officiel OpenAI",
"Conversation Archive Alert": "Êtes-vous sûr de vouloir archiver toutes les conversations sélectionnées ?",
"Conversation Archived Message": "Toutes les conversations sélectionnées ont été archivées. Veuillez actualiser la page pour voir les changements.",

View File

@@ -33,6 +33,8 @@
"Export Format": "Format Ekspor",
"Export Metadata": "Ekspor Metada",
"Export Metadata Description": "Tambahkan metadata ke file Markdown dan HTML yang diekspor.",
"Export Thinking Process": "Ekspor Proses Berpikir",
"Export Thinking Process Description": "Sertakan proses berpikir/penalaran model dalam file Markdown dan HTML yang diekspor.",
"OpenAI Official Format": "Format Resmi OpenAI",
"Conversation Archive Alert": "Apakah Anda yakin ingin mengarsipkan semua percakapan yang dipilih?",
"Conversation Archived Message": "Semua percakapan yang dipilih telah diarsipkan. Harap segarkan halaman untuk melihat perubahan.",

View File

@@ -33,6 +33,8 @@
"Export Format": "エクスポートフォーマット",
"Export Metadata": "メタデータをエクスポート",
"Export Metadata Description": "エクスポートされたMarkdownおよびHTMLファイルにメタデータを追加します。",
"Export Thinking Process": "思考プロセスをエクスポート",
"Export Thinking Process Description": "エクスポートされたMarkdownおよびHTMLファイルにモデルの思考・推論プロセスを含めます。",
"OpenAI Official Format": "OpenAI公式フォーマット",
"Conversation Archive Alert": "選択したすべての会話をアーカイブしてもよろしいですか?",
"Conversation Archived Message": "選択したすべての会話がアーカイブされました。変更を表示するには、ページを更新してください。",

View File

@@ -33,6 +33,8 @@
"Export Format": "Формат экспорта",
"Export Metadata": "Экспорт метаданных",
"Export Metadata Description": "Добавляйте метаданные в экспортированные файлы Markdown и HTML.",
"Export Thinking Process": "Экспорт процесса мышления",
"Export Thinking Process Description": "Включить процесс мышления/рассуждения модели в экспортированные файлы Markdown и HTML.",
"OpenAI Official Format": "Официальный формат OpenAI",
"Conversation Archive Alert": "Вы уверены, что хотите архивировать все выбранные разговоры?",
"Conversation Archived Message": "Все выбранные разговоры были заархивированы. Пожалуйста, обновите страницу, чтобы увидеть изменения.",

View File

@@ -33,6 +33,8 @@
"Export Format": "Dışa Aktarma Formatı",
"Export Metadata": "Üst veriyi dışa aktar",
"Export Metadata Description": "Dışa aktarılan Markdown ve HTML dosyalarına üst veri ekle",
"Export Thinking Process": "Düşünme Sürecini Dışa Aktar",
"Export Thinking Process Description": "Dışa aktarılan Markdown ve HTML dosyalarına modelin düşünme/akıl yürütme sürecini dahil et.",
"OpenAI Official Format": "OpenAI Resmi Format",
"Conversation Archive Alert": "Seçilen tüm konuşmaları arşivlemek istediğinizden emin misiniz?",
"Conversation Archived Message": "Seçilen tüm konuşmalar arşivlendi. Değişiklikleri görmek için sayfayı yenileyin.",

View File

@@ -33,6 +33,8 @@
"Export Format": "导出格式",
"Export Metadata": "导出元数据",
"Export Metadata Description": "会添加至 Markdown 以及 HTML 导出。",
"Export Thinking Process": "导出思考过程",
"Export Thinking Process Description": "在导出的 Markdown 和 HTML 文件中包含模型的思考/推理过程。",
"OpenAI Official Format": "OpenAI 官方格式",
"Conversation Archive Alert": "确定要归档所有选取的对话?",
"Conversation Archived Message": "所有所选的对话已归档。请刷新页面。",

View File

@@ -33,6 +33,8 @@
"Export Format": "匯出格式",
"Export Metadata": "匯出元資料",
"Export Metadata Description": "會添加至 Markdown 以及 HTML 匯出。",
"Export Thinking Process": "匯出思考過程",
"Export Thinking Process Description": "在匯出的 Markdown 和 HTML 檔案中包含模型的思考/推理過程。",
"OpenAI Official Format": "OpenAI 官方格式",
"Conversation Archive Alert": "確定要封存所有選取的對話?",
"Conversation Archived Message": "所有選取的對話已封存。請重新整理頁面。",

View File

@@ -521,6 +521,35 @@
flex-direction: column;
}
.thinking {
font-size: 0.875rem;
line-height: 1.5;
margin-bottom: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
}
.thinking summary {
cursor: pointer;
font-weight: 500;
color: #6b7280;
}
.thinking p {
margin: 0.5rem 0;
color: #6b7280;
}
.dark .thinking {
border-color: #4b5563;
}
.dark .thinking summary,
.dark .thinking p {
color: #9ca3af;
}
.conversation-content {
font-size: 1rem;
line-height: 1.5;

View File

@@ -5,6 +5,7 @@ import {
KEY_FILENAME_FORMAT,
KEY_META_ENABLED,
KEY_META_LIST,
KEY_THINKING_ENABLED,
KEY_TIMESTAMP_24H,
KEY_TIMESTAMP_ENABLED,
KEY_TIMESTAMP_HTML,
@@ -43,6 +44,8 @@ const SettingContext = createContext({
setEnableMeta: (_: boolean) => {},
exportMetaList: defaultExportMetaList,
setExportMetaList: (_: ExportMeta[]) => {},
enableThinking: false,
setEnableThinking: (_: boolean) => {},
exportAllLimit: defaultExportAllLimit,
setExportAllLimit: (_: number) => {},
resetDefault: () => {},
@@ -59,6 +62,7 @@ export const SettingProvider: FC = ({ children }) => {
const [enableMeta, setEnableMeta] = useGMStorage(KEY_META_ENABLED, false)
const [exportMetaList, setExportMetaList] = useGMStorage(KEY_META_LIST, defaultExportMetaList)
const [enableThinking, setEnableThinking] = useGMStorage(KEY_THINKING_ENABLED, false)
const [exportAllLimit, setExportAllLimit] = useGMStorage(KEY_EXPORT_ALL_LIMIT, defaultExportAllLimit)
const resetDefault = useCallback(() => {
@@ -66,12 +70,14 @@ export const SettingProvider: FC = ({ children }) => {
setEnableTimestamp(false)
setEnableMeta(false)
setExportMetaList(defaultExportMetaList)
setEnableThinking(false)
setExportAllLimit(defaultExportAllLimit)
}, [
setFormat,
setEnableTimestamp,
setEnableMeta,
setExportMetaList,
setEnableThinking,
setExportAllLimit,
])
@@ -95,6 +101,9 @@ export const SettingProvider: FC = ({ children }) => {
exportMetaList,
setExportMetaList,
enableThinking,
setEnableThinking,
exportAllLimit,
setExportAllLimit,

View File

@@ -35,6 +35,7 @@ export const SettingDialog: FC<SettingDialogProps> = ({
enableTimestampMarkdown, setEnableTimestampMarkdown,
enableMeta, setEnableMeta,
exportMetaList, setExportMetaList,
enableThinking, setEnableThinking,
exportAllLimit, setExportAllLimit,
/* eslint-enable pionxzh/consistent-list-newline */
} = useSettingContext()
@@ -111,6 +112,19 @@ export const SettingDialog: FC<SettingDialogProps> = ({
</dd>
</div>
</div>
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
<div>
<dt className="text-md font-medium text-gray-800 dark:text-white">
{t('Export Thinking Process')}
</dt>
<dd className="text-sm text-gray-700 dark:text-gray-300">
{t('Export Thinking Process Description')}
</dd>
</div>
<div className="absolute right-4">
<Toggle label="" checked={enableThinking} onCheckedUpdate={setEnableThinking} />
</div>
</div>
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
<div>
<dt className="text-md font-medium text-gray-800 dark:text-white">