diff --git a/client/src/plus/ai-help/index.scss b/client/src/plus/ai-help/index.scss index fadb3fc43ec3..359e721f8233 100644 --- a/client/src/plus/ai-help/index.scss +++ b/client/src/plus/ai-help/index.scss @@ -356,6 +356,23 @@ &.role-user { white-space: pre-wrap; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + + .ai-help-message-nav { + display: flex; + justify-content: space-between; + > span { + width: 4rem; + text-align: center; + } + } + .ai-help-user-message { + width: 100%; + padding: 0 1rem; + } } p { diff --git a/client/src/plus/ai-help/index.tsx b/client/src/plus/ai-help/index.tsx index d520ba353810..16b044f5ff57 100644 --- a/client/src/plus/ai-help/index.tsx +++ b/client/src/plus/ai-help/index.tsx @@ -43,6 +43,7 @@ import React from "react"; import { SESSION_KEY } from "../../playground/utils"; import { PlayQueue } from "../../playground/queue"; import useSWR, { KeyedMutator } from "swr"; +import { AIHelpHistory } from "./history"; type Category = "apis" | "css" | "html" | "http" | "js" | "learn"; @@ -171,217 +172,107 @@ export default function AiHelp() { ); } -function monthYearLabel(date: Date): string { - const formattedDate = date.toLocaleString(undefined, { - month: "short", - year: "numeric", - }); - return formattedDate; -} - -interface HistoryEntry { - chat_id: string; - label: string; - last: string; -} -interface HistoryEntries { - label: string; - entries: HistoryEntry[]; -} - -function groupHistory(history) { - const now = new Date(); - const today = new Date(now.toDateString()); - const yesterday = new Date( - structuredClone(today).setDate(today.getDate() - 1) - ); - const last30Days = new Date( - structuredClone(today).setDate(today.getDate() - 30) - ); - const groups = [ - { label: "Last 30 Days", d: last30Days }, - { label: "Yesterday", d: yesterday }, - { label: "Today", d: today }, - ]; - const grouped: HistoryEntries[] = []; - - let { label = "unknown", d } = groups.pop() || {}; - let current: HistoryEntries = { label, entries: [] }; - for (const entry of history) { - let last = new Date(entry.last); - while (!d || last < d) { - if (!d) { - label = monthYearLabel(last); - break; - } else if (last < d) { - ({ label = "unknown", d } = groups.pop() || {}); - continue; - } - break; - } - if (current.label !== label) { - grouped.push(current); - current = { label, entries: [entry] }; - } else { - current.entries.push(entry); - } - } - - if (current.entries.length) { - grouped.push(current); - } - return grouped; -} - -function AIHelpHistorySubList({ - currentChatId, - entries, - mutate, -}: { - currentChatId?: string; - entries: HistoryEntries; - mutate: KeyedMutator; -}) { - return ( - <> - -
    - {entries.entries.map(({ chat_id, last, label }, index) => { - return ( -
  1. - - {label} - - {chat_id === currentChatId && ( -
  2. - ); - })} -
- - ); -} - -export function AIHelpHistory({ - currentChatId, - lastUpdate, -}: { - currentChatId?: string; - lastUpdate: Date; -}) { - const { data, mutate } = useSWR( - `/api/v1/plus/ai/help/history/list`, - async (url) => { - const res = await (await fetch(url)).json(); - return Array.isArray(res) ? res : []; - }, - { - fallbackData: [], - } - ); - - useEffect(() => { - mutate(); - }, [lastUpdate, mutate]); - - return ( - - ); -} - function AIHelpUserQuestion({ message, submit, nextPrev, siblingCount }) { const [editing, setEditing] = useState(false); const [question, setQuestion] = useState(message.content); + const inputRef = useRef(null); const { pos, total } = siblingCount(message.messageId); + + useEffect(() => { + setQuestion(message.content); + }, [message.content]); + return editing ? ( -
- { + event.preventDefault(); + setEditing(false); + submit(question, message.chatId, message.parentId, message.messageId); + }} + > + { + if (event.key === "Enter" && !event.shiftKey) { + setEditing(false); + submit( + question, + message.chatId, + message.parentId, + message.messageId + ); + } + }} onChange={(e) => setQuestion(e.target.value)} - > + value={question} + rows={1} + /> + {question ? ( + + ) : null} -
+ ) : ( - <> +
{total > 1 && ( - <> + )} -
{message.content}
+
{message.content}
); } @@ -495,226 +386,211 @@ export function AIHelpInner() {
-
- {message.role === "user" ? ( - - ) : ( -
- {message.content ? ( - { - if (isExternalUrl(props.href ?? "")) { - props = { - ...props, - className: "external", - rel: "noopener noreferrer", - target: "_blank", - }; - } - // eslint-disable-next-line jsx-a11y/anchor-has-content - return ; - }, - pre: ({ - node, - className, - children, - ...props - }) => { - const code = Children.toArray(children) - .map( - (child) => - /language-(\w+)/.exec( - (child as ReactElement)?.props - ?.className || "" - )?.[1] - ) - .find(Boolean); - - if (!code) { - return ( -
-                                            {children}
-                                          
- ); - } - sample += 1; + {message.role === "user" ? ( + + ) : ( +
+ {message.content ? ( + { + if (isExternalUrl(props.href ?? "")) { + props = { + ...props, + className: "external", + rel: "noopener noreferrer", + target: "_blank", + }; + } + // eslint-disable-next-line jsx-a11y/anchor-has-content + return ; + }, + pre: ({ + node, + className, + children, + ...props + }) => { + const code = Children.toArray(children) + .map( + (child) => + /language-(\w+)/.exec( + (child as ReactElement)?.props + ?.className || "" + )?.[1] + ) + .find(Boolean); + + if (!code) { return ( -
-
- - {code} - - {message.status === - MessageStatus.Complete && ( -
- { - e.target.dataset.queued = `${e.target.checked} `; - }} - id={`sample-${index}-${sample}`} - /> - - -
- )} -
-
-                                            {children}
-                                          
-
- ); - }, - code: ({ - inline, - className, - children, - ...props - }) => { - const match = /language-(\w+)/.exec( - className || "" - ); - const lang = Prism.languages[match?.[1]]; - return !inline && lang ? ( - - ) : ( - +
                                           {children}
-                                        
+                                        
); - }, - }} - > - {message.content?.replace( - SORRY_BACKEND, - SORRY_FRONTEND - )} - - ) : ( - "Retrieving answer…" - )} - {message.status === "complete" && - !message.content?.includes(SORRY_BACKEND) && ( - <> - {message.sources && - message.sources.length > 0 && ( - <> -

- MDN content that I've consulted that - you might want to check: -

-
- - )} -
- { - user?.experiments?.active && - message.messageId && - (await sendFeedback( - message.messageId, - value - )); + } + sample += 1; + return ( +
+
+ + {code} + + {message.status === + MessageStatus.Complete && ( +
+ { + e.target.dataset.queued = `${e.target.checked} `; + }} + id={`sample-${index}-${sample}`} + /> + + +
+ )} +
+
+                                          {children}
+                                        
+
+ ); + }, + code: ({ + inline, + className, + children, + ...props + }) => { + const match = /language-(\w+)/.exec( + className || "" + ); + const lang = Prism.languages[match?.[1]]; + return !inline && lang ? ( + - - Report an issue with this answer on - GitHub - -
- + ) : ( + + {children} + + ); + }, + }} + > + {message.content?.replace( + SORRY_BACKEND, + SORRY_FRONTEND )} -
- )} -
+ + ) : ( + "Retrieving answer…" + )} + {message.status === "complete" && + !message.content?.includes(SORRY_BACKEND) && ( + <> + {message.sources && + message.sources.length > 0 && ( + <> +

+ MDN content that I've consulted that + you might want to check: +

+
    + {message.sources.map( + ({ url, title }, index) => ( +
  • + {title} +
  • + ) + )} +
+ + )} +
+ { + user?.experiments?.active && + message.messageId && + (await sendFeedback( + message.messageId, + value + )); + }} + /> + + Report an issue with this answer on GitHub + +
+ + )} +
+ )} ); })} diff --git a/client/src/plus/ai-help/use-ai.ts b/client/src/plus/ai-help/use-ai.ts index d233d0db62df..a65b42371b80 100644 --- a/client/src/plus/ai-help/use-ai.ts +++ b/client/src/plus/ai-help/use-ai.ts @@ -575,6 +575,7 @@ export function useAiChat({ content: messageTemplate(query), }); + return; const eventSource = new SSE(`/api/v1/plus/ai/help`, { headers: { "Content-Type": "application/json", diff --git a/client/src/plus/index.tsx b/client/src/plus/index.tsx index 80eb4944a60a..57b9dfcbf9cf 100644 --- a/client/src/plus/index.tsx +++ b/client/src/plus/index.tsx @@ -72,7 +72,7 @@ export function Plus({ pageTitle, ...props }: { pageTitle?: string }) { } /> diff --git a/client/src/ui/molecules/main-menu/index.tsx b/client/src/ui/molecules/main-menu/index.tsx index a0e44062a0e3..66ad1210d227 100644 --- a/client/src/ui/molecules/main-menu/index.tsx +++ b/client/src/ui/molecules/main-menu/index.tsx @@ -90,7 +90,7 @@ export default function MainMenu({ isOpenOnMobile }) {
  • - + AI Help Beta