Merge branch 'main' into pr/terryoy/41
This commit is contained in:
commit
aa6bc9d0ab
|
@ -29,7 +29,8 @@
|
|||
"avatar_url": "https://avatars.githubusercontent.com/u/8343178?v=4",
|
||||
"profile": "https://danb.me",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
"ideas",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -68,6 +69,24 @@
|
|||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "fuegovic",
|
||||
"name": "fuegovic",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/32828263?v=4",
|
||||
"profile": "https://github.com/fuegovic",
|
||||
"contributions": [
|
||||
"ideas"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Sixzeroo",
|
||||
"name": "Sixzeroo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/20949383?v=4",
|
||||
"profile": "https://www.liuin.cn",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"files": [
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
module.exports = {
|
||||
extends: 'standard-with-typescript',
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: { // add these parser options
|
||||
project: ['./tsconfig.json']
|
||||
},
|
||||
plugins: [
|
||||
'svelte3',
|
||||
'@typescript-eslint'
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/*.svelte'
|
||||
],
|
||||
processor: 'svelte3/svelte3'
|
||||
}
|
||||
],
|
||||
settings: {
|
||||
'svelte3/typescript': true
|
||||
},
|
||||
ignorePatterns: ['node_modules/*', 'dist/*', 'vite-env.d.ts']
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
name: Linting
|
||||
|
||||
# Run on PRs only
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: Lint
|
||||
run: npm ci && npm run lint
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
node-version: '18'
|
||||
|
||||
- name: Build
|
||||
run: npm ci && npm run build:github
|
||||
run: npm ci && npm run lint && npm run build:github
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
FROM node:18-alpine
|
||||
|
||||
ADD . /work
|
||||
WORKDIR /work
|
||||
|
||||
RUN npm ci
|
||||
|
||||
CMD ["npm", "run", "dev:public"]
|
12
README.md
12
README.md
|
@ -21,6 +21,12 @@ npm ci
|
|||
npm run dev # or: npm run build
|
||||
```
|
||||
|
||||
## Use with Docker compose
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Contributors
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
|
@ -31,12 +37,16 @@ npm run dev # or: npm run build
|
|||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Michael-Tanzer"><img src="https://avatars.githubusercontent.com/u/23483071?v=4?s=100" width="100px;" alt="Michael Tanzer"/><br /><sub><b>Michael Tanzer</b></sub></a><br /><a href="#ideas-Michael-Tanzer" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Niek/chatgpt-web/commits?author=Michael-Tanzer" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/petergeneric"><img src="https://avatars.githubusercontent.com/u/870655?v=4?s=100" width="100px;" alt="Peter"/><br /><sub><b>Peter</b></sub></a><br /><a href="#ideas-petergeneric" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://danb.me"><img src="https://avatars.githubusercontent.com/u/8343178?v=4?s=100" width="100px;" alt="Dan Brown"/><br /><sub><b>Dan Brown</b></sub></a><br /><a href="#ideas-ssddanbrown" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://danb.me"><img src="https://avatars.githubusercontent.com/u/8343178?v=4?s=100" width="100px;" alt="Dan Brown"/><br /><sub><b>Dan Brown</b></sub></a><br /><a href="#ideas-ssddanbrown" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Niek/chatgpt-web/commits?author=ssddanbrown" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/littlemoonstones"><img src="https://avatars.githubusercontent.com/u/32943414?v=4?s=100" width="100px;" alt="littlemoonstones"/><br /><sub><b>littlemoonstones</b></sub></a><br /><a href="https://github.com/Niek/chatgpt-web/commits?author=littlemoonstones" title="Code">💻</a> <a href="#ideas-littlemoonstones" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/maxrye1996"><img src="https://avatars.githubusercontent.com/u/28844671?v=4?s=100" width="100px;" alt="maxrye1996"/><br /><sub><b>maxrye1996</b></sub></a><br /><a href="https://github.com/Niek/chatgpt-web/issues?q=author%3Amaxrye1996" title="Bug reports">🐛</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Mikemansour"><img src="https://avatars.githubusercontent.com/u/50986937?v=4?s=100" width="100px;" alt="Mikemansour"/><br /><sub><b>Mikemansour</b></sub></a><br /><a href="#ideas-Mikemansour" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/abc91199"><img src="https://avatars.githubusercontent.com/u/16594734?v=4?s=100" width="100px;" alt="abc91199"/><br /><sub><b>abc91199</b></sub></a><br /><a href="#ideas-abc91199" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/fuegovic"><img src="https://avatars.githubusercontent.com/u/32828263?v=4?s=100" width="100px;" alt="fuegovic"/><br /><sub><b>fuegovic</b></sub></a><br /><a href="#ideas-fuegovic" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.liuin.cn"><img src="https://avatars.githubusercontent.com/u/20949383?v=4?s=100" width="100px;" alt="Sixzeroo"/><br /><sub><b>Sixzeroo</b></sub></a><br /><a href="https://github.com/Niek/chatgpt-web/commits?author=Sixzeroo" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
version: "3"
|
||||
|
||||
services:
|
||||
chatgpt_web:
|
||||
container_name: chatgpt_web
|
||||
restart: always
|
||||
ports:
|
||||
- 5173:5173
|
||||
build:
|
||||
context: "."
|
||||
dockerfile: Dockerfile
|
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
|
@ -5,11 +5,13 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:public": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"build:github": "vite build --base=/chatgpt-web/",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-check --tsconfig ./tsconfig.json",
|
||||
"tauri": "tauri"
|
||||
"tauri": "tauri",
|
||||
"lint": "eslint . --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fullhuman/postcss-purgecss": "^5.0.0",
|
||||
|
@ -21,15 +23,19 @@
|
|||
"@types/marked": "^4.0.8",
|
||||
"bulma": "^0.9.4",
|
||||
"bulma-prefers-dark": "^0.1.0-beta.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"eslint-config-standard-with-typescript": "^34.0.1",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"postcss": "^8.4.21",
|
||||
"sass": "^1.58.3",
|
||||
"sass": "^1.59.2",
|
||||
"svelte": "^3.55.1",
|
||||
"svelte-check": "^3.0.4",
|
||||
"svelte-check": "^3.1.2",
|
||||
"svelte-highlight": "^7.2.0",
|
||||
"svelte-local-storage-store": "^0.4.0",
|
||||
"svelte-markdown": "^0.2.3",
|
||||
"svelte-spa-router": "^3.3.0",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^4.9.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
import Router, { location } from "svelte-spa-router";
|
||||
import routes from "./routes";
|
||||
|
||||
import Navbar from "./lib/Navbar.svelte";
|
||||
import Sidebar from "./lib/Sidebar.svelte";
|
||||
import Home from "./lib/Home.svelte";
|
||||
import Chat from "./lib/Chat.svelte";
|
||||
import Footer from "./lib/Footer.svelte";
|
||||
|
||||
import { apiKeyStorage, chatsStorage } from "./lib/Storage.svelte";
|
||||
|
@ -11,12 +12,10 @@
|
|||
$: apiKey = $apiKeyStorage;
|
||||
|
||||
// Check if the API key is passed in as a "key" query parameter - if so, save it
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlParams: URLSearchParams = new URLSearchParams(window.location.search);
|
||||
if (urlParams.has("key")) {
|
||||
apiKeyStorage.set(urlParams.get("key")!);
|
||||
apiKeyStorage.set(urlParams.get("key") as string);
|
||||
}
|
||||
|
||||
let activeChatId: number;
|
||||
</script>
|
||||
|
||||
<Navbar />
|
||||
|
@ -25,14 +24,12 @@
|
|||
<div class="container is-fullhd">
|
||||
<div class="columns">
|
||||
<div class="column is-one-fifth">
|
||||
<Sidebar bind:apiKey bind:sortedChats bind:activeChatId />
|
||||
<Sidebar bind:apiKey bind:sortedChats />
|
||||
</div>
|
||||
<div class="column is-four-fifths">
|
||||
{#if activeChatId}
|
||||
<Chat bind:chatId={activeChatId} />
|
||||
{:else}
|
||||
<Home bind:activeChatId />
|
||||
{/if}
|
||||
{#key $location}
|
||||
<Router {routes} />
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
13
src/app.scss
13
src/app.scss
|
@ -84,3 +84,16 @@ $modal-background-background-color-dark: rgba($dark, 0.86) !default; // remove t
|
|||
.modal-card-body { // remove this once https: //github.com/jloh/bulma-prefers-dark/pull/90 is merged and released
|
||||
background-color: $background-dark;
|
||||
}
|
||||
|
||||
/* Support for copy code button */
|
||||
.code-block>button {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Make menu stick on the left side */
|
||||
.menu {
|
||||
position: sticky;
|
||||
top: 1rem;
|
||||
}
|
|
@ -7,13 +7,23 @@
|
|||
addMessage,
|
||||
clearMessages,
|
||||
} from "./Storage.svelte";
|
||||
import type { Request, Response, Message, Settings } from "./Types.svelte";
|
||||
import {
|
||||
type Request,
|
||||
type Response,
|
||||
type Message,
|
||||
type Settings,
|
||||
supportedModels,
|
||||
type ResponseModels,
|
||||
type SettingsSelect,
|
||||
} from "./Types.svelte";
|
||||
import Code from "./Code.svelte";
|
||||
|
||||
import { afterUpdate, onMount } from "svelte";
|
||||
import { replace } from "svelte-spa-router";
|
||||
import SvelteMarkdown from "svelte-markdown";
|
||||
|
||||
export let chatId: number;
|
||||
export let params = { chatId: undefined };
|
||||
let chatId: number = parseInt(params.chatId);
|
||||
let updating: boolean = false;
|
||||
|
||||
let input: HTMLTextAreaElement;
|
||||
|
@ -23,40 +33,65 @@
|
|||
let recording = false;
|
||||
|
||||
const settingsMap: Settings[] = [
|
||||
{
|
||||
key: "model",
|
||||
name: "Model",
|
||||
default: "gpt-3.5-turbo",
|
||||
options: supportedModels,
|
||||
type: "select",
|
||||
},
|
||||
{
|
||||
key: "temperature",
|
||||
name: "Sampling Temperature",
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 2,
|
||||
step: 0.1,
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
key: "top_p",
|
||||
name: "Nucleus Sampling",
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
key: "n",
|
||||
name: "Number of Messages",
|
||||
default: 1,
|
||||
min: 1,
|
||||
max: 10,
|
||||
step: 1,
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
key: "max_tokens",
|
||||
name: "Max Tokens",
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 32768,
|
||||
step: 1024,
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
key: "presence_penalty",
|
||||
name: "Presence Penalty",
|
||||
default: 0,
|
||||
min: -2,
|
||||
max: 2,
|
||||
step: 0.2,
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
key: "frequency_penalty",
|
||||
name: "Frequency Penalty",
|
||||
default: 0,
|
||||
min: -2,
|
||||
max: 2,
|
||||
step: 0.2,
|
||||
type: "number",
|
||||
},
|
||||
];
|
||||
|
@ -65,7 +100,7 @@
|
|||
const token_price = 0.000002; // $0.002 per 1000 tokens
|
||||
|
||||
// Focus the input on mount
|
||||
onMount(() => {
|
||||
onMount(async () => {
|
||||
input.focus();
|
||||
|
||||
// Try to detect speech recognition support
|
||||
|
@ -105,8 +140,9 @@
|
|||
|
||||
// Marked options
|
||||
const markedownOptions = {
|
||||
gfm: true,
|
||||
breaks: true,
|
||||
gfm: true, // Use GitHub Flavored Markdown
|
||||
breaks: true, // Enable line breaks in markdown
|
||||
mangle: false, // Do not mangle email addresses
|
||||
};
|
||||
|
||||
const sendRequest = async (messages: Message[]): Promise<Response> => {
|
||||
|
@ -135,7 +171,6 @@
|
|||
let response: Response;
|
||||
try {
|
||||
const request: Request = {
|
||||
model: "gpt-3.5-turbo",
|
||||
// Submit only the role and content of the messages, provide the previous messages as well for context
|
||||
messages: messages
|
||||
.map((message): Message => {
|
||||
|
@ -241,10 +276,11 @@
|
|||
|
||||
const deleteChat = () => {
|
||||
if (confirm("Are you sure you want to delete this chat?")) {
|
||||
replace("/").then(() => {
|
||||
chatsStorage.update((chats) =>
|
||||
chats.filter((chat) => chat.id !== chatId)
|
||||
);
|
||||
chatId = null;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -266,8 +302,23 @@
|
|||
chatNameSettings.classList.remove("is-active");
|
||||
};
|
||||
|
||||
const showSettings = () => {
|
||||
const showSettings = async () => {
|
||||
settings.classList.add("is-active");
|
||||
|
||||
// Load available models from OpenAI
|
||||
const allModels = (await (
|
||||
await fetch("https://api.openai.com/v1/models", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${$apiKeyStorage}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
).json()) as ResponseModels;
|
||||
const filteredModels = supportedModels.filter((model) => allModels.data.find((m) => m.id === model));
|
||||
|
||||
// Update the models in the settings
|
||||
(settingsMap[0] as SettingsSelect).options = filteredModels;
|
||||
};
|
||||
|
||||
const closeSettings = () => {
|
||||
|
@ -472,18 +523,32 @@
|
|||
{#each settingsMap as setting}
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-label is-normal">
|
||||
<label class="label" for="settings-temperature"
|
||||
<label class="label" for="settings-{setting.key}"
|
||||
>{setting.name}</label
|
||||
>
|
||||
</div>
|
||||
<div class="field-body">
|
||||
<div class="field">
|
||||
{#if setting.type === "number"}
|
||||
<input
|
||||
class="input"
|
||||
inputmode="decimal"
|
||||
type={setting.type}
|
||||
id="settings-{setting.key}"
|
||||
min={setting.min}
|
||||
max={setting.max}
|
||||
step={setting.step}
|
||||
placeholder={String(setting.default)}
|
||||
/>
|
||||
{:else if setting.type === "select"}
|
||||
<div class="select">
|
||||
<select id="settings-{setting.key}">
|
||||
{#each setting.options as option}
|
||||
<option value={option} selected={option == setting.default}>{option}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
// Style depends on system theme
|
||||
const style = window.matchMedia("(prefers-color-scheme: dark)").matches ? githubDark : github;
|
||||
|
||||
// Copy function for the code block
|
||||
import copy from "copy-to-clipboard";
|
||||
|
||||
// Import all supported languages
|
||||
import {
|
||||
javascript,
|
||||
|
@ -68,10 +71,38 @@
|
|||
default:
|
||||
language = plaintext;
|
||||
}
|
||||
|
||||
// For copying code - reference: https://vyacheslavbasharov.com/blog/adding-click-to-copy-code-markdown-blog
|
||||
const copyFunction = (event) => {
|
||||
// Get the button the user clicked on
|
||||
const clickedElement = event.target as HTMLButtonElement;
|
||||
|
||||
// Get the next element
|
||||
const nextElement = clickedElement.nextElementSibling;
|
||||
|
||||
// Modify the appearance of the button
|
||||
const originalButtonContent = clickedElement.innerHTML;
|
||||
clickedElement.classList.add("is-success");
|
||||
clickedElement.innerHTML = "Copied!";
|
||||
|
||||
// Retrieve the code in the code block
|
||||
const codeBlock = (nextElement.querySelector("pre > code") as HTMLPreElement).innerText;
|
||||
copy(codeBlock);
|
||||
|
||||
// Restored the button after copying the text in 1 second.
|
||||
setTimeout(() => {
|
||||
clickedElement.innerHTML = originalButtonContent;
|
||||
clickedElement.classList.remove("is-success");
|
||||
clickedElement.blur();
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{@html style}
|
||||
</svelte:head>
|
||||
|
||||
<div class="code-block is-relative">
|
||||
<button class="button is-light is-outlined is-small p-2" on:click={copyFunction}>Copy</button>
|
||||
<Highlight code={text} {language} />
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { addChat, apiKeyStorage } from "./Storage.svelte";
|
||||
import { apiKeyStorage } from './Storage.svelte'
|
||||
|
||||
$: apiKey = $apiKeyStorage;
|
||||
|
||||
export let activeChatId: number;
|
||||
$: apiKey = $apiKeyStorage
|
||||
</script>
|
||||
|
||||
<article class="message">
|
||||
<div class="message-body">
|
||||
<strong><a href="https://github.com/Niek/chatgpt-web">ChatGPT-web</a></strong>
|
||||
is a simple one-page web interface to the OpenAI ChatGPT API. To use it, you need to register for
|
||||
<a href="https://platform.openai.com/account/api-key" target="_blank" rel="noreferrer">an OpenAI API key</a>
|
||||
<a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noreferrer">an OpenAI API key</a>
|
||||
first. OpenAI bills per token (usage-based), which means it is a lot cheaper than
|
||||
<a href="https://openai.com/blog/chatgpt-plus" target="_blank" rel="noreferrer">ChatGPT Plus</a>, unless you use
|
||||
more than 10 million tokens per month. All messages are stored in your browser's local storage, so everything is
|
||||
|
@ -24,7 +22,7 @@
|
|||
<form
|
||||
class="field has-addons has-addons-right"
|
||||
on:submit|preventDefault={(event) => {
|
||||
apiKeyStorage.set(event.target[0].value);
|
||||
apiKeyStorage.set(event.target[0].value)
|
||||
}}
|
||||
>
|
||||
<p class="control is-expanded">
|
||||
|
@ -44,7 +42,7 @@
|
|||
|
||||
{#if !apiKey}
|
||||
<p class="help is-danger">
|
||||
Please enter your <a href="https://platform.openai.com/account/api-key">OpenAI API key</a> above to use ChatGPT-web.
|
||||
Please enter your <a href="https://platform.openai.com/account/api-keys">OpenAI API key</a> above to use ChatGPT-web.
|
||||
It is required to use ChatGPT-web.
|
||||
</p>
|
||||
{/if}
|
||||
|
@ -54,12 +52,7 @@
|
|||
<article class="message is-info">
|
||||
<div class="message-body">
|
||||
Select an existing chat on the sidebar, or
|
||||
<a
|
||||
href={"#"}
|
||||
on:click|preventDefault={() => {
|
||||
activeChatId = addChat();
|
||||
}}>create a new chat</a
|
||||
>
|
||||
<a href={'#/chat/new'}>create a new chat</a>
|
||||
</div>
|
||||
</article>
|
||||
{/if}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import logo from "../assets/logo.svg";
|
||||
import logo from '../assets/logo.svg'
|
||||
</script>
|
||||
|
||||
<nav class="navbar" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href={"#"} on:click|preventDefault={() => (window.location = window.location)}>
|
||||
<a class="navbar-item" href={'#/'}>
|
||||
<img src={logo} alt="ChatGPT-web" width="28" height="28" />
|
||||
<p class="ml-2 is-size-4 has-text-weight-bold">ChatGPT-web</p>
|
||||
</a>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<script lang="ts">
|
||||
import { addChat } from './Storage.svelte'
|
||||
import { replace } from 'svelte-spa-router'
|
||||
|
||||
// Create the new chat instance then redirect to it
|
||||
const chatId = addChat()
|
||||
replace(`/chat/${chatId}`)
|
||||
</script>
|
|
@ -1,11 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { addChat, clearChats } from "./Storage.svelte";
|
||||
import { params, replace } from "svelte-spa-router";
|
||||
|
||||
import { clearChats } from "./Storage.svelte";
|
||||
import { exportAsMarkdown } from "./Export.svelte";
|
||||
import type { Chat } from "./Types.svelte";
|
||||
|
||||
export let activeChatId: number;
|
||||
export let sortedChats: Chat[];
|
||||
export let apiKey: string;
|
||||
|
||||
$: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined;
|
||||
</script>
|
||||
|
||||
<aside class="menu">
|
||||
|
@ -18,11 +21,8 @@
|
|||
<ul>
|
||||
{#each sortedChats as chat}
|
||||
<li>
|
||||
<a
|
||||
href={"#"}
|
||||
class:is-disabled={!apiKey}
|
||||
class:is-active={activeChatId === chat.id}
|
||||
on:click|preventDefault={() => (activeChatId = chat.id)}>{chat.name || `Chat ${chat.id}`}</a
|
||||
<a href={`#/chat/${chat.id}`} class:is-disabled={!apiKey} class:is-active={activeChatId === chat.id}
|
||||
>{chat.name || `Chat ${chat.id}`}</a
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
|
@ -33,41 +33,31 @@
|
|||
<p class="menu-label">Actions</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a
|
||||
href={"#"}
|
||||
class="panel-block"
|
||||
class:is-disabled={!apiKey}
|
||||
class:is-active={!activeChatId}
|
||||
on:click|preventDefault={() => {
|
||||
activeChatId = null;
|
||||
}}><span class="greyscale mr-2">🔑</span> API key</a
|
||||
<a href={"#/"} class="panel-block" class:is-disabled={!apiKey} class:is-active={!activeChatId}
|
||||
><span class="greyscale mr-2">🔑</span> API key</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a href={"#/chat/new"} class="panel-block" class:is-disabled={!apiKey}
|
||||
><span class="greyscale mr-2">➕</span> New chat</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={"#"}
|
||||
href={"#/"}
|
||||
class="panel-block"
|
||||
class:is-disabled={!apiKey}
|
||||
on:click|preventDefault={() => {
|
||||
activeChatId = addChat();
|
||||
}}><span class="greyscale mr-2">➕</span> New chat</a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href={"#"}
|
||||
class="panel-block"
|
||||
class:is-disabled={!apiKey}
|
||||
on:click|preventDefault={() => {
|
||||
on:click={() => {
|
||||
replace("#/").then(() => {
|
||||
clearChats();
|
||||
activeChatId = null;
|
||||
});
|
||||
}}><span class="greyscale mr-2">🗑️</span> Clear chats</a
|
||||
>
|
||||
</li>
|
||||
{#if activeChatId}
|
||||
<li>
|
||||
<a
|
||||
href={"#"}
|
||||
href={"#/"}
|
||||
class="panel-block"
|
||||
class:is-disabled={!apiKey}
|
||||
on:click|preventDefault={() => {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
};
|
||||
|
||||
export type Request = {
|
||||
model: "gpt-3.5-turbo" | "gpt-3.5-turbo-0301";
|
||||
model?: Model;
|
||||
messages: Message[];
|
||||
temperature?: number;
|
||||
top_p?: number;
|
||||
|
@ -32,12 +32,35 @@
|
|||
user?: string;
|
||||
};
|
||||
|
||||
// See: https://platform.openai.com/docs/models/model-endpoint-compatibility
|
||||
export const supportedModels = [
|
||||
"gpt-4",
|
||||
"gpt-4-0314",
|
||||
"gpt-4-32k",
|
||||
"gpt-4-32k-0314",
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0301",
|
||||
];
|
||||
type Model = typeof supportedModels[number];
|
||||
|
||||
type SettingsNumber = {
|
||||
type: "number";
|
||||
default: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
};
|
||||
|
||||
export type SettingsSelect = {
|
||||
type: "select";
|
||||
default: Model;
|
||||
options: Model[];
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
key: string;
|
||||
name: string;
|
||||
default: number;
|
||||
type: "number";
|
||||
};
|
||||
} & (SettingsNumber | SettingsSelect);
|
||||
|
||||
type ResponseOK = {
|
||||
id: string;
|
||||
|
@ -61,4 +84,11 @@
|
|||
};
|
||||
|
||||
export type Response = ResponseOK & ResponseError;
|
||||
|
||||
export type ResponseModels = {
|
||||
object: "list";
|
||||
data: {
|
||||
id: string;
|
||||
}[];
|
||||
};
|
||||
</script>
|
||||
|
|
12
src/main.ts
12
src/main.ts
|
@ -1,11 +1,11 @@
|
|||
// This can be false if you're using a fallback (i.e. SPA mode)
|
||||
export const prerender = false;
|
||||
import './app.scss'
|
||||
import App from './App.svelte'
|
||||
|
||||
import "./app.scss";
|
||||
import App from "./App.svelte";
|
||||
export const prerender = false
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById("app"),
|
||||
});
|
||||
target: document.getElementById('app') as HTMLElement
|
||||
})
|
||||
|
||||
export default app;
|
||||
export default app
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import Home from './lib/Home.svelte'
|
||||
import Chat from './lib/Chat.svelte'
|
||||
import NewChat from './lib/NewChat.svelte'
|
||||
|
||||
export default {
|
||||
'/': Home,
|
||||
|
||||
'/chat/new': NewChat,
|
||||
'/chat/:chatId': Chat,
|
||||
|
||||
'*': Home
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import adapter from "@sveltejs/adapter-static"; // This was changed from adapter-auto
|
||||
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} */
|
||||
|
||||
|
@ -8,6 +8,6 @@ export default {
|
|||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
kit: {
|
||||
adapter: adapter(),
|
||||
},
|
||||
};
|
||||
adapter: adapter()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"resolveJsonModule": true,
|
||||
"strictNullChecks": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable checkJs if you'd like to use dynamic types in JS.
|
||||
|
@ -15,6 +16,6 @@
|
|||
"checkJs": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"],
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte", "vite.config.ts", "svelte.config.js"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { defineConfig } from "vite";
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
||||
import { defineConfig } from 'vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
import purgecss from "@fullhuman/postcss-purgecss";
|
||||
import purgecss from '@fullhuman/postcss-purgecss'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ command, mode, ssrBuild }) => {
|
||||
// Only run PurgeCSS in production builds
|
||||
if (command === "build") {
|
||||
if (command === 'build') {
|
||||
return {
|
||||
plugins: [svelte()],
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
purgecss({
|
||||
content: ["./**/*.html", "./**/*.svelte"],
|
||||
safelist: ["pre", "code"],
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
content: ['./**/*.html', './**/*.svelte'],
|
||||
safelist: ['pre', 'code']
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
plugins: [svelte()],
|
||||
};
|
||||
plugins: [svelte()]
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue