mirror of
https://github.com/pionxzh/chatgpt-exporter.git
synced 2026-09-21 13:37:42 -05:00
Vendored
+1
@@ -35,6 +35,7 @@
|
|||||||
"headlessui",
|
"headlessui",
|
||||||
"mdast",
|
"mdast",
|
||||||
"micromark",
|
"micromark",
|
||||||
|
"ooba",
|
||||||
"preact",
|
"preact",
|
||||||
"tabler",
|
"tabler",
|
||||||
"unist"
|
"unist"
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ export const apiUrl = API_MAPPING[baseUrl]
|
|||||||
|
|
||||||
export const KEY_LANGUAGE = 'exporter:language'
|
export const KEY_LANGUAGE = 'exporter:language'
|
||||||
export const KEY_FILENAME_FORMAT = 'exporter:filename_format'
|
export const KEY_FILENAME_FORMAT = 'exporter:filename_format'
|
||||||
export const KEY_OFFICIAL_JSON_FORMAT = 'exporter:official_json_format'
|
// export const KEY_OFFICIAL_JSON_FORMAT = 'exporter:official_json_format'
|
||||||
export const KEY_TIMESTAMP_ENABLED = 'exporter:enable_timestamp'
|
export const KEY_TIMESTAMP_ENABLED = 'exporter:enable_timestamp'
|
||||||
export const KEY_TIMESTAMP_24H = 'exporter:timestamp_24h'
|
export const KEY_TIMESTAMP_24H = 'exporter:timestamp_24h'
|
||||||
export const KEY_TIMESTAMP_MARKDOWN = 'exporter:timestamp_markdown'
|
export const KEY_TIMESTAMP_MARKDOWN = 'exporter:timestamp_markdown'
|
||||||
|
|||||||
+37
-2
@@ -2,10 +2,11 @@ import JSZip from 'jszip'
|
|||||||
import { fetchConversation, getCurrentChatId, processConversation } from '../api'
|
import { fetchConversation, getCurrentChatId, processConversation } from '../api'
|
||||||
import i18n from '../i18n'
|
import i18n from '../i18n'
|
||||||
import { checkIfConversationStarted } from '../page'
|
import { checkIfConversationStarted } from '../page'
|
||||||
|
import { convertToOoba, convertToTavern } from '../utils/conversion'
|
||||||
import { downloadFile, getFileNameWithFormat } from '../utils/download'
|
import { downloadFile, getFileNameWithFormat } from '../utils/download'
|
||||||
import type { ApiConversationWithId } from '../api'
|
import type { ApiConversationWithId } from '../api'
|
||||||
|
|
||||||
export async function exportToJson(fileNameFormat: string, options: { officialFormat: boolean }) {
|
export async function exportToJson(fileNameFormat: string) {
|
||||||
if (!checkIfConversationStarted()) {
|
if (!checkIfConversationStarted()) {
|
||||||
alert(i18n.t('Please start a conversation first'))
|
alert(i18n.t('Please start a conversation first'))
|
||||||
return false
|
return false
|
||||||
@@ -19,7 +20,41 @@ export async function exportToJson(fileNameFormat: string, options: { officialFo
|
|||||||
/**
|
/**
|
||||||
* The official format is just an array of the API response.
|
* The official format is just an array of the API response.
|
||||||
*/
|
*/
|
||||||
const content = conversationToJson(options.officialFormat ? [rawConversation] : rawConversation)
|
const content = conversationToJson([rawConversation])
|
||||||
|
downloadFile(fileName, 'application/json', content)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportToTavern(fileNameFormat: string) {
|
||||||
|
if (!checkIfConversationStarted()) {
|
||||||
|
alert(i18n.t('Please start a conversation first'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatId = await getCurrentChatId()
|
||||||
|
const rawConversation = await fetchConversation(chatId, false)
|
||||||
|
const conversation = processConversation(rawConversation)
|
||||||
|
|
||||||
|
const fileName = getFileNameWithFormat(`${fileNameFormat}.tavern`, 'jsonl', { title: conversation.title, chatId })
|
||||||
|
const content = convertToTavern(conversation)
|
||||||
|
downloadFile(fileName, 'application/json-lines', content)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exportToOoba(fileNameFormat: string) {
|
||||||
|
if (!checkIfConversationStarted()) {
|
||||||
|
alert(i18n.t('Please start a conversation first'))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const chatId = await getCurrentChatId()
|
||||||
|
const rawConversation = await fetchConversation(chatId, false)
|
||||||
|
const conversation = processConversation(rawConversation)
|
||||||
|
|
||||||
|
const fileName = getFileNameWithFormat(`${fileNameFormat}.ooba`, 'json', { title: conversation.title, chatId })
|
||||||
|
const content = convertToOoba(conversation)
|
||||||
downloadFile(fileName, 'application/json', content)
|
downloadFile(fileName, 'application/json', content)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "Enable on Markdown files",
|
"Enable on Markdown": "Enable on Markdown files",
|
||||||
"Use 24-hour format": "Use 24-hour format (eg. 23:59)",
|
"Use 24-hour format": "Use 24-hour format (eg. 23:59)",
|
||||||
"Export Format": "Export Format",
|
"Export Format": "Export Format",
|
||||||
"Export JSON Format Description": "Export JSON in OpenAI Official Format",
|
|
||||||
"Export Metadata": "Export Metadata",
|
"Export Metadata": "Export Metadata",
|
||||||
"Export Metadata Description": "Add metadata to exported Markdown and HTML files.",
|
"Export Metadata Description": "Add metadata to 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 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.",
|
"Conversation Archived Message": "All selected conversations have been archived. Please refresh the page to see the changes.",
|
||||||
"Conversation Delete Alert": "Are you sure you want to delete all selected conversations?",
|
"Conversation Delete Alert": "Are you sure you want to delete all selected conversations?",
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "Habilitar en archivos Markdown",
|
"Enable on Markdown": "Habilitar en archivos Markdown",
|
||||||
"Use 24-hour format": "Usar formato de 24 horas (ej. 23:59)",
|
"Use 24-hour format": "Usar formato de 24 horas (ej. 23:59)",
|
||||||
"Export Format": "Formato de Exportación",
|
"Export Format": "Formato de Exportación",
|
||||||
"Export JSON Format Description": "Exportar JSON en el Formato Oficial de OpenAI",
|
|
||||||
"Export Metadata": "Exportar Metadatos",
|
"Export Metadata": "Exportar Metadatos",
|
||||||
"Export Metadata Description": "Añadir Metadatos a los archivos Markdown y HTML exportados.",
|
"Export Metadata Description": "Añadir Metadatos a 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 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.",
|
"Conversation Archived Message": "Todos las conversaciones seleccionadas se han archivado. Por favor refresca la página para ver los cambios.",
|
||||||
"Conversation Delete Alert": "¿Estás seguro que quieres borrar todas las conversaciones seleccionadas?",
|
"Conversation Delete Alert": "¿Estás seguro que quieres borrar todas las conversaciones seleccionadas?",
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "Aktifkan pada file Markdown",
|
"Enable on Markdown": "Aktifkan pada file Markdown",
|
||||||
"Use 24-hour format": "Gunakan format 24 jam (contohnya: 23:59)",
|
"Use 24-hour format": "Gunakan format 24 jam (contohnya: 23:59)",
|
||||||
"Export Format": "Format Ekspor",
|
"Export Format": "Format Ekspor",
|
||||||
"Export JSON Format Description": "Ekspor JSON dalam Format Resmi OpenAI",
|
|
||||||
"Export Metadata": "Ekspor Metada",
|
"Export Metadata": "Ekspor Metada",
|
||||||
"Export Metadata Description": "Tambahkan metadata ke file Markdown dan HTML yang diekspor.",
|
"Export Metadata Description": "Tambahkan metadata ke 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 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.",
|
"Conversation Archived Message": "Semua percakapan yang dipilih telah diarsipkan. Harap segarkan halaman untuk melihat perubahan.",
|
||||||
"Conversation Delete Alert": "Apakah Anda yakin ingin menghapus semua percakapan yang dipilih?",
|
"Conversation Delete Alert": "Apakah Anda yakin ingin menghapus semua percakapan yang dipilih?",
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "Markdown ファイルで有効にする",
|
"Enable on Markdown": "Markdown ファイルで有効にする",
|
||||||
"Use 24-hour format": "24時間形式を使用する (例: 23:59)",
|
"Use 24-hour format": "24時間形式を使用する (例: 23:59)",
|
||||||
"Export Format": "エクスポートフォーマット",
|
"Export Format": "エクスポートフォーマット",
|
||||||
"Export JSON Format Description": "OpenAI公式フォーマットでのJSONのエクスポート",
|
|
||||||
"Export Metadata": "メタデータをエクスポート",
|
"Export Metadata": "メタデータをエクスポート",
|
||||||
"Export Metadata Description": "エクスポートされたMarkdownおよびHTMLファイルにメタデータを追加します。",
|
"Export Metadata Description": "エクスポートされたMarkdownおよびHTMLファイルにメタデータを追加します。",
|
||||||
|
"OpenAI Official Format": "OpenAI公式フォーマット",
|
||||||
"Conversation Archive Alert": "選択したすべての会話をアーカイブしてもよろしいですか?",
|
"Conversation Archive Alert": "選択したすべての会話をアーカイブしてもよろしいですか?",
|
||||||
"Conversation Archived Message": "選択したすべての会話がアーカイブされました。変更を表示するには、ページを更新してください。",
|
"Conversation Archived Message": "選択したすべての会話がアーカイブされました。変更を表示するには、ページを更新してください。",
|
||||||
"Conversation Delete Alert": "選択したすべての会話を削除してもよろしいですか?",
|
"Conversation Delete Alert": "選択したすべての会話を削除してもよろしいですか?",
|
||||||
|
|||||||
+1
-1
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "Markdown dosyalarında etkinleştir",
|
"Enable on Markdown": "Markdown dosyalarında etkinleştir",
|
||||||
"Use 24-hour format": "24 saat biçimini kullan (örn. 23:59)",
|
"Use 24-hour format": "24 saat biçimini kullan (örn. 23:59)",
|
||||||
"Export Format": "Dışa Aktarma Formatı",
|
"Export Format": "Dışa Aktarma Formatı",
|
||||||
"Export JSON Format Description": "OpenAI Resmi Formatında JSON Dışa Aktarma",
|
|
||||||
"Export Metadata": "Üst veriyi dışa aktar",
|
"Export Metadata": "Üst veriyi dışa aktar",
|
||||||
"Export Metadata Description": "Dışa aktarılan Markdown ve HTML dosyalarına üst veri ekle",
|
"Export Metadata Description": "Dışa aktarılan Markdown ve HTML dosyalarına üst veri ekle",
|
||||||
|
"OpenAI Official Format": "OpenAI Resmi Format",
|
||||||
"Conversation Archive Alert": "Seçilen tüm konuşmaları arşivlemek istediğinizden emin misiniz?",
|
"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.",
|
"Conversation Archived Message": "Seçilen tüm konuşmalar arşivlendi. Değişiklikleri görmek için sayfayı yenileyin.",
|
||||||
"Conversation Delete Alert": "Seçilen tüm konuşmaları silmek istediğinizden emin misiniz?",
|
"Conversation Delete Alert": "Seçilen tüm konuşmaları silmek istediğinizden emin misiniz?",
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "在 Markdown 文件上启用",
|
"Enable on Markdown": "在 Markdown 文件上启用",
|
||||||
"Use 24-hour format": "使用24小时制 (例如 23:59)",
|
"Use 24-hour format": "使用24小时制 (例如 23:59)",
|
||||||
"Export Format": "导出格式",
|
"Export Format": "导出格式",
|
||||||
"Export JSON Format Description": "以 OpenAI 官方格式导出JSON",
|
|
||||||
"Export Metadata": "导出元数据",
|
"Export Metadata": "导出元数据",
|
||||||
"Export Metadata Description": "会添加至 Markdown 以及 HTML 导出。",
|
"Export Metadata Description": "会添加至 Markdown 以及 HTML 导出。",
|
||||||
|
"OpenAI Official Format": "OpenAI 官方格式",
|
||||||
"Conversation Archive Alert": "确定要归档所有选取的对话?",
|
"Conversation Archive Alert": "确定要归档所有选取的对话?",
|
||||||
"Conversation Archived Message": "所有所选的对话已归档。请刷新页面。",
|
"Conversation Archived Message": "所有所选的对话已归档。请刷新页面。",
|
||||||
"Conversation Delete Alert": "确定要删除所有选取的对话?",
|
"Conversation Delete Alert": "确定要删除所有选取的对话?",
|
||||||
|
|||||||
@@ -31,9 +31,9 @@
|
|||||||
"Enable on Markdown": "在 Markdown 檔案上啟用",
|
"Enable on Markdown": "在 Markdown 檔案上啟用",
|
||||||
"Use 24-hour format": "使用24小時制 (例如 23:59)",
|
"Use 24-hour format": "使用24小時制 (例如 23:59)",
|
||||||
"Export Format": "匯出格式",
|
"Export Format": "匯出格式",
|
||||||
"Export JSON Format Description": "以 OpenAI 官方格式匯出 JSON",
|
|
||||||
"Export Metadata": "匯出元資料",
|
"Export Metadata": "匯出元資料",
|
||||||
"Export Metadata Description": "會添加至 Markdown 以及 HTML 匯出。",
|
"Export Metadata Description": "會添加至 Markdown 以及 HTML 匯出。",
|
||||||
|
"OpenAI Official Format": "OpenAI 官方格式",
|
||||||
"Conversation Archive Alert": "確定要封存所有選取的對話?",
|
"Conversation Archive Alert": "確定要封存所有選取的對話?",
|
||||||
"Conversation Archived Message": "所有選取的對話已封存。請重新整理頁面。",
|
"Conversation Archived Message": "所有選取的對話已封存。請重新整理頁面。",
|
||||||
"Conversation Delete Alert": "確定要刪除所有選取的對話?",
|
"Conversation Delete Alert": "確定要刪除所有選取的對話?",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
|
outline: none;
|
||||||
animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,14 +75,15 @@ interface DialogContentProps {
|
|||||||
|
|
||||||
const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
const DialogContent: FC<DialogContentProps> = ({ format }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { enableMeta, exportMetaList, exportOfficialJsonFormat } = useSettingContext()
|
const { enableMeta, exportMetaList } = useSettingContext()
|
||||||
const metaList = useMemo(() => enableMeta ? exportMetaList : [], [enableMeta, exportMetaList])
|
const metaList = useMemo(() => enableMeta ? exportMetaList : [], [enableMeta, exportMetaList])
|
||||||
|
|
||||||
const exportAllOptions = useMemo(() => [
|
const exportAllOptions = useMemo(() => [
|
||||||
{ label: 'Markdown', callback: exportAllToMarkdown },
|
{ label: 'Markdown', callback: exportAllToMarkdown },
|
||||||
{ label: 'JSON', callback: exportOfficialJsonFormat ? exportAllToOfficialJson : exportAllToJson },
|
|
||||||
{ label: 'HTML', callback: exportAllToHtml },
|
{ label: 'HTML', callback: exportAllToHtml },
|
||||||
], [exportOfficialJsonFormat])
|
{ label: 'JSON', callback: exportAllToOfficialJson },
|
||||||
|
{ label: 'JSON (ZIP)', callback: exportAllToJson },
|
||||||
|
], [])
|
||||||
|
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||||
const [exportSource, setExportSource] = useState<ExportSource>('API')
|
const [exportSource, setExportSource] = useState<ExportSource>('API')
|
||||||
|
|||||||
+42
-4
@@ -1,9 +1,10 @@
|
|||||||
|
import * as Dialog from '@radix-ui/react-dialog'
|
||||||
import * as HoverCard from '@radix-ui/react-hover-card'
|
import * as HoverCard from '@radix-ui/react-hover-card'
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { exportToHtml } from '../exporter/html'
|
import { exportToHtml } from '../exporter/html'
|
||||||
import { exportToPng } from '../exporter/image'
|
import { exportToPng } from '../exporter/image'
|
||||||
import { exportToJson } from '../exporter/json'
|
import { exportToJson, exportToOoba, exportToTavern } from '../exporter/json'
|
||||||
import { exportToMarkdown } from '../exporter/markdown'
|
import { exportToMarkdown } from '../exporter/markdown'
|
||||||
import { exportToText } from '../exporter/text'
|
import { exportToText } from '../exporter/text'
|
||||||
import { useWindowResize } from '../hooks/useWindowResize'
|
import { useWindowResize } from '../hooks/useWindowResize'
|
||||||
@@ -23,12 +24,12 @@ function MenuInner({ container }: { container: HTMLDivElement }) {
|
|||||||
const disabled = getHistoryDisabled()
|
const disabled = getHistoryDisabled()
|
||||||
|
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
|
const [jsonOpen, setJsonOpen] = useState(false)
|
||||||
const [exportOpen, setExportOpen] = useState(false)
|
const [exportOpen, setExportOpen] = useState(false)
|
||||||
const [settingOpen, setSettingOpen] = useState(false)
|
const [settingOpen, setSettingOpen] = useState(false)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
format,
|
format,
|
||||||
exportOfficialJsonFormat,
|
|
||||||
enableTimestamp,
|
enableTimestamp,
|
||||||
timeStamp24H,
|
timeStamp24H,
|
||||||
enableMeta,
|
enableMeta,
|
||||||
@@ -50,7 +51,13 @@ function MenuInner({ container }: { container: HTMLDivElement }) {
|
|||||||
const onClickPng = useCallback(() => exportToPng(format), [format])
|
const onClickPng = useCallback(() => exportToPng(format), [format])
|
||||||
const onClickMarkdown = useCallback(() => exportToMarkdown(format, metaList), [format, metaList])
|
const onClickMarkdown = useCallback(() => exportToMarkdown(format, metaList), [format, metaList])
|
||||||
const onClickHtml = useCallback(() => exportToHtml(format, metaList), [format, metaList])
|
const onClickHtml = useCallback(() => exportToHtml(format, metaList), [format, metaList])
|
||||||
const onClickJSON = useCallback(() => exportToJson(format, { officialFormat: exportOfficialJsonFormat }), [format, exportOfficialJsonFormat])
|
const onClickJSON = useCallback(() => {
|
||||||
|
setJsonOpen(true)
|
||||||
|
return true
|
||||||
|
}, [])
|
||||||
|
const onClickOfficialJSON = useCallback(() => exportToJson(format), [format])
|
||||||
|
const onClickTavern = useCallback(() => exportToTavern(format), [format])
|
||||||
|
const onClickOoba = useCallback(() => exportToOoba(format), [format])
|
||||||
|
|
||||||
const width = useWindowResize(() => window.innerWidth)
|
const width = useWindowResize(() => window.innerWidth)
|
||||||
const isMobile = width < 768
|
const isMobile = width < 768
|
||||||
@@ -95,7 +102,7 @@ function MenuInner({ container }: { container: HTMLDivElement }) {
|
|||||||
</HoverCard.Trigger>
|
</HoverCard.Trigger>
|
||||||
<Portal
|
<Portal
|
||||||
container={isMobile ? container : document.body}
|
container={isMobile ? container : document.body}
|
||||||
forceMount={open || settingOpen || exportOpen}
|
forceMount={open || jsonOpen || settingOpen || exportOpen}
|
||||||
>
|
>
|
||||||
<HoverCard.Content
|
<HoverCard.Content
|
||||||
className={`
|
className={`
|
||||||
@@ -151,12 +158,43 @@ function MenuInner({ container }: { container: HTMLDivElement }) {
|
|||||||
className="row-half"
|
className="row-half"
|
||||||
onClick={onClickHtml}
|
onClick={onClickHtml}
|
||||||
/>
|
/>
|
||||||
|
<Dialog.Root
|
||||||
|
open={jsonOpen}
|
||||||
|
onOpenChange={setJsonOpen}
|
||||||
|
>
|
||||||
|
<Dialog.Trigger asChild>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={t('JSON')}
|
text={t('JSON')}
|
||||||
icon={IconJSON}
|
icon={IconJSON}
|
||||||
className="row-half"
|
className="row-half"
|
||||||
onClick={onClickJSON}
|
onClick={onClickJSON}
|
||||||
/>
|
/>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay className="DialogOverlay" />
|
||||||
|
<Dialog.Content className="DialogContent" style={{ width: '320px' }}>
|
||||||
|
<Dialog.Title className="DialogTitle">{t('JSON')}</Dialog.Title>
|
||||||
|
<MenuItem
|
||||||
|
text={t('OpenAI Official Format')}
|
||||||
|
icon={IconCopy}
|
||||||
|
className="row-full"
|
||||||
|
onClick={onClickOfficialJSON}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
text="JSONL (TavernAI, SillyTavern)"
|
||||||
|
icon={IconCopy}
|
||||||
|
className="row-full"
|
||||||
|
onClick={onClickTavern}
|
||||||
|
/>
|
||||||
|
<MenuItem
|
||||||
|
text="Ooba (text-generation-webui)"
|
||||||
|
icon={IconCopy}
|
||||||
|
className="row-full"
|
||||||
|
onClick={onClickOoba}
|
||||||
|
/>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Portal>
|
||||||
|
</Dialog.Root>
|
||||||
<ExportDialog
|
<ExportDialog
|
||||||
format={format}
|
format={format}
|
||||||
open={exportOpen}
|
open={exportOpen}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
KEY_FILENAME_FORMAT,
|
KEY_FILENAME_FORMAT,
|
||||||
KEY_META_ENABLED,
|
KEY_META_ENABLED,
|
||||||
KEY_META_LIST,
|
KEY_META_LIST,
|
||||||
KEY_OFFICIAL_JSON_FORMAT,
|
|
||||||
KEY_TIMESTAMP_24H,
|
KEY_TIMESTAMP_24H,
|
||||||
KEY_TIMESTAMP_ENABLED,
|
KEY_TIMESTAMP_ENABLED,
|
||||||
KEY_TIMESTAMP_HTML,
|
KEY_TIMESTAMP_HTML,
|
||||||
@@ -29,9 +28,6 @@ const SettingContext = createContext({
|
|||||||
format: defaultFormat,
|
format: defaultFormat,
|
||||||
setFormat: (_: string) => {},
|
setFormat: (_: string) => {},
|
||||||
|
|
||||||
exportOfficialJsonFormat: false,
|
|
||||||
setExportOfficialJsonFormat: (_: boolean) => {},
|
|
||||||
|
|
||||||
enableTimestamp: false,
|
enableTimestamp: false,
|
||||||
setEnableTimestamp: (_: boolean) => {},
|
setEnableTimestamp: (_: boolean) => {},
|
||||||
timeStamp24H: false,
|
timeStamp24H: false,
|
||||||
@@ -52,8 +48,6 @@ const SettingContext = createContext({
|
|||||||
export const SettingProvider: FC = ({ children }) => {
|
export const SettingProvider: FC = ({ children }) => {
|
||||||
const [format, setFormat] = useGMStorage(KEY_FILENAME_FORMAT, defaultFormat)
|
const [format, setFormat] = useGMStorage(KEY_FILENAME_FORMAT, defaultFormat)
|
||||||
|
|
||||||
const [exportOfficialJsonFormat, setExportOfficialJsonFormat] = useGMStorage(KEY_OFFICIAL_JSON_FORMAT, false)
|
|
||||||
|
|
||||||
const [enableTimestamp, setEnableTimestamp] = useGMStorage(KEY_TIMESTAMP_ENABLED, false)
|
const [enableTimestamp, setEnableTimestamp] = useGMStorage(KEY_TIMESTAMP_ENABLED, false)
|
||||||
const [timeStamp24H, setTimeStamp24H] = useGMStorage(KEY_TIMESTAMP_24H, false)
|
const [timeStamp24H, setTimeStamp24H] = useGMStorage(KEY_TIMESTAMP_24H, false)
|
||||||
const [enableTimestampHTML, setEnableTimestampHTML] = useGMStorage(KEY_TIMESTAMP_HTML, false)
|
const [enableTimestampHTML, setEnableTimestampHTML] = useGMStorage(KEY_TIMESTAMP_HTML, false)
|
||||||
@@ -75,9 +69,6 @@ export const SettingProvider: FC = ({ children }) => {
|
|||||||
format,
|
format,
|
||||||
setFormat,
|
setFormat,
|
||||||
|
|
||||||
exportOfficialJsonFormat,
|
|
||||||
setExportOfficialJsonFormat,
|
|
||||||
|
|
||||||
enableTimestamp,
|
enableTimestamp,
|
||||||
setEnableTimestamp,
|
setEnableTimestamp,
|
||||||
timeStamp24H,
|
timeStamp24H,
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ export const SettingDialog: FC<SettingDialogProps> = ({
|
|||||||
timeStamp24H, setTimeStamp24H,
|
timeStamp24H, setTimeStamp24H,
|
||||||
enableTimestampHTML, setEnableTimestampHTML,
|
enableTimestampHTML, setEnableTimestampHTML,
|
||||||
enableTimestampMarkdown, setEnableTimestampMarkdown,
|
enableTimestampMarkdown, setEnableTimestampMarkdown,
|
||||||
exportOfficialJsonFormat, setExportOfficialJsonFormat,
|
|
||||||
enableMeta, setEnableMeta,
|
enableMeta, setEnableMeta,
|
||||||
exportMetaList, setExportMetaList,
|
exportMetaList, setExportMetaList,
|
||||||
/* eslint-enable pionxzh/consistent-list-newline */
|
/* eslint-enable pionxzh/consistent-list-newline */
|
||||||
@@ -111,22 +110,6 @@ export const SettingDialog: FC<SettingDialogProps> = ({
|
|||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
</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 Format')}
|
|
||||||
</dt>
|
|
||||||
<dd className="text-sm text-gray-700 dark:text-gray-300">
|
|
||||||
<div className="mt-2">
|
|
||||||
<Toggle
|
|
||||||
label={t('Export JSON Format Description')}
|
|
||||||
checked={exportOfficialJsonFormat}
|
|
||||||
onCheckedUpdate={setExportOfficialJsonFormat}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
|
<div className="relative flex bg-white dark:bg-white/5 rounded p-4">
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-md font-medium text-gray-800 dark:text-white">
|
<dt className="text-md font-medium text-gray-800 dark:text-white">
|
||||||
|
|||||||
+36
-88
@@ -1,23 +1,9 @@
|
|||||||
interface Message {
|
import { jsonlStringify, nonNullable } from './utils'
|
||||||
parent?: string
|
import type { ConversationNode, ConversationResult } from '../api'
|
||||||
message?: {
|
|
||||||
author: {
|
|
||||||
role: string
|
|
||||||
}
|
|
||||||
create_time: number
|
|
||||||
content: {
|
|
||||||
parts: string[]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MessageMapping {
|
interface NameMessage {
|
||||||
[key: string]: Message
|
user_name: string
|
||||||
}
|
character_name: string
|
||||||
|
|
||||||
interface ConversationData {
|
|
||||||
current_node: string
|
|
||||||
mapping: MessageMapping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TavernMessage {
|
interface TavernMessage {
|
||||||
@@ -30,50 +16,25 @@ interface TavernMessage {
|
|||||||
swipe_id: number
|
swipe_id: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NameMessage {
|
|
||||||
user_name: string
|
|
||||||
character_name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OobaData {
|
interface OobaData {
|
||||||
internal: [string, string][]
|
internal: [string, string][]
|
||||||
visible: [string, string][]
|
visible: [string, string][]
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractConversation(data: ConversationData): Message[] {
|
function convertMessageToTavern(node: ConversationNode): TavernMessage | null {
|
||||||
const currentNode = data.current_node
|
if (!node.message || node.message.content.content_type !== 'text') {
|
||||||
const mapping = data.mapping
|
|
||||||
|
|
||||||
const messagesReversed: Message[] = []
|
|
||||||
let nodeId: string | undefined = currentNode
|
|
||||||
|
|
||||||
while (nodeId !== null && nodeId !== undefined) {
|
|
||||||
const currentMessage: Message = mapping[nodeId]
|
|
||||||
messagesReversed.push(currentMessage)
|
|
||||||
nodeId = currentMessage.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
return messagesReversed.reverse()
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertMessageToTavern(messageData: Message): TavernMessage | null {
|
|
||||||
if (!messageData.message) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const senderRole: string = messageData.message.author.role
|
const authorRole = node.message.author.role
|
||||||
if (senderRole === 'system') {
|
const createTime = node.message.create_time || (new Date()).getTime() / 1000
|
||||||
return null
|
const text = node.message.content.parts.join('\n')
|
||||||
}
|
|
||||||
|
|
||||||
const isAssistant = senderRole === 'assistant'
|
|
||||||
const createTime: number = messageData.message.create_time
|
|
||||||
const text: string = messageData.message.content.parts[0]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: isAssistant ? 'Assistant' : 'You',
|
name: authorRole === 'assistant' ? 'Assistant' : 'You',
|
||||||
is_user: !isAssistant,
|
is_user: authorRole === 'user',
|
||||||
is_name: isAssistant,
|
// This seems to be always true
|
||||||
|
is_name: true,
|
||||||
send_date: createTime,
|
send_date: createTime,
|
||||||
mes: text,
|
mes: text,
|
||||||
swipes: [text],
|
swipes: [text],
|
||||||
@@ -81,55 +42,41 @@ function convertMessageToTavern(messageData: Message): TavernMessage | null {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonlStringify(messageArray: any[]): string {
|
export function convertToTavern(conversation: ConversationResult): string {
|
||||||
return messageArray.map((msg: any) => JSON.stringify(msg)).join('\n')
|
const messages: (NameMessage | TavernMessage)[] = [
|
||||||
}
|
{
|
||||||
|
|
||||||
export function getTavernString(jsonData: ConversationData): string {
|
|
||||||
// Takes the OAI JSON data as input, outputs the JSONL string
|
|
||||||
const conversation = extractConversation(jsonData)
|
|
||||||
|
|
||||||
const convertedConvo: (TavernMessage | NameMessage)[] = [{
|
|
||||||
user_name: 'You',
|
user_name: 'You',
|
||||||
character_name: 'Assistant',
|
character_name: 'Assistant',
|
||||||
}]
|
},
|
||||||
|
...conversation.conversationNodes.map(convertMessageToTavern).filter(nonNullable),
|
||||||
|
]
|
||||||
|
|
||||||
conversation.forEach((message) => {
|
return jsonlStringify(messages)
|
||||||
const convertedMsg = convertMessageToTavern(message)
|
|
||||||
if (convertedMsg !== null) {
|
|
||||||
convertedConvo.push(convertedMsg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// This _has_ to be stringified without adding any indentation, due to the JSONL format.
|
|
||||||
return jsonlStringify(convertedConvo)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOobaString(jsonData: ConversationData): string {
|
export function convertToOoba(conversation: ConversationResult): string {
|
||||||
// Takes the OAI JSON data as input, outputs the serialized JSON
|
const pairs: [string, string][] = []
|
||||||
const messages = extractConversation(jsonData)
|
const messages = conversation.conversationNodes.filter(node => node.message?.author.role !== 'tool' && node.message?.content.content_type === 'text')
|
||||||
const pairs: any[] = []
|
|
||||||
let idx = 0
|
let idx = 0
|
||||||
|
|
||||||
while (idx < messages.length - 1) {
|
while (idx < messages.length - 1) {
|
||||||
const message = messages[idx]
|
const message = messages[idx]
|
||||||
const nextMessage = messages[idx + 1]
|
const nextMessage = messages[idx + 1]
|
||||||
let role: string, text: string, nextRole: string, nextText: string
|
|
||||||
|
|
||||||
if (!message.message || !nextMessage.message) {
|
if (
|
||||||
|
!message.message
|
||||||
|
|| !nextMessage.message
|
||||||
|
|| message.message.content.content_type !== 'text'
|
||||||
|
|| nextMessage.message.content.content_type !== 'text'
|
||||||
|
) {
|
||||||
idx += 1
|
idx += 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const role = message.message.author.role
|
||||||
role = message.message.author.role
|
const text = message.message.content.parts[0]
|
||||||
text = message.message.content.parts[0]
|
const nextRole = nextMessage.message.author.role
|
||||||
nextRole = nextMessage.message.author.role
|
const nextText = nextMessage.message.content.parts[0]
|
||||||
nextText = nextMessage.message.content.parts[0]
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
idx += 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role === 'system') {
|
if (role === 'system') {
|
||||||
if (text !== '') {
|
if (text !== '') {
|
||||||
@@ -157,6 +104,7 @@ export function getOobaString(jsonData: ConversationData): string {
|
|||||||
idx += 1
|
idx += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const oobaData: OobaData = {
|
const oobaData: OobaData = {
|
||||||
internal: pairs,
|
internal: pairs,
|
||||||
visible: JSON.parse(JSON.stringify(pairs)),
|
visible: JSON.parse(JSON.stringify(pairs)),
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
export function noop() {}
|
export function noop() {}
|
||||||
|
|
||||||
|
export function nonNullable<T>(x: T): x is NonNullable<T> {
|
||||||
|
return x != null
|
||||||
|
}
|
||||||
|
|
||||||
export function onloadSafe(fn: () => void) {
|
export function onloadSafe(fn: () => void) {
|
||||||
if (document.readyState === 'complete') {
|
if (document.readyState === 'complete') {
|
||||||
fn()
|
fn()
|
||||||
@@ -32,3 +36,8 @@ export function unixTimestampToISOString(timestamp: number) {
|
|||||||
if (!timestamp) return ''
|
if (!timestamp) return ''
|
||||||
return (new Date(timestamp * 1000)).toISOString()
|
return (new Date(timestamp * 1000)).toISOString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function jsonlStringify(list: any[]): string {
|
||||||
|
// This _has_ to be stringified without adding any indentation
|
||||||
|
return list.map((msg: any) => JSON.stringify(msg)).join('\n')
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user