mirror of
https://github.com/pionxzh/chatgpt-exporter.git
synced 2026-07-23 09:00:51 -05:00
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:
109
src/api.ts
109
src/api.ts
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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, '&')
|
||||
|
||||
@@ -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`
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "選択したすべての会話がアーカイブされました。変更を表示するには、ページを更新してください。",
|
||||
|
||||
@@ -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": "Все выбранные разговоры были заархивированы. Пожалуйста, обновите страницу, чтобы увидеть изменения.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "所有所选的对话已归档。请刷新页面。",
|
||||
|
||||
@@ -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": "所有選取的對話已封存。請重新整理頁面。",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user