mirror of
https://github.com/pionxzh/chatgpt-exporter.git
synced 2026-07-23 09:00:51 -05:00
fix: set a hard cap 100 on export conversations to avoid OOM
This commit is contained in:
@@ -43,5 +43,8 @@
|
||||
"(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.",
|
||||
"Select a source to load conversations": "Select a project above to load conversations."
|
||||
"Select a source to load conversations": "Select a project above to load conversations.",
|
||||
"Search": "Search",
|
||||
"Last 100": "Last 100",
|
||||
"No results": "No results"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(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'.",
|
||||
"Select a source to load conversations": "Selecciona un proyecto arriba para cargar conversaciones."
|
||||
"Select a source to load conversations": "Selecciona un proyecto arriba para cargar conversaciones.",
|
||||
"Search": "Buscar",
|
||||
"Last 100": "Últimas 100",
|
||||
"No results": "Sin resultados"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(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'.",
|
||||
"Select a source to load conversations": "Sélectionnez un projet ci-dessus pour charger les conversations."
|
||||
"Select a source to load conversations": "Sélectionnez un projet ci-dessus pour charger les conversations.",
|
||||
"Search": "Rechercher",
|
||||
"Last 100": "100 dernières",
|
||||
"No results": "Aucun résultat"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(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'.",
|
||||
"Select a source to load conversations": "Pilih proyek di atas untuk memuat percakapan."
|
||||
"Select a source to load conversations": "Pilih proyek di atas untuk memuat percakapan.",
|
||||
"Search": "Cari",
|
||||
"Last 100": "100 Terakhir",
|
||||
"No results": "Tidak ada hasil"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(no project)": "(プロジェクトなし)",
|
||||
"Export All Limit": "すべてエクスポートの上限",
|
||||
"Export All Limit Description": "「すべてエクスポート」ダイアログで読み込む会話の最大数を設定します。",
|
||||
"Select a source to load conversations": "上からプロジェクトを選択して会話を読み込んでください。"
|
||||
"Select a source to load conversations": "上からプロジェクトを選択して会話を読み込んでください。",
|
||||
"Search": "検索",
|
||||
"Last 100": "最新100件",
|
||||
"No results": "結果なし"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(no project)": "(нет проекта)",
|
||||
"Export All Limit": "Лимит экспорта всех",
|
||||
"Export All Limit Description": "Установите максимальное количество бесед для загрузки в диалоге 'Экспортировать все'.",
|
||||
"Select a source to load conversations": "Выберите проект выше, чтобы загрузить беседы."
|
||||
"Select a source to load conversations": "Выберите проект выше, чтобы загрузить беседы.",
|
||||
"Search": "Поиск",
|
||||
"Last 100": "Последние 100",
|
||||
"No results": "Нет результатов"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(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.",
|
||||
"Select a source to load conversations": "Konuşmaları yüklemek için yukarıdan bir proje seçin."
|
||||
"Select a source to load conversations": "Konuşmaları yüklemek için yukarıdan bir proje seçin.",
|
||||
"Search": "Ara",
|
||||
"Last 100": "Son 100",
|
||||
"No results": "Sonuç yok"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(no project)": "(无项目)",
|
||||
"Export All Limit": "批量导出上限",
|
||||
"Export All Limit Description": "设置“批量导出”对话框中加载的最大对话数量。",
|
||||
"Select a source to load conversations": "请在上方选择一个项目以加载对话。"
|
||||
"Select a source to load conversations": "请在上方选择一个项目以加载对话。",
|
||||
"Search": "搜索",
|
||||
"Last 100": "最新 100 条",
|
||||
"No results": "无结果"
|
||||
}
|
||||
|
||||
@@ -43,5 +43,8 @@
|
||||
"(no project)": "(無專案)",
|
||||
"Export All Limit": "批量匯出上限",
|
||||
"Export All Limit Description": "設定「批量匯出」對話方塊中載入的最大對話數量。",
|
||||
"Select a source to load conversations": "請在上方選擇一個專案以載入對話。"
|
||||
"Select a source to load conversations": "請在上方選擇一個專案以載入對話。",
|
||||
"Search": "搜尋",
|
||||
"Last 100": "最新 100 條",
|
||||
"No results": "無結果"
|
||||
}
|
||||
|
||||
@@ -66,9 +66,27 @@
|
||||
background-color: #f9d9d9;
|
||||
color: #a71d2a;
|
||||
}
|
||||
.Button.neutral {
|
||||
background-color: transparent;
|
||||
color: #6f6e77;
|
||||
border: 1px solid #6f6e77;
|
||||
font-size: 13px;
|
||||
height: 26px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
.Button.green:hover {
|
||||
background-color: #ccebd7;
|
||||
}
|
||||
.Button.neutral:hover {
|
||||
background-color: rgba(111, 110, 119, 0.1);
|
||||
}
|
||||
.dark .Button.neutral {
|
||||
color: #a0a0a8;
|
||||
border-color: #a0a0a8;
|
||||
}
|
||||
.dark .Button.neutral:hover {
|
||||
background-color: rgba(160, 160, 168, 0.1);
|
||||
}
|
||||
.Button:disabled {
|
||||
opacity: 0.5;
|
||||
color: #6f6e77;
|
||||
@@ -151,11 +169,26 @@
|
||||
color: #bcbcbc;
|
||||
}
|
||||
|
||||
.SelectSearch {
|
||||
width: 100%;
|
||||
padding: 8px 16px;
|
||||
border: 1px solid #6f6e77;
|
||||
border-bottom: none;
|
||||
border-radius: 4px 4px 0 0;
|
||||
background-color: transparent;
|
||||
color: inherit;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
}
|
||||
.SelectSearch::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.SelectToolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-radius: 4px 4px 0 0;
|
||||
border-radius: 0;
|
||||
border: 1px solid #6f6e77;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ const ProjectSelect: FC<ProjectSelectProps> = ({ projects, selected, setSelected
|
||||
)
|
||||
}
|
||||
|
||||
const EXPORT_LIMIT = 100
|
||||
|
||||
interface ConversationSelectProps {
|
||||
conversations: ApiConversationItem[]
|
||||
selected: ApiConversationItem[]
|
||||
@@ -68,35 +70,68 @@ const ConversationSelect: FC<ConversationSelectProps> = ({
|
||||
error,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const q = query.trim().toLowerCase()
|
||||
if (!q) return conversations
|
||||
return conversations.filter(c => c.title.toLowerCase().includes(q))
|
||||
}, [conversations, query])
|
||||
|
||||
const atCap = selected.length >= EXPORT_LIMIT
|
||||
const allFilteredSelected = filtered.length > 0
|
||||
&& filtered.slice(0, EXPORT_LIMIT).every(c => selected.some(x => x.id === c.id))
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
type="search"
|
||||
className="SelectSearch"
|
||||
placeholder={t('Search')}
|
||||
value={query}
|
||||
onInput={e => setQuery(e.currentTarget.value)}
|
||||
/>
|
||||
<div className="SelectToolbar">
|
||||
<CheckBox
|
||||
label={t('Select All')}
|
||||
disabled={disabled}
|
||||
checked={selected.length === conversations.length}
|
||||
checked={allFilteredSelected}
|
||||
onCheckedChange={(checked) => {
|
||||
setSelected(checked ? conversations : [])
|
||||
setSelected(checked ? filtered.slice(0, EXPORT_LIMIT) : [])
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
{loading && conversations.length > 0 && (
|
||||
<span className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<IconLoading className="w-3 h-3" />
|
||||
{t('Loading')}... ({conversations.length})
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
className="Button neutral"
|
||||
disabled={disabled || conversations.length === 0}
|
||||
onClick={() => setSelected(conversations.slice(0, EXPORT_LIMIT))}
|
||||
>
|
||||
{t('Last 100')}
|
||||
</button>
|
||||
<span className={`text-sm font-medium tabular-nums ${atCap ? 'text-red-500 dark:text-red-400' : 'text-gray-500 dark:text-gray-400'}`}>
|
||||
{selected.length}/{EXPORT_LIMIT}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="SelectList">
|
||||
{loading && conversations.length === 0 && <li className="SelectItem">{t('Loading')}...</li>}
|
||||
{error && <li className="SelectItem">{t('Error')}: {error}</li>}
|
||||
{conversations.map(c => (
|
||||
{filtered.map((c) => {
|
||||
const isSelected = selected.some(x => x.id === c.id)
|
||||
return (
|
||||
<li className="SelectItem" key={c.id}>
|
||||
<CheckBox
|
||||
label={c.title}
|
||||
disabled={disabled}
|
||||
checked={selected.some(x => x.id === c.id)}
|
||||
disabled={disabled || (atCap && !isSelected)}
|
||||
checked={isSelected}
|
||||
onCheckedChange={(checked) => {
|
||||
if (checked && atCap) return
|
||||
setSelected(checked
|
||||
? [...selected, c]
|
||||
: selected.filter(x => x.id !== c.id),
|
||||
@@ -104,7 +139,11 @@ const ConversationSelect: FC<ConversationSelectProps> = ({
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
{!loading && !error && filtered.length === 0 && conversations.length > 0 && (
|
||||
<li className="SelectItem text-gray-400 dark:text-gray-500">{t('No results')}</li>
|
||||
)}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
@@ -353,6 +392,7 @@ const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
||||
)
|
||||
: (
|
||||
<ConversationSelect
|
||||
key={selectedProject?.id ?? 'no-project'}
|
||||
conversations={conversations}
|
||||
selected={selected}
|
||||
setSelected={setSelected}
|
||||
|
||||
Reference in New Issue
Block a user