add: tauri init

This commit is contained in:
terryoy 2023-03-13 18:19:57 +08:00
parent 4c195b96f4
commit e04b703615
26 changed files with 3407 additions and 23 deletions

View File

@ -8,12 +8,15 @@
"build": "vite build", "build": "vite build",
"build:github": "vite build --base=/chatgpt-web/", "build:github": "vite build --base=/chatgpt-web/",
"preview": "vite preview", "preview": "vite preview",
"check": "svelte-check --tsconfig ./tsconfig.json" "check": "svelte-check --tsconfig ./tsconfig.json",
"tauri": "tauri"
}, },
"devDependencies": { "devDependencies": {
"@fullhuman/postcss-purgecss": "^5.0.0", "@fullhuman/postcss-purgecss": "^5.0.0",
"@microsoft/fetch-event-source": "^2.0.1", "@microsoft/fetch-event-source": "^2.0.1",
"@sveltejs/adapter-static": "^1.0.0-next.50",
"@sveltejs/vite-plugin-svelte": "^2.0.2", "@sveltejs/vite-plugin-svelte": "^2.0.2",
"@tauri-apps/cli": "^1.2.3",
"@tsconfig/svelte": "^3.0.0", "@tsconfig/svelte": "^3.0.0",
"@types/marked": "^4.0.8", "@types/marked": "^4.0.8",
"bulma": "^0.9.4", "bulma": "^0.9.4",
@ -29,4 +32,4 @@
"typescript": "^4.9.3", "typescript": "^4.9.3",
"vite": "^4.1.0" "vite": "^4.1.0"
} }
} }

3
src-tauri/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Generated by Cargo
# will have compiled files and executables
/target/

3178
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

28
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,28 @@
[package]
name = "app"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
default-run = "app"
edition = "2021"
rust-version = "1.59"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.2.1", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2.4", features = [] }
[features]
# by default Tauri runs in production mode
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
default = [ "custom-protocol" ]
# this feature is used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = [ "tauri/custom-protocol" ]

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

10
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,10 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
fn main() {
tauri::Builder::default()
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

66
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,66 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"beforeBuildCommand": "npm run build",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:5173",
"distDir": "../dist"
},
"package": {
"productName": "chatgpt-web-desktop",
"version": "0.1.1"
},
"tauri": {
"allowlist": {
"all": false
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "cn.lucki.chatgpt",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
"height": 600,
"resizable": true,
"title": "ChatGPT Web Desktop",
"width": 800
}
]
}
}

View File

