add: tauri init
|
@ -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",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
|
@ -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" ]
|
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 7.4 KiB |
After Width: | Height: | Size: 3.9 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 49 KiB |
|
@ -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");
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 -->
|
||||||
|
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|