208 lines
7.5 KiB
Svelte
208 lines
7.5 KiB
Svelte
<script lang="ts">
|
|
import { params } from 'svelte-spa-router'
|
|
import ChatMenuItem from './ChatMenuItem.svelte'
|
|
import { chatsStorage, pinMainMenu, checkStateChange, getChatSortOption, setChatSortOption } from './Storage.svelte'
|
|
import Fa from 'svelte-fa/src/fa.svelte'
|
|
import { faSquarePlus, faKey, faDownload, faRotate, faUpload } from '@fortawesome/free-solid-svg-icons/index'
|
|
import ChatOptionMenu from './ChatOptionMenu.svelte'
|
|
import logo from '../assets/logo.svg'
|
|
import { clickOutside } from 'svelte-use-click-outside'
|
|
import { startNewChatWithWarning } from './Util.svelte'
|
|
import { chatSortOptions } from './Settings.svelte'
|
|
import { hasActiveModels } from './Models.svelte'
|
|
import { onMount } from 'svelte';
|
|
|
|
$: sortedChats = $chatsStorage.sort(getChatSortOption().sortFn)
|
|
$: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined
|
|
|
|
let sortOption = getChatSortOption()
|
|
let hasModels = hasActiveModels()
|
|
|
|
const onStateChange = (...args:any) => {
|
|
sortOption = getChatSortOption()
|
|
sortedChats = $chatsStorage.sort(sortOption.sortFn)
|
|
hasModels = hasActiveModels()
|
|
}
|
|
|
|
$: onStateChange($checkStateChange)
|
|
|
|
let showSortMenu = false
|
|
|
|
async function uploadLocalStorage(){
|
|
try {
|
|
let storageObject = {};
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key) {
|
|
storageObject[key] = localStorage.getItem(key);
|
|
}
|
|
}
|
|
const response = await fetch('https://api.morgan.kr/localstore/19492', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({data: storageObject}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok.');
|
|
}
|
|
|
|
const data = await response.json();
|
|
console.log(data)
|
|
console.log("Uploaded savedata.");
|
|
alert("Uploaded savedata.");
|
|
return data.id;
|
|
|
|
} catch (error) {
|
|
console.error('Error uploading localStorage:', error);
|
|
|
|
}
|
|
}
|
|
|
|
async function fetchLocalStorage(){
|
|
try {
|
|
const response = await fetch('https://api.morgan.kr/localstore/19492', {
|
|
method: 'GET',
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok.');
|
|
}
|
|
|
|
const lastModifiedServer = new Date(response.headers.get('Last-Modified'));
|
|
const lastModifiedLocal = new Date(localStorage.getItem('lastModified'));
|
|
|
|
if (lastModifiedServer > lastModifiedLocal || !lastModifiedLocal) {
|
|
const newData = await response.json();
|
|
localStorage.clear();
|
|
|
|
Object.entries(newData).forEach(([key, value]) => {
|
|
localStorage.setItem(key, value);
|
|
});
|
|
|
|
localStorage.setItem('lastModified', lastModifiedServer.toISOString());
|
|
console.log('Fetched savedata');
|
|
alert('Fetched savedata');
|
|
} else {
|
|
console.log('Local data is up-to-date or server data older.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching localStorage:', error);
|
|
}
|
|
}
|
|
async function syncLocalStorage(){
|
|
console.log("Syncing...")
|
|
uploadLocalStorage();
|
|
localStorage.setItem('lastModified', new Date().toISOString());
|
|
}
|
|
|
|
function dumpLocalStorage(){
|
|
try {
|
|
let storageObject = {};
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key) {
|
|
storageObject[key] = localStorage.getItem(key);
|
|
}
|
|
}
|
|
|
|
const dataStr = JSON.stringify(storageObject, null, 2);
|
|
const blob = new Blob([dataStr], { type: "application/json" });
|
|
const url = URL.createObjectURL(blob);
|
|
const link = document.createElement("a");
|
|
link.href = url;
|
|
const now = new Date();
|
|
const dateTimeStr = now.toISOString().replace(/:\d+\.\d+Z$/, '').replace(/-|:/g, '_');
|
|
link.download = `ChatGPT-web-${dateTimeStr}.json`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
URL.revokeObjectURL(url);
|
|
|
|
} catch (error) {
|
|
console.error('Error dumping localStorage:', error);
|
|
}
|
|
}
|
|
|
|
onMount(() => {
|
|
console.log('Downloading from server.');
|
|
// fetchLocalStorage();
|
|
});
|
|
|
|
// setInterval(syncLocalStorage, 10000);
|
|
</script>
|
|
|
|
<aside class="menu main-menu" class:pinned={$pinMainMenu} use:clickOutside={() => { $pinMainMenu = false }}>
|
|
<div style="font-size:8px;position:fixed;bottom:1px;right:1px;color:blue;">&&&BUILDVER&&&</div>
|
|
<div class="menu-expanse">
|
|
<div class="navbar-brand menu-nav-bar">
|
|
<a class="navbar-item gpt-logo" href={'#/'}>
|
|
<img src={logo} alt="ChatGPT-web" width="24" height="24" />
|
|
</a>
|
|
<div class="chat-option-menu navbar-item is-pulled-right">
|
|
<ChatOptionMenu bind:chatId={activeChatId} />
|
|
</div>
|
|
</div>
|
|
<ul class="menu-list menu-expansion-list">
|
|
{#if sortedChats.length === 0}
|
|
<li><a href={'#'} class="is-disabled">No chats yet...</a></li>
|
|
{:else}
|
|
{#key $checkStateChange}
|
|
{#each sortedChats as chat, i}
|
|
{#key chat.id}
|
|
<ChatMenuItem activeChatId={activeChatId} chat={chat} prevChat={sortedChats[i - 1]} nextChat={sortedChats[i + 1]} />
|
|
{/key}
|
|
{/each}
|
|
{/key}
|
|
{/if}
|
|
</ul>
|
|
<!-- <p class="menu-label">Actions</p> -->
|
|
<div class="level is-mobile bottom-buttons p-1">
|
|
<div class="level-left">
|
|
<div class="dropdown is-left is-up" class:is-active={showSortMenu} use:clickOutside={() => { showSortMenu = false }}>
|
|
<div class="dropdown-trigger">
|
|
<button class="button" aria-haspopup="true" aria-controls="dropdown-menu3" on:click|preventDefault|stopPropagation={() => { showSortMenu = !showSortMenu }}>
|
|
<span class="icon"><Fa icon={sortOption.icon}/></span>
|
|
</button>
|
|
</div>
|
|
<div class="dropdown-menu" id="dropdown-menu3" role="menu">
|
|
<div class="dropdown-content">
|
|
{#each Object.values(chatSortOptions) as opt}
|
|
<a href={'#'} class="dropdown-item" class:is-active={sortOption === opt} on:click|preventDefault={() => { showSortMenu = false; setChatSortOption(opt.value) }}>
|
|
<span class="menu-icon"><Fa icon={opt.icon}/></span>
|
|
{opt.text}
|
|
</a>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="is-left is-up ml-2">
|
|
<button class="button" aria-haspopup="true" on:click|preventDefault|stopPropagation={() => { uploadLocalStorage(); }}>
|
|
<span class="icon"><Fa icon={faUpload}/></span>
|
|
</button>
|
|
</div>
|
|
<div class="is-left is-up ml-2">
|
|
<button class="button" aria-haspopup="true" on:click|preventDefault|stopPropagation={() => { fetchLocalStorage(); }}>
|
|
<span class="icon"><Fa icon={faDownload}/></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="level-right">
|
|
{#if !hasModels}
|
|
<div class="level-item">
|
|
<a href={'#/'} class="panel-block" class:is-disabled={!hasModels}
|
|
><span class="greyscale mr-1"><Fa icon={faKey} /></span> API Setting</a
|
|
></div>
|
|
{:else}
|
|
<div class="level-item">
|
|
<button on:click={() => { $pinMainMenu = false; startNewChatWithWarning(activeChatId) }} class="panel-block button" title="Start new chat with default profile" class:is-disabled={!hasModels}
|
|
><span class="greyscale"><Fa icon={faSquarePlus} /></span></button>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|