diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index e83c5f4949..9a236c3ea9 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -310,9 +310,9 @@ export const PromptInput: Component = (props) => { }) const hasUserPrompt = createMemo(() => { - const id = params.id - if (!id) return false - const messages = sync.data.message[id] + const sessionID = params.id + if (!sessionID) return false + const messages = sync.data.message[sessionID] if (!messages) return false return messages.some((m) => m.role === "user") }) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 47d34c9881..024df1b3b0 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1620,7 +1620,6 @@ export default function Page() { const { clearMessageHash, scrollToMessage } = useSessionHashScroll({ sessionKey, - sessionID: () => params.id, messagesReady, visibleUserMessages, turnStart: historyWindow.turnStart, @@ -1694,48 +1693,51 @@ export default function Page() {
- - { - content = el - autoScroll.contentRef(el) + {(sessionID) => ( + + { + content = el + autoScroll.contentRef(el) - const root = scroller - if (root) scheduleScrollState(root) - }} - turnStart={historyWindow.turnStart()} - historyMore={historyMore()} - historyLoading={historyLoading()} - onLoadEarlier={() => { - void historyWindow.loadAndReveal() - }} - renderedUserMessages={historyWindow.renderedUserMessages()} - anchor={anchor} - /> - + const root = scroller + if (root) scheduleScrollState(root) + }} + turnStart={historyWindow.turnStart()} + historyMore={historyMore()} + historyLoading={historyLoading()} + onLoadEarlier={() => { + void historyWindow.loadAndReveal() + }} + renderedUserMessages={historyWindow.renderedUserMessages()} + anchor={anchor} + /> + + )} diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index ad874254e4..03558412ef 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -29,7 +29,6 @@ import { normalizeWheelDelta, shouldMarkBoundaryGesture } from "@/pages/session/ import { useSessionKey } from "@/pages/session/session-layout" import { messageAgentColor } from "@/utils/agent" import { parseCommentNote, readCommentMetadata } from "@/utils/comment-note" -import { Optional } from "@/utils/optional" type MessageComment = { path: string @@ -196,6 +195,7 @@ function createTimelineStaging(input: TimelineStageInput) { } export function MessageTimeline(props: { + sessionID: string mobileChanges: boolean mobileFallback: JSX.Element actions?: UserActions @@ -227,17 +227,17 @@ export function MessageTimeline(props: { const settings = useSettings() const dialog = useDialog() const language = useLanguage() - const { params, sessionKey } = useSessionKey() + const { sessionKey } = useSessionKey() const platform = usePlatform() const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id)) - const sessionMessages = createMemo(() => Optional.map(params.id, (id) => sync.data.message[id]) ?? emptyMessages) + const sessionMessages = createMemo(() => sync.data.message[props.sessionID] ?? emptyMessages) const pending = createMemo(() => sessionMessages().findLast( (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number", ), ) - const sessionStatus = createMemo(() => Optional.map(params.id, (id) => sync.data.session_status[id]) ?? idle) + const sessionStatus = createMemo(() => sync.data.session_status[props.sessionID] ?? idle) const working = createMemo(() => !!pending() || sessionStatus().type !== "idle") const tint = createMemo(() => messageAgentColor(sessionMessages(), sync.data.agent)) @@ -292,7 +292,7 @@ export function MessageTimeline(props: { return undefined }) - const info = createMemo(() => Optional.map(params.id, (id) => sync.session.get(id))) + const info = createMemo(() => sync.session.get(props.sessionID)) const titleValue = createMemo(() => info()?.title) const shareUrl = createMemo(() => info()?.share?.url) const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") @@ -326,12 +326,11 @@ export function MessageTimeline(props: { const [req, setReq] = createStore({ share: false, unshare: false }) const shareSession = () => { - const id = params.id - if (!id || req.share) return + if (req.share) return if (!shareEnabled()) return setReq("share", true) globalSDK.client.session - .share({ sessionID: id, directory: sdk.directory }) + .share({ sessionID: props.sessionID, directory: sdk.directory }) .catch((err: unknown) => { console.error("Failed to share session", err) }) @@ -341,12 +340,11 @@ export function MessageTimeline(props: { } const unshareSession = () => { - const id = params.id - if (!id || req.unshare) return + if (req.unshare) return if (!shareEnabled()) return setReq("unshare", true) globalSDK.client.session - .unshare({ sessionID: id, directory: sdk.directory }) + .unshare({ sessionID: props.sessionID, directory: sdk.directory }) .catch((err: unknown) => { console.error("Failed to unshare session", err) }) @@ -387,7 +385,6 @@ export function MessageTimeline(props: { ) const openTitleEditor = () => { - if (!params.id) return setTitle({ editing: true, draft: titleValue() ?? "" }) requestAnimationFrame(() => { titleRef?.focus() @@ -401,8 +398,6 @@ export function MessageTimeline(props: { } const saveTitleEditor = async () => { - const id = params.id - if (!id) return if (title.saving) return const next = title.draft.trim() @@ -413,11 +408,11 @@ export function MessageTimeline(props: { setTitle("saving", true) await sdk.client.session - .update({ sessionID: id, title: next }) + .update({ sessionID: props.sessionID, title: next }) .then(() => { sync.set( produce((draft) => { - const index = draft.session.findIndex((s) => s.id === id) + const index = draft.session.findIndex((s) => s.id === props.sessionID) if (index !== -1) draft.session[index].title = next }), ) @@ -433,7 +428,7 @@ export function MessageTimeline(props: { } const navigateAfterSessionRemoval = (id: string, parentID?: string, nextSessionID?: string) => { - if (params.id !== id) return + if (props.sessionID !== id) return if (parentID) { navigate(`../${parentID}`) return @@ -722,184 +717,180 @@ export function MessageTimeline(props: {
- - {(id) => ( -
- - { - setTitle("menuOpen", open) - if (open) return +
+ + { + setTitle("menuOpen", open) + if (open) return + }} + > + { + more = el + }} + /> + + { + if (title.pendingRename) { + event.preventDefault() + setTitle("pendingRename", false) + openTitleEditor() + return + } + if (title.pendingShare) { + event.preventDefault() + requestAnimationFrame(() => { + setShare({ open: true, dismiss: null }) + setTitle("pendingShare", false) + }) + } }} > - { + setTitle("pendingRename", true) + setTitle("menuOpen", false) }} - aria-label={language.t("common.moreOptions")} - aria-expanded={title.menuOpen || share.open || title.pendingShare} - ref={(el: HTMLButtonElement) => { - more = el - }} - /> - - { - if (title.pendingRename) { - event.preventDefault() - setTitle("pendingRename", false) - openTitleEditor() - return - } - if (title.pendingShare) { - event.preventDefault() - requestAnimationFrame(() => { - setShare({ open: true, dismiss: null }) - setTitle("pendingShare", false) - }) - } + > + {language.t("common.rename")} + + + { + setTitle({ pendingShare: true, menuOpen: false }) }} > - { - setTitle("pendingRename", true) - setTitle("menuOpen", false) - }} - > - {language.t("common.rename")} - - - { - setTitle({ pendingShare: true, menuOpen: false }) - }} - > - - {language.t("session.share.action.share")} - - - - void archiveSession(id())}> - {language.t("common.archive")} - - - dialog.show(() => )} - > - {language.t("common.delete")} - - - - + + {language.t("session.share.action.share")} + + + + void archiveSession(props.sessionID)}> + {language.t("common.archive")} + + + dialog.show(() => )} + > + {language.t("common.delete")} + + + + - more} - placement="bottom-end" - gutter={4} - modal={false} - onOpenChange={(open) => { - if (open) setShare("dismiss", null) - setShare("open", open) + more} + placement="bottom-end" + gutter={4} + modal={false} + onOpenChange={(open) => { + if (open) setShare("dismiss", null) + setShare("open", open) + }} + > + + { + setShare({ dismiss: "escape", open: false }) + event.preventDefault() + event.stopPropagation() + }} + onPointerDownOutside={() => { + setShare({ dismiss: "outside", open: false }) + }} + onFocusOutside={() => { + setShare({ dismiss: "outside", open: false }) + }} + onCloseAutoFocus={(event) => { + if (share.dismiss === "outside") event.preventDefault() + setShare("dismiss", null) }} > - - { - setShare({ dismiss: "escape", open: false }) - event.preventDefault() - event.stopPropagation() - }} - onPointerDownOutside={() => { - setShare({ dismiss: "outside", open: false }) - }} - onFocusOutside={() => { - setShare({ dismiss: "outside", open: false }) - }} - onCloseAutoFocus={(event) => { - if (share.dismiss === "outside") event.preventDefault() - setShare("dismiss", null) - }} - > -
-
-
- {language.t("session.share.popover.title")} -
-
- {shareUrl() - ? language.t("session.share.popover.description.shared") - : language.t("session.share.popover.description.unshared")} -
-
-
- - {req.share - ? language.t("session.share.action.publishing") - : language.t("session.share.action.publish")} - - } - > -
- -
- - -
-
-
-
+
+
+
+ {language.t("session.share.popover.title")}
- - - -
- )} - +
+ {shareUrl() + ? language.t("session.share.popover.description.shared") + : language.t("session.share.popover.description.unshared")} +
+
+
+ + {req.share + ? language.t("session.share.action.publishing") + : language.t("session.share.action.publish")} + + } + > +
+ +
+ + +
+
+
+
+
+
+
+
+
@@ -987,7 +978,7 @@ export function MessageTimeline(props: { { const permissionsCommand = withCategory(language.t("command.category.permissions")) const isAutoAcceptActive = () => { - const id = params.id - if (id) return permission.isAutoAccepting(id, sdk.directory) + const sessionID = params.id + if (sessionID) return permission.isAutoAccepting(sessionID, sdk.directory) return permission.isAutoAcceptingDirectory(sdk.directory) } command.register("session", () => { @@ -146,8 +146,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { slash: "share", disabled: !params.id, onSelect: async () => { - const id = params.id - if (!id) return + if (!params.id) return const write = (value: string) => { const body = typeof document === "undefined" ? undefined : document.body @@ -199,7 +198,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { } const url = await sdk.client.session - .share({ sessionID: id }) + .share({ sessionID: params.id }) .then((res) => res.data?.share?.url) .catch(() => undefined) if (!url) { @@ -391,12 +390,12 @@ export const useSessionCommands = (actions: SessionCommandContext) => { keybind: "mod+shift+a", disabled: false, onSelect: () => { - const id = params.id - if (id) permission.toggleAutoAccept(id, sdk.directory) + const sessionID = params.id + if (sessionID) permission.toggleAutoAccept(sessionID, sdk.directory) else permission.toggleAutoAcceptDirectory(sdk.directory) - const active = id - ? permission.isAutoAccepting(id, sdk.directory) + const active = sessionID + ? permission.isAutoAccepting(sessionID, sdk.directory) : permission.isAutoAcceptingDirectory(sdk.directory) showToast({ title: active @@ -415,16 +414,16 @@ export const useSessionCommands = (actions: SessionCommandContext) => { slash: "undo", disabled: !params.id || visibleUserMessages().length === 0, onSelect: async () => { - const id = params.id - if (!id) return + const sessionID = params.id + if (!sessionID) return if (status().type !== "idle") { - await sdk.client.session.abort({ sessionID: id }).catch(() => {}) + await sdk.client.session.abort({ sessionID }).catch(() => {}) } const revert = info()?.revert?.messageID const message = findLast(userMessages(), (x) => !revert || x.id < revert) if (!message) return await sdk.client.session.revert({ - sessionID: id, + sessionID, messageID: message.id, }) const parts = sync.data.part[message.id] @@ -445,20 +444,20 @@ export const useSessionCommands = (actions: SessionCommandContext) => { slash: "redo", disabled: !params.id || !info()?.revert?.messageID, onSelect: async () => { - const id = params.id - if (!id) return + const sessionID = params.id + if (!sessionID) return const revertMessageID = info()?.revert?.messageID if (!revertMessageID) return const nextMessage = userMessages().find((x) => x.id > revertMessageID) if (!nextMessage) { - await sdk.client.session.unrevert({ sessionID: id }) + await sdk.client.session.unrevert({ sessionID }) prompt.reset() const lastMsg = findLast(userMessages(), (x) => x.id >= revertMessageID) setActiveMessage(lastMsg) return } await sdk.client.session.revert({ - sessionID: id, + sessionID, messageID: nextMessage.id, }) const priorMsg = findLast(userMessages(), (x) => x.id < nextMessage.id) @@ -472,8 +471,8 @@ export const useSessionCommands = (actions: SessionCommandContext) => { slash: "compact", disabled: !params.id || visibleUserMessages().length === 0, onSelect: async () => { - const id = params.id - if (!id) return + const sessionID = params.id + if (!sessionID) return const model = local.model.current() if (!model) { showToast({ @@ -483,7 +482,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { return } await sdk.client.session.summarize({ - sessionID: id, + sessionID, modelID: model.id, providerID: model.provider.id, }) diff --git a/packages/app/src/pages/session/use-session-hash-scroll.ts b/packages/app/src/pages/session/use-session-hash-scroll.ts index d6ab273dce..e28bb1f294 100644 --- a/packages/app/src/pages/session/use-session-hash-scroll.ts +++ b/packages/app/src/pages/session/use-session-hash-scroll.ts @@ -6,7 +6,7 @@ import { useSessionLayout } from "./session-layout" export const useSessionHashScroll = (input: { sessionKey: () => string - sessionID: () => string | undefined + messagesReady: () => boolean visibleUserMessages: () => UserMessage[] turnStart: () => number