forked from XiaoMo/ChatGPT-Next-Web
feat: update style and timeout handler
This commit is contained in:
parent
ff0cf2f9dc
commit
5c70456e18
@ -1,3 +1,5 @@
|
||||
@import "./window.scss";
|
||||
|
||||
@mixin container {
|
||||
background-color: var(--white);
|
||||
border: var(--border-in-light);
|
||||
@ -11,6 +13,9 @@
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
|
||||
width: var(--window-width);
|
||||
height: var(--window-height);
|
||||
}
|
||||
|
||||
.container {
|
||||
@ -18,20 +23,20 @@
|
||||
|
||||
max-width: 1080px;
|
||||
max-height: 864px;
|
||||
width: 90vw;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.tight-container {
|
||||
--window-width: 100vw;
|
||||
--window-height: 100vw;
|
||||
|
||||
@include container();
|
||||
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 300px;
|
||||
width: var(--sidebar-width);
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
background-color: var(--second);
|
||||
display: flex;
|
||||
@ -159,7 +164,7 @@
|
||||
}
|
||||
|
||||
.window-content {
|
||||
width: 100%;
|
||||
width: var(--window-content-width);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@ -170,38 +175,6 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.window-header {
|
||||
padding: 14px 20px;
|
||||
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.window-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bolder;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.window-header-sub-title {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.window-actions {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.window-action-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.chat-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
@ -220,7 +193,7 @@
|
||||
}
|
||||
|
||||
.chat-message-container {
|
||||
max-width: 60%;
|
||||
max-width: 80%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@ -254,13 +227,14 @@
|
||||
}
|
||||
|
||||
.chat-message-item {
|
||||
max-width: 100%;
|
||||
margin-top: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
border: var(--border-in-light);
|
||||
}
|
||||
|
||||
@ -327,16 +301,3 @@
|
||||
right: 30px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
.settings {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
font-size: 14px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import "katex/dist/katex.min.css";
|
||||
import RemarkMath from "remark-math";
|
||||
import RehypeKatex from "rehype-katex";
|
||||
|
||||
import EmojiPicker, { Emoji, Theme as EmojiTheme } from "emoji-picker-react";
|
||||
import { Emoji } from "emoji-picker-react";
|
||||
|
||||
import { IconButton } from "./button";
|
||||
import styles from "./home.module.scss";
|
||||
@ -21,10 +21,21 @@ import BotIcon from "../icons/bot.svg";
|
||||
import AddIcon from "../icons/add.svg";
|
||||
import DeleteIcon from "../icons/delete.svg";
|
||||
import LoadingIcon from "../icons/three-dots.svg";
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
|
||||
import { Message, SubmitKey, useChatStore, Theme } from "../store";
|
||||
import { Card, List, ListItem, Popover } from "./ui-lib";
|
||||
import { Settings } from "./settings";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
export const LazySettings = dynamic(
|
||||
async () => await (await import("./settings")).Settings,
|
||||
{
|
||||
loading: () => (
|
||||
<div className="">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
export function Markdown(props: { content: string }) {
|
||||
return (
|
||||
@ -288,6 +299,7 @@ function useSwitchTheme() {
|
||||
|
||||
export function Home() {
|
||||
const [createNewSession] = useChatStore((state) => [state.newSession]);
|
||||
const loading = !useChatStore.persist.hasHydrated();
|
||||
|
||||
// settings
|
||||
const [openSettings, setOpenSettings] = useState(false);
|
||||
@ -295,6 +307,15 @@ export function Home() {
|
||||
|
||||
useSwitchTheme();
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div>
|
||||
<Avatar role="assistant"></Avatar>
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
@ -344,154 +365,8 @@ export function Home() {
|
||||
</div>
|
||||
|
||||
<div className={styles["window-content"]}>
|
||||
{openSettings ? <Settings /> : <Chat key="chat" />}
|
||||
{openSettings ? <LazySettings /> : <Chat key="chat" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Settings() {
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
const [config, updateConfig, resetConfig] = useChatStore((state) => [
|
||||
state.config,
|
||||
state.updateConfig,
|
||||
state.resetConfig,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles["window-header"]}>
|
||||
<div>
|
||||
<div className={styles["window-header-title"]}>设置</div>
|
||||
<div className={styles["window-header-sub-title"]}>设置选项</div>
|
||||
</div>
|
||||
<div className={styles["window-actions"]}>
|
||||
<div className={styles["window-action-button"]}>
|
||||
<IconButton
|
||||
icon={<ResetIcon />}
|
||||
onClick={resetConfig}
|
||||
bordered
|
||||
title="重置所有选项"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["settings"]}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>头像</div>
|
||||
<Popover
|
||||
onClose={() => setShowEmojiPicker(false)}
|
||||
content={
|
||||
<EmojiPicker
|
||||
lazyLoadEmojis
|
||||
theme={EmojiTheme.AUTO}
|
||||
onEmojiClick={(e) => {
|
||||
updateConfig((config) => (config.avatar = e.unified));
|
||||
setShowEmojiPicker(false);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
open={showEmojiPicker}
|
||||
>
|
||||
<div
|
||||
className={styles.avatar}
|
||||
onClick={() => setShowEmojiPicker(true)}
|
||||
>
|
||||
<Avatar role="user" />
|
||||
</div>
|
||||
</Popover>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>发送键</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={config.submitKey}
|
||||
onChange={(e) => {
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.submitKey = e.target.value as any as SubmitKey)
|
||||
);
|
||||
}}
|
||||
>
|
||||
{Object.values(SubmitKey).map((v) => (
|
||||
<option value={v} key={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>主题</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={config.theme}
|
||||
onChange={(e) => {
|
||||
updateConfig(
|
||||
(config) => (config.theme = e.target.value as any as Theme)
|
||||
);
|
||||
}}
|
||||
>
|
||||
{Object.values(Theme).map((v) => (
|
||||
<option value={v} key={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>紧凑边框</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.tightBorder}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) => (config.tightBorder = e.currentTarget.checked)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
<List>
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>最大上下文消息数</div>
|
||||
<input
|
||||
type="range"
|
||||
title={config.historyMessageCount.toString()}
|
||||
value={config.historyMessageCount}
|
||||
min="5"
|
||||
max="20"
|
||||
step="5"
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.historyMessageCount = e.target.valueAsNumber)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>
|
||||
上下文中包含机器人消息
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sendBotMessages}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) => (config.sendBotMessages = e.currentTarget.checked)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
14
app/components/settings.module.scss
Normal file
14
app/components/settings.module.scss
Normal file
@ -0,0 +1,14 @@
|
||||
@import "./window.scss";
|
||||
|
||||
.settings {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.settings-title {
|
||||
font-size: 14px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
}
|
160
app/components/settings.tsx
Normal file
160
app/components/settings.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
|
||||
import EmojiPicker, { Emoji, Theme as EmojiTheme } from "emoji-picker-react";
|
||||
|
||||
import styles from "./settings.module.scss";
|
||||
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
|
||||
import { List, ListItem, Popover } from "./ui-lib";
|
||||
|
||||
import { IconButton } from "./button";
|
||||
import { SubmitKey, useChatStore, Theme } from "../store";
|
||||
import { Avatar } from "./home";
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
export function Settings() {
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
||||
const [config, updateConfig, resetConfig] = useChatStore((state) => [
|
||||
state.config,
|
||||
state.updateConfig,
|
||||
state.resetConfig,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles["window-header"]}>
|
||||
<div>
|
||||
<div className={styles["window-header-title"]}>设置</div>
|
||||
<div className={styles["window-header-sub-title"]}>设置选项</div>
|
||||
</div>
|
||||
<div className={styles["window-actions"]}>
|
||||
<div className={styles["window-action-button"]}>
|
||||
<IconButton
|
||||
icon={<ResetIcon />}
|
||||
onClick={resetConfig}
|
||||
bordered
|
||||
title="重置所有选项"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles["settings"]}>
|
||||
<List>
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>头像</div>
|
||||
<Popover
|
||||
onClose={() => setShowEmojiPicker(false)}
|
||||
content={
|
||||
<EmojiPicker
|
||||
lazyLoadEmojis
|
||||
theme={EmojiTheme.AUTO}
|
||||
onEmojiClick={(e) => {
|
||||
updateConfig((config) => (config.avatar = e.unified));
|
||||
setShowEmojiPicker(false);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
open={showEmojiPicker}
|
||||
>
|
||||
<div
|
||||
className={styles.avatar}
|
||||
onClick={() => setShowEmojiPicker(true)}
|
||||
>
|
||||
<Avatar role="user" />
|
||||
</div>
|
||||
</Popover>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>发送键</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={config.submitKey}
|
||||
onChange={(e) => {
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.submitKey = e.target.value as any as SubmitKey)
|
||||
);
|
||||
}}
|
||||
>
|
||||
{Object.values(SubmitKey).map((v) => (
|
||||
<option value={v} key={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>主题</div>
|
||||
<div className="">
|
||||
<select
|
||||
value={config.theme}
|
||||
onChange={(e) => {
|
||||
updateConfig(
|
||||
(config) => (config.theme = e.target.value as any as Theme)
|
||||
);
|
||||
}}
|
||||
>
|
||||
{Object.values(Theme).map((v) => (
|
||||
<option value={v} key={v}>
|
||||
{v}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>紧凑边框</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.tightBorder}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) => (config.tightBorder = e.currentTarget.checked)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
<List>
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>最大上下文消息数</div>
|
||||
<input
|
||||
type="range"
|
||||
title={config.historyMessageCount.toString()}
|
||||
value={config.historyMessageCount}
|
||||
min="5"
|
||||
max="20"
|
||||
step="5"
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) =>
|
||||
(config.historyMessageCount = e.target.valueAsNumber)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
|
||||
<ListItem>
|
||||
<div className={styles["settings-title"]}>
|
||||
上下文中包含机器人消息
|
||||
</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.sendBotMessages}
|
||||
onChange={(e) =>
|
||||
updateConfig(
|
||||
(config) => (config.sendBotMessages = e.currentTarget.checked)
|
||||
)
|
||||
}
|
||||
></input>
|
||||
</ListItem>
|
||||
</List>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
31
app/components/window.scss
Normal file
31
app/components/window.scss
Normal file
@ -0,0 +1,31 @@
|
||||
.window-header {
|
||||
padding: 14px 20px;
|
||||
border-bottom: rgba(0, 0, 0, 0.1) 1px solid;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.window-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: bolder;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.window-header-sub-title {
|
||||
font-size: 14px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.window-actions {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.window-action-button {
|
||||
margin-left: 10px;
|
||||
}
|
@ -41,6 +41,11 @@
|
||||
|
||||
:root {
|
||||
@include light;
|
||||
|
||||
--window-width: 90vw;
|
||||
--window-height: 90vh;
|
||||
--sidebar-width: 300px;
|
||||
--window-content-width: calc(var(--window-width) - var(--sidebar-width));
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "./globals.scss";
|
||||
import "./markdown.css";
|
||||
import "./markdown.scss";
|
||||
|
||||
export const metadata = {
|
||||
title: "ChatGPT Next Web",
|
||||
|
@ -1,97 +1,93 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.markdown-body {
|
||||
color-scheme: dark;
|
||||
--color-prettylights-syntax-comment: #8b949e;
|
||||
--color-prettylights-syntax-constant: #79c0ff;
|
||||
--color-prettylights-syntax-entity: #d2a8ff;
|
||||
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
|
||||
--color-prettylights-syntax-entity-tag: #7ee787;
|
||||
--color-prettylights-syntax-keyword: #ff7b72;
|
||||
--color-prettylights-syntax-string: #a5d6ff;
|
||||
--color-prettylights-syntax-variable: #ffa657;
|
||||
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
|
||||
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
|
||||
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
|
||||
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
|
||||
--color-prettylights-syntax-carriage-return-bg: #b62324;
|
||||
--color-prettylights-syntax-string-regexp: #7ee787;
|
||||
--color-prettylights-syntax-markup-list: #f2cc60;
|
||||
--color-prettylights-syntax-markup-heading: #1f6feb;
|
||||
--color-prettylights-syntax-markup-italic: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-bold: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
|
||||
--color-prettylights-syntax-markup-deleted-bg: #67060c;
|
||||
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
|
||||
--color-prettylights-syntax-markup-inserted-bg: #033a16;
|
||||
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
|
||||
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
|
||||
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
|
||||
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
|
||||
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
|
||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
|
||||
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
|
||||
--color-fg-default: #c9d1d9;
|
||||
--color-fg-muted: #8b949e;
|
||||
--color-fg-subtle: #6e7681;
|
||||
--color-canvas-default: transparent;
|
||||
--color-canvas-subtle: #161b22;
|
||||
--color-border-default: #30363d;
|
||||
--color-border-muted: #21262d;
|
||||
--color-neutral-muted: rgba(110,118,129,0.4);
|
||||
--color-accent-fg: #58a6ff;
|
||||
--color-accent-emphasis: #1f6feb;
|
||||
--color-attention-subtle: rgba(187,128,9,0.15);
|
||||
--color-danger-fg: #f85149;
|
||||
}
|
||||
@mixin light {
|
||||
color-scheme: light;
|
||||
--color-prettylights-syntax-comment: #6e7781;
|
||||
--color-prettylights-syntax-constant: #0550ae;
|
||||
--color-prettylights-syntax-entity: #8250df;
|
||||
--color-prettylights-syntax-storage-modifier-import: #24292f;
|
||||
--color-prettylights-syntax-entity-tag: #116329;
|
||||
--color-prettylights-syntax-keyword: #cf222e;
|
||||
--color-prettylights-syntax-string: #0a3069;
|
||||
--color-prettylights-syntax-variable: #953800;
|
||||
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
|
||||
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
|
||||
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
|
||||
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
|
||||
--color-prettylights-syntax-carriage-return-bg: #cf222e;
|
||||
--color-prettylights-syntax-string-regexp: #116329;
|
||||
--color-prettylights-syntax-markup-list: #3b2300;
|
||||
--color-prettylights-syntax-markup-heading: #0550ae;
|
||||
--color-prettylights-syntax-markup-italic: #24292f;
|
||||
--color-prettylights-syntax-markup-bold: #24292f;
|
||||
--color-prettylights-syntax-markup-deleted-text: #82071e;
|
||||
--color-prettylights-syntax-markup-deleted-bg: #ffebe9;
|
||||
--color-prettylights-syntax-markup-inserted-text: #116329;
|
||||
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
|
||||
--color-prettylights-syntax-markup-changed-text: #953800;
|
||||
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
|
||||
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
|
||||
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
|
||||
--color-prettylights-syntax-meta-diff-range: #8250df;
|
||||
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
|
||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
|
||||
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
|
||||
--color-fg-default: #24292f;
|
||||
--color-fg-muted: #57606a;
|
||||
--color-fg-subtle: #6e7781;
|
||||
--color-canvas-default: transparent;
|
||||
--color-canvas-subtle: #f6f8fa;
|
||||
--color-border-default: #d0d7de;
|
||||
--color-border-muted: hsla(210, 18%, 87%, 1);
|
||||
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
||||
--color-accent-fg: #0969da;
|
||||
--color-accent-emphasis: #0969da;
|
||||
--color-attention-subtle: #fff8c5;
|
||||
--color-danger-fg: #cf222e;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.markdown-body {
|
||||
color-scheme: light;
|
||||
--color-prettylights-syntax-comment: #6e7781;
|
||||
--color-prettylights-syntax-constant: #0550ae;
|
||||
--color-prettylights-syntax-entity: #8250df;
|
||||
--color-prettylights-syntax-storage-modifier-import: #24292f;
|
||||
--color-prettylights-syntax-entity-tag: #116329;
|
||||
--color-prettylights-syntax-keyword: #cf222e;
|
||||
--color-prettylights-syntax-string: #0a3069;
|
||||
--color-prettylights-syntax-variable: #953800;
|
||||
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
|
||||
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
|
||||
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
|
||||
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
|
||||
--color-prettylights-syntax-carriage-return-bg: #cf222e;
|
||||
--color-prettylights-syntax-string-regexp: #116329;
|
||||
--color-prettylights-syntax-markup-list: #3b2300;
|
||||
--color-prettylights-syntax-markup-heading: #0550ae;
|
||||
--color-prettylights-syntax-markup-italic: #24292f;
|
||||
--color-prettylights-syntax-markup-bold: #24292f;
|
||||
--color-prettylights-syntax-markup-deleted-text: #82071e;
|
||||
--color-prettylights-syntax-markup-deleted-bg: #ffebe9;
|
||||
--color-prettylights-syntax-markup-inserted-text: #116329;
|
||||
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
|
||||
--color-prettylights-syntax-markup-changed-text: #953800;
|
||||
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
|
||||
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
|
||||
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
|
||||
--color-prettylights-syntax-meta-diff-range: #8250df;
|
||||
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
|
||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
|
||||
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
|
||||
--color-fg-default: #24292f;
|
||||
--color-fg-muted: #57606a;
|
||||
--color-fg-subtle: #6e7781;
|
||||
--color-canvas-default: transparent;
|
||||
--color-canvas-subtle: #f6f8fa;
|
||||
--color-border-default: #d0d7de;
|
||||
--color-border-muted: hsla(210,18%,87%,1);
|
||||
--color-neutral-muted: rgba(175,184,193,0.2);
|
||||
--color-accent-fg: #0969da;
|
||||
--color-accent-emphasis: #0969da;
|
||||
--color-attention-subtle: #fff8c5;
|
||||
--color-danger-fg: #cf222e;
|
||||
}
|
||||
@mixin dark {
|
||||
color-scheme: dark;
|
||||
--color-prettylights-syntax-comment: #8b949e;
|
||||
--color-prettylights-syntax-constant: #79c0ff;
|
||||
--color-prettylights-syntax-entity: #d2a8ff;
|
||||
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
|
||||
--color-prettylights-syntax-entity-tag: #7ee787;
|
||||
--color-prettylights-syntax-keyword: #ff7b72;
|
||||
--color-prettylights-syntax-string: #a5d6ff;
|
||||
--color-prettylights-syntax-variable: #ffa657;
|
||||
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
|
||||
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
|
||||
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
|
||||
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
|
||||
--color-prettylights-syntax-carriage-return-bg: #b62324;
|
||||
--color-prettylights-syntax-string-regexp: #7ee787;
|
||||
--color-prettylights-syntax-markup-list: #f2cc60;
|
||||
--color-prettylights-syntax-markup-heading: #1f6feb;
|
||||
--color-prettylights-syntax-markup-italic: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-bold: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
|
||||
--color-prettylights-syntax-markup-deleted-bg: #67060c;
|
||||
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
|
||||
--color-prettylights-syntax-markup-inserted-bg: #033a16;
|
||||
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
|
||||
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
|
||||
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
|
||||
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
|
||||
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
|
||||
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
|
||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
|
||||
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
|
||||
--color-fg-default: #c9d1d9;
|
||||
--color-fg-muted: #8b949e;
|
||||
--color-fg-subtle: #6e7681;
|
||||
--color-canvas-default: transparent;
|
||||
--color-canvas-subtle: #161b22;
|
||||
--color-border-default: #30363d;
|
||||
--color-border-muted: #21262d;
|
||||
--color-neutral-muted: rgba(110, 118, 129, 0.4);
|
||||
--color-accent-fg: #58a6ff;
|
||||
--color-accent-emphasis: #1f6feb;
|
||||
--color-attention-subtle: rgba(187, 128, 9, 0.15);
|
||||
--color-danger-fg: #f85149;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
@ -100,12 +96,31 @@
|
||||
margin: 0;
|
||||
color: var(--color-fg-default);
|
||||
background-color: var(--color-canvas-default);
|
||||
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
|
||||
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.light {
|
||||
@include light;
|
||||
}
|
||||
|
||||
.dark {
|
||||
@include dark;
|
||||
}
|
||||
|
||||
:root {
|
||||
@include light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
@include dark;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-body .octicon {
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
@ -120,7 +135,7 @@
|
||||
.markdown-body h6:hover .anchor .octicon-link:before {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
content: ' ';
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
background-color: currentColor;
|
||||
-webkit-mask-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' aria-hidden='true'><path fill-rule='evenodd' d='M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-4.69 9.64a2 2 0 010-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25a.75.75 0 00-1.06-1.06l-1.25 1.25a2 2 0 01-2.83 0z'></path></svg>");
|
||||
@ -162,9 +177,9 @@
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
margin: .67em 0;
|
||||
margin: 0.67em 0;
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
padding-bottom: .3em;
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2em;
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
}
|
||||
@ -218,7 +233,7 @@
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
height: .25em;
|
||||
height: 0.25em;
|
||||
padding: 0;
|
||||
margin: 24px 0;
|
||||
background-color: var(--color-border-default);
|
||||
@ -234,31 +249,31 @@
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.markdown-body [type=button],
|
||||
.markdown-body [type=reset],
|
||||
.markdown-body [type=submit] {
|
||||
.markdown-body [type="button"],
|
||||
.markdown-body [type="reset"],
|
||||
.markdown-body [type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
.markdown-body [type=checkbox],
|
||||
.markdown-body [type=radio] {
|
||||
.markdown-body [type="checkbox"],
|
||||
.markdown-body [type="radio"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.markdown-body [type=number]::-webkit-inner-spin-button,
|
||||
.markdown-body [type=number]::-webkit-outer-spin-button {
|
||||
.markdown-body [type="number"]::-webkit-inner-spin-button,
|
||||
.markdown-body [type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.markdown-body [type=search]::-webkit-search-cancel-button,
|
||||
.markdown-body [type=search]::-webkit-search-decoration {
|
||||
.markdown-body [type="search"]::-webkit-search-cancel-button,
|
||||
.markdown-body [type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.markdown-body ::-webkit-input-placeholder {
|
||||
color: inherit;
|
||||
opacity: .54;
|
||||
opacity: 0.54;
|
||||
}
|
||||
|
||||
.markdown-body ::-webkit-file-upload-button {
|
||||
@ -304,30 +319,30 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.markdown-body details:not([open])>*:not(summary) {
|
||||
.markdown-body details:not([open]) > *:not(summary) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.markdown-body a:focus,
|
||||
.markdown-body [role=button]:focus,
|
||||
.markdown-body input[type=radio]:focus,
|
||||
.markdown-body input[type=checkbox]:focus {
|
||||
.markdown-body [role="button"]:focus,
|
||||
.markdown-body input[type="radio"]:focus,
|
||||
.markdown-body input[type="checkbox"]:focus {
|
||||
outline: 2px solid var(--color-accent-fg);
|
||||
outline-offset: -2px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.markdown-body a:focus:not(:focus-visible),
|
||||
.markdown-body [role=button]:focus:not(:focus-visible),
|
||||
.markdown-body input[type=radio]:focus:not(:focus-visible),
|
||||
.markdown-body input[type=checkbox]:focus:not(:focus-visible) {
|
||||
.markdown-body [role="button"]:focus:not(:focus-visible),
|
||||
.markdown-body input[type="radio"]:focus:not(:focus-visible),
|
||||
.markdown-body input[type="checkbox"]:focus:not(:focus-visible) {
|
||||
outline: solid 1px transparent;
|
||||
}
|
||||
|
||||
.markdown-body a:focus-visible,
|
||||
.markdown-body [role=button]:focus-visible,
|
||||
.markdown-body input[type=radio]:focus-visible,
|
||||
.markdown-body input[type=checkbox]:focus-visible {
|
||||
.markdown-body [role="button"]:focus-visible,
|
||||
.markdown-body input[type="radio"]:focus-visible,
|
||||
.markdown-body input[type="checkbox"]:focus-visible {
|
||||
outline: 2px solid var(--color-accent-fg);
|
||||
outline-offset: -2px;
|
||||
box-shadow: none;
|
||||
@ -335,17 +350,18 @@
|
||||
|
||||
.markdown-body a:not([class]):focus,
|
||||
.markdown-body a:not([class]):focus-visible,
|
||||
.markdown-body input[type=radio]:focus,
|
||||
.markdown-body input[type=radio]:focus-visible,
|
||||
.markdown-body input[type=checkbox]:focus,
|
||||
.markdown-body input[type=checkbox]:focus-visible {
|
||||
.markdown-body input[type="radio"]:focus,
|
||||
.markdown-body input[type="radio"]:focus-visible,
|
||||
.markdown-body input[type="checkbox"]:focus,
|
||||
.markdown-body input[type="checkbox"]:focus-visible {
|
||||
outline-offset: 0;
|
||||
}
|
||||
|
||||
.markdown-body kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font: 11px ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||
Liberation Mono, monospace;
|
||||
line-height: 10px;
|
||||
color: var(--color-fg-default);
|
||||
vertical-align: middle;
|
||||
@ -370,7 +386,7 @@
|
||||
|
||||
.markdown-body h2 {
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
padding-bottom: .3em;
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.5em;
|
||||
border-bottom: 1px solid var(--color-border-muted);
|
||||
}
|
||||
@ -387,12 +403,12 @@
|
||||
|
||||
.markdown-body h5 {
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
font-size: .875em;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.markdown-body h6 {
|
||||
font-weight: var(--base-text-weight-semibold, 600);
|
||||
font-size: .85em;
|
||||
font-size: 0.85em;
|
||||
color: var(--color-fg-muted);
|
||||
}
|
||||
|
||||
@ -405,7 +421,7 @@
|
||||
margin: 0;
|
||||
padding: 0 1em;
|
||||
color: var(--color-fg-muted);
|
||||
border-left: .25em solid var(--color-border-default);
|
||||
border-left: 0.25em solid var(--color-border-default);
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
@ -434,14 +450,16 @@
|
||||
.markdown-body tt,
|
||||
.markdown-body code,
|
||||
.markdown-body samp {
|
||||
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||
Liberation Mono, monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown-body pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font-family: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace;
|
||||
font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||
Liberation Mono, monospace;
|
||||
font-size: 12px;
|
||||
word-wrap: normal;
|
||||
}
|
||||
@ -471,11 +489,11 @@
|
||||
content: "";
|
||||
}
|
||||
|
||||
.markdown-body>*:first-child {
|
||||
.markdown-body > *:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.markdown-body>*:last-child {
|
||||
.markdown-body > *:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
@ -511,11 +529,11 @@
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:first-child {
|
||||
.markdown-body blockquote > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body blockquote>:last-child {
|
||||
.markdown-body blockquote > :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -560,7 +578,7 @@
|
||||
.markdown-body h5 code,
|
||||
.markdown-body h6 tt,
|
||||
.markdown-body h6 code {
|
||||
padding: 0 .2em;
|
||||
padding: 0 0.2em;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
@ -594,19 +612,19 @@
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.markdown-body ol[type=a] {
|
||||
.markdown-body ol[type="a"] {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
|
||||
.markdown-body ol[type=A] {
|
||||
.markdown-body ol[type="A"] {
|
||||
list-style-type: upper-alpha;
|
||||
}
|
||||
|
||||
.markdown-body ol[type=i] {
|
||||
.markdown-body ol[type="i"] {
|
||||
list-style-type: lower-roman;
|
||||
}
|
||||
|
||||
.markdown-body ol[type=I] {
|
||||
.markdown-body ol[type="I"] {
|
||||
list-style-type: upper-roman;
|
||||
}
|
||||
|
||||
@ -614,7 +632,7 @@
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
.markdown-body div>ol:not([type]) {
|
||||
.markdown-body div > ol:not([type]) {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
@ -626,12 +644,12 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.markdown-body li>p {
|
||||
.markdown-body li > p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.markdown-body li+li {
|
||||
margin-top: .25em;
|
||||
.markdown-body li + li {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-body dl {
|
||||
@ -674,11 +692,11 @@
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.markdown-body img[align=right] {
|
||||
.markdown-body img[align="right"] {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.markdown-body img[align=left] {
|
||||
.markdown-body img[align="left"] {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
@ -693,7 +711,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.markdown-body span.frame>span {
|
||||
.markdown-body span.frame > span {
|
||||
display: block;
|
||||
float: left;
|
||||
width: auto;
|
||||
@ -721,7 +739,7 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown-body span.align-center>span {
|
||||
.markdown-body span.align-center > span {
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
@ -739,7 +757,7 @@
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown-body span.align-right>span {
|
||||
.markdown-body span.align-right > span {
|
||||
display: block;
|
||||
margin: 13px 0 0;
|
||||
overflow: hidden;
|
||||
@ -769,7 +787,7 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.markdown-body span.float-right>span {
|
||||
.markdown-body span.float-right > span {
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
@ -778,7 +796,7 @@
|
||||
|
||||
.markdown-body code,
|
||||
.markdown-body tt {
|
||||
padding: .2em .4em;
|
||||
padding: 0.2em 0.4em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
white-space: break-spaces;
|
||||
@ -803,7 +821,7 @@
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
.markdown-body pre>code {
|
||||
.markdown-body pre > code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
word-break: normal;
|
||||
@ -1042,7 +1060,7 @@
|
||||
.markdown-body g-emoji {
|
||||
display: inline-block;
|
||||
min-width: 1ch;
|
||||
font-family: "Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";
|
||||
font-family: "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1em;
|
||||
font-style: normal !important;
|
||||
font-weight: var(--base-text-weight-normal, 400);
|
||||
@ -1067,7 +1085,7 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item+.task-list-item {
|
||||
.markdown-body .task-list-item + .task-list-item {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
@ -1076,12 +1094,12 @@
|
||||
}
|
||||
|
||||
.markdown-body .task-list-item-checkbox {
|
||||
margin: 0 .2em .25em -1.4em;
|
||||
margin: 0 0.2em 0.25em -1.4em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.markdown-body .contains-task-list:dir(rtl) .task-list-item-checkbox {
|
||||
margin: 0 -1.6em .25em .2em;
|
||||
margin: 0 -1.6em 0.25em 0.2em;
|
||||
}
|
||||
|
||||
.markdown-body .contains-task-list {
|
||||
@ -1089,7 +1107,9 @@
|
||||
}
|
||||
|
||||
.markdown-body .contains-task-list:hover .task-list-item-convert-container,
|
||||
.markdown-body .contains-task-list:focus-within .task-list-item-convert-container {
|
||||
.markdown-body
|
||||
.contains-task-list:focus-within
|
||||
.task-list-item-convert-container {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: 24px;
|
||||
@ -1100,4 +1120,3 @@
|
||||
.markdown-body ::-webkit-calendar-picker-indicator {
|
||||
filter: invert(50%);
|
||||
}
|
||||
|
@ -51,35 +51,48 @@ export async function requestChatStream(
|
||||
filterBot: options?.filterBot,
|
||||
});
|
||||
|
||||
const res = await fetch("/api/chat-stream", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(req),
|
||||
});
|
||||
const controller = new AbortController();
|
||||
setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
let responseText = "";
|
||||
try {
|
||||
const res = await fetch("/api/chat-stream", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(req),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const reader = res.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
let responseText = "";
|
||||
|
||||
while (true) {
|
||||
const content = await reader?.read();
|
||||
const text = decoder.decode(content?.value);
|
||||
responseText += text;
|
||||
const finish = () => options?.onMessage(responseText, true);
|
||||
|
||||
const done = !content || content.done;
|
||||
options?.onMessage(responseText, false);
|
||||
if (res.ok) {
|
||||
const reader = res.body?.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
while (true) {
|
||||
// handle time out, will stop if no response in 10 secs
|
||||
const timeoutId = setTimeout(() => finish(), 10000);
|
||||
const content = await reader?.read();
|
||||
clearTimeout(timeoutId);
|
||||
const text = decoder.decode(content?.value);
|
||||
responseText += text;
|
||||
|
||||
const done = !content || content.done;
|
||||
options?.onMessage(responseText, false);
|
||||
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options?.onMessage(responseText, true);
|
||||
} else {
|
||||
finish();
|
||||
} else {
|
||||
options?.onError(new Error("Stream Error"));
|
||||
}
|
||||
} catch (err) {
|
||||
options?.onError(new Error("NetWork Error"));
|
||||
}
|
||||
}
|
||||
@ -87,7 +100,7 @@ export async function requestChatStream(
|
||||
export async function requestWithPrompt(messages: Message[], prompt: string) {
|
||||
messages = messages.concat([
|
||||
{
|
||||
role: "system",
|
||||
role: "user",
|
||||
content: prompt,
|
||||
date: new Date().toLocaleString(),
|
||||
},
|
||||
|
24
app/store.ts
24
app/store.ts
@ -108,6 +108,8 @@ interface ChatStore {
|
||||
updateConfig: (updater: (config: ChatConfig) => void) => void;
|
||||
}
|
||||
|
||||
const LOCAL_KEY = "chat-next-web-store";
|
||||
|
||||
export const useChatStore = create<ChatStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
@ -254,16 +256,16 @@ export const useChatStore = create<ChatStore>()(
|
||||
summarizeSession() {
|
||||
const session = get().currentSession();
|
||||
|
||||
if (session.topic !== DEFAULT_TOPIC) return;
|
||||
|
||||
requestWithPrompt(
|
||||
session.messages,
|
||||
"简明扼要地 10 字以内总结主题"
|
||||
).then((res) => {
|
||||
get().updateCurrentSession(
|
||||
(session) => (session.topic = trimTopic(res))
|
||||
if (session.topic === DEFAULT_TOPIC) {
|
||||
// should summarize topic
|
||||
requestWithPrompt(session.messages, "返回这句话的简要主题").then(
|
||||
(res) => {
|
||||
get().updateCurrentSession(
|
||||
(session) => (session.topic = trimTopic(res))
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updateStat(message) {
|
||||
@ -280,6 +282,8 @@ export const useChatStore = create<ChatStore>()(
|
||||
set(() => ({ sessions }));
|
||||
},
|
||||
}),
|
||||
{ name: "chat-next-web-store" }
|
||||
{
|
||||
name: LOCAL_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -4,6 +4,8 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"local:dev": "./dev/proxychains.exe -f ./scripts/proxychains.conf yarn dev",
|
||||
"local:start": "./dev/proxychains.exe -f ./scripts/proxychains.conf yarn start",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
|
228
scripts/proxychains.conf
Normal file
228
scripts/proxychains.conf
Normal file
@ -0,0 +1,228 @@
|
||||
# proxychains.conf FOR PROXYCHAINS.EXE ALPHA
|
||||
#
|
||||
# SOCKS5 tunneling proxifier with Fake DNS.
|
||||
#
|
||||
|
||||
# The option below identifies how the ProxyList is treated.
|
||||
# only one option should be uncommented at time,
|
||||
# otherwise the last appearing option will be accepted
|
||||
#
|
||||
#
|
||||
# DYNAMIC_CHAIN IS NOT SUPPORTED AT PRESENT
|
||||
#dynamic_chain
|
||||
# DYNAMIC_CHAIN IS NOT SUPPORTED AT PRESENT
|
||||
#
|
||||
# Dynamic - Each connection will be done via chained proxies
|
||||
# all proxies chained in the order as they appear in the list
|
||||
# at least one proxy must be online to play in chain
|
||||
# (dead proxies are skipped)
|
||||
# otherwise EINTR is returned to the app
|
||||
#
|
||||
#
|
||||
strict_chain
|
||||
#
|
||||
# Strict - Each connection will be done via chained proxies
|
||||
# all proxies chained in the order as they appear in the list
|
||||
# all proxies must be online to play in chain
|
||||
# otherwise EINTR is returned to the app
|
||||
#
|
||||
# RANDOM_CHAIN IS NOT SUPPORTED AT PRESENT
|
||||
#random_chain
|
||||
# RANDOM_CHAIN IS NOT SUPPORTED AT PRESENT
|
||||
#
|
||||
# Random - Each connection will be done via random proxy
|
||||
# (or proxy chain, see chain_len) from the list.
|
||||
# this option is good to test your IDS :)
|
||||
|
||||
# Make sense only if random_chain
|
||||
#chain_len = 2
|
||||
|
||||
# Quiet mode (no output from library)
|
||||
#quiet_mode
|
||||
|
||||
# Proxy DNS requests using Fake IP - no leak for DNS data
|
||||
proxy_dns
|
||||
|
||||
# Proxy DNS requests using UDP associate feature provided by SOCKS5 proxy
|
||||
# NOT SUPPORTED AT PRESENT
|
||||
#proxy_dns_udp_associate
|
||||
# NOT SUPPORTED AT PRESENT
|
||||
|
||||
# set the class A subnet number to usefor use of the internal remote DNS mapping
|
||||
# we use the reserved 224.x.x.x range by default,
|
||||
# if the proxified app does a DNS request, we will return an IP from that range.
|
||||
# on further accesses to this ip we will send the saved DNS name to the proxy.
|
||||
# in case some control-freak app checks the returned ip, and denies to
|
||||
# connect, you can use another subnet, e.g. 10.x.x.x or 127.x.x.x.
|
||||
# of course you should make sure that the proxified app does not need
|
||||
# *real* access to this subnet.
|
||||
# i.e. dont use the same subnet then in the localnet section
|
||||
#remote_dns_subnet 127
|
||||
#remote_dns_subnet 10
|
||||
remote_dns_subnet 224
|
||||
|
||||
# This enables you to set a CIDR block for the internal remote DNS mapping
|
||||
# for example, remote_dns_subnet_cidr_v4 224.0.0.0/8 is equivalent to
|
||||
# remote_dns_subnet 224.
|
||||
# subnet mask format like 255.255.0.0 is not allowed here
|
||||
# By default 224.0.0.0/8 and 250d::/16
|
||||
#remote_dns_subnet_cidr_v4 224.0.0.0/8
|
||||
#remote_dns_subnet_cidr_v6 250d::/16
|
||||
|
||||
# Some timeouts in milliseconds
|
||||
# Defaults: tcp_read_time_out 5000, tcp_connect_time_out 3000
|
||||
#tcp_read_time_out 15000
|
||||
#tcp_connect_time_out 8000
|
||||
|
||||
|
||||
# ==== Rules ====
|
||||
# You can control which IP range not to be proxied.
|
||||
# First matched rule decides the target (PROXIED, DIRECT or BLOCK) of a
|
||||
# connection.
|
||||
|
||||
# localnet always has a "DIRECT" target, which means they will not be
|
||||
# proxied.
|
||||
# By default enable localnet for loopback address ranges
|
||||
# RFC5735 Loopback address range
|
||||
localnet 127.0.0.0/255.0.0.0
|
||||
# RFC1918 Private Address Ranges
|
||||
# localnet 10.0.0.0/255.0.0.0
|
||||
# localnet 172.16.0.0/255.240.0.0
|
||||
# localnet 192.168.0.0/255.255.0.0
|
||||
|
||||
|
||||
# Example for localnet exclusion
|
||||
## Exclude connections to 192.168.1.0/24 with port 80
|
||||
# localnet 192.168.1.0:80/255.255.255.0
|
||||
|
||||
## Exclude connections to 192.168.100.0/24
|
||||
# localnet 192.168.100.0/255.255.255.0
|
||||
|
||||
## Exclude connections to ANYwhere with port 80
|
||||
# localnet 0.0.0.0:80/0.0.0.0
|
||||
|
||||
# === Additional routing rules ===
|
||||
# These rules enables further control on websites/addresses which
|
||||
# should be proxied or not.
|
||||
# All rules can have an extra optional restriction of target port.
|
||||
# However, if the proxied application uses gethostbyname() to do DNS
|
||||
# query instead of getaddrinfo() series, this port part of the rule
|
||||
# is invalidated.
|
||||
# Three target is allowed: PROXY, DIRECT and BLOCK.
|
||||
#
|
||||
# - DOMAIN-KEYWORD rule, matching requests where the FQDN contains
|
||||
# a specific string.
|
||||
# e.g. DOMAIN-KEYWORD,google:80,DIRECT means any request to FQDN
|
||||
# containing "google" with the target port 80 will NOT be proxied.
|
||||
#
|
||||
# - DOMAIN-SUFFIX rule, matching requests where the FQDN is suffixed
|
||||
# with a specific string.
|
||||
# e.g. DOMAIN-SUFFIX,.ru,PROXY means any FQDN that ends with ".ru"
|
||||
# will be proxied.
|
||||
#
|
||||
# - DOMAIN-FULL rule, matching requests where the FQDN is exactly
|
||||
# identical to a specific string.
|
||||
# e.g. DOMAIN-FULL,duckduckgo.com,DIRECT means every request to
|
||||
# "duckduckgo.com" (must entirely match) will NOT be proxied.
|
||||
#
|
||||
# - DOMAIN rule, the alias of DOMAIN-FULL rule.
|
||||
#
|
||||
# - IP-CIDR rule, matching requests to IP address in a CIDR block.
|
||||
# Note if an FQDN is previously matched by a DOMAIN* rule, this rule
|
||||
# is not applied to the resolved IPs. (Because fake IPs are used in
|
||||
# this case)
|
||||
# e.g. IP-CIDR,8.8.8.8/32,DIRECT
|
||||
# IP-CIDR,250e::/16,PROXY
|
||||
# IP-CIDR,[250c::]:443/16,PROXY
|
||||
# IP-CIDR,10.0.0.0:80/8,DIRECT
|
||||
# Note that "IP-CIDR,127.0.0.0/8,DIRECT" is equivalent to
|
||||
# "localnet 127.0.0.0/255.0.0.0".
|
||||
IP-CIDR,10.0.0.0/8,DIRECT
|
||||
IP-CIDR,172.16.0.0/12,DIRECT
|
||||
IP-CIDR,192.168.0.0/255.255.0.0,DIRECT
|
||||
IP-CIDR,fe80::/8,DIRECT
|
||||
# - PORT rule, matching requests to a target port.
|
||||
# e.g. PORT,25,BLOCK
|
||||
#
|
||||
# - FINAL "rule", deciding the destiny of a request immediately.
|
||||
# When this "rule" is used, it is not treated as a "match".
|
||||
# If you want an unconditional match, try other rules instead, like
|
||||
# IP-CIDR,0.0.0.0/0,PROXY or DOMAIN-KEYWORD,,PROXY.
|
||||
#
|
||||
# When no rules and no FINAL "rule" matched, a connection will be
|
||||
# PROXIED by default, unless you specify option default_target.
|
||||
# e.g. FINAL,PROXY
|
||||
|
||||
# Will fake IP entries created by a descendant process be removed if this
|
||||
# process exited? 1 by default.
|
||||
delete_fake_ip_after_child_exits 1
|
||||
|
||||
# When no rules and no FINAL "rule" matched, a connection's default
|
||||
# target. PROXY by default.
|
||||
default_target PROXY
|
||||
|
||||
# Will the rules apply to the resolved IP if corresponding hostname
|
||||
# did not match any rules? (FINAL is not counted as a rule)
|
||||
# IF SO, SET THIS OPTION'S VALUE TO 0. 1 by default.
|
||||
use_fake_ip_when_hostname_not_matched 1
|
||||
|
||||
# ===== Keep them as-is =====
|
||||
|
||||
map_resolved_ip_to_host 0
|
||||
search_for_host_by_resolved_ip 0
|
||||
# or force_resolve_by_hosts_file 1
|
||||
resolve_locally_if_match_hosts 1
|
||||
|
||||
# ===== Keep them as-is - end =====
|
||||
|
||||
# Generate fake ips by FQDN hash - 1 (better to get rid of SSH safe
|
||||
# warnings)
|
||||
# Generate fake ips sequentially - 0
|
||||
# Default: 1
|
||||
gen_fake_ip_using_hashed_hostname 1
|
||||
|
||||
# If your *first* proxy supports connecting to it by an IPv4 address
|
||||
# (resolved by a hostname or specified manually), set its value to 1.
|
||||
# This enables proxying IPv4 address.
|
||||
# If disabled, fake IPv4 address is not returned.
|
||||
# 1 by default
|
||||
first_tunnel_uses_ipv4 1
|
||||
|
||||
# If your *first* proxy supports connecting to it by an IPv6 address
|
||||
# (resolved by a hostname or specified manually), set its value to 1.
|
||||
# This enables proxying IPv6 address.
|
||||
# If disabled, fake IPv6 address is not returned.
|
||||
# 0 by default
|
||||
first_tunnel_uses_ipv6 0
|
||||
|
||||
# Custom hosts file path
|
||||
#custom_hosts_file_path C:\Some Path\hosts
|
||||
#custom_hosts_file_path /etc/alternative/hosts
|
||||
|
||||
# Custom log level.
|
||||
# 600 - VERBOSE
|
||||
# 500 - DEBUG
|
||||
# 400 - INFO
|
||||
# 300 - WARNING
|
||||
# 200 - ERROR
|
||||
# 100 - CRITICAL
|
||||
# "log_level 200" is equivalent to "quiet_mode"
|
||||
log_level 400
|
||||
|
||||
# ProxyList format
|
||||
# type host port [user pass]
|
||||
# (values separated by 'tab' or 'blank')
|
||||
#
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# socks5 localhost 1080
|
||||
# socks5 localhost 1080 user password
|
||||
# socks5 192.168.67.78 1080 lamer secret
|
||||
#
|
||||
#
|
||||
# proxy types: socks5
|
||||
# ( auth types supported: "user/pass"-socks5 )
|
||||
#
|
||||
[ProxyList]
|
||||
socks5 localhost 7890
|
Loading…
Reference in New Issue
Block a user