Eduards commited on
Commit
a6060b8
·
1 Parent(s): 95776af

Export chat from sidebar

Browse files
app/components/sidebar/HistoryItem.tsx CHANGED
@@ -1,6 +1,7 @@
1
  import * as Dialog from '@radix-ui/react-dialog';
2
  import { useEffect, useRef, useState } from 'react';
3
  import { type ChatHistoryItem } from '~/lib/persistence';
 
4
 
5
  interface HistoryItemProps {
6
  item: ChatHistoryItem;
@@ -43,9 +44,17 @@ export function HistoryItem({ item, onDelete, onDuplicate }: HistoryItemProps) {
43
  >
44
  <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
45
  {item.description}
46
- <div className="absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 to-transparent w-10 flex justify-end group-hover:w-15 group-hover:from-45%">
47
  {hovering && (
48
  <div className="flex items-center p-1 text-bolt-elements-textSecondary">
 
 
 
 
 
 
 
 
49
  {onDuplicate && (
50
  <button
51
  className="i-ph:copy scale-110 mr-2"
 
1
  import * as Dialog from '@radix-ui/react-dialog';
2
  import { useEffect, useRef, useState } from 'react';
3
  import { type ChatHistoryItem } from '~/lib/persistence';
4
+ import { exportChat } from '~/utils/chatExport';
5
 
6
  interface HistoryItemProps {
7
  item: ChatHistoryItem;
 
44
  >
45
  <a href={`/chat/${item.urlId}`} className="flex w-full relative truncate block">
46
  {item.description}
47
+ <div className="absolute right-0 z-1 top-0 bottom-0 bg-gradient-to-l from-bolt-elements-background-depth-2 group-hover:from-bolt-elements-background-depth-3 box-content pl-3 to-transparent w-10 flex justify-end group-hover:w-15 group-hover:from-99%">
48
  {hovering && (
49
  <div className="flex items-center p-1 text-bolt-elements-textSecondary">
50
+ <button
51
+ className="i-ph:download-simple scale-110 mr-2"
52
+ onClick={(event) => {
53
+ event.preventDefault();
54
+ exportChat(item.messages, item.description);
55
+ }}
56
+ title="Export chat"
57
+ />
58
  {onDuplicate && (
59
  <button
60
  className="i-ph:copy scale-110 mr-2"
app/utils/chatExport.ts ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Message } from 'ai';
2
+ import { toast } from 'react-toastify';
3
+
4
+ export interface ChatExportData {
5
+ messages: Message[];
6
+ description?: string;
7
+ exportDate: string;
8
+ }
9
+
10
+ export const exportChat = (messages: Message[], description?: string) => {
11
+ const chatData: ChatExportData = {
12
+ messages,
13
+ description,
14
+ exportDate: new Date().toISOString(),
15
+ };
16
+
17
+ const blob = new Blob([JSON.stringify(chatData, null, 2)], { type: 'application/json' });
18
+ const url = URL.createObjectURL(blob);
19
+ const a = document.createElement('a');
20
+ a.href = url;
21
+ a.download = `chat-${new Date().toISOString()}.json`;
22
+ document.body.appendChild(a);
23
+ a.click();
24
+ document.body.removeChild(a);
25
+ URL.revokeObjectURL(url);
26
+ };
27
+
28
+ export const importChat = async (file: File): Promise<ChatExportData> => {
29
+ return new Promise((resolve, reject) => {
30
+ const reader = new FileReader();
31
+ reader.onload = (e) => {
32
+ try {
33
+ const content = e.target?.result as string;
34
+ const data = JSON.parse(content);
35
+ if (!Array.isArray(data.messages)) {
36
+ throw new Error('Invalid chat file format');
37
+ }
38
+ resolve(data);
39
+ } catch (error) {
40
+ reject(new Error('Failed to parse chat file'));
41
+ }
42
+ };
43
+ reader.onerror = () => reject(new Error('Failed to read chat file'));
44
+ reader.readAsText(file);
45
+ });
46
+ };