mirror of
https://github.com/enricoros/big-AGI.git
synced 2026-05-10 21:50:14 -07:00
203 lines
5.5 KiB
TypeScript
203 lines
5.5 KiB
TypeScript
// noinspection ExceptionCaughtLocallyJS
|
|
|
|
import { ApiExportBody, ApiExportResponse } from '../pages/api/export';
|
|
import { DConversation } from '@/lib/store-chats';
|
|
import { SystemPurposes } from '@/lib/data';
|
|
|
|
|
|
/**
|
|
* Primitive rendering of a Conversation to Markdown
|
|
*/
|
|
function conversationToMarkdown(conversation: DConversation) {
|
|
|
|
// const title =
|
|
// `# ${conversation.name || 'Conversation'}\n` +
|
|
// (new Date(conversation.created)).toLocaleString() + '\n\n';
|
|
|
|
return conversation.messages.map(message => {
|
|
let sender: string = message.sender;
|
|
switch (message.role) {
|
|
case 'system':
|
|
sender = '✨ System message';
|
|
break;
|
|
case 'assistant':
|
|
sender = `Assistant ${prettyBaseModel(message.modelId)}`.trim();
|
|
const purpose = conversation.systemPurposeId || null;
|
|
if (purpose && purpose in SystemPurposes)
|
|
sender = `${SystemPurposes[purpose]?.symbol || ''} ${sender}`.trim();
|
|
break;
|
|
case 'user':
|
|
sender = '👤 You';
|
|
break;
|
|
}
|
|
return `### ${sender}\n\n${message.text}\n\n`;
|
|
}).join('---\n\n');
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns the origin of the current page
|
|
*/
|
|
function getOrigin() {
|
|
let origin = (typeof window !== 'undefined') ? window.location.href : '';
|
|
if (!origin || origin.includes('//localhost'))
|
|
origin = 'https://github.com/enricoros/nextjs-chatgpt-app';
|
|
origin = origin.replace('https://', '');
|
|
if (origin.endsWith('/'))
|
|
origin = origin.slice(0, -1);
|
|
return origin;
|
|
}
|
|
|
|
/**
|
|
* Exports a markdown rendering of the conversation to a service of choice
|
|
*
|
|
* **Called by the UI to render the data and post it to the API**
|
|
*
|
|
* NOTE: we are calling our own API here, which in turn calls the paste.gg API. We do this
|
|
* because the browser wouldn't otherwise allow us to perform a CORS to paste.gg
|
|
*
|
|
* @param gg Only one service for now
|
|
* @param conversation The conversation to export
|
|
*/
|
|
export async function exportConversation(gg: 'paste.gg', conversation: DConversation): Promise<ApiExportResponse | null> {
|
|
|
|
const body: ApiExportBody = {
|
|
to: gg,
|
|
title: '🤖💬 Chat Conversation',
|
|
fileContent: conversationToMarkdown(conversation),
|
|
fileName: 'my-chat.md',
|
|
origin: getOrigin(),
|
|
};
|
|
|
|
try {
|
|
|
|
const response = await fetch('/api/export', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
|
|
if (response.ok) {
|
|
const paste: ApiExportResponse = await response.json();
|
|
|
|
if (paste.type === 'success') {
|
|
// we log this to the console for extra safety
|
|
console.log('Data from your export to \'paste.gg\'', paste);
|
|
return paste;
|
|
}
|
|
|
|
if (paste.type === 'error')
|
|
throw new Error(`Failed to send the paste: ${paste.error}`);
|
|
}
|
|
|
|
throw new Error(`Failed to export conversation: ${response.status}: ${response.statusText}`);
|
|
|
|
} catch (error) {
|
|
console.error('Export issue', error);
|
|
alert(`Export issue: ${error}`);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
export function prettyBaseModel(model: string | undefined): string {
|
|
if (!model) return '';
|
|
if (model.startsWith('gpt-4')) return 'gpt-4';
|
|
if (model.startsWith('gpt-3.5-turbo')) return '3.5 Turbo';
|
|
return model;
|
|
}
|
|
|
|
|
|
/**
|
|
* Post a paste to paste.gg
|
|
* [called by the API]
|
|
* - API description: https://github.com/ascclemens/paste/blob/master/api.md
|
|
*
|
|
* @param title Title of the paste
|
|
* @param fileName File with extension, e.g. 'conversation.md'
|
|
* @param fileContent Textual content (e.g. markdown text)
|
|
* @param origin the URL of the page that generated the paste
|
|
* @param expireDays Number of days after which the paste will expire (0 = never expires, default = 30)
|
|
*/
|
|
export async function postToPasteGG(title: string, fileName: string, fileContent: string, origin: string, expireDays: number = 30): Promise<PasteGGAPI.PasteResponse> {
|
|
|
|
// Default: expire in 30 days
|
|
let expires = null;
|
|
if (expireDays && expireDays >= 1) {
|
|
const expirationDate = new Date();
|
|
expirationDate.setDate(expirationDate.getDate() + expireDays);
|
|
expires = expirationDate.toISOString();
|
|
}
|
|
|
|
const pasteData: PasteGGAPI.Paste = {
|
|
name: title,
|
|
description: `Generated by ${origin} 🚀`,
|
|
visibility: 'unlisted',
|
|
...(expires && { expires }),
|
|
files: [{
|
|
name: fileName,
|
|
content: {
|
|
format: 'text',
|
|
value: fileContent,
|
|
},
|
|
}],
|
|
};
|
|
|
|
const response = await fetch('https://api.paste.gg/v1/pastes', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify(pasteData),
|
|
});
|
|
|
|
if (response.ok)
|
|
return await response.json() as PasteGGAPI.PasteResponse;
|
|
|
|
console.error(`Failed to create paste: ${response.status}`, response);
|
|
throw new Error(`Failed to create paste: ${response.statusText}`);
|
|
|
|
}
|
|
|
|
|
|
export namespace PasteGGAPI {
|
|
export interface Paste {
|
|
name?: string;
|
|
description?: string;
|
|
visibility?: 'public' | 'unlisted' | 'private';
|
|
expires?: string;
|
|
files: File[];
|
|
}
|
|
|
|
interface File {
|
|
name?: string;
|
|
content: Content;
|
|
}
|
|
|
|
interface Content {
|
|
format: 'text' | 'base64' | 'gzip' | 'xz';
|
|
highlight_language?: string | null;
|
|
value: string;
|
|
}
|
|
|
|
export type PasteResponse = {
|
|
status: 'success'
|
|
result: Paste & {
|
|
id: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
files: {
|
|
id: string;
|
|
name: string;
|
|
highlight_language?: string | null;
|
|
}[];
|
|
deletion_key?: string;
|
|
};
|
|
} | {
|
|
status: 'error';
|
|
error: string;
|
|
message?: string;
|
|
}
|
|
|
|
} |