chatgpt-web/src/lib/Sidebar.svelte

164 lines
6.1 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, 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'
$: 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', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(storageObject),
});
if (!response.ok) {
throw new Error('Network response was not ok.');
}
const data = await response.json();
alert("Uploaded LocalStorage: ", data.id);
return data.id;
} catch (error) {
console.error('Error uploading localStorage:', error);
}
}
async function fetchLocalStorage(id){
try {
const id = prompt("RemoteStorage ID?");
if(id === null || id.trim() === "") {
alert("No ID provided. Fetching aborted.");
return;
}
let currentStorageId = await uploadLocalStorage();
alert('Uploaded LocalStorage: ', currentStorageId);
const response = await fetch(`https://api.morgan.kr/localstore/${id}`, {
method: 'GET',
});
if (!response.ok) {
throw new Error('Network response was not ok.');
}
const newData = await response.json();
localStorage.clear();
Object.entries(newData).forEach(([key, value]) => {
localStorage.setItem(key, value);
});
alert('Fetched RemoteStorage.');
} catch (error) {
console.error('Error fetching new localStorage:', error);
}
}
</script>
<aside class="menu main-menu" class:pinned={$pinMainMenu} use:clickOutside={() => { $pinMainMenu = false }}>
<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" />
<p class="ml-2 is-size-5 has-text-weight-bold">ChatGPT-web</p>
<p style="font-size:10px;width:0px;" class="ml-2">&&&BUILDVER&&&</p>
</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 mr-1"><Fa icon={faSquarePlus} /></span> New chat</button>
</div>
{/if}
</div>
</div>
</div>
</aside>