@ -1,7 +1,12 @@
<script lang="ts"> <script lang="ts">
//import { fetchEventSource } from "@microsoft/fetch-event-source"; //import { fetchEventSource } from "@microsoft/fetch-event-source";
import { apiKeyStorage, chatsStorage, addMessage, clearMessages } from "./Storage.svelte"; import {
apiKeyStorage,
chatsStorage,
addMessage,
clearMessages,
} from "./Storage.svelte";
import type { Request, Response, Message, Settings } from "./Types.svelte"; import type { Request, Response, Message, Settings } from "./Types.svelte";
import Code from "./Code.svelte"; import Code from "./Code.svelte";
@ -13,6 +18,7 @@
let input: HTMLTextAreaElement; let input: HTMLTextAreaElement;
let settings: HTMLDivElement; let settings: HTMLDivElement;
let chatNameSettings: HTMLDivElement;
let recognition: any = null; let recognition: any = null;
let recording = false; let recording = false;
@ -141,9 +147,14 @@
// Provide the settings by mapping the settingsMap to key/value pairs // Provide the settings by mapping the settingsMap to key/value pairs
...settingsMap.reduce((acc, setting) => { ...settingsMap.reduce((acc, setting) => {
const value = (settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement).value; const value = (
settings.querySelector(
`#settings-${setting.key}`
) as HTMLInputElement
).value;
if (value) { if (value) {
acc[setting.key] = setting.type === "number" ? parseFloat(value) : value; acc[setting.key] =
setting.type === "number" ? parseFloat(value) : value;
} }
return acc; return acc;
}, {}), }, {}),
@ -195,7 +206,9 @@
addMessage(chatId, choice.message); addMessage(chatId, choice.message);
// Use TTS to read the response, if query was recorded // Use TTS to read the response, if query was recorded
if (recorded && "SpeechSynthesisUtterance" in window) { if (recorded && "SpeechSynthesisUtterance" in window) {
const utterance = new SpeechSynthesisUtterance(choice.message.content); const utterance = new SpeechSynthesisUtterance(
choice.message.content
);
speechSynthesis.speak(utterance); speechSynthesis.speak(utterance);
} }
}); });
@ -228,11 +241,31 @@
const deleteChat = () => { const deleteChat = () => {
if (confirm("Are you sure you want to delete this chat?")) { if (confirm("Are you sure you want to delete this chat?")) {
chatsStorage.update((chats) => chats.filter((chat) => chat.id !== chatId)); chatsStorage.update((chats) =>
chats.filter((chat) => chat.id !== chatId)
);
chatId = null; chatId = null;
} }
}; };
const showChatNameSettings = () => {
chatNameSettings.classList.add("is-active");
};
const saveChatNameSettings = () => {
const newChatName = (
chatNameSettings.querySelector("#settings-chat-name") as HTMLInputElement
).value;
// save if changed
if (newChatName && newChatName !== chat.name) {
chat.name = newChatName;
chatsStorage.set($chatsStorage);
}
closeChatNameSettings();
};
const closeChatNameSettings = () => {
chatNameSettings.classList.remove("is-active");
};
const showSettings = () => { const showSettings = () => {
settings.classList.add("is-active"); settings.classList.add("is-active");
}; };
@ -243,7 +276,9 @@
const clearSettings = () => { const clearSettings = () => {
settingsMap.forEach((setting) => { settingsMap.forEach((setting) => {
const input = settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement; const input = settings.querySelector(
`#settings-${setting.key}`
) as HTMLInputElement;
input.value = ""; input.value = "";
}); });
}; };
@ -269,11 +304,7 @@
class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" class="greyscale ml-2 is-hidden has-text-weight-bold editbutton"
title="Rename chat" title="Rename chat"
on:click|preventDefault={() => { on:click|preventDefault={() => {
let newChatName = prompt("Enter a new name for this chat", chat.name); showChatNameSettings();
if (newChatName) {
chat.name = newChatName;
chatsStorage.set($chatsStorage);
}
}} }}
> >
✏️ ✏️
@ -314,7 +345,9 @@
{#if message.role === "user"} {#if message.role === "user"}
<article <article
class="message is-info user-message" class="message is-info user-message"
class:has-text-right={message.content.split("\n").filter((line) => line.trim()).length === 1} class:has-text-right={message.content
.split("\n")
.filter((line) => line.trim()).length === 1}
> >
<div class="message-body content"> <div class="message-body content">
<a <a
@ -360,9 +393,13 @@
/> />
{#if message.usage} {#if message.usage}
<p class="is-size-7"> <p class="is-size-7">
This message was generated using <span class="has-text-weight-bold">{message.usage.total_tokens}</span> This message was generated using <span class="has-text-weight-bold"
>{message.usage.total_tokens}</span
>
tokens ~= tokens ~=
<span class="has-text-weight-bold">${(message.usage.total_tokens * token_price).toFixed(6)}</span> <span class="has-text-weight-bold"
>${(message.usage.total_tokens * token_price).toFixed(6)}</span
>
</p> </p>
{/if} {/if}
</div> </div>
@ -374,7 +411,10 @@
<progress class="progress is-small is-dark" max="100" /> <progress class="progress is-small is-dark" max="100" />
{/if} {/if}
<form class="field has-addons has-addons-right" on:submit|preventDefault={() => submitForm()}> <form
class="field has-addons has-addons-right"
on:submit|preventDefault={() => submitForm()}
>
<p class="control is-expanded"> <p class="control is-expanded">
<textarea <textarea
class="input is-info is-focused chat-input" class="input is-info is-focused chat-input"
@ -396,12 +436,17 @@
/> />
</p> </p>
<p class="control" class:is-hidden={!recognition}> <p class="control" class:is-hidden={!recognition}>
<button class="button" class:is-pulse={recording} on:click|preventDefault={recordToggle} <button
class="button"
class:is-pulse={recording}
on:click|preventDefault={recordToggle}
><span class="greyscale">🎤</span></button ><span class="greyscale">🎤</span></button
> >
</p> </p>
<p class="control"> <p class="control">
<button class="button" on:click|preventDefault={showSettings}><span class="greyscale">⚙️</span></button> <button class="button" on:click|preventDefault={showSettings}
><span class="greyscale">⚙️</span></button
>
</p> </p>
<p class="control"> <p class="control">
<button class="button is-info" type="submit">Send</button> <button class="button is-info" type="submit">Send</button>
@ -427,7 +472,9 @@
{#each settingsMap as setting} {#each settingsMap as setting}
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label class="label" for="settings-temperature">{setting.name}</label> <label class="label" for="settings-temperature"
>{setting.name}</label
>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
@ -444,8 +491,45 @@
</section> </section>
<footer class="modal-card-foot"> <footer class="modal-card-foot">
<button class="button is-info" on:click={closeSettings}>Close settings</button> <button class="button is-info" on:click={closeSettings}
>Close settings</button
>
<button class="button" on:click={clearSettings}>Clear settings</button> <button class="button" on:click={clearSettings}>Clear settings</button>
</footer> </footer>
</div> </div>
</div> </div>
<!-- rename modal -->
<div class="modal" bind:this={chatNameSettings}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="modal-background" on:click={closeChatNameSettings} />
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Enter a new name this chat</p>
</header>
<section class="modal-card-body">
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label" for="settings-temperature">New name:</label>
</div>
<div class="field-body">
<div class="field">
<input
class="input"
type="text"
id="settings-chat-name"
value={chat.name}
/>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-info" on:click={saveChatNameSettings}
>Save</button
>
<button class="button" on:click={closeChatNameSettings}>Cancel</button>
</footer>
</div>
</div>
<!-- end -->

View File

@ -1,3 +1,6 @@
// This can be false if you're using a fallback (i.e. SPA mode)
export const prerender = false;
import "./app.scss"; import "./app.scss";
import App from "./App.svelte"; import App from "./App.svelte";

View File

@ -1,7 +1,13 @@
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' import adapter from "@sveltejs/adapter-static"; // This was changed from adapter-auto
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
export default { export default {
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
// for more information about preprocessors // for more information about preprocessors
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
} kit: {
adapter: adapter(),
},
};