mirror of
https://github.com/pionxzh/chatgpt-exporter.git
synced 2026-07-23 09:00:51 -05:00
feat(export): make max-message-export configurable
This commit is contained in:
committed by
Pionxzh
parent
7e57605a21
commit
832732b90c
36
src/api.ts
36
src/api.ts
@@ -25,7 +25,13 @@ interface ApiSession {
|
||||
}
|
||||
}
|
||||
|
||||
type ModelSlug = 'text-davinci-002-render-sha' | 'text-davinci-002-render-paid' | 'text-davinci-002-browse' | 'gpt-4' | 'gpt-4-browsing' | 'gpt-4o'
|
||||
type ModelSlug =
|
||||
| 'text-davinci-002-render-sha'
|
||||
| 'text-davinci-002-render-paid'
|
||||
| 'text-davinci-002-browse'
|
||||
| 'gpt-4'
|
||||
| 'gpt-4-browsing'
|
||||
| 'gpt-4o'
|
||||
|
||||
export interface Citation {
|
||||
start_ix: number
|
||||
@@ -432,19 +438,31 @@ async function fetchProjectConversations(project: string, offset = 0, limit = 20
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAllConversations(project: string | null = null): Promise<ApiConversationItem[]> {
|
||||
export async function fetchAllConversations(project: string | null = null, maxConversations = 1000): Promise<ApiConversationItem[]> {
|
||||
const conversations: ApiConversationItem[] = []
|
||||
const limit = project === null ? 100 : 50 // gizmos api uses a smaller limit
|
||||
let offset = 0
|
||||
while (true) {
|
||||
const result = await fetchConversations(offset, limit, project)
|
||||
conversations.push(...result.items)
|
||||
if (result.total !== null && offset + limit >= result.total) break
|
||||
if (result.items.length === 0) break
|
||||
if (offset + limit >= 1000) break
|
||||
offset += limit
|
||||
try {
|
||||
const result = await fetchConversations(offset, limit, project)
|
||||
if (!result.items) {
|
||||
// Handle potential API errors or empty responses
|
||||
console.warn('fetchAllConversations received no items at offset:', offset)
|
||||
break
|
||||
}
|
||||
conversations.push(...result.items)
|
||||
// Stop if we've reached the total reported by the API OR the user-defined limit
|
||||
if (result.total !== null && offset + limit >= result.total) break
|
||||
if (conversations.length >= maxConversations) break
|
||||
offset += limit
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error fetching conversations batch:', error)
|
||||
break
|
||||
}
|
||||
}
|
||||
return conversations
|
||||
// Ensure we don't return more than the requested limit if the last batch pushed us over
|
||||
return conversations.slice(0, maxConversations)
|
||||
}
|
||||
|
||||
export async function archiveConversation(chatId: string): Promise<boolean> {
|
||||
|
||||
@@ -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_EXPORT_ALL_LIMIT = 'exporter:export_all_limit'
|
||||
|
||||
export const KEY_OAI_LOCALE = 'oai/apps/locale'
|
||||
export const KEY_OAI_HISTORY_DISABLED = 'oai/apps/historyDisabled'
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "All selected conversations have been deleted. Please refresh the page to see the changes.",
|
||||
"Please start a conversation first": "Please start a conversation first.",
|
||||
"Select Project": "Select Project",
|
||||
"(no project)": "(no project)"
|
||||
"(no project)": "(no project)",
|
||||
"Export All Limit": "Export All Limit",
|
||||
"Export All Limit Description": "Set the maximum number of conversations to load in the 'Export All' dialog."
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "Todos las conversaciones seleccionadas se han borrado. Por favor refresca la página para ver los cambios.",
|
||||
"Please start a conversation first": "Por favor empieza una conversación antes.",
|
||||
"Select Project": "Seleccionar proyecto",
|
||||
"(no project)": "(sin proyecto)"
|
||||
"(no project)": "(sin proyecto)",
|
||||
"Export All Limit": "Límite de Exportar Todos",
|
||||
"Export All Limit Description": "Establece el número máximo de conversaciones a cargar en el diálogo 'Exportar Todos'."
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "Toutes les conversations sélectionnées ont été supprimées. Veuillez actualiser la page pour voir les changements.",
|
||||
"Please start a conversation first": "Veuillez commencer une conversation d'abord.",
|
||||
"Select Project": "Sélectionner un projet",
|
||||
"(no project)": "(aucun projet)"
|
||||
"(no project)": "(aucun projet)",
|
||||
"Export All Limit": "Limite d'Exportation Multiple",
|
||||
"Export All Limit Description": "Définit le nombre maximal de conversations à charger dans la boîte de dialogue 'Tout exporter'."
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "Semua percakapan yang dipilih telah dihapus. Harap segarkan halaman untuk melihat perubahan.",
|
||||
"Please start a conversation first": "Harap mulai percakapan terlebih dahulu.",
|
||||
"Select Project": "Pilih Proyek",
|
||||
"(no project)": "(tidak ada proyek)"
|
||||
"(no project)": "(tidak ada proyek)",
|
||||
"Export All Limit": "Batas Ekspor Semua",
|
||||
"Export All Limit Description": "Atur jumlah maksimum percakapan yang akan dimuat dalam dialog 'Ekspor Semua'."
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "選択したすべての会話が削除されました。変更を表示するには、ページを更新してください。",
|
||||
"Please start a conversation first": "まず会話を開始してください。",
|
||||
"Select Project": "プロジェクトを選択",
|
||||
"(no project)": "(プロジェクトなし)"
|
||||
"(no project)": "(プロジェクトなし)",
|
||||
"Export All Limit": "すべてエクスポートの上限",
|
||||
"Export All Limit Description": "「すべてエクスポート」ダイアログで読み込む会話の最大数を設定します。"
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "Все выбранные разговоры были удалены. Пожалуйста, обновите страницу, чтобы увидеть изменения.",
|
||||
"Please start a conversation first": "Пожалуйста, начните разговор первым.",
|
||||
"Select Project": "Выберите проект",
|
||||
"(no project)": "(нет проекта)"
|
||||
"(no project)": "(нет проекта)",
|
||||
"Export All Limit": "Лимит экспорта всех",
|
||||
"Export All Limit Description": "Установите максимальное количество бесед для загрузки в диалоге 'Экспортировать все'."
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "Seçilen tüm konuşmalar silindi. Değişiklikleri görmek için sayfayı yenileyin.",
|
||||
"Please start a conversation first": "Lütfen önce bir konuşma başlatın.",
|
||||
"Select Project": "Proje Seç",
|
||||
"(no project)": "(proje yok)"
|
||||
"(no project)": "(proje yok)",
|
||||
"Export All Limit": "Tümünü Dışa Aktarma Limiti",
|
||||
"Export All Limit Description": "'Tümünü Dışa Aktar' iletişim kutusunda yüklenecek maksimum konuşma sayısını ayarlayın."
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "所有所选的对话已删除。请刷新页面。",
|
||||
"Please start a conversation first": "请先开始对话。",
|
||||
"Select Project": "选择项目",
|
||||
"(no project)": "(无项目)"
|
||||
"(no project)": "(无项目)",
|
||||
"Export All Limit": "批量导出上限",
|
||||
"Export All Limit Description": "设置“批量导出”对话框中加载的最大对话数量。"
|
||||
}
|
||||
|
||||
@@ -40,5 +40,7 @@
|
||||
"Conversation Deleted Message": "所有選取的對話已刪除。請重新整理頁面。",
|
||||
"Please start a conversation first": "請先開始對話。",
|
||||
"Select Project": "選擇專案",
|
||||
"(no project)": "(無專案)"
|
||||
"(no project)": "(無專案)",
|
||||
"Export All Limit": "批量匯出上限",
|
||||
"Export All Limit Description": "設定「批量匯出」對話方塊中載入的最大對話數量。"
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ interface DialogContentProps {
|
||||
|
||||
const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
||||
const { t } = useTranslation()
|
||||
const { enableMeta, exportMetaList } = useSettingContext()
|
||||
const { enableMeta, exportMetaList, exportAllLimit } = useSettingContext()
|
||||
const metaList = useMemo(() => enableMeta ? exportMetaList : [], [enableMeta, exportMetaList])
|
||||
|
||||
const exportAllOptions = useMemo(() => [
|
||||
@@ -238,7 +238,15 @@ const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
||||
const results = localConversations.filter(c => selected.some(s => s.id === c.id))
|
||||
const callback = exportAllOptions.find(o => o.label === exportType)?.callback
|
||||
if (callback) callback(format, results, metaList)
|
||||
}, [disabled, selected, localConversations, exportAllOptions, exportType, format, metaList])
|
||||
}, [
|
||||
disabled,
|
||||
selected,
|
||||
localConversations,
|
||||
exportAllOptions,
|
||||
exportType,
|
||||
format,
|
||||
metaList,
|
||||
])
|
||||
|
||||
const exportAll = useMemo(() => {
|
||||
return exportSource === 'API' ? exportAllFromApi : exportAllFromLocal
|
||||
@@ -288,11 +296,14 @@ const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true)
|
||||
fetchAllConversations(selectedProject?.id)
|
||||
fetchAllConversations(selectedProject?.id, exportAllLimit)
|
||||
.then(setApiConversations)
|
||||
.catch(err => setError(err.toString()))
|
||||
.catch((err) => {
|
||||
console.error('Error fetching conversations:', err)
|
||||
setError(err.message || 'Failed to load conversations')
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [selectedProject])
|
||||
}, [selectedProject, exportAllLimit])
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -350,7 +361,7 @@ const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
||||
<span>{`${progress.completed}/${progress.total}`}</span>
|
||||
</div>
|
||||
<div className="w-full bg-gray-200 rounded-full h-2.5 mb-4 dark:bg-gray-700">
|
||||
<div className="bg-blue-600 h-2.5 rounded-full" style={{ width: `${progress.completed / progress.total * 100}%` }} />
|
||||
<div className="bg-blue-600 h-2.5 rounded-full" style={{ width: `${(progress.completed / progress.total) * 100}%` }} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createContext, useContext } from 'preact/compat'
|
||||
import { useCallback } from 'preact/hooks'
|
||||
import {
|
||||
KEY_EXPORT_ALL_LIMIT,
|
||||
KEY_FILENAME_FORMAT,
|
||||
KEY_META_ENABLED,
|
||||
KEY_META_LIST,
|
||||
@@ -13,6 +14,7 @@ import { useGMStorage } from '../hooks/useGMStorage'
|
||||
import type { FC } from 'preact/compat'
|
||||
|
||||
const defaultFormat = 'ChatGPT-{title}'
|
||||
const defaultExportAllLimit = 1000
|
||||
|
||||
export interface ExportMeta {
|
||||
name: string
|
||||
@@ -41,7 +43,8 @@ const SettingContext = createContext({
|
||||
setEnableMeta: (_: boolean) => {},
|
||||
exportMetaList: defaultExportMetaList,
|
||||
setExportMetaList: (_: ExportMeta[]) => {},
|
||||
|
||||
exportAllLimit: defaultExportAllLimit,
|
||||
setExportAllLimit: (_: number) => {},
|
||||
resetDefault: () => {},
|
||||
})
|
||||
|
||||
@@ -56,12 +59,21 @@ export const SettingProvider: FC = ({ children }) => {
|
||||
const [enableMeta, setEnableMeta] = useGMStorage(KEY_META_ENABLED, false)
|
||||
|
||||
const [exportMetaList, setExportMetaList] = useGMStorage(KEY_META_LIST, defaultExportMetaList)
|
||||
const [exportAllLimit, setExportAllLimit] = useGMStorage(KEY_EXPORT_ALL_LIMIT, defaultExportAllLimit)
|
||||
|
||||
const resetDefault = useCallback(() => {
|
||||
setFormat(defaultFormat)
|
||||
setEnableTimestamp(false)
|
||||
setEnableMeta(false)
|
||||
setExportMetaList(defaultExportMetaList)
|
||||
}, [setFormat, setEnableMeta, setExportMetaList])
|
||||
setExportAllLimit(defaultExportAllLimit)
|
||||
}, [
|
||||
setFormat,
|
||||
setEnableTimestamp,
|
||||
setEnableMeta,
|
||||
setExportMetaList,
|
||||
setExportAllLimit,
|
||||
])
|
||||
|
||||
return (
|
||||
<SettingContext.Provider
|
||||
@@ -83,6 +95,9 @@ export const SettingProvider: FC = ({ children }) => {
|
||||
exportMetaList,
|
||||
setExportMetaList,
|
||||
|
||||
exportAllLimit,
|
||||
setExportAllLimit,
|
||||
|
||||
resetDefault,
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -35,6 +35,7 @@ export const SettingDialog: FC<SettingDialogProps> = ({
|
||||
enableTimestampMarkdown, setEnableTimestampMarkdown,
|
||||
enableMeta, setEnableMeta,
|
||||
exportMetaList, setExportMetaList,
|
||||
exportAllLimit, setExportAllLimit,
|
||||
/* eslint-enable pionxzh/consistent-list-newline */
|
||||
} = useSettingContext()
|
||||
const { t, i18n } = useTranslation()
|
||||
@@ -110,6 +111,39 @@ 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 All Limit')}{' '}
|
||||
{/* Add translation key */}
|
||||
</dt>
|
||||
<dd className="text-sm text-gray-700 dark:text-gray-300 mt-2">
|
||||
{t('Export All Limit Description')}{' '}
|
||||
{/* Add translation key */}
|
||||
<div className="flex items-center gap-4 mt-3">
|
||||
<input
|
||||
type="range"
|
||||
min="100" // Set min value
|
||||
max="20000" // Set max value (adjust as needed)
|
||||
step="100" // Set step value
|
||||
value={exportAllLimit}
|
||||
onChange={e =>
|
||||
setExportAllLimit(
|
||||
Number.parseInt(
|
||||
e.currentTarget.value,
|
||||
10,
|
||||
),
|
||||
)}
|
||||
className="flex-grow h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700"
|
||||
id="exportAllLimitSlider"
|
||||
/>
|
||||
<span className="font-medium text-gray-900 dark:text-gray-300 w-12 text-right">
|
||||
{exportAllLimit}
|
||||
</span>
|
||||
</div>
|
||||
</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">
|
||||
|
||||
Reference in New Issue
Block a user