diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index c3e7c619b..b908f2205 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -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; } } diff --git a/app/components/chat.tsx b/app/components/chat.tsx index ad38dd650..6db5eb2b6 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -294,8 +294,8 @@ function ChatAction(props: { const iconRef = useRef(null); const textRef = useRef(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 (
{ - 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() { /> ) : ( <> - } - onClick={() => onDelete(message.id ?? i)} - /> - } onClick={() => onResend(message.id ?? i)} /> + } + onClick={() => onDelete(message.id ?? i)} + /> + } onClick={() => onPinMessage(message)} /> + } + onClick={() => copyToClipboard(message.content)} + /> )} - } - onClick={() => copyToClipboard(message.content)} - />
diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 41fed620c..1d45a0c69 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, + }, + ), +);