use @ import alias
All checks were successful
Build static content / build (push) Successful in 10m51s

This commit is contained in:
2024-10-14 18:09:07 +08:00
parent 1c3c94bae4
commit f0f040c42c
25 changed files with 209 additions and 180 deletions

View File

@@ -1,7 +1,7 @@
import { useState } from "preact/hooks"; import { useState } from "preact/hooks";
import { ChatStore } from "./app"; import { ChatStore } from "@/app";
import { MessageDetail } from "./chatgpt"; import { MessageDetail } from "@/chatgpt";
import { Tr } from "./translate"; import { Tr } from "@/translate";
interface Props { interface Props {
chatStore: ChatStore; chatStore: ChatStore;

View File

@@ -1,14 +1,14 @@
import { IDBPDatabase, openDB } from "idb"; import { IDBPDatabase, openDB } from "idb";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import "./global.css"; import "@/global.css";
import { calculate_token_length, Logprobs, Message } from "./chatgpt"; import { calculate_token_length, Logprobs, Message } from "@/chatgpt";
import getDefaultParams from "./getDefaultParam"; import getDefaultParams from "@/getDefaultParam";
import ChatBOX from "./chatbox"; import ChatBOX from "@/chatbox";
import models, { defaultModel } from "./models"; import models, { defaultModel } from "@/models";
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate"; import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import CHATGPT_API_WEB_VERSION from "./CHATGPT_API_WEB_VERSION"; import CHATGPT_API_WEB_VERSION from "@/CHATGPT_API_WEB_VERSION";
export interface ChatStoreMessage extends Message { export interface ChatStoreMessage extends Message {
hide: boolean; hide: boolean;
@@ -88,7 +88,7 @@ export const newChatStore = (
image_gen_api = "https://api.openai.com/v1/images/generations", image_gen_api = "https://api.openai.com/v1/images/generations",
image_gen_key = "", image_gen_key = "",
json_mode = false, json_mode = false,
logprobs = false logprobs = false,
): ChatStore => { ): ChatStore => {
return { return {
chatgpt_api_web_version: CHATGPT_API_WEB_VERSION, chatgpt_api_web_version: CHATGPT_API_WEB_VERSION,
@@ -100,7 +100,7 @@ export const newChatStore = (
totalTokens: 0, totalTokens: 0,
maxTokens: getDefaultParams( maxTokens: getDefaultParams(
"max", "max",
models[getDefaultParams("model", model)]?.maxToken ?? 2048 models[getDefaultParams("model", model)]?.maxToken ?? 2048,
), ),
maxGenTokens: 2048, maxGenTokens: 2048,
maxGenTokens_enabled: false, maxGenTokens_enabled: false,
@@ -152,7 +152,7 @@ export function addTotalCost(cost: number) {
export function getTotalCost(): number { export function getTotalCost(): number {
let totalCost = parseFloat( let totalCost = parseFloat(
localStorage.getItem(STORAGE_NAME_TOTALCOST) ?? "0" localStorage.getItem(STORAGE_NAME_TOTALCOST) ?? "0",
); );
return totalCost; return totalCost;
} }
@@ -190,7 +190,7 @@ export function BuildFiledForSearch(chatStore: ChatStore): string[] {
export function App() { export function App() {
// init selected index // init selected index
const [selectedChatIndex, setSelectedChatIndex] = useState( const [selectedChatIndex, setSelectedChatIndex] = useState(
parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1") parseInt(localStorage.getItem(STORAGE_NAME_SELECTED) ?? "1"),
); );
console.log("selectedChatIndex", selectedChatIndex); console.log("selectedChatIndex", selectedChatIndex);
useEffect(() => { useEffect(() => {
@@ -207,7 +207,7 @@ export function App() {
// copy from localStorage to indexedDB // copy from localStorage to indexedDB
const allChatStoreIndexes: number[] = JSON.parse( const allChatStoreIndexes: number[] = JSON.parse(
localStorage.getItem(STORAGE_NAME_INDEXES) ?? "[]" localStorage.getItem(STORAGE_NAME_INDEXES) ?? "[]",
); );
let keyCount = 0; let keyCount = 0;
for (const i of allChatStoreIndexes) { for (const i of allChatStoreIndexes) {
@@ -221,7 +221,7 @@ export function App() {
setSelectedChatIndex(keyCount); setSelectedChatIndex(keyCount);
if (keyCount > 0) { if (keyCount > 0) {
alert( alert(
"v2.0.0 Update: Imported chat history from localStorage to indexedDB. 🎉" "v2.0.0 Update: Imported chat history from localStorage to indexedDB. 🎉",
); );
} }
} }
@@ -229,7 +229,7 @@ export function App() {
if (oldVersion < 11) { if (oldVersion < 11) {
if (oldVersion < 11 && oldVersion >= 1) { if (oldVersion < 11 && oldVersion >= 1) {
alert( alert(
"Start upgrading storage, just a sec... (Click OK to continue)" "Start upgrading storage, just a sec... (Click OK to continue)",
); );
} }
if ( if (
@@ -247,7 +247,7 @@ export function App() {
{ {
multiEntry: true, multiEntry: true,
unique: false, unique: false,
} },
); );
// iter through all chatStore and update contents_for_index // iter through all chatStore and update contents_for_index
@@ -294,7 +294,7 @@ export function App() {
const max = chatStore.maxTokens - chatStore.tokenMargin; const max = chatStore.maxTokens - chatStore.tokenMargin;
let sum = 0; let sum = 0;
chatStore.postBeginIndex = chatStore.history.filter( chatStore.postBeginIndex = chatStore.history.filter(
({ hide }) => !hide ({ hide }) => !hide,
).length; ).length;
for (const msg of chatStore.history for (const msg of chatStore.history
.filter(({ hide }) => !hide) .filter(({ hide }) => !hide)
@@ -309,7 +309,7 @@ export function App() {
// manually estimate token // manually estimate token
chatStore.totalTokens = calculate_token_length( chatStore.totalTokens = calculate_token_length(
chatStore.systemMessageContent chatStore.systemMessageContent,
); );
for (const msg of chatStore.history for (const msg of chatStore.history
.filter(({ hide }) => !hide) .filter(({ hide }) => !hide)
@@ -331,7 +331,7 @@ export function App() {
// all chat store indexes // all chat store indexes
const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>( const [allChatStoreIndexes, setAllChatStoreIndexes] = useState<IDBValidKey>(
[] [],
); );
const handleNewChatStoreWithOldOne = async (chatStore: ChatStore) => { const handleNewChatStoreWithOldOne = async (chatStore: ChatStore) => {
@@ -358,8 +358,8 @@ export function App() {
chatStore.image_gen_api, chatStore.image_gen_api,
chatStore.image_gen_key, chatStore.image_gen_key,
chatStore.json_mode, chatStore.json_mode,
false // logprobs default to false false, // logprobs default to false
) ),
); );
setSelectedChatIndex(newKey as number); setSelectedChatIndex(newKey as number);
setAllChatStoreIndexes(await (await db).getAllKeys(STORAGE_NAME)); setAllChatStoreIndexes(await (await db).getAllKeys(STORAGE_NAME));
@@ -445,7 +445,7 @@ export function App() {
return; return;
console.log( console.log(
"remove item", "remove item",
`${STORAGE_NAME}-${selectedChatIndex}` `${STORAGE_NAME}-${selectedChatIndex}`,
); );
(await db).delete(STORAGE_NAME, selectedChatIndex); (await db).delete(STORAGE_NAME, selectedChatIndex);
const newAllChatStoreIndexes = await ( const newAllChatStoreIndexes = await (
@@ -473,7 +473,7 @@ export function App() {
onClick={async () => { onClick={async () => {
if ( if (
!confirm( !confirm(
"Are you sure you want to delete **ALL** chat history?" "Are you sure you want to delete **ALL** chat history?",
) )
) )
return; return;

View File

@@ -1,7 +1,19 @@
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate"; import {
MagnifyingGlassIcon,
CubeIcon,
BanknotesIcon,
DocumentTextIcon,
ChatBubbleLeftEllipsisIcon,
ScissorsIcon,
SwatchIcon,
SparklesIcon,
} from "@heroicons/react/24/outline";
import { IDBPDatabase } from "idb";
import structuredClone from "@ungap/structured-clone"; import structuredClone from "@ungap/structured-clone";
import { createRef } from "preact"; import { createRef } from "preact";
import { StateUpdater, useEffect, useState, Dispatch } from "preact/hooks"; import { StateUpdater, useEffect, useState, Dispatch } from "preact/hooks";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { import {
ChatStore, ChatStore,
ChatStoreMessage, ChatStoreMessage,
@@ -15,7 +27,7 @@ import {
TemplateTools, TemplateTools,
addTotalCost, addTotalCost,
getTotalCost, getTotalCost,
} from "./app"; } from "@/app";
import ChatGPT, { import ChatGPT, {
calculate_token_length, calculate_token_length,
ChunkMessage, ChunkMessage,
@@ -24,28 +36,16 @@ import ChatGPT, {
MessageDetail, MessageDetail,
ToolCall, ToolCall,
Logprobs, Logprobs,
} from "./chatgpt"; } from "@/chatgpt";
import Message from "./message"; import Message from "@/message";
import models from "./models"; import models from "@/models";
import Settings from "./settings"; import Settings from "@/settings";
import getDefaultParams from "./getDefaultParam"; import getDefaultParams from "@/getDefaultParam";
import { AddImage } from "./addImage"; import { AddImage } from "@/addImage";
import { ListAPIs } from "./listAPIs"; import { ListAPIs } from "@/listAPIs";
import { ListToolsTempaltes } from "./listToolsTemplates"; import { ListToolsTempaltes } from "@/listToolsTemplates";
import { autoHeight } from "./textarea"; import { autoHeight } from "@/textarea";
import Search from "./search"; import Search from "@/search";
import { IDBPDatabase } from "idb";
import {
MagnifyingGlassIcon,
CubeIcon,
BanknotesIcon,
DocumentTextIcon,
ChatBubbleLeftEllipsisIcon,
ScissorsIcon,
SwatchIcon,
SparklesIcon,
} from "@heroicons/react/24/outline";
export interface TemplateChatStore extends ChatStore { export interface TemplateChatStore extends ChatStore {
name: string; name: string;
} }
@@ -96,7 +96,7 @@ export default function ChatBOX(props: {
const update_total_tokens = () => { const update_total_tokens = () => {
// manually estimate token // manually estimate token
client.total_tokens = calculate_token_length( client.total_tokens = calculate_token_length(
chatStore.systemMessageContent chatStore.systemMessageContent,
); );
for (const msg of chatStore.history for (const msg of chatStore.history
.filter(({ hide }) => !hide) .filter(({ hide }) => !hide)
@@ -152,7 +152,7 @@ export default function ChatBOX(props: {
// update tool call arguments // update tool call arguments
const tool = allChunkTool.find( const tool = allChunkTool.find(
(tool) => tool.index === tool_call.index (tool) => tool.index === tool_call.index,
); );
if (!tool) { if (!tool) {
@@ -167,7 +167,7 @@ export default function ChatBOX(props: {
allChunkMessage.join("") + allChunkMessage.join("") +
allChunkTool.map((tool) => { allChunkTool.map((tool) => {
return `Tool Call ID: ${tool.id}\nType: ${tool.type}\nFunction: ${tool.function.name}\nArguments: ${tool.function.arguments}`; return `Tool Call ID: ${tool.id}\nType: ${tool.type}\nFunction: ${tool.function.name}\nArguments: ${tool.function.arguments}`;
}) }),
); );
} }
setShowGenerating(false); setShowGenerating(false);
@@ -305,7 +305,7 @@ export default function ChatBOX(props: {
setShowGenerating(true); setShowGenerating(true);
const response = await client._fetch( const response = await client._fetch(
chatStore.streamMode, chatStore.streamMode,
chatStore.logprobs chatStore.logprobs,
); );
const contentType = response.headers.get("content-type"); const contentType = response.headers.get("content-type");
if (contentType?.startsWith("text/event-stream")) { if (contentType?.startsWith("text/event-stream")) {
@@ -375,33 +375,33 @@ export default function ChatBOX(props: {
const [templates, _setTemplates] = useState( const [templates, _setTemplates] = useState(
JSON.parse( JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]" localStorage.getItem(STORAGE_NAME_TEMPLATE) || "[]",
) as TemplateChatStore[] ) as TemplateChatStore[],
); );
const [templateAPIs, _setTemplateAPIs] = useState( const [templateAPIs, _setTemplateAPIs] = useState(
JSON.parse( JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]" localStorage.getItem(STORAGE_NAME_TEMPLATE_API) || "[]",
) as TemplateAPI[] ) as TemplateAPI[],
); );
const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState( const [templateAPIsWhisper, _setTemplateAPIsWhisper] = useState(
JSON.parse( JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]" localStorage.getItem(STORAGE_NAME_TEMPLATE_API_WHISPER) || "[]",
) as TemplateAPI[] ) as TemplateAPI[],
); );
const [templateAPIsTTS, _setTemplateAPIsTTS] = useState( const [templateAPIsTTS, _setTemplateAPIsTTS] = useState(
JSON.parse( JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]" localStorage.getItem(STORAGE_NAME_TEMPLATE_API_TTS) || "[]",
) as TemplateAPI[] ) as TemplateAPI[],
); );
const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState( const [templateAPIsImageGen, _setTemplateAPIsImageGen] = useState(
JSON.parse( JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]" localStorage.getItem(STORAGE_NAME_TEMPLATE_API_IMAGE_GEN) || "[]",
) as TemplateAPI[] ) as TemplateAPI[],
); );
const [toolsTemplates, _setToolsTemplates] = useState( const [toolsTemplates, _setToolsTemplates] = useState(
JSON.parse( JSON.parse(
localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]" localStorage.getItem(STORAGE_NAME_TEMPLATE_TOOLS) || "[]",
) as TemplateTools[] ) as TemplateTools[],
); );
const setTemplates = (templates: TemplateChatStore[]) => { const setTemplates = (templates: TemplateChatStore[]) => {
localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates)); localStorage.setItem(STORAGE_NAME_TEMPLATE, JSON.stringify(templates));
@@ -410,35 +410,35 @@ export default function ChatBOX(props: {
const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => { const setTemplateAPIs = (templateAPIs: TemplateAPI[]) => {
localStorage.setItem( localStorage.setItem(
STORAGE_NAME_TEMPLATE_API, STORAGE_NAME_TEMPLATE_API,
JSON.stringify(templateAPIs) JSON.stringify(templateAPIs),
); );
_setTemplateAPIs(templateAPIs); _setTemplateAPIs(templateAPIs);
}; };
const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => { const setTemplateAPIsWhisper = (templateAPIWhisper: TemplateAPI[]) => {
localStorage.setItem( localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_WHISPER, STORAGE_NAME_TEMPLATE_API_WHISPER,
JSON.stringify(templateAPIWhisper) JSON.stringify(templateAPIWhisper),
); );
_setTemplateAPIsWhisper(templateAPIWhisper); _setTemplateAPIsWhisper(templateAPIWhisper);
}; };
const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => { const setTemplateAPIsTTS = (templateAPITTS: TemplateAPI[]) => {
localStorage.setItem( localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_TTS, STORAGE_NAME_TEMPLATE_API_TTS,
JSON.stringify(templateAPITTS) JSON.stringify(templateAPITTS),
); );
_setTemplateAPIsTTS(templateAPITTS); _setTemplateAPIsTTS(templateAPITTS);
}; };
const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => { const setTemplateAPIsImageGen = (templateAPIImageGen: TemplateAPI[]) => {
localStorage.setItem( localStorage.setItem(
STORAGE_NAME_TEMPLATE_API_IMAGE_GEN, STORAGE_NAME_TEMPLATE_API_IMAGE_GEN,
JSON.stringify(templateAPIImageGen) JSON.stringify(templateAPIImageGen),
); );
_setTemplateAPIsImageGen(templateAPIImageGen); _setTemplateAPIsImageGen(templateAPIImageGen);
}; };
const setTemplateTools = (templateTools: TemplateTools[]) => { const setTemplateTools = (templateTools: TemplateTools[]) => {
localStorage.setItem( localStorage.setItem(
STORAGE_NAME_TEMPLATE_TOOLS, STORAGE_NAME_TEMPLATE_TOOLS,
JSON.stringify(templateTools) JSON.stringify(templateTools),
); );
_setToolsTemplates(templateTools); _setToolsTemplates(templateTools);
}; };
@@ -478,7 +478,11 @@ export default function ChatBOX(props: {
<div className="navbar bg-base-100 p-0"> <div className="navbar bg-base-100 p-0">
<div className="navbar-start"> <div className="navbar-start">
<div className="dropdown lg:hidden"> <div className="dropdown lg:hidden">
<div tabindex={0} role="button" className="btn btn-ghost btn-circle"> <div
tabindex={0}
role="button"
className="btn btn-ghost btn-circle"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className="h-6 w-6" className="h-6 w-6"
@@ -556,7 +560,9 @@ export default function ChatBOX(props: {
<ChatBubbleLeftEllipsisIcon className="h-10 w-10" /> <ChatBubbleLeftEllipsisIcon className="h-10 w-10" />
</div> </div>
<div className="stat-title">Tokens</div> <div className="stat-title">Tokens</div>
<div className="stat-value text-base">{chatStore.totalTokens}</div> <div className="stat-value text-base">
{chatStore.totalTokens}
</div>
<div className="stat-desc">Max: {chatStore.maxTokens}</div> <div className="stat-desc">Max: {chatStore.maxTokens}</div>
</div> </div>
@@ -565,7 +571,9 @@ export default function ChatBOX(props: {
<ScissorsIcon className="h-10 w-10" /> <ScissorsIcon className="h-10 w-10" />
</div> </div>
<div className="stat-title">Cut</div> <div className="stat-title">Cut</div>
<div className="stat-value text-base">{chatStore.postBeginIndex}</div> <div className="stat-value text-base">
{chatStore.postBeginIndex}
</div>
<div className="stat-desc"> <div className="stat-desc">
Max: {chatStore.history.filter(({ hide }) => !hide).length} Max: {chatStore.history.filter(({ hide }) => !hide).length}
</div> </div>
@@ -801,49 +809,49 @@ export default function ChatBOX(props: {
if (!newChatStore.apiEndpoint) { if (!newChatStore.apiEndpoint) {
newChatStore.apiEndpoint = getDefaultParams( newChatStore.apiEndpoint = getDefaultParams(
"api", "api",
chatStore.apiEndpoint chatStore.apiEndpoint,
); );
} }
if (!newChatStore.apiKey) { if (!newChatStore.apiKey) {
newChatStore.apiKey = getDefaultParams( newChatStore.apiKey = getDefaultParams(
"key", "key",
chatStore.apiKey chatStore.apiKey,
); );
} }
if (!newChatStore.whisper_api) { if (!newChatStore.whisper_api) {
newChatStore.whisper_api = getDefaultParams( newChatStore.whisper_api = getDefaultParams(
"whisper-api", "whisper-api",
chatStore.whisper_api chatStore.whisper_api,
); );
} }
if (!newChatStore.whisper_key) { if (!newChatStore.whisper_key) {
newChatStore.whisper_key = getDefaultParams( newChatStore.whisper_key = getDefaultParams(
"whisper-key", "whisper-key",
chatStore.whisper_key chatStore.whisper_key,
); );
} }
if (!newChatStore.tts_api) { if (!newChatStore.tts_api) {
newChatStore.tts_api = getDefaultParams( newChatStore.tts_api = getDefaultParams(
"tts-api", "tts-api",
chatStore.tts_api chatStore.tts_api,
); );
} }
if (!newChatStore.tts_key) { if (!newChatStore.tts_key) {
newChatStore.tts_key = getDefaultParams( newChatStore.tts_key = getDefaultParams(
"tts-key", "tts-key",
chatStore.tts_key chatStore.tts_key,
); );
} }
if (!newChatStore.image_gen_api) { if (!newChatStore.image_gen_api) {
newChatStore.image_gen_api = getDefaultParams( newChatStore.image_gen_api = getDefaultParams(
"image-gen-api", "image-gen-api",
chatStore.image_gen_api chatStore.image_gen_api,
); );
} }
if (!newChatStore.image_gen_key) { if (!newChatStore.image_gen_key) {
newChatStore.image_gen_key = getDefaultParams( newChatStore.image_gen_key = getDefaultParams(
"image-gen-key", "image-gen-key",
chatStore.image_gen_key chatStore.image_gen_key,
); );
} }
newChatStore.cost = 0; newChatStore.cost = 0;
@@ -900,7 +908,7 @@ export default function ChatBOX(props: {
<br />{Tr("Click the conor to create a new chat")} <br />{Tr("Click the conor to create a new chat")}
<br /> <br />
{Tr( {Tr(
"All chat history and settings are stored in the local browser" "All chat history and settings are stored in the local browser",
)} )}
<br /> <br />
</p> </p>
@@ -1143,7 +1151,7 @@ export default function ChatBOX(props: {
} else { } else {
return content.map((c) => c?.text).join(" "); return content.map((c) => c?.text).join(" ");
} }
}) }),
) )
.concat([inputMsg]) .concat([inputMsg])
.join(" "); .join(" ");
@@ -1157,7 +1165,7 @@ export default function ChatBOX(props: {
await navigator.mediaDevices.getUserMedia({ await navigator.mediaDevices.getUserMedia({
audio: true, audio: true,
}), }),
{ audioBitsPerSecond: 64 * 1000 } { audioBitsPerSecond: 64 * 1000 },
); );
// mount mediaRecorder to ref // mount mediaRecorder to ref

View File

@@ -1,4 +1,4 @@
import { defaultModel } from "./models"; import { defaultModel } from "@/models";
export interface ImageURL { export interface ImageURL {
url: string; url: string;
@@ -110,7 +110,7 @@ function calculate_token_length_from_text(text: string): number {
} }
// https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them // https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
export function calculate_token_length( export function calculate_token_length(
content: string | MessageDetail[] content: string | MessageDetail[],
): number { ): number {
if (typeof content === "string") { if (typeof content === "string") {
return calculate_token_length_from_text(content); return calculate_token_length_from_text(content);
@@ -165,7 +165,7 @@ class Chat {
presence_penalty = 0, presence_penalty = 0,
frequency_penalty = 0, frequency_penalty = 0,
json_mode = false, json_mode = false,
} = {} } = {},
) { ) {
this.OPENAI_API_KEY = OPENAI_API_KEY ?? ""; this.OPENAI_API_KEY = OPENAI_API_KEY ?? "";
this.messages = []; this.messages = [];
@@ -200,14 +200,14 @@ class Chat {
} }
if (msg.role === "system") { if (msg.role === "system") {
console.log( console.log(
"Warning: detected system message in the middle of history" "Warning: detected system message in the middle of history",
); );
} }
} }
for (const msg of this.messages) { for (const msg of this.messages) {
if (msg.name && msg.role !== "system") { if (msg.name && msg.role !== "system") {
console.log( console.log(
"Warning: detected message where name field set but role is system" "Warning: detected message where name field set but role is system",
); );
} }
} }

View File

@@ -1,10 +1,8 @@
import { Tr, langCodeContext, LANG_OPTIONS, tr } from "./translate";
import { useState, useEffect, StateUpdater, Dispatch } from "preact/hooks"; import { useState, useEffect, StateUpdater, Dispatch } from "preact/hooks";
import { ChatStore, ChatStoreMessage } from "./app"; import { Tr, langCodeContext, LANG_OPTIONS, tr } from "@/translate";
import { calculate_token_length, getMessageText } from "./chatgpt"; import { ChatStore, ChatStoreMessage } from "@/app";
import { isVailedJSON } from "./message"; import { EditMessageString } from "@/editMessageString";
import { EditMessageString } from "./editMessageString"; import { EditMessageDetail } from "@/editMessageDetail";
import { EditMessageDetail } from "./editMessageDetail";
interface EditMessageProps { interface EditMessageProps {
chat: ChatStoreMessage; chat: ChatStoreMessage;
@@ -49,7 +47,7 @@ export function EditMessage(props: EditMessageProps) {
className="w-full m-2 p-1 rounded bg-red-500" className="w-full m-2 p-1 rounded bg-red-500"
onClick={() => { onClick={() => {
const confirm = window.confirm( const confirm = window.confirm(
"Change message type will clear the content, are you sure?" "Change message type will clear the content, are you sure?",
); );
if (!confirm) return; if (!confirm) return;

View File

@@ -1,6 +1,6 @@
import { ChatStore, ChatStoreMessage } from "./app"; import { ChatStore, ChatStoreMessage } from "@/app";
import { calculate_token_length } from "./chatgpt"; import { calculate_token_length } from "@/chatgpt";
import { Tr } from "./translate"; import { Tr } from "@/translate";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;

View File

@@ -1,7 +1,7 @@
import { ChatStore, ChatStoreMessage } from "./app"; import { ChatStore, ChatStoreMessage } from "@/app";
import { isVailedJSON } from "./message"; import { isVailedJSON } from "@/message";
import { calculate_token_length } from "./chatgpt"; import { calculate_token_length } from "@/chatgpt";
import { Tr } from "./translate"; import { Tr } from "@/translate";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;
@@ -69,7 +69,7 @@ export function EditMessageString({
onClick={() => { onClick={() => {
if (!chat.tool_calls) return; if (!chat.tool_calls) return;
chat.tool_calls = chat.tool_calls.filter( chat.tool_calls = chat.tool_calls.filter(
(tc) => tc.id !== tool_call.id (tc) => tc.id !== tool_call.id,
); );
setChatStore({ ...chatStore }); setChatStore({ ...chatStore });
}} }}

View File

@@ -79,8 +79,14 @@ body::-webkit-scrollbar {
white-space: break-space; white-space: break-space;
background-color: rgba(175, 184, 193, 0.2); background-color: rgba(175, 184, 193, 0.2);
border-radius: 6px; border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, font-family:
Liberation Mono, monospace; ui-monospace,
SFMono-Regular,
SF Mono,
Menlo,
Consolas,
Liberation Mono,
monospace;
} }
.markup > pre { .markup > pre {

View File

@@ -1,5 +1,5 @@
import { ChatStore, TemplateAPI } from "./app"; import { ChatStore, TemplateAPI } from "@/app";
import { Tr } from "./translate"; import { Tr } from "@/translate";
interface Props { interface Props {
chatStore: ChatStore; chatStore: ChatStore;
@@ -62,7 +62,7 @@ export function ListAPIs({
onClick={() => { onClick={() => {
if ( if (
!confirm( !confirm(
`Are you sure to delete this **${label}** template?` `Are you sure to delete this **${label}** template?`,
) )
) { ) {
return; return;

View File

@@ -1,5 +1,5 @@
import { ChatStore, TemplateTools } from "./app"; import { ChatStore, TemplateTools } from "@/app";
import { Tr } from "./translate"; import { Tr } from "@/translate";
interface Props { interface Props {
templateTools: TemplateTools[]; templateTools: TemplateTools[];

View File

@@ -1,9 +1,8 @@
import { render } from "preact";
import { App } from "./app";
import { useState, useEffect } from "preact/hooks";
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import { themeChange } from "theme-change"; import { themeChange } from "theme-change";
import { render } from "preact";
import { useState, useEffect } from "preact/hooks";
import { App } from "@/app";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
function Base() { function Base() {
const [langCode, _setLangCode] = useState("en-US"); const [langCode, _setLangCode] = useState("en-US");

View File

@@ -1,16 +1,17 @@
import { Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import { useState, useEffect, StateUpdater } from "preact/hooks";
import { ChatStore, ChatStoreMessage } from "./app";
import { calculate_token_length, getMessageText } from "./chatgpt";
import Markdown from "preact-markdown";
import TTSButton, { TTSPlay } from "./tts";
import { MessageHide } from "./messageHide";
import { MessageDetail } from "./messageDetail";
import { MessageToolCall } from "./messageToolCall";
import { MessageToolResp } from "./messageToolResp";
import { EditMessage } from "./editMessage";
import logprobToColor from "./logprob";
import { XMarkIcon } from "@heroicons/react/24/outline"; import { XMarkIcon } from "@heroicons/react/24/outline";
import Markdown from "preact-markdown";
import { useState, useEffect, StateUpdater } from "preact/hooks";
import { Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { ChatStore, ChatStoreMessage } from "@/app";
import { calculate_token_length, getMessageText } from "@/chatgpt";
import TTSButton, { TTSPlay } from "@/tts";
import { MessageHide } from "@/messageHide";
import { MessageDetail } from "@/messageDetail";
import { MessageToolCall } from "@/messageToolCall";
import { MessageToolResp } from "@/messageToolResp";
import { EditMessage } from "@/editMessage";
import logprobToColor from "@/logprob";
export const isVailedJSON = (str: string): boolean => { export const isVailedJSON = (str: string): boolean => {
try { try {
@@ -213,7 +214,7 @@ export default function Message(props: Props) {
chatStore.history.splice(messageIndex, 1); chatStore.history.splice(messageIndex, 1);
chatStore.postBeginIndex = Math.max( chatStore.postBeginIndex = Math.max(
chatStore.postBeginIndex - 1, chatStore.postBeginIndex - 1,
0 0,
); );
//chatStore.totalTokens = //chatStore.totalTokens =
chatStore.totalTokens = 0; chatStore.totalTokens = 0;

View File

@@ -1,4 +1,4 @@
import { ChatStoreMessage } from "./app"; import { ChatStoreMessage } from "@/app";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;
@@ -28,7 +28,7 @@ export function MessageDetail({ chat, renderMarkdown }: Props) {
window.open(mdt.image_url?.url, "_blank"); window.open(mdt.image_url?.url, "_blank");
}} }}
/> />
) ),
)} )}
</div> </div>
); );

View File

@@ -1,5 +1,5 @@
import { ChatStoreMessage } from "./app"; import { ChatStoreMessage } from "@/app";
import { getMessageText } from "./chatgpt"; import { getMessageText } from "@/chatgpt";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;

View File

@@ -1,4 +1,4 @@
import { ChatStoreMessage } from "./app"; import { ChatStoreMessage } from "@/app";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;

View File

@@ -1,4 +1,4 @@
import { ChatStoreMessage } from "./app"; import { ChatStoreMessage } from "@/app";
interface Props { interface Props {
chat: ChatStoreMessage; chat: ChatStoreMessage;

View File

@@ -13,7 +13,7 @@ const models: Record<string, Model> = {
}, },
"gpt-4o-2024-08-06": { "gpt-4o-2024-08-06": {
maxToken: 128000, maxToken: 128000,
price: { prompt: 0.0025 / 1000, completion: 0.010 / 1000 }, price: { prompt: 0.0025 / 1000, completion: 0.01 / 1000 },
}, },
"gpt-4o-2024-05-13": { "gpt-4o-2024-05-13": {
maxToken: 128000, maxToken: 128000,

View File

@@ -1,7 +1,8 @@
import { IDBPDatabase } from "idb"; import { IDBPDatabase } from "idb";
import { ChatStore } from "./app";
import { StateUpdater, useRef, useState, Dispatch } from "preact/hooks"; import { StateUpdater, useRef, useState, Dispatch } from "preact/hooks";
import { ChatStore } from "@/app";
interface ChatStoreSearchResult { interface ChatStoreSearchResult {
key: IDBValidKey; key: IDBValidKey;
cs: ChatStore; cs: ChatStore;
@@ -77,7 +78,7 @@ export default function Search(props: {
} }
const now = Math.floor( const now = Math.floor(
(result.length / resultKeys.length) * 100 (result.length / resultKeys.length) * 100,
); );
if (now !== searchingNow) setSearchingNow(now); if (now !== searchingNow) setSearchingNow(now);
@@ -89,7 +90,7 @@ export default function Search(props: {
const beginIndex: number = content.indexOf(query); const beginIndex: number = content.indexOf(query);
const preview = content.slice( const preview = content.slice(
Math.max(0, beginIndex - 100), Math.max(0, beginIndex - 100),
Math.min(content.length, beginIndex + 239) Math.min(content.length, beginIndex + 239),
); );
result.push({ result.push({
key, key,

View File

@@ -1,5 +1,5 @@
import { TemplateAPI } from "./app"; import { TemplateAPI } from "@/app";
import { Tr } from "./translate"; import { Tr } from "@/translate";
interface Props { interface Props {
tmps: TemplateAPI[]; tmps: TemplateAPI[];

View File

@@ -1,21 +1,4 @@
import { createRef } from "preact"; import { themeChange } from "theme-change";
import { StateUpdater, useContext, useEffect, useState, Dispatch } from "preact/hooks";
import {
ChatStore,
TemplateAPI,
TemplateTools,
clearTotalCost,
getTotalCost,
} from "./app";
import models from "./models";
import { TemplateChatStore } from "./chatbox";
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "./translate";
import p from "preact-markdown";
import { isVailedJSON } from "./message";
import { SetAPIsTemplate } from "./setAPIsTemplate";
import { autoHeight } from "./textarea";
import getDefaultParams from "./getDefaultParam";
import { import {
InformationCircleIcon, InformationCircleIcon,
CheckIcon, CheckIcon,
@@ -30,7 +13,28 @@ import {
ListBulletIcon, ListBulletIcon,
} from "@heroicons/react/24/outline"; } from "@heroicons/react/24/outline";
import { themeChange } from "theme-change"; import { createRef } from "preact";
import {
StateUpdater,
useContext,
useEffect,
useState,
Dispatch,
} from "preact/hooks";
import {
ChatStore,
TemplateAPI,
TemplateTools,
clearTotalCost,
getTotalCost,
} from "@/app";
import models from "@/models";
import { TemplateChatStore } from "@/chatbox";
import { tr, Tr, langCodeContext, LANG_OPTIONS } from "@/translate";
import { isVailedJSON } from "@/message";
import { SetAPIsTemplate } from "@/setAPIsTemplate";
import { autoHeight } from "@/textarea";
import getDefaultParams from "@/getDefaultParam";
const TTS_VOICES: string[] = [ const TTS_VOICES: string[] = [
"alloy", "alloy",
@@ -109,7 +113,7 @@ const SelectModel = (props: {
props.chatStore.model = model; props.chatStore.model = model;
props.chatStore.maxTokens = getDefaultParams( props.chatStore.maxTokens = getDefaultParams(
"max", "max",
models[model].maxToken models[model].maxToken,
); );
props.setChatStore({ ...props.chatStore }); props.setChatStore({ ...props.chatStore });
}} }}
@@ -435,11 +439,11 @@ export default (props: {
location.host + location.host +
location.pathname + location.pathname +
`?key=${encodeURIComponent( `?key=${encodeURIComponent(
props.chatStore.apiKey props.chatStore.apiKey,
)}&api=${encodeURIComponent(props.chatStore.apiEndpoint)}&mode=${ )}&api=${encodeURIComponent(props.chatStore.apiEndpoint)}&mode=${
props.chatStore.streamMode ? "stream" : "fetch" props.chatStore.streamMode ? "stream" : "fetch"
}&model=${props.chatStore.model}&sys=${encodeURIComponent( }&model=${props.chatStore.model}&sys=${encodeURIComponent(
props.chatStore.systemMessageContent props.chatStore.systemMessageContent,
)}`; )}`;
if (props.chatStore.develop_mode) { if (props.chatStore.develop_mode) {
link = link + `&dev=true`; link = link + `&dev=true`;
@@ -529,7 +533,7 @@ export default (props: {
className="btn" className="btn"
onClick={() => { onClick={() => {
const name = prompt( const name = prompt(
`Give this **Tools** template a name:` `Give this **Tools** template a name:`,
); );
if (!name) { if (!name) {
alert("No template name specified"); alert("No template name specified");
@@ -632,12 +636,12 @@ export default (props: {
onClick={() => { onClick={() => {
if ( if (
!confirm( !confirm(
tr("Are you sure to clear all history?", langCode) tr("Are you sure to clear all history?", langCode),
) )
) )
return; return;
props.chatStore.history = props.chatStore.history.filter( props.chatStore.history = props.chatStore.history.filter(
(msg) => msg.example && !msg.hide (msg) => msg.example && !msg.hide,
); );
props.chatStore.postBeginIndex = 0; props.chatStore.postBeginIndex = 0;
props.setChatStore({ ...props.chatStore }); props.setChatStore({ ...props.chatStore });
@@ -651,13 +655,13 @@ export default (props: {
let dataStr = let dataStr =
"data:text/json;charset=utf-8," + "data:text/json;charset=utf-8," +
encodeURIComponent( encodeURIComponent(
JSON.stringify(props.chatStore, null, "\t") JSON.stringify(props.chatStore, null, "\t"),
); );
let downloadAnchorNode = document.createElement("a"); let downloadAnchorNode = document.createElement("a");
downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute( downloadAnchorNode.setAttribute(
"download", "download",
`chatgpt-api-web-${props.selectedChatStoreIndex}.json` `chatgpt-api-web-${props.selectedChatStoreIndex}.json`,
); );
document.body.appendChild(downloadAnchorNode); // required for firefox document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click(); downloadAnchorNode.click();
@@ -670,7 +674,7 @@ export default (props: {
className="btn btn-sm btn-outline btn-neural" className="btn btn-sm btn-outline btn-neural"
onClick={() => { onClick={() => {
const name = prompt( const name = prompt(
tr("Give this template a name:", langCode) tr("Give this template a name:", langCode),
); );
if (!name) { if (!name) {
alert(tr("No template name specified", langCode)); alert(tr("No template name specified", langCode));
@@ -702,8 +706,8 @@ export default (props: {
!confirm( !confirm(
tr( tr(
"This will OVERWRITE the current chat history! Continue?", "This will OVERWRITE the current chat history! Continue?",
langCode langCode,
) ),
) )
) )
return; return;
@@ -734,18 +738,19 @@ export default (props: {
} }
try { try {
const newChatStore: ChatStore = JSON.parse( const newChatStore: ChatStore = JSON.parse(
reader.result as string reader.result as string,
); );
if (!newChatStore.chatgpt_api_web_version) { if (!newChatStore.chatgpt_api_web_version) {
throw tr( throw tr(
"This is not an exported chatgpt-api-web chatstore file. The key 'chatgpt_api_web_version' is missing!", "This is not an exported chatgpt-api-web chatstore file. The key 'chatgpt_api_web_version' is missing!",
langCode langCode,
); );
} }
props.setChatStore({ ...newChatStore }); props.setChatStore({ ...newChatStore });
} catch (e) { } catch (e) {
alert( alert(
tr(`Import error on parsing json:`, langCode) + `${e}` tr(`Import error on parsing json:`, langCode) +
`${e}`,
); );
} }
}; };

View File

@@ -3,7 +3,7 @@ export const autoHeight = (target: any) => {
// max 70% of screen height // max 70% of screen height
target.style.height = `${Math.min( target.style.height = `${Math.min(
target.scrollHeight, target.scrollHeight,
window.innerHeight * 0.7 window.innerHeight * 0.7,
)}px`; )}px`;
console.log("set auto height", target.style.height); console.log("set auto height", target.style.height);
}; };

View File

@@ -1,5 +1,5 @@
import { createContext } from "preact"; import { createContext } from "preact";
import MAP_zh_CN from "./zh_CN"; import MAP_zh_CN from "@/translate/zh_CN";
interface LangOption { interface LangOption {
name: string; name: string;

View File

@@ -1,7 +1,8 @@
import { useMemo, useState } from "preact/hooks";
import { ChatStore, ChatStoreMessage, addTotalCost } from "./app";
import { Message, getMessageText } from "./chatgpt";
import { SpeakerWaveIcon } from "@heroicons/react/24/outline"; import { SpeakerWaveIcon } from "@heroicons/react/24/outline";
import { useMemo, useState } from "preact/hooks";
import { ChatStore, ChatStoreMessage, addTotalCost } from "@/app";
import { Message, getMessageText } from "@/chatgpt";
interface TTSProps { interface TTSProps {
chatStore: ChatStore; chatStore: ChatStore;

View File

@@ -1,5 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": "src",
"paths": {
"@/*": ["*"]
},
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],

View File

@@ -1,8 +1,14 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import preact from '@preact/preset-vite' import preact from '@preact/preset-vite'
import path from 'path'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [preact()], plugins: [preact()],
base: './', base: './',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}) })