Merge branch 'Yidadaa:main' into main
This commit is contained in:
commit
22dd01dc59
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"presets": [
|
||||||
|
[
|
||||||
|
"next/babel",
|
||||||
|
{
|
||||||
|
"preset-env": {
|
||||||
|
"targets": {
|
||||||
|
"browsers": ["> 0.25%, not dead"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ One-Click to get well-designed cross-platform ChatGPT web UI.
|
||||||
[![MacOS][MacOS-image]][download-url]
|
[![MacOS][MacOS-image]][download-url]
|
||||||
[![Linux][Linux-image]][download-url]
|
[![Linux][Linux-image]][download-url]
|
||||||
|
|
||||||
[Web App](https://chatgpt.nextweb.fun/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
[Web App](https://chatgpt.nextweb.fun/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discord](https://discord.gg/YCkeafCafC) / [Buy Me a Coffee](https://www.buymeacoffee.com/yidadaa)
|
||||||
|
|
||||||
[网页版](https://chatgpt.nextweb.fun/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
[网页版](https://chatgpt.nextweb.fun/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [QQ 群](https://github.com/Yidadaa/ChatGPT-Next-Web/discussions/1724) / [打赏开发者](https://user-images.githubusercontent.com/16968934/227772541-5bcd52d8-61b7-488c-a203-0330d8006e2b.jpg)
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@ import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
export const OPENAI_URL = "api.openai.com";
|
export const OPENAI_URL = "api.openai.com";
|
||||||
const DEFAULT_PROTOCOL = "https";
|
const DEFAULT_PROTOCOL = "https";
|
||||||
const PROTOCOL = process.env.PROTOCOL ?? DEFAULT_PROTOCOL;
|
const PROTOCOL = process.env.PROTOCOL || DEFAULT_PROTOCOL;
|
||||||
const BASE_URL = process.env.BASE_URL ?? OPENAI_URL;
|
const BASE_URL = process.env.BASE_URL || OPENAI_URL;
|
||||||
const DISABLE_GPT4 = !!process.env.DISABLE_GPT4;
|
const DISABLE_GPT4 = !!process.env.DISABLE_GPT4;
|
||||||
|
|
||||||
export async function requestOpenai(req: NextRequest) {
|
export async function requestOpenai(req: NextRequest) {
|
||||||
|
|
|
@ -101,6 +101,19 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.context-drag {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-drag {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: all ease 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
.context-role {
|
.context-role {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
Modal,
|
Modal,
|
||||||
Select,
|
Select,
|
||||||
showImageModal,
|
showImageModal,
|
||||||
|
showModal,
|
||||||
showToast,
|
showToast,
|
||||||
} from "./ui-lib";
|
} from "./ui-lib";
|
||||||
import { IconButton } from "./button";
|
import { IconButton } from "./button";
|
||||||
|
@ -287,7 +288,30 @@ export function PreviewActions(props: {
|
||||||
.share(msgs)
|
.share(msgs)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
copyToClipboard(res);
|
showModal({
|
||||||
|
title: Locale.Export.Share,
|
||||||
|
children: [
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={res}
|
||||||
|
key="input"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "unset",
|
||||||
|
}}
|
||||||
|
readOnly
|
||||||
|
onClick={(e) => e.currentTarget.select()}
|
||||||
|
></input>,
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
<IconButton
|
||||||
|
icon={<CopyIcon />}
|
||||||
|
text={Locale.Chat.Actions.Copy}
|
||||||
|
key="copy"
|
||||||
|
onClick={() => copyToClipboard(res)}
|
||||||
|
/>,
|
||||||
|
],
|
||||||
|
});
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.open(res, "_blank");
|
window.open(res, "_blank");
|
||||||
}, 800);
|
}, 800);
|
||||||
|
|
|
@ -61,24 +61,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
.sidebar-drag {
|
||||||
|
background-color: rgba($color: #000000, $alpha: 0.01);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-drag {
|
.sidebar-drag {
|
||||||
$width: 10px;
|
$width: 14px;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: $width;
|
width: $width;
|
||||||
background-color: var(--black);
|
background-color: rgba($color: #000000, $alpha: 0);
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
opacity: 0;
|
|
||||||
transition: all ease 0.3s;
|
transition: all ease 0.3s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
&:hover,
|
svg {
|
||||||
&:active {
|
opacity: 0;
|
||||||
opacity: 0.2;
|
margin-left: -2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
require("../polyfill");
|
// require("../polyfill");
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import CloseIcon from "../icons/close.svg";
|
||||||
import DeleteIcon from "../icons/delete.svg";
|
import DeleteIcon from "../icons/delete.svg";
|
||||||
import EyeIcon from "../icons/eye.svg";
|
import EyeIcon from "../icons/eye.svg";
|
||||||
import CopyIcon from "../icons/copy.svg";
|
import CopyIcon from "../icons/copy.svg";
|
||||||
|
import DragIcon from "../icons/drag.svg";
|
||||||
|
|
||||||
import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
|
import { DEFAULT_MASK_AVATAR, Mask, useMaskStore } from "../store/mask";
|
||||||
import {
|
import {
|
||||||
|
@ -42,6 +43,20 @@ import { ModelConfigList } from "./model-config";
|
||||||
import { FileName, Path } from "../constant";
|
import { FileName, Path } from "../constant";
|
||||||
import { BUILTIN_MASK_STORE } from "../masks";
|
import { BUILTIN_MASK_STORE } from "../masks";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import {
|
||||||
|
DragDropContext,
|
||||||
|
Droppable,
|
||||||
|
Draggable,
|
||||||
|
OnDragEndResponder,
|
||||||
|
} from "@hello-pangea/dnd";
|
||||||
|
|
||||||
|
// drag and drop helper function
|
||||||
|
function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
|
||||||
|
const result = [...list];
|
||||||
|
const [removed] = result.splice(startIndex, 1);
|
||||||
|
result.splice(endIndex, 0, removed);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
export function MaskAvatar(props: { mask: Mask }) {
|
export function MaskAvatar(props: { mask: Mask }) {
|
||||||
return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
|
return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
|
||||||
|
@ -192,6 +207,7 @@ export function MaskConfig(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ContextPromptItem(props: {
|
function ContextPromptItem(props: {
|
||||||
|
index: number;
|
||||||
prompt: ChatMessage;
|
prompt: ChatMessage;
|
||||||
update: (prompt: ChatMessage) => void;
|
update: (prompt: ChatMessage) => void;
|
||||||
remove: () => void;
|
remove: () => void;
|
||||||
|
@ -199,53 +215,67 @@ function ContextPromptItem(props: {
|
||||||
const [focusingInput, setFocusingInput] = useState(false);
|
const [focusingInput, setFocusingInput] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={chatStyle["context-prompt-row"]}>
|
<Draggable draggableId={props.prompt.id.toString()} index={props.index}>
|
||||||
{!focusingInput && (
|
{(provided) => (
|
||||||
<Select
|
<div
|
||||||
value={props.prompt.role}
|
className={chatStyle["context-prompt-row"]}
|
||||||
className={chatStyle["context-role"]}
|
ref={provided.innerRef}
|
||||||
onChange={(e) =>
|
{...provided.draggableProps}
|
||||||
props.update({
|
{...provided.dragHandleProps}
|
||||||
...props.prompt,
|
|
||||||
role: e.target.value as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{ROLES.map((r) => (
|
{!focusingInput && (
|
||||||
<option key={r} value={r}>
|
<>
|
||||||
{r}
|
<div className={chatStyle["context-drag"]}>
|
||||||
</option>
|
<DragIcon />
|
||||||
))}
|
</div>
|
||||||
</Select>
|
<Select
|
||||||
|
value={props.prompt.role}
|
||||||
|
className={chatStyle["context-role"]}
|
||||||
|
onChange={(e) =>
|
||||||
|
props.update({
|
||||||
|
...props.prompt,
|
||||||
|
role: e.target.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{ROLES.map((r) => (
|
||||||
|
<option key={r} value={r}>
|
||||||
|
{r}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
value={props.prompt.content}
|
||||||
|
type="text"
|
||||||
|
className={chatStyle["context-content"]}
|
||||||
|
rows={focusingInput ? 5 : 1}
|
||||||
|
onFocus={() => setFocusingInput(true)}
|
||||||
|
onBlur={() => {
|
||||||
|
setFocusingInput(false);
|
||||||
|
// If the selection is not removed when the user loses focus, some
|
||||||
|
// extensions like "Translate" will always display a floating bar
|
||||||
|
window?.getSelection()?.removeAllRanges();
|
||||||
|
}}
|
||||||
|
onInput={(e) =>
|
||||||
|
props.update({
|
||||||
|
...props.prompt,
|
||||||
|
content: e.currentTarget.value as any,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{!focusingInput && (
|
||||||
|
<IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
className={chatStyle["context-delete-button"]}
|
||||||
|
onClick={() => props.remove()}
|
||||||
|
bordered
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
<Input
|
</Draggable>
|
||||||
value={props.prompt.content}
|
|
||||||
type="text"
|
|
||||||
className={chatStyle["context-content"]}
|
|
||||||
rows={focusingInput ? 5 : 1}
|
|
||||||
onFocus={() => setFocusingInput(true)}
|
|
||||||
onBlur={() => {
|
|
||||||
setFocusingInput(false);
|
|
||||||
// If the selection is not removed when the user loses focus, some
|
|
||||||
// extensions like "Translate" will always display a floating bar
|
|
||||||
window?.getSelection()?.removeAllRanges();
|
|
||||||
}}
|
|
||||||
onInput={(e) =>
|
|
||||||
props.update({
|
|
||||||
...props.prompt,
|
|
||||||
content: e.currentTarget.value as any,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{!focusingInput && (
|
|
||||||
<IconButton
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
className={chatStyle["context-delete-button"]}
|
|
||||||
onClick={() => props.remove()}
|
|
||||||
bordered
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,17 +297,41 @@ export function ContextPrompts(props: {
|
||||||
props.updateContext((context) => (context[i] = prompt));
|
props.updateContext((context) => (context[i] = prompt));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd: OnDragEndResponder = (result) => {
|
||||||
|
if (!result.destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const newContext = reorder(
|
||||||
|
context,
|
||||||
|
result.source.index,
|
||||||
|
result.destination.index,
|
||||||
|
);
|
||||||
|
props.updateContext((context) => {
|
||||||
|
context.splice(0, context.length, ...newContext);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
|
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
|
||||||
{context.map((c, i) => (
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<ContextPromptItem
|
<Droppable droppableId="context-prompt-list">
|
||||||
key={i}
|
{(provided) => (
|
||||||
prompt={c}
|
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||||
update={(prompt) => updateContextPrompt(i, prompt)}
|
{context.map((c, i) => (
|
||||||
remove={() => removeContextPrompt(i)}
|
<ContextPromptItem
|
||||||
/>
|
index={i}
|
||||||
))}
|
key={c.id}
|
||||||
|
prompt={c}
|
||||||
|
update={(prompt) => updateContextPrompt(i, prompt)}
|
||||||
|
remove={() => removeContextPrompt(i)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{provided.placeholder}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Droppable>
|
||||||
|
</DragDropContext>
|
||||||
|
|
||||||
<div className={chatStyle["context-prompt-row"]}>
|
<div className={chatStyle["context-prompt-row"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
|
|
@ -10,6 +10,7 @@ import AddIcon from "../icons/add.svg";
|
||||||
import CloseIcon from "../icons/close.svg";
|
import CloseIcon from "../icons/close.svg";
|
||||||
import MaskIcon from "../icons/mask.svg";
|
import MaskIcon from "../icons/mask.svg";
|
||||||
import PluginIcon from "../icons/plugin.svg";
|
import PluginIcon from "../icons/plugin.svg";
|
||||||
|
import DragIcon from "../icons/drag.svg";
|
||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
|
||||||
|
@ -198,7 +199,9 @@ export function SideBar(props: { className?: string }) {
|
||||||
<div
|
<div
|
||||||
className={styles["sidebar-drag"]}
|
className={styles["sidebar-drag"]}
|
||||||
onMouseDown={(e) => onDragMouseDown(e as any)}
|
onMouseDown={(e) => onDragMouseDown(e as any)}
|
||||||
></div>
|
>
|
||||||
|
<DragIcon />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><g opacity="1" transform="translate(0 0) rotate(0)"><mask id="bg-mask-0" fill="white"><use transform="translate(0 0) rotate(0)" xlink:href="#path_0"></use></mask><g mask="url(#bg-mask-0)" ><path id="路径 1" fill-rule="evenodd" style="fill:#333333" opacity="1" d="M6.33663,3.33c0,0.74 -0.6,1.34 -1.34,1.34c-0.73,0 -1.33,-0.6 -1.33,-1.34c0,-0.73 0.6,-1.33 1.33,-1.33c0.74,0 1.34,0.6 1.34,1.33zM4.99663,9.33c-0.73,0 -1.33,-0.59 -1.33,-1.33c0,-0.74 0.6,-1.33 1.33,-1.33c0.74,0 1.34,0.59 1.34,1.33c0,0.74 -0.6,1.33 -1.34,1.33zM4.99663,14c-0.73,0 -1.33,-0.6 -1.33,-1.33c0,-0.74 0.6,-1.34 1.33,-1.34c0.74,0 1.34,0.6 1.34,1.34c0,0.73 -0.6,1.33 -1.34,1.33z"></path><path id="路径 2" fill-rule="evenodd" style="fill:#333333" opacity="1" d="M12.3366,3.33c0,0.74 -0.6,1.34 -1.34,1.34c-0.73,0 -1.32997,-0.6 -1.32997,-1.34c0,-0.73 0.59997,-1.33 1.32997,-1.33c0.74,0 1.34,0.6 1.34,1.33zM10.9966,9.33c-0.73,0 -1.32997,-0.59 -1.32997,-1.33c0,-0.74 0.59997,-1.33 1.32997,-1.33c0.74,0 1.34,0.59 1.34,1.33c0,0.74 -0.6,1.33 -1.34,1.33zM10.9966,14c-0.73,0 -1.32997,-0.6 -1.32997,-1.33c0,-0.74 0.59997,-1.34 1.32997,-1.34c0.74,0 1.34,0.6 1.34,1.34c0,0.73 -0.6,1.33 -1.34,1.33z"></path></g></g><defs><rect id="path_0" x="0" y="0" width="16" height="16" /></defs></svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -622,7 +622,7 @@ Reply in ${getLang()} and markdown.`;
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: StoreKey.Chat,
|
name: StoreKey.Chat,
|
||||||
version: 3,
|
version: 3.1,
|
||||||
migrate(persistedState, version) {
|
migrate(persistedState, version) {
|
||||||
const state = persistedState as any;
|
const state = persistedState as any;
|
||||||
const newState = JSON.parse(JSON.stringify(state)) as ChatStore;
|
const newState = JSON.parse(JSON.stringify(state)) as ChatStore;
|
||||||
|
@ -650,6 +650,23 @@ Reply in ${getLang()} and markdown.`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enable `enableInjectSystemPrompts` attribute for old sessions.
|
||||||
|
// Resolve issue of old sessions not automatically enabling.
|
||||||
|
if (version < 3.1) {
|
||||||
|
newState.sessions.forEach((s) => {
|
||||||
|
if (
|
||||||
|
// Exclude those already set by user
|
||||||
|
!s.mask.modelConfig.hasOwnProperty("enableInjectSystemPrompts")
|
||||||
|
) {
|
||||||
|
// Because users may have changed this configuration,
|
||||||
|
// the user's current configuration is used instead of the default
|
||||||
|
const config = useAppConfig.getState();
|
||||||
|
s.mask.modelConfig.enableInjectSystemPrompts =
|
||||||
|
config.modelConfig.enableInjectSystemPrompts;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -30,6 +30,9 @@ const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
unoptimized: mode === "export",
|
unoptimized: mode === "export",
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
forceSwcTransforms: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mode !== "export") {
|
if (mode !== "export") {
|
||||||
|
|
Loading…
Reference in New Issue