This commit is contained in:
GH Action - Upstream Sync 2023-06-28 00:45:50 +00:00
commit de93c01527
5 changed files with 208 additions and 30 deletions

View File

@ -281,15 +281,13 @@
border: var(--border-in-light);
position: relative;
transition: all ease 0.3s;
min-width: 0;
&:hover {
min-width: 330px;
.chat-message-actions {
height: 40px;
opacity: 1;
transform: translateY(0px);
max-width: 100%;
height: 40px;
.chat-message-action-date {
opacity: 0.3;
@ -299,7 +297,6 @@
.chat-message-actions {
display: flex;
width: 100%;
box-sizing: border-box;
font-size: 12px;
align-items: flex-end;
@ -308,11 +305,21 @@
transform: translateY(10px);
opacity: 0;
height: 0;
max-width: 0;
.chat-input-actions {
display: flex;
flex-wrap: nowrap;
}
}
.chat-message-action-date {
white-space: nowrap;
transition: all ease 0.6s;
color: var(--black);
opacity: 0;
text-align: right;
margin-left: 20px;
}
}

View File

@ -294,8 +294,8 @@ function ChatAction(props: {
const iconRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLDivElement>(null);
const [width, setWidth] = useState({
full: 20,
icon: 20,
full: 16,
icon: 16,
});
function updateWidth() {
@ -309,10 +309,6 @@ function ChatAction(props: {
});
}
useEffect(() => {
updateWidth();
}, []);
return (
<div
className={`${styles["chat-input-action"]} clickable`}
@ -320,6 +316,8 @@ function ChatAction(props: {
props.onClick();
setTimeout(updateWidth, 1);
}}
onMouseEnter={updateWidth}
onTouchStart={updateWidth}
style={
{
"--icon-width": `${width.icon}px`,
@ -515,14 +513,6 @@ export function Chat() {
{ leading: true, trailing: true },
);
const onPromptSelect = (prompt: Prompt) => {
setTimeout(() => {
setPromptHints([]);
setUserInput(prompt.content);
inputRef.current?.focus();
}, 30);
};
// auto grow input
const [inputRows, setInputRows] = useState(2);
const measure = useDebouncedCallback(
@ -595,6 +585,23 @@ export function Chat() {
setAutoScroll(true);
};
const onPromptSelect = (prompt: Prompt) => {
setTimeout(() => {
setPromptHints([]);
const matchedChatCommand = chatCommands.match(prompt.content);
if (matchedChatCommand.matched) {
// if user is selecting a chat command, just trigger it
matchedChatCommand.invoke();
setUserInput("");
} else {
// or fill the prompt
setUserInput(prompt.content);
}
inputRef.current?.focus();
}, 30);
};
// stop response
const onUserStop = (messageId: number) => {
ChatControllerPool.stop(sessionIndex, messageId);
@ -937,30 +944,30 @@ export function Chat() {
/>
) : (
<>
<ChatAction
text={Locale.Chat.Actions.Delete}
icon={<DeleteIcon />}
onClick={() => onDelete(message.id ?? i)}
/>
<ChatAction
text={Locale.Chat.Actions.Retry}
icon={<ResetIcon />}
onClick={() => onResend(message.id ?? i)}
/>
<ChatAction
text={Locale.Chat.Actions.Delete}
icon={<DeleteIcon />}
onClick={() => onDelete(message.id ?? i)}
/>
<ChatAction
text={Locale.Chat.Actions.Pin}
icon={<PinIcon />}
onClick={() => onPinMessage(message)}
/>
<ChatAction
text={Locale.Chat.Actions.Copy}
icon={<CopyIcon />}
onClick={() => copyToClipboard(message.content)}
/>
</>
)}
<ChatAction
text={Locale.Chat.Actions.Copy}
icon={<CopyIcon />}
onClick={() => copyToClipboard(message.content)}
/>
</div>
<div className={styles["chat-message-action-date"]}>

View File

@ -46,6 +46,7 @@ import { InputRange } from "./input-range";
import { useNavigate } from "react-router-dom";
import { Avatar, AvatarPicker } from "./emoji";
import { getClientConfig } from "../config/client";
import { useSyncStore } from "../store/sync";
function EditPromptModal(props: { id: number; onClose: () => void }) {
const promptStore = usePromptStore();
@ -198,6 +199,78 @@ function UserPromptModal(props: { onClose?: () => void }) {
);
}
function SyncItems() {
const syncStore = useSyncStore();
const webdav = syncStore.webDavConfig;
// not ready: https://github.com/Yidadaa/ChatGPT-Next-Web/issues/920#issuecomment-1609866332
return null;
return (
<List>
<ListItem
title={"上次同步:" + new Date().toLocaleString()}
subTitle={"20 次对话100 条消息200 提示词20 面具"}
>
<IconButton
icon={<ResetIcon />}
text="同步"
onClick={() => {
syncStore.check().then(console.log);
}}
/>
</ListItem>
<ListItem
title={"本地备份"}
subTitle={"20 次对话100 条消息200 提示词20 面具"}
></ListItem>
<ListItem
title={"Web Dav Server"}
subTitle={Locale.Settings.AccessCode.SubTitle}
>
<input
value={webdav.server}
type="text"
placeholder={"https://example.com"}
onChange={(e) => {
syncStore.update(
(config) => (config.server = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem title="Web Dav User Name" subTitle="user name here">
<input
value={webdav.username}
type="text"
placeholder={"username"}
onChange={(e) => {
syncStore.update(
(config) => (config.username = e.currentTarget.value),
);
}}
/>
</ListItem>
<ListItem title="Web Dav Password" subTitle="password here">
<input
value={webdav.password}
type="text"
placeholder={"password"}
onChange={(e) => {
syncStore.update(
(config) => (config.password = e.currentTarget.value),
);
}}
/>
</ListItem>
</List>
);
}
function formatVersionDate(t: string) {
const d = new Date(+t);
const year = d.getUTCFullYear();
@ -556,6 +629,7 @@ export function Settings() {
<input
type="text"
value={accessStore.openaiUrl}
placeholder="https://api.openai.com/"
onChange={(e) =>
accessStore.updateOpenAiUrl(e.currentTarget.value)
}
@ -596,6 +670,8 @@ export function Settings() {
</ListItem>
</List>
<SyncItems />
<List>
<ModelConfigList
modelConfig={config.modelConfig}

View File

@ -33,6 +33,7 @@ export enum StoreKey {
Mask = "mask-store",
Prompt = "prompt-store",
Update = "chat-update",
Sync = "sync",
}
export const MAX_SIDEBAR_WIDTH = 500;

87
app/store/sync.ts Normal file
View File

@ -0,0 +1,87 @@
import { Updater } from "../typing";
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { StoreKey } from "../constant";
export interface WebDavConfig {
server: string;
username: string;
password: string;
}
export interface SyncStore {
webDavConfig: WebDavConfig;
lastSyncTime: number;
update: Updater<WebDavConfig>;
check: () => Promise<boolean>;
path: (path: string) => string;
headers: () => { Authorization: string };
}
const FILE = {
root: "/chatgpt-next-web/",
};
export const useSyncStore = create<SyncStore>()(
persist(
(set, get) => ({
webDavConfig: {
server: "",
username: "",
password: "",
},
lastSyncTime: 0,
update(updater) {
const config = { ...get().webDavConfig };
updater(config);
set({ webDavConfig: config });
},
async check() {
try {
const res = await fetch(this.path(""), {
method: "PROFIND",
headers: this.headers(),
});
console.log(res);
return res.status === 207;
} catch (e) {
console.error("[Sync] ", e);
return false;
}
},
path(path: string) {
let url = get().webDavConfig.server;
if (!url.endsWith("/")) {
url += "/";
}
if (path.startsWith("/")) {
path = path.slice(1);
}
return url + path;
},
headers() {
const auth = btoa(
[get().webDavConfig.username, get().webDavConfig.password].join(":"),
);
return {
Authorization: `Basic ${auth}`,
};
},
}),
{
name: StoreKey.Sync,
version: 1,
},
),
);