forked from XiaoMo/ChatGPT-Next-Web
Merge branch 'main' into bugfix-0503
This commit is contained in:
commit
f250594e97
@ -26,8 +26,11 @@ export async function requestOpenai(req: NextRequest) {
|
|||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
...(process.env.OPENAI_ORG_ID && { "OpenAI-Organization": process.env.OPENAI_ORG_ID }),
|
...(process.env.OPENAI_ORG_ID && {
|
||||||
|
"OpenAI-Organization": process.env.OPENAI_ORG_ID,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
|
cache: "no-store",
|
||||||
method: req.method,
|
method: req.method,
|
||||||
body: req.body,
|
body: req.body,
|
||||||
});
|
});
|
||||||
|
@ -67,7 +67,10 @@ export function ChatItem(props: {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles["chat-item-delete"]} onClick={props.onDelete}>
|
<div
|
||||||
|
className={styles["chat-item-delete"]}
|
||||||
|
onClickCapture={props.onDelete}
|
||||||
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -77,14 +80,14 @@ export function ChatItem(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ChatList(props: { narrow?: boolean }) {
|
export function ChatList(props: { narrow?: boolean }) {
|
||||||
const [sessions, selectedIndex, selectSession, removeSession, moveSession] =
|
const [sessions, selectedIndex, selectSession, moveSession] = useChatStore(
|
||||||
useChatStore((state) => [
|
(state) => [
|
||||||
state.sessions,
|
state.sessions,
|
||||||
state.currentSessionIndex,
|
state.currentSessionIndex,
|
||||||
state.selectSession,
|
state.selectSession,
|
||||||
state.removeSession,
|
|
||||||
state.moveSession,
|
state.moveSession,
|
||||||
]);
|
],
|
||||||
|
);
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useDebouncedCallback } from "use-debounce";
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
import { memo, useState, useRef, useEffect, useLayoutEffect } from "react";
|
import { useState, useRef, useEffect, useLayoutEffect } from "react";
|
||||||
|
|
||||||
import SendWhiteIcon from "../icons/send-white.svg";
|
import SendWhiteIcon from "../icons/send-white.svg";
|
||||||
import BrainIcon from "../icons/brain.svg";
|
import BrainIcon from "../icons/brain.svg";
|
||||||
@ -64,12 +64,9 @@ import {
|
|||||||
useMaskStore,
|
useMaskStore,
|
||||||
} from "../store/mask";
|
} from "../store/mask";
|
||||||
|
|
||||||
const Markdown = dynamic(
|
const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
|
||||||
async () => memo((await import("./markdown")).Markdown),
|
loading: () => <LoadingIcon />,
|
||||||
{
|
});
|
||||||
loading: () => <LoadingIcon />,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
function exportMessages(messages: Message[], topic: string) {
|
function exportMessages(messages: Message[], topic: string) {
|
||||||
const mdText =
|
const mdText =
|
||||||
@ -394,7 +391,7 @@ export function Chat() {
|
|||||||
const onPromptSelect = (prompt: Prompt) => {
|
const onPromptSelect = (prompt: Prompt) => {
|
||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
setUserInput(prompt.content);
|
setTimeout(() => setUserInput(prompt.content), 60);
|
||||||
};
|
};
|
||||||
|
|
||||||
// auto grow input
|
// auto grow input
|
||||||
@ -728,6 +725,7 @@ export function Chat() {
|
|||||||
}}
|
}}
|
||||||
fontSize={fontSize}
|
fontSize={fontSize}
|
||||||
parentRef={scrollRef}
|
parentRef={scrollRef}
|
||||||
|
defaultShow={i >= messages.length - 10}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!isUser && !message.preview && (
|
{!isUser && !message.preview && (
|
||||||
|
@ -9,6 +9,7 @@ import { useRef, useState, RefObject, useEffect } from "react";
|
|||||||
import { copyToClipboard } from "../utils";
|
import { copyToClipboard } from "../utils";
|
||||||
|
|
||||||
import LoadingIcon from "../icons/three-dots.svg";
|
import LoadingIcon from "../icons/three-dots.svg";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
export function PreCode(props: { children: any }) {
|
export function PreCode(props: { children: any }) {
|
||||||
const ref = useRef<HTMLPreElement>(null);
|
const ref = useRef<HTMLPreElement>(null);
|
||||||
@ -29,78 +30,94 @@ export function PreCode(props: { children: any }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _MarkDownContent(props: { content: string }) {
|
||||||
|
return (
|
||||||
|
<ReactMarkdown
|
||||||
|
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||||
|
rehypePlugins={[
|
||||||
|
RehypeKatex,
|
||||||
|
[
|
||||||
|
RehypeHighlight,
|
||||||
|
{
|
||||||
|
detect: false,
|
||||||
|
ignoreMissing: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
components={{
|
||||||
|
pre: PreCode,
|
||||||
|
a: (aProps) => {
|
||||||
|
const href = aProps.href || "";
|
||||||
|
const isInternal = /^\/#/i.test(href);
|
||||||
|
const target = isInternal ? "_self" : aProps.target ?? "_blank";
|
||||||
|
return <a {...aProps} target={target} />;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.content}
|
||||||
|
</ReactMarkdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MarkdownContent = React.memo(_MarkDownContent);
|
||||||
|
|
||||||
export function Markdown(
|
export function Markdown(
|
||||||
props: {
|
props: {
|
||||||
content: string;
|
content: string;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
parentRef: RefObject<HTMLDivElement>;
|
parentRef: RefObject<HTMLDivElement>;
|
||||||
|
defaultShow?: boolean;
|
||||||
} & React.DOMAttributes<HTMLDivElement>,
|
} & React.DOMAttributes<HTMLDivElement>,
|
||||||
) {
|
) {
|
||||||
const mdRef = useRef<HTMLDivElement>(null);
|
const mdRef = useRef<HTMLDivElement>(null);
|
||||||
|
const renderedHeight = useRef(0);
|
||||||
|
const inView = useRef(!!props.defaultShow);
|
||||||
|
|
||||||
const parent = props.parentRef.current;
|
const parent = props.parentRef.current;
|
||||||
const md = mdRef.current;
|
const md = mdRef.current;
|
||||||
const rendered = useRef(true); // disable lazy loading for bad ux
|
|
||||||
const [counter, setCounter] = useState(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
const checkInView = () => {
|
||||||
// to triggr rerender
|
if (parent && md) {
|
||||||
setCounter(counter + 1);
|
const parentBounds = parent.getBoundingClientRect();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const twoScreenHeight = Math.max(500, parentBounds.height * 2);
|
||||||
}, [props.loading]);
|
const mdBounds = md.getBoundingClientRect();
|
||||||
|
const isInRange = (x: number) =>
|
||||||
|
x <= parentBounds.bottom + twoScreenHeight &&
|
||||||
|
x >= parentBounds.top - twoScreenHeight;
|
||||||
|
inView.current = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
|
||||||
|
}
|
||||||
|
|
||||||
const inView =
|
if (inView.current && md) {
|
||||||
rendered.current ||
|
renderedHeight.current = Math.max(
|
||||||
(() => {
|
renderedHeight.current,
|
||||||
if (parent && md) {
|
md.getBoundingClientRect().height,
|
||||||
const parentBounds = parent.getBoundingClientRect();
|
);
|
||||||
const mdBounds = md.getBoundingClientRect();
|
}
|
||||||
const isInRange = (x: number) =>
|
};
|
||||||
x <= parentBounds.bottom && x >= parentBounds.top;
|
|
||||||
const inView = isInRange(mdBounds.top) || isInRange(mdBounds.bottom);
|
|
||||||
|
|
||||||
if (inView) {
|
checkInView();
|
||||||
rendered.current = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return inView;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
const shouldLoading = props.loading || !inView;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="markdown-body"
|
className="markdown-body"
|
||||||
style={{ fontSize: `${props.fontSize ?? 14}px` }}
|
style={{
|
||||||
|
fontSize: `${props.fontSize ?? 14}px`,
|
||||||
|
height:
|
||||||
|
!inView.current && renderedHeight.current > 0
|
||||||
|
? renderedHeight.current
|
||||||
|
: "auto",
|
||||||
|
}}
|
||||||
ref={mdRef}
|
ref={mdRef}
|
||||||
onContextMenu={props.onContextMenu}
|
onContextMenu={props.onContextMenu}
|
||||||
onDoubleClickCapture={props.onDoubleClickCapture}
|
onDoubleClickCapture={props.onDoubleClickCapture}
|
||||||
>
|
>
|
||||||
{shouldLoading ? (
|
{inView.current &&
|
||||||
<LoadingIcon />
|
(props.loading ? (
|
||||||
) : (
|
<LoadingIcon />
|
||||||
<ReactMarkdown
|
) : (
|
||||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
<MarkdownContent content={props.content} />
|
||||||
rehypePlugins={[
|
))}
|
||||||
RehypeKatex,
|
|
||||||
[
|
|
||||||
RehypeHighlight,
|
|
||||||
{
|
|
||||||
detect: false,
|
|
||||||
ignoreMissing: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
]}
|
|
||||||
components={{
|
|
||||||
pre: PreCode,
|
|
||||||
}}
|
|
||||||
linkTarget={"_blank"}
|
|
||||||
>
|
|
||||||
{props.content}
|
|
||||||
</ReactMarkdown>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,4 @@
|
|||||||
@import "../styles/animation.scss";
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
@keyframes search-in {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(5vh) scaleX(0.5);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0) scaleX(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mask-page {
|
.mask-page {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -23,8 +11,9 @@
|
|||||||
.mask-filter {
|
.mask-filter {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 20px;
|
||||||
animation: search-in ease 0.3s;
|
animation: slide-in ease 0.3s;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@ -32,8 +21,6 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin-bottom: 20px;
|
|
||||||
animation: search-in ease 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mask-filter-lang {
|
.mask-filter-lang {
|
||||||
@ -45,10 +32,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
min-width: 80px;
|
||||||
button {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,14 +291,16 @@ export function MaskPage() {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div className={styles["mask-create"]}>
|
<IconButton
|
||||||
<IconButton
|
className={styles["mask-create"]}
|
||||||
icon={<AddIcon />}
|
icon={<AddIcon />}
|
||||||
text={Locale.Mask.Page.Create}
|
text={Locale.Mask.Page.Create}
|
||||||
bordered
|
bordered
|
||||||
onClick={() => maskStore.create()}
|
onClick={() => {
|
||||||
/>
|
const createdMask = maskStore.create();
|
||||||
</div>
|
setEditingMaskId(createdMask.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -59,10 +59,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
.search-bar {
|
.more {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-right: 10px;
|
margin-left: 10px;
|
||||||
width: 40vw;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,10 +5,11 @@ import { EmojiAvatar } from "./emoji";
|
|||||||
import styles from "./new-chat.module.scss";
|
import styles from "./new-chat.module.scss";
|
||||||
|
|
||||||
import LeftIcon from "../icons/left.svg";
|
import LeftIcon from "../icons/left.svg";
|
||||||
import AddIcon from "../icons/lightning.svg";
|
import LightningIcon from "../icons/lightning.svg";
|
||||||
|
import EyeIcon from "../icons/eye.svg";
|
||||||
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createEmptyMask, Mask, useMaskStore } from "../store/mask";
|
import { Mask, useMaskStore } from "../store/mask";
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
import { useAppConfig, useChatStore } from "../store";
|
import { useAppConfig, useChatStore } from "../store";
|
||||||
import { MaskAvatar } from "./mask";
|
import { MaskAvatar } from "./mask";
|
||||||
@ -148,20 +149,22 @@ export function NewChat() {
|
|||||||
<div className={styles["sub-title"]}>{Locale.NewChat.SubTitle}</div>
|
<div className={styles["sub-title"]}>{Locale.NewChat.SubTitle}</div>
|
||||||
|
|
||||||
<div className={styles["actions"]}>
|
<div className={styles["actions"]}>
|
||||||
<input
|
|
||||||
className={styles["search-bar"]}
|
|
||||||
placeholder={Locale.NewChat.More}
|
|
||||||
type="text"
|
|
||||||
onClick={() => navigate(Path.Masks)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<IconButton
|
<IconButton
|
||||||
text={Locale.NewChat.Skip}
|
text={Locale.NewChat.Skip}
|
||||||
onClick={() => startChat()}
|
onClick={() => startChat()}
|
||||||
icon={<AddIcon />}
|
icon={<LightningIcon />}
|
||||||
type="primary"
|
type="primary"
|
||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
className={styles["more"]}
|
||||||
|
text={Locale.NewChat.More}
|
||||||
|
onClick={() => navigate(Path.Masks)}
|
||||||
|
icon={<EyeIcon />}
|
||||||
|
bordered
|
||||||
|
shadow
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles["masks"]}>
|
<div className={styles["masks"]}>
|
||||||
|
@ -7,6 +7,20 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-prompt-modal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.edit-prompt-title {
|
||||||
|
max-width: unset;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.edit-prompt-content {
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.user-prompt-modal {
|
.user-prompt-modal {
|
||||||
min-height: 40vh;
|
min-height: 40vh;
|
||||||
|
|
||||||
@ -18,47 +32,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-prompt-list {
|
.user-prompt-list {
|
||||||
padding: 10px 0;
|
border: var(--border-in-light);
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
.user-prompt-item {
|
.user-prompt-item {
|
||||||
margin-bottom: 10px;
|
display: flex;
|
||||||
widows: 100%;
|
justify-content: space-between;
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: var(--border-in-light);
|
||||||
|
}
|
||||||
|
|
||||||
.user-prompt-header {
|
.user-prompt-header {
|
||||||
display: flex;
|
max-width: calc(100% - 100px);
|
||||||
widows: 100%;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
|
|
||||||
.user-prompt-title {
|
.user-prompt-title {
|
||||||
flex-grow: 1;
|
font-size: 14px;
|
||||||
max-width: 100%;
|
line-height: 2;
|
||||||
margin-right: 5px;
|
font-weight: bold;
|
||||||
padding: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
.user-prompt-content {
|
||||||
.user-prompt-buttons {
|
font-size: 12px;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.user-prompt-button {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&:not(:last-child) {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-prompt-content {
|
.user-prompt-buttons {
|
||||||
width: 100%;
|
display: flex;
|
||||||
box-sizing: border-box;
|
align-items: center;
|
||||||
padding: 5px;
|
|
||||||
margin-right: 10px;
|
.user-prompt-button {
|
||||||
font-size: 12px;
|
height: 100%;
|
||||||
flex-grow: 1;
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ import { useState, useEffect, useMemo, HTMLProps, useRef } from "react";
|
|||||||
import styles from "./settings.module.scss";
|
import styles from "./settings.module.scss";
|
||||||
|
|
||||||
import ResetIcon from "../icons/reload.svg";
|
import ResetIcon from "../icons/reload.svg";
|
||||||
|
import AddIcon from "../icons/add.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import CopyIcon from "../icons/copy.svg";
|
import CopyIcon from "../icons/copy.svg";
|
||||||
import ClearIcon from "../icons/clear.svg";
|
import ClearIcon from "../icons/clear.svg";
|
||||||
import EditIcon from "../icons/edit.svg";
|
import EditIcon from "../icons/edit.svg";
|
||||||
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
|
import { Input, List, ListItem, Modal, PasswordInput, Popover } from "./ui-lib";
|
||||||
import { ModelConfigList } from "./model-config";
|
import { ModelConfigList } from "./model-config";
|
||||||
|
|
||||||
@ -30,6 +32,55 @@ import { InputRange } from "./input-range";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Avatar, AvatarPicker } from "./emoji";
|
import { Avatar, AvatarPicker } from "./emoji";
|
||||||
|
|
||||||
|
function EditPromptModal(props: { id: number; onClose: () => void }) {
|
||||||
|
const promptStore = usePromptStore();
|
||||||
|
const prompt = promptStore.get(props.id);
|
||||||
|
|
||||||
|
return prompt ? (
|
||||||
|
<div className="modal-mask">
|
||||||
|
<Modal
|
||||||
|
title={Locale.Settings.Prompt.EditModal.Title}
|
||||||
|
onClose={props.onClose}
|
||||||
|
actions={[
|
||||||
|
<IconButton
|
||||||
|
key=""
|
||||||
|
onClick={props.onClose}
|
||||||
|
text={Locale.UI.Confirm}
|
||||||
|
bordered
|
||||||
|
/>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div className={styles["edit-prompt-modal"]}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={prompt.title}
|
||||||
|
readOnly={!prompt.isUser}
|
||||||
|
className={styles["edit-prompt-title"]}
|
||||||
|
onInput={(e) =>
|
||||||
|
promptStore.update(
|
||||||
|
props.id,
|
||||||
|
(prompt) => (prompt.title = e.currentTarget.value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
<Input
|
||||||
|
value={prompt.content}
|
||||||
|
readOnly={!prompt.isUser}
|
||||||
|
className={styles["edit-prompt-content"]}
|
||||||
|
rows={10}
|
||||||
|
onInput={(e) =>
|
||||||
|
promptStore.update(
|
||||||
|
props.id,
|
||||||
|
(prompt) => (prompt.content = e.currentTarget.value),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></Input>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
function UserPromptModal(props: { onClose?: () => void }) {
|
function UserPromptModal(props: { onClose?: () => void }) {
|
||||||
const promptStore = usePromptStore();
|
const promptStore = usePromptStore();
|
||||||
const userPrompts = promptStore.getUserPrompts();
|
const userPrompts = promptStore.getUserPrompts();
|
||||||
@ -39,6 +90,8 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
|||||||
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
|
const [searchPrompts, setSearchPrompts] = useState<Prompt[]>([]);
|
||||||
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
|
const prompts = searchInput.length > 0 ? searchPrompts : allPrompts;
|
||||||
|
|
||||||
|
const [editingPromptId, setEditingPromptId] = useState<number>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (searchInput.length > 0) {
|
if (searchInput.length > 0) {
|
||||||
const searchResult = SearchService.search(searchInput);
|
const searchResult = SearchService.search(searchInput);
|
||||||
@ -56,8 +109,13 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
|||||||
actions={[
|
actions={[
|
||||||
<IconButton
|
<IconButton
|
||||||
key="add"
|
key="add"
|
||||||
onClick={() => promptStore.add({ title: "", content: "" })}
|
onClick={() =>
|
||||||
icon={<ClearIcon />}
|
promptStore.add({
|
||||||
|
title: "Empty Prompt",
|
||||||
|
content: "Empty Prompt Content",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
icon={<AddIcon />}
|
||||||
bordered
|
bordered
|
||||||
text={Locale.Settings.Prompt.Modal.Add}
|
text={Locale.Settings.Prompt.Modal.Add}
|
||||||
/>,
|
/>,
|
||||||
@ -76,57 +134,51 @@ function UserPromptModal(props: { onClose?: () => void }) {
|
|||||||
{prompts.map((v, _) => (
|
{prompts.map((v, _) => (
|
||||||
<div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
|
<div className={styles["user-prompt-item"]} key={v.id ?? v.title}>
|
||||||
<div className={styles["user-prompt-header"]}>
|
<div className={styles["user-prompt-header"]}>
|
||||||
<input
|
<div className={styles["user-prompt-title"]}>{v.title}</div>
|
||||||
type="text"
|
<div className={styles["user-prompt-content"] + " one-line"}>
|
||||||
className={styles["user-prompt-title"]}
|
{v.content}
|
||||||
value={v.title}
|
|
||||||
readOnly={!v.isUser}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (v.isUser) {
|
|
||||||
promptStore.updateUserPrompts(
|
|
||||||
v.id!,
|
|
||||||
(prompt) => (prompt.title = e.currentTarget.value),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
></input>
|
|
||||||
|
|
||||||
<div className={styles["user-prompt-buttons"]}>
|
|
||||||
{v.isUser && (
|
|
||||||
<IconButton
|
|
||||||
icon={<ClearIcon />}
|
|
||||||
bordered
|
|
||||||
className={styles["user-prompt-button"]}
|
|
||||||
onClick={() => promptStore.remove(v.id!)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<IconButton
|
|
||||||
icon={<CopyIcon />}
|
|
||||||
bordered
|
|
||||||
className={styles["user-prompt-button"]}
|
|
||||||
onClick={() => copyToClipboard(v.content)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
|
||||||
rows={2}
|
<div className={styles["user-prompt-buttons"]}>
|
||||||
value={v.content}
|
{v.isUser && (
|
||||||
className={styles["user-prompt-content"]}
|
<IconButton
|
||||||
readOnly={!v.isUser}
|
icon={<ClearIcon />}
|
||||||
onChange={(e) => {
|
className={styles["user-prompt-button"]}
|
||||||
if (v.isUser) {
|
onClick={() => promptStore.remove(v.id!)}
|
||||||
promptStore.updateUserPrompts(
|
/>
|
||||||
v.id!,
|
)}
|
||||||
(prompt) => (prompt.content = e.currentTarget.value),
|
{v.isUser ? (
|
||||||
);
|
<IconButton
|
||||||
}
|
icon={<EditIcon />}
|
||||||
}}
|
className={styles["user-prompt-button"]}
|
||||||
/>
|
onClick={() => setEditingPromptId(v.id)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconButton
|
||||||
|
icon={<EyeIcon />}
|
||||||
|
className={styles["user-prompt-button"]}
|
||||||
|
onClick={() => setEditingPromptId(v.id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<IconButton
|
||||||
|
icon={<CopyIcon />}
|
||||||
|
className={styles["user-prompt-button"]}
|
||||||
|
onClick={() => copyToClipboard(v.content)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
|
{editingPromptId !== undefined && (
|
||||||
|
<EditPromptModal
|
||||||
|
id={editingPromptId!}
|
||||||
|
onClose={() => setEditingPromptId(undefined)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -138,7 +138,11 @@ export function SideBar(props: { className?: string }) {
|
|||||||
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<CloseIcon />}
|
icon={<CloseIcon />}
|
||||||
onClick={chatStore.deleteSession}
|
onClick={() => {
|
||||||
|
if (confirm(Locale.Home.DeleteChat)) {
|
||||||
|
chatStore.deleteSession(chatStore.currentSessionIndex);
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["sidebar-action"]}>
|
<div className={styles["sidebar-action"]}>
|
||||||
|
@ -158,6 +158,7 @@ export type ToastProps = {
|
|||||||
text: string;
|
text: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
onClose?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Toast(props: ToastProps) {
|
export function Toast(props: ToastProps) {
|
||||||
@ -167,7 +168,10 @@ export function Toast(props: ToastProps) {
|
|||||||
<span>{props.content}</span>
|
<span>{props.content}</span>
|
||||||
{props.action && (
|
{props.action && (
|
||||||
<button
|
<button
|
||||||
onClick={props.action.onClick}
|
onClick={() => {
|
||||||
|
props.action?.onClick?.();
|
||||||
|
props.onClose?.();
|
||||||
|
}}
|
||||||
className={styles["toast-action"]}
|
className={styles["toast-action"]}
|
||||||
>
|
>
|
||||||
{props.action.text}
|
{props.action.text}
|
||||||
@ -201,7 +205,7 @@ export function showToast(
|
|||||||
close();
|
close();
|
||||||
}, delay);
|
}, delay);
|
||||||
|
|
||||||
root.render(<Toast content={content} action={action} />);
|
root.render(<Toast content={content} action={action} onClose={close} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type InputProps = React.HTMLProps<HTMLTextAreaElement> & {
|
export type InputProps = React.HTMLProps<HTMLTextAreaElement> & {
|
||||||
|
@ -116,9 +116,12 @@ const cn = {
|
|||||||
Edit: "编辑",
|
Edit: "编辑",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "提示词列表",
|
Title: "提示词列表",
|
||||||
Add: "增加一条",
|
Add: "新建",
|
||||||
Search: "搜索提示词",
|
Search: "搜索提示词",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "编辑提示词",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "附带历史消息数",
|
Title: "附带历史消息数",
|
||||||
@ -221,7 +224,15 @@ const cn = {
|
|||||||
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
|
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
|
||||||
Title: "挑选一个面具",
|
Title: "挑选一个面具",
|
||||||
SubTitle: "现在开始,与面具背后的灵魂思维碰撞",
|
SubTitle: "现在开始,与面具背后的灵魂思维碰撞",
|
||||||
More: "搜索更多",
|
More: "查看全部",
|
||||||
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "确认",
|
||||||
|
Cancel: "取消",
|
||||||
|
Close: "关闭",
|
||||||
|
Create: "新建",
|
||||||
|
Edit: "编辑",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,6 +121,9 @@ const de: LocaleType = {
|
|||||||
Add: "Add One",
|
Add: "Add One",
|
||||||
Search: "Search Prompts",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Edit Prompt",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Anzahl der angehängten Nachrichten",
|
Title: "Anzahl der angehängten Nachrichten",
|
||||||
@ -230,6 +233,14 @@ const de: LocaleType = {
|
|||||||
NotShow: "Not Show Again",
|
NotShow: "Not Show Again",
|
||||||
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Confirm",
|
||||||
|
Cancel: "Cancel",
|
||||||
|
Close: "Close",
|
||||||
|
Create: "Create",
|
||||||
|
Edit: "Edit",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default de;
|
export default de;
|
||||||
|
@ -120,6 +120,9 @@ const en: LocaleType = {
|
|||||||
Add: "Add One",
|
Add: "Add One",
|
||||||
Search: "Search Prompts",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Edit Prompt",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Attached Messages Count",
|
Title: "Attached Messages Count",
|
||||||
@ -226,6 +229,14 @@ const en: LocaleType = {
|
|||||||
NotShow: "Not Show Again",
|
NotShow: "Not Show Again",
|
||||||
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Confirm",
|
||||||
|
Cancel: "Cancel",
|
||||||
|
Close: "Close",
|
||||||
|
Create: "Create",
|
||||||
|
Edit: "Edit",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default en;
|
export default en;
|
||||||
|
@ -120,6 +120,9 @@ const es: LocaleType = {
|
|||||||
Add: "Add One",
|
Add: "Add One",
|
||||||
Search: "Search Prompts",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Edit Prompt",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Cantidad de mensajes adjuntos",
|
Title: "Cantidad de mensajes adjuntos",
|
||||||
@ -227,6 +230,14 @@ const es: LocaleType = {
|
|||||||
NotShow: "Not Show Again",
|
NotShow: "Not Show Again",
|
||||||
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Confirm",
|
||||||
|
Cancel: "Cancel",
|
||||||
|
Close: "Close",
|
||||||
|
Create: "Create",
|
||||||
|
Edit: "Edit",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default es;
|
export default es;
|
||||||
|
@ -120,6 +120,9 @@ const it: LocaleType = {
|
|||||||
Add: "Add One",
|
Add: "Add One",
|
||||||
Search: "Search Prompts",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Edit Prompt",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Conteggio dei messaggi allegati",
|
Title: "Conteggio dei messaggi allegati",
|
||||||
@ -228,6 +231,14 @@ const it: LocaleType = {
|
|||||||
NotShow: "Not Show Again",
|
NotShow: "Not Show Again",
|
||||||
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Confirm",
|
||||||
|
Cancel: "Cancel",
|
||||||
|
Close: "Close",
|
||||||
|
Create: "Create",
|
||||||
|
Edit: "Edit",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default it;
|
export default it;
|
||||||
|
@ -122,6 +122,9 @@ const jp: LocaleType = {
|
|||||||
Add: "新規追加",
|
Add: "新規追加",
|
||||||
Search: "プロンプトワード検索",
|
Search: "プロンプトワード検索",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "编辑提示词",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "履歴メッセージ数を添付",
|
Title: "履歴メッセージ数を添付",
|
||||||
@ -226,6 +229,14 @@ const jp: LocaleType = {
|
|||||||
NotShow: "不再展示",
|
NotShow: "不再展示",
|
||||||
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
|
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "确认",
|
||||||
|
Cancel: "取消",
|
||||||
|
Close: "关闭",
|
||||||
|
Create: "新建",
|
||||||
|
Edit: "编辑",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default jp;
|
export default jp;
|
||||||
|
@ -120,6 +120,9 @@ const tr: LocaleType = {
|
|||||||
Add: "Add One",
|
Add: "Add One",
|
||||||
Search: "Search Prompts",
|
Search: "Search Prompts",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "Edit Prompt",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "Ekli Mesaj Sayısı",
|
Title: "Ekli Mesaj Sayısı",
|
||||||
@ -228,6 +231,14 @@ const tr: LocaleType = {
|
|||||||
NotShow: "Not Show Again",
|
NotShow: "Not Show Again",
|
||||||
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
ConfirmNoShow: "Confirm to disable?You can enable it in settings later.",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
UI: {
|
||||||
|
Confirm: "Confirm",
|
||||||
|
Cancel: "Cancel",
|
||||||
|
Close: "Close",
|
||||||
|
Create: "Create",
|
||||||
|
Edit: "Edit",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tr;
|
export default tr;
|
||||||
|
@ -118,6 +118,9 @@ const tw: LocaleType = {
|
|||||||
Add: "新增一條",
|
Add: "新增一條",
|
||||||
Search: "搜尋提示詞",
|
Search: "搜尋提示詞",
|
||||||
},
|
},
|
||||||
|
EditModal: {
|
||||||
|
Title: "编辑提示词",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
HistoryCount: {
|
HistoryCount: {
|
||||||
Title: "附帶歷史訊息數",
|
Title: "附帶歷史訊息數",
|
||||||
@ -219,6 +222,13 @@ const tw: LocaleType = {
|
|||||||
NotShow: "不再展示",
|
NotShow: "不再展示",
|
||||||
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
|
ConfirmNoShow: "确认禁用?禁用后可以随时在设置中重新启用。",
|
||||||
},
|
},
|
||||||
|
UI: {
|
||||||
|
Confirm: "确认",
|
||||||
|
Cancel: "取消",
|
||||||
|
Close: "关闭",
|
||||||
|
Create: "新建",
|
||||||
|
Edit: "编辑",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default tw;
|
export default tw;
|
||||||
|
@ -14,9 +14,8 @@ const TIME_OUT_MS = 60000;
|
|||||||
const makeRequestParam = (
|
const makeRequestParam = (
|
||||||
messages: Message[],
|
messages: Message[],
|
||||||
options?: {
|
options?: {
|
||||||
filterBot?: boolean;
|
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
model?: ModelType;
|
overrideModel?: ModelType;
|
||||||
},
|
},
|
||||||
): ChatRequest => {
|
): ChatRequest => {
|
||||||
let sendMessages = messages.map((v) => ({
|
let sendMessages = messages.map((v) => ({
|
||||||
@ -24,18 +23,14 @@ const makeRequestParam = (
|
|||||||
content: v.content,
|
content: v.content,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (options?.filterBot) {
|
|
||||||
sendMessages = sendMessages.filter((m) => m.role !== "assistant");
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelConfig = {
|
const modelConfig = {
|
||||||
...useAppConfig.getState().modelConfig,
|
...useAppConfig.getState().modelConfig,
|
||||||
...useChatStore.getState().currentSession().mask.modelConfig,
|
...useChatStore.getState().currentSession().mask.modelConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
// override model config
|
// override model config
|
||||||
if (options?.model) {
|
if (options?.overrideModel) {
|
||||||
modelConfig.model = options.model;
|
modelConfig.model = options.overrideModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -82,8 +77,7 @@ export async function requestChat(
|
|||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const req: ChatRequest = makeRequestParam(messages, {
|
const req: ChatRequest = makeRequestParam(messages, {
|
||||||
filterBot: true,
|
overrideModel: options?.model,
|
||||||
model: options?.model,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await requestOpenaiClient("v1/chat/completions")(req);
|
const res = await requestOpenaiClient("v1/chat/completions")(req);
|
||||||
@ -102,11 +96,11 @@ export async function requestUsage() {
|
|||||||
.getDate()
|
.getDate()
|
||||||
.toString()
|
.toString()
|
||||||
.padStart(2, "0")}`;
|
.padStart(2, "0")}`;
|
||||||
const ONE_DAY = 2 * 24 * 60 * 60 * 1000;
|
const ONE_DAY = 1 * 24 * 60 * 60 * 1000;
|
||||||
const now = new Date(Date.now() + ONE_DAY);
|
const now = new Date();
|
||||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
const startDate = formatDate(startOfMonth);
|
const startDate = formatDate(startOfMonth);
|
||||||
const endDate = formatDate(now);
|
const endDate = formatDate(new Date(Date.now() + ONE_DAY));
|
||||||
|
|
||||||
const [used, subs] = await Promise.all([
|
const [used, subs] = await Promise.all([
|
||||||
requestOpenaiClient(
|
requestOpenaiClient(
|
||||||
@ -149,9 +143,8 @@ export async function requestUsage() {
|
|||||||
export async function requestChatStream(
|
export async function requestChatStream(
|
||||||
messages: Message[],
|
messages: Message[],
|
||||||
options?: {
|
options?: {
|
||||||
filterBot?: boolean;
|
|
||||||
modelConfig?: ModelConfig;
|
modelConfig?: ModelConfig;
|
||||||
model?: ModelType;
|
overrideModel?: ModelType;
|
||||||
onMessage: (message: string, done: boolean) => void;
|
onMessage: (message: string, done: boolean) => void;
|
||||||
onError: (error: Error, statusCode?: number) => void;
|
onError: (error: Error, statusCode?: number) => void;
|
||||||
onController?: (controller: AbortController) => void;
|
onController?: (controller: AbortController) => void;
|
||||||
@ -159,8 +152,7 @@ export async function requestChatStream(
|
|||||||
) {
|
) {
|
||||||
const req = makeRequestParam(messages, {
|
const req = makeRequestParam(messages, {
|
||||||
stream: true,
|
stream: true,
|
||||||
filterBot: options?.filterBot,
|
overrideModel: options?.overrideModel,
|
||||||
model: options?.model,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("[Request] ", req);
|
console.log("[Request] ", req);
|
||||||
|
@ -83,11 +83,10 @@ interface ChatStore {
|
|||||||
currentSessionIndex: number;
|
currentSessionIndex: number;
|
||||||
globalId: number;
|
globalId: number;
|
||||||
clearSessions: () => void;
|
clearSessions: () => void;
|
||||||
removeSession: (index: number) => void;
|
|
||||||
moveSession: (from: number, to: number) => void;
|
moveSession: (from: number, to: number) => void;
|
||||||
selectSession: (index: number) => void;
|
selectSession: (index: number) => void;
|
||||||
newSession: (mask?: Mask) => void;
|
newSession: (mask?: Mask) => void;
|
||||||
deleteSession: (index?: number) => void;
|
deleteSession: (index: number) => void;
|
||||||
currentSession: () => ChatSession;
|
currentSession: () => ChatSession;
|
||||||
onNewMessage: (message: Message) => void;
|
onNewMessage: (message: Message) => void;
|
||||||
onUserInput: (content: string) => Promise<void>;
|
onUserInput: (content: string) => Promise<void>;
|
||||||
@ -130,31 +129,6 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
removeSession(index: number) {
|
|
||||||
set((state) => {
|
|
||||||
let nextIndex = state.currentSessionIndex;
|
|
||||||
const sessions = state.sessions;
|
|
||||||
|
|
||||||
if (sessions.length === 1) {
|
|
||||||
return {
|
|
||||||
currentSessionIndex: 0,
|
|
||||||
sessions: [createEmptySession()],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sessions.splice(index, 1);
|
|
||||||
|
|
||||||
if (nextIndex === index) {
|
|
||||||
nextIndex -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
currentSessionIndex: nextIndex,
|
|
||||||
sessions,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
moveSession(from: number, to: number) {
|
moveSession(from: number, to: number) {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const { sessions, currentSessionIndex: oldIndex } = state;
|
const { sessions, currentSessionIndex: oldIndex } = state;
|
||||||
@ -197,31 +171,46 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteSession(i?: number) {
|
deleteSession(index) {
|
||||||
const deletedSession = get().currentSession();
|
const deletingLastSession = get().sessions.length === 1;
|
||||||
const index = i ?? get().currentSessionIndex;
|
const deletedSession = get().sessions.at(index);
|
||||||
const isLastSession = get().sessions.length === 1;
|
|
||||||
if (!isMobileScreen() || confirm(Locale.Home.DeleteChat)) {
|
|
||||||
get().removeSession(index);
|
|
||||||
|
|
||||||
showToast(
|
if (!deletedSession) return;
|
||||||
Locale.Home.DeleteToast,
|
|
||||||
{
|
const sessions = get().sessions.slice();
|
||||||
text: Locale.Home.Revert,
|
sessions.splice(index, 1);
|
||||||
onClick() {
|
|
||||||
set((state) => ({
|
let nextIndex = Math.min(
|
||||||
sessions: state.sessions
|
get().currentSessionIndex,
|
||||||
.slice(0, index)
|
sessions.length - 1,
|
||||||
.concat([deletedSession])
|
);
|
||||||
.concat(
|
|
||||||
state.sessions.slice(index + Number(isLastSession)),
|
if (deletingLastSession) {
|
||||||
),
|
nextIndex = 0;
|
||||||
}));
|
sessions.push(createEmptySession());
|
||||||
},
|
|
||||||
},
|
|
||||||
5000,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for undo delete action
|
||||||
|
const restoreState = {
|
||||||
|
currentSessionIndex: get().currentSessionIndex,
|
||||||
|
sessions: get().sessions.slice(),
|
||||||
|
};
|
||||||
|
|
||||||
|
set(() => ({
|
||||||
|
currentSessionIndex: nextIndex,
|
||||||
|
sessions,
|
||||||
|
}));
|
||||||
|
|
||||||
|
showToast(
|
||||||
|
Locale.Home.DeleteToast,
|
||||||
|
{
|
||||||
|
text: Locale.Home.Revert,
|
||||||
|
onClick() {
|
||||||
|
set(() => restoreState);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
5000,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
currentSession() {
|
currentSession() {
|
||||||
@ -247,6 +236,9 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
},
|
},
|
||||||
|
|
||||||
async onUserInput(content) {
|
async onUserInput(content) {
|
||||||
|
const session = get().currentSession();
|
||||||
|
const modelConfig = session.mask.modelConfig;
|
||||||
|
|
||||||
const userMessage: Message = createMessage({
|
const userMessage: Message = createMessage({
|
||||||
role: "user",
|
role: "user",
|
||||||
content,
|
content,
|
||||||
@ -256,7 +248,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
role: "assistant",
|
role: "assistant",
|
||||||
streaming: true,
|
streaming: true,
|
||||||
id: userMessage.id! + 1,
|
id: userMessage.id! + 1,
|
||||||
model: useAppConfig.getState().modelConfig.model,
|
model: modelConfig.model,
|
||||||
});
|
});
|
||||||
|
|
||||||
// get recent messages
|
// get recent messages
|
||||||
@ -290,14 +282,16 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError(error, statusCode) {
|
onError(error, statusCode) {
|
||||||
|
const isAborted = error.message.includes("aborted");
|
||||||
if (statusCode === 401) {
|
if (statusCode === 401) {
|
||||||
botMessage.content = Locale.Error.Unauthorized;
|
botMessage.content = Locale.Error.Unauthorized;
|
||||||
} else if (!error.message.includes("aborted")) {
|
} else if (!isAborted) {
|
||||||
botMessage.content += "\n\n" + Locale.Store.Error;
|
botMessage.content += "\n\n" + Locale.Store.Error;
|
||||||
}
|
}
|
||||||
botMessage.streaming = false;
|
botMessage.streaming = false;
|
||||||
userMessage.isError = true;
|
userMessage.isError = !isAborted;
|
||||||
botMessage.isError = true;
|
botMessage.isError = !isAborted;
|
||||||
|
|
||||||
set(() => ({}));
|
set(() => ({}));
|
||||||
ControllerPool.remove(sessionIndex, botMessage.id ?? messageIndex);
|
ControllerPool.remove(sessionIndex, botMessage.id ?? messageIndex);
|
||||||
},
|
},
|
||||||
@ -309,8 +303,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
controller,
|
controller,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
filterBot: !useAppConfig.getState().sendBotMessages,
|
modelConfig: { ...modelConfig },
|
||||||
modelConfig: useAppConfig.getState().modelConfig,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -329,7 +322,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
|
|
||||||
getMessagesWithMemory() {
|
getMessagesWithMemory() {
|
||||||
const session = get().currentSession();
|
const session = get().currentSession();
|
||||||
const config = useAppConfig.getState();
|
const modelConfig = session.mask.modelConfig;
|
||||||
const messages = session.messages.filter((msg) => !msg.isError);
|
const messages = session.messages.filter((msg) => !msg.isError);
|
||||||
const n = messages.length;
|
const n = messages.length;
|
||||||
|
|
||||||
@ -337,7 +330,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
|
|
||||||
// long term memory
|
// long term memory
|
||||||
if (
|
if (
|
||||||
session.mask.modelConfig.sendMemory &&
|
modelConfig.sendMemory &&
|
||||||
session.memoryPrompt &&
|
session.memoryPrompt &&
|
||||||
session.memoryPrompt.length > 0
|
session.memoryPrompt.length > 0
|
||||||
) {
|
) {
|
||||||
@ -348,14 +341,14 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
// get short term and unmemoried long term memory
|
// get short term and unmemoried long term memory
|
||||||
const shortTermMemoryMessageIndex = Math.max(
|
const shortTermMemoryMessageIndex = Math.max(
|
||||||
0,
|
0,
|
||||||
n - config.modelConfig.historyMessageCount,
|
n - modelConfig.historyMessageCount,
|
||||||
);
|
);
|
||||||
const longTermMemoryMessageIndex = session.lastSummarizeIndex;
|
const longTermMemoryMessageIndex = session.lastSummarizeIndex;
|
||||||
const oldestIndex = Math.max(
|
const oldestIndex = Math.max(
|
||||||
shortTermMemoryMessageIndex,
|
shortTermMemoryMessageIndex,
|
||||||
longTermMemoryMessageIndex,
|
longTermMemoryMessageIndex,
|
||||||
);
|
);
|
||||||
const threshold = config.modelConfig.compressMessageLengthThreshold;
|
const threshold = modelConfig.compressMessageLengthThreshold;
|
||||||
|
|
||||||
// get recent messages as many as possible
|
// get recent messages as many as possible
|
||||||
const reversedRecentMessages = [];
|
const reversedRecentMessages = [];
|
||||||
@ -414,17 +407,17 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = useAppConfig.getState();
|
const modelConfig = session.mask.modelConfig;
|
||||||
let toBeSummarizedMsgs = session.messages.slice(
|
let toBeSummarizedMsgs = session.messages.slice(
|
||||||
session.lastSummarizeIndex,
|
session.lastSummarizeIndex,
|
||||||
);
|
);
|
||||||
|
|
||||||
const historyMsgLength = countMessages(toBeSummarizedMsgs);
|
const historyMsgLength = countMessages(toBeSummarizedMsgs);
|
||||||
|
|
||||||
if (historyMsgLength > config?.modelConfig?.max_tokens ?? 4000) {
|
if (historyMsgLength > modelConfig?.max_tokens ?? 4000) {
|
||||||
const n = toBeSummarizedMsgs.length;
|
const n = toBeSummarizedMsgs.length;
|
||||||
toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
|
toBeSummarizedMsgs = toBeSummarizedMsgs.slice(
|
||||||
Math.max(0, n - config.modelConfig.historyMessageCount),
|
Math.max(0, n - modelConfig.historyMessageCount),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -437,12 +430,11 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
"[Chat History] ",
|
"[Chat History] ",
|
||||||
toBeSummarizedMsgs,
|
toBeSummarizedMsgs,
|
||||||
historyMsgLength,
|
historyMsgLength,
|
||||||
config.modelConfig.compressMessageLengthThreshold,
|
modelConfig.compressMessageLengthThreshold,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
historyMsgLength >
|
historyMsgLength > modelConfig.compressMessageLengthThreshold &&
|
||||||
config.modelConfig.compressMessageLengthThreshold &&
|
|
||||||
session.mask.modelConfig.sendMemory
|
session.mask.modelConfig.sendMemory
|
||||||
) {
|
) {
|
||||||
requestChatStream(
|
requestChatStream(
|
||||||
@ -452,8 +444,7 @@ export const useChatStore = create<ChatStore>()(
|
|||||||
date: "",
|
date: "",
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
filterBot: false,
|
overrideModel: "gpt-3.5-turbo",
|
||||||
model: "gpt-3.5-turbo",
|
|
||||||
onMessage(message, done) {
|
onMessage(message, done) {
|
||||||
session.memoryPrompt = message;
|
session.memoryPrompt = message;
|
||||||
if (done) {
|
if (done) {
|
||||||
|
@ -17,7 +17,6 @@ export enum Theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_CONFIG = {
|
export const DEFAULT_CONFIG = {
|
||||||
sendBotMessages: true as boolean,
|
|
||||||
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
||||||
avatar: "1f603",
|
avatar: "1f603",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
@ -17,11 +17,12 @@ export interface PromptStore {
|
|||||||
prompts: Record<number, Prompt>;
|
prompts: Record<number, Prompt>;
|
||||||
|
|
||||||
add: (prompt: Prompt) => number;
|
add: (prompt: Prompt) => number;
|
||||||
|
get: (id: number) => Prompt | undefined;
|
||||||
remove: (id: number) => void;
|
remove: (id: number) => void;
|
||||||
search: (text: string) => Prompt[];
|
search: (text: string) => Prompt[];
|
||||||
|
update: (id: number, updater: (prompt: Prompt) => void) => void;
|
||||||
|
|
||||||
getUserPrompts: () => Prompt[];
|
getUserPrompts: () => Prompt[];
|
||||||
updateUserPrompts: (id: number, updater: (prompt: Prompt) => void) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchService = {
|
export const SearchService = {
|
||||||
@ -81,6 +82,16 @@ export const usePromptStore = create<PromptStore>()(
|
|||||||
return prompt.id!;
|
return prompt.id!;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get(id) {
|
||||||
|
const targetPrompt = get().prompts[id];
|
||||||
|
|
||||||
|
if (!targetPrompt) {
|
||||||
|
return SearchService.builtinPrompts.find((v) => v.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetPrompt;
|
||||||
|
},
|
||||||
|
|
||||||
remove(id) {
|
remove(id) {
|
||||||
const prompts = get().prompts;
|
const prompts = get().prompts;
|
||||||
delete prompts[id];
|
delete prompts[id];
|
||||||
@ -98,7 +109,7 @@ export const usePromptStore = create<PromptStore>()(
|
|||||||
return userPrompts;
|
return userPrompts;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateUserPrompts(id: number, updater) {
|
update(id: number, updater) {
|
||||||
const prompt = get().prompts[id] ?? {
|
const prompt = get().prompts[id] ?? {
|
||||||
title: "",
|
title: "",
|
||||||
content: "",
|
content: "",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
dir="$(dirname "$0")"
|
dir="$(dirname "$0")"
|
||||||
config=$dir/proxychains.conf
|
config=$dir/proxychains.conf
|
||||||
host_ip=$(grep nameserver /etc/resolv.conf | sed 's/nameserver //')
|
host_ip=$(grep nameserver /etc/resolv.conf | sed 's/nameserver //')
|
||||||
|
echo "proxying to $host_ip"
|
||||||
cp $dir/proxychains.template.conf $config
|
cp $dir/proxychains.template.conf $config
|
||||||
sed -i "\$s/.*/http $host_ip 7890/" $config
|
sed -i "\$s/.*/http $host_ip 7890/" $config
|
||||||
|
Loading…
Reference in New Issue
Block a user