diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 81dadd8d..32cab346 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -515,14 +515,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 +587,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); diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 41fed620..1d45a0c6 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -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 ( + + + } + text="同步" + onClick={() => { + syncStore.check().then(console.log); + }} + /> + + + + + + { + syncStore.update( + (config) => (config.server = e.currentTarget.value), + ); + }} + /> + + + + { + syncStore.update( + (config) => (config.username = e.currentTarget.value), + ); + }} + /> + + + + { + syncStore.update( + (config) => (config.password = e.currentTarget.value), + ); + }} + /> + + + ); +} + function formatVersionDate(t: string) { const d = new Date(+t); const year = d.getUTCFullYear(); @@ -556,6 +629,7 @@ export function Settings() { accessStore.updateOpenAiUrl(e.currentTarget.value) } @@ -596,6 +670,8 @@ export function Settings() { + + ; + check: () => Promise; + + path: (path: string) => string; + headers: () => { Authorization: string }; +} + +const FILE = { + root: "/chatgpt-next-web/", +}; + +export const useSyncStore = create()( + 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, + }, + ), +);