@saadnvd1/agent-os 0.3.768 → 0.3.775
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/api/sessions/[id]/artifacts/[artifactId]/compare/route.ts +41 -0
- package/app/api/sessions/resolve/route.test.ts +81 -0
- package/app/api/sessions/resolve/route.ts +14 -0
- package/app/api/shares/preview/route.ts +6 -1
- package/app/api/shares/route.test.ts +51 -0
- package/app/page.tsx +1 -0
- package/components/Artifacts/ArtifactActions.tsx +27 -3
- package/components/Artifacts/ArtifactPreview.tsx +27 -12
- package/components/Artifacts/Compare/DiffView.tsx +116 -0
- package/components/Artifacts/Compare/SideBySide.tsx +60 -0
- package/components/Artifacts/Compare/VersionPicker.tsx +47 -0
- package/components/Artifacts/Compare/index.tsx +191 -0
- package/components/Chat/ChatPanel.tsx +3 -9
- package/components/Chat/EventRow.tsx +4 -4
- package/components/Chat/Messages.tsx +12 -6
- package/components/Chat/SessionAnchor.tsx +22 -0
- package/components/Chat/ShareFromHere.tsx +39 -0
- package/components/Chat/session-link.ts +21 -4
- package/components/CommandPalette/CommandPalette.tsx +5 -4
- package/components/CommandPalette/CommandsBody.tsx +97 -131
- package/components/CommandPalette/PaletteRows.tsx +79 -0
- package/components/CommandPalette/PaletteTabs.tsx +51 -0
- package/components/CommandPalette/conversations/ConversationSearch.tsx +13 -11
- package/components/CommandPalette/useAppCommands.ts +14 -0
- package/components/CommandPalette/useConversationRows.ts +55 -0
- package/components/Health/EnvFailures/SessionList.tsx +2 -2
- package/components/Pane/PaneMenu.tsx +1 -1
- package/components/SessionList/QueuedRow.tsx +4 -4
- package/components/SessionList/SessionRowMenu.tsx +1 -1
- package/components/SessionList/useSessionSwitching.ts +2 -2
- package/components/Share/MessageList.tsx +87 -0
- package/components/Share/RangePicker.tsx +59 -0
- package/components/Share/ShareDialog.tsx +113 -52
- package/components/Shares/SharedLinksPanel.tsx +3 -5
- package/data/session-artifacts/keys.ts +2 -0
- package/data/session-artifacts/queries.ts +25 -0
- package/data/shares/queries.ts +11 -0
- package/hooks/useCopyToClipboard.ts +5 -2
- package/hooks/usePaletteCommands.ts +4 -1
- package/hooks/useSessionHref.ts +20 -0
- package/hooks/useSessionUrl.ts +40 -11
- package/lib/artifacts/actions.ts +11 -1
- package/lib/artifacts/compare/compareVersions.ts +66 -0
- package/lib/artifacts/compare/diff.ts +178 -0
- package/lib/artifacts/compare/extractText.ts +64 -0
- package/lib/artifacts/compare/index.ts +11 -0
- package/lib/artifacts/compare/types.ts +44 -0
- package/lib/artifacts/match.ts +64 -0
- package/lib/artifacts/store.ts +39 -10
- package/lib/artifacts/tools.ts +9 -4
- package/lib/artifacts/versions.ts +2 -1
- package/lib/away/deploy.ts +39 -2
- package/lib/palette/registry.ts +71 -17
- package/lib/palette/sections.ts +143 -0
- package/lib/palette/tabs.ts +21 -0
- package/lib/palette/usage.ts +81 -0
- package/lib/reports/evidence/build.ts +4 -3
- package/lib/security/demo.ts +3 -0
- package/lib/session-audit/render.ts +2 -1
- package/lib/session-logs/gather.ts +2 -2
- package/lib/session-slug.ts +29 -0
- package/lib/session-url.ts +162 -40
- package/lib/sessions/resolve.ts +55 -0
- package/lib/settings-url.ts +1 -1
- package/lib/share/markdown.ts +3 -0
- package/lib/share/range.ts +189 -0
- package/lib/share/render.tsx +3 -0
- package/lib/share/service.ts +64 -19
- package/lib/share/snapshot.ts +39 -2
- package/lib/share/styles.ts +2 -0
- package/lib/share/view.ts +11 -0
- package/lib/welcome-url.ts +1 -1
- package/next.config.ts +4 -0
- package/package.json +1 -1
- package/stores/chatJump.ts +5 -1
- package/stores/palette.ts +13 -11
- package/stores/shareUi.ts +6 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { ArrowRight, ChevronLeft } from "lucide-react";
|
|
5
|
+
import { Button } from "@/components/ui/button";
|
|
6
|
+
import { Notice } from "@/components/ui/notice";
|
|
7
|
+
import { Segmented } from "@/components/ui/segmented";
|
|
8
|
+
import { useArtifactCompare } from "@/data/session-artifacts";
|
|
9
|
+
import type { ArtifactVersion } from "@/lib/artifacts/actions";
|
|
10
|
+
import { summaryLabel } from "@/lib/artifacts/compare/diff";
|
|
11
|
+
import { relativeTime } from "@/lib/schedules/when";
|
|
12
|
+
import { DiffView } from "./DiffView";
|
|
13
|
+
import { SideBySide } from "./SideBySide";
|
|
14
|
+
import { VersionPicker } from "./VersionPicker";
|
|
15
|
+
|
|
16
|
+
type View = "changes" | "side" | "source";
|
|
17
|
+
|
|
18
|
+
// The shown version against the one before it; the first version against
|
|
19
|
+
// the one after.
|
|
20
|
+
export function defaultPair(
|
|
21
|
+
versions: ArtifactVersion[],
|
|
22
|
+
shownId: string
|
|
23
|
+
): { from: string; to: string } {
|
|
24
|
+
const i = Math.max(
|
|
25
|
+
0,
|
|
26
|
+
versions.findIndex((v) => v.id === shownId)
|
|
27
|
+
);
|
|
28
|
+
return i > 0
|
|
29
|
+
? { from: versions[i - 1].id, to: versions[i].id }
|
|
30
|
+
: {
|
|
31
|
+
from: versions[0].id,
|
|
32
|
+
to: versions[Math.min(1, versions.length - 1)].id,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function Note({ v, label }: { v: ArtifactVersion; label: string }) {
|
|
37
|
+
return (
|
|
38
|
+
<div className="bg-muted/50 min-w-0 rounded-lg px-3 py-2 text-[12.5px]">
|
|
39
|
+
<div className="text-muted-foreground text-[11px]">
|
|
40
|
+
{label} · v{v.version} · {relativeTime(v.createdAt)}
|
|
41
|
+
</div>
|
|
42
|
+
<div className="break-words">
|
|
43
|
+
{v.note || (
|
|
44
|
+
<span className="text-muted-foreground">No note on this version</span>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function Changes({
|
|
52
|
+
sessionId,
|
|
53
|
+
from,
|
|
54
|
+
to,
|
|
55
|
+
mode,
|
|
56
|
+
onSide,
|
|
57
|
+
}: {
|
|
58
|
+
sessionId: string;
|
|
59
|
+
from: ArtifactVersion;
|
|
60
|
+
to: ArtifactVersion;
|
|
61
|
+
mode: "changes" | "source";
|
|
62
|
+
onSide: () => void;
|
|
63
|
+
}) {
|
|
64
|
+
const q = useArtifactCompare(sessionId, from.id, to.id, mode);
|
|
65
|
+
if (q.isPending)
|
|
66
|
+
return <div className="bg-muted/40 m-3 h-40 animate-pulse rounded-lg" />;
|
|
67
|
+
if (q.isError)
|
|
68
|
+
return <p className="text-destructive p-4 text-sm">{q.error.message}</p>;
|
|
69
|
+
const d = q.data;
|
|
70
|
+
if ("tooLarge" in d)
|
|
71
|
+
return (
|
|
72
|
+
<div className="p-3">
|
|
73
|
+
<Notice
|
|
74
|
+
tone="warning"
|
|
75
|
+
action={
|
|
76
|
+
<Button variant="outline" className="h-11 md:h-8" onClick={onSide}>
|
|
77
|
+
Side by side
|
|
78
|
+
</Button>
|
|
79
|
+
}
|
|
80
|
+
>
|
|
81
|
+
These pages are {d.reason}. Compare them side by side instead.
|
|
82
|
+
</Notice>
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
const same = d.summary.added + d.summary.removed === 0;
|
|
86
|
+
return (
|
|
87
|
+
<div>
|
|
88
|
+
<div className="px-4 pt-3 text-[13px] font-medium">
|
|
89
|
+
{summaryLabel(d.summary)}
|
|
90
|
+
<span className="text-muted-foreground font-normal">
|
|
91
|
+
{mode === "changes" ? " · visible text" : " · HTML source"}
|
|
92
|
+
</span>
|
|
93
|
+
</div>
|
|
94
|
+
{same ? (
|
|
95
|
+
<p className="text-muted-foreground p-4 text-sm">
|
|
96
|
+
{mode === "changes"
|
|
97
|
+
? "The visible text is the same. Look at Side by side or Source for styling and script changes."
|
|
98
|
+
: "The source is identical."}
|
|
99
|
+
</p>
|
|
100
|
+
) : (
|
|
101
|
+
<DiffView lines={d.lines} wrap={mode === "changes"} />
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Two versions of a page: what changed in its text, side by side, or in its
|
|
108
|
+
// HTML. Owner's viewer only; never offered on a share or public link.
|
|
109
|
+
export function ArtifactCompare({
|
|
110
|
+
sessionId,
|
|
111
|
+
versions,
|
|
112
|
+
shownId,
|
|
113
|
+
onClose,
|
|
114
|
+
}: {
|
|
115
|
+
sessionId: string;
|
|
116
|
+
versions: ArtifactVersion[];
|
|
117
|
+
shownId: string;
|
|
118
|
+
onClose: () => void;
|
|
119
|
+
}) {
|
|
120
|
+
const [pair, setPair] = useState(() => defaultPair(versions, shownId));
|
|
121
|
+
const [view, setView] = useState<View>("changes");
|
|
122
|
+
const from = versions.find((v) => v.id === pair.from) ?? versions[0];
|
|
123
|
+
const to =
|
|
124
|
+
versions.find((v) => v.id === pair.to) ?? versions[versions.length - 1];
|
|
125
|
+
const [a, b] = from.version <= to.version ? [from, to] : [to, from];
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="@container flex h-full min-w-0 flex-col">
|
|
129
|
+
<div className="flex flex-col gap-3 border-b px-3 py-3 sm:px-4">
|
|
130
|
+
<div className="flex items-center gap-2">
|
|
131
|
+
<Button
|
|
132
|
+
variant="ghost"
|
|
133
|
+
size="icon"
|
|
134
|
+
aria-label="Back to the page"
|
|
135
|
+
onClick={onClose}
|
|
136
|
+
className="-ml-1 h-11 w-11 shrink-0 md:h-8 md:w-8"
|
|
137
|
+
>
|
|
138
|
+
<ChevronLeft className="h-5 w-5" />
|
|
139
|
+
</Button>
|
|
140
|
+
<div className="text-[13px] font-medium">Compare versions</div>
|
|
141
|
+
</div>
|
|
142
|
+
<div className="flex items-end gap-2">
|
|
143
|
+
<VersionPicker
|
|
144
|
+
name="From"
|
|
145
|
+
value={from.id}
|
|
146
|
+
versions={versions}
|
|
147
|
+
onChange={(id) => setPair({ ...pair, from: id })}
|
|
148
|
+
/>
|
|
149
|
+
<ArrowRight className="text-muted-foreground mb-3.5 h-4 w-4 shrink-0 md:mb-2.5" />
|
|
150
|
+
<VersionPicker
|
|
151
|
+
name="To"
|
|
152
|
+
value={to.id}
|
|
153
|
+
versions={versions}
|
|
154
|
+
onChange={(id) => setPair({ ...pair, to: id })}
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
<div className="grid gap-2 @xl:grid-cols-2">
|
|
158
|
+
<Note v={a} label="Before" />
|
|
159
|
+
<Note v={b} label="After" />
|
|
160
|
+
</div>
|
|
161
|
+
<Segmented
|
|
162
|
+
label="Comparison"
|
|
163
|
+
value={view}
|
|
164
|
+
onChange={setView}
|
|
165
|
+
options={[
|
|
166
|
+
{ value: "changes", label: "Changes" },
|
|
167
|
+
{ value: "side", label: "Side by side" },
|
|
168
|
+
{ value: "source", label: "Source" },
|
|
169
|
+
]}
|
|
170
|
+
/>
|
|
171
|
+
</div>
|
|
172
|
+
<div className="min-h-0 flex-1 overflow-auto">
|
|
173
|
+
{from.id === to.id ? (
|
|
174
|
+
<p className="text-muted-foreground p-4 text-sm">
|
|
175
|
+
Pick two different versions.
|
|
176
|
+
</p>
|
|
177
|
+
) : view === "side" ? (
|
|
178
|
+
<SideBySide from={a} to={b} />
|
|
179
|
+
) : (
|
|
180
|
+
<Changes
|
|
181
|
+
sessionId={sessionId}
|
|
182
|
+
from={a}
|
|
183
|
+
to={b}
|
|
184
|
+
mode={view === "source" ? "source" : "changes"}
|
|
185
|
+
onSide={() => setView("side")}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -66,7 +66,7 @@ import { useGoToItem } from "./useGoToItem";
|
|
|
66
66
|
import { NeedsYouStrip } from "@/components/Orchestrator/NeedsYouStrip";
|
|
67
67
|
import { useResolveQuestion } from "@/data/orchestrators";
|
|
68
68
|
import { needsYouFocus, needsYouFocusActions } from "@/stores/needsYouFocus";
|
|
69
|
-
import {
|
|
69
|
+
import { openSessionById } from "./session-link";
|
|
70
70
|
import { questionSource } from "@/lib/chat/turns";
|
|
71
71
|
import { useViewport } from "@/hooks/useViewport";
|
|
72
72
|
import { useChatCommands } from "./useChatCommands";
|
|
@@ -767,15 +767,9 @@ export function ChatPanel({
|
|
|
767
767
|
if (item.kind === "ask") needsYouFocusActions.ask(item.ask.id);
|
|
768
768
|
else if (item.kind === "chat") toEnd();
|
|
769
769
|
else if (item.kind === "task" || item.kind === "card") {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
"",
|
|
773
|
-
hrefWithSession(
|
|
774
|
-
location.href,
|
|
775
|
-
item.kind === "task" ? item.sessionId : item.card.sessionId
|
|
776
|
-
)
|
|
770
|
+
openSessionById(
|
|
771
|
+
item.kind === "task" ? item.sessionId : item.card.sessionId
|
|
777
772
|
);
|
|
778
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
779
773
|
} else goTo((all) => questionSource(all, item.question.text));
|
|
780
774
|
}}
|
|
781
775
|
onReply={(item) => {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "lucide-react";
|
|
13
13
|
import type { ChatItem, ChatOrigin, ChatOriginKind } from "@/lib/chat/events";
|
|
14
14
|
import { eventParts, originOf } from "@/lib/chat/origin";
|
|
15
|
-
import {
|
|
15
|
+
import { SessionAnchor } from "./SessionAnchor";
|
|
16
16
|
import { cn } from "@/lib/utils";
|
|
17
17
|
import { UserMessage } from "./Messages";
|
|
18
18
|
import { openSessionLink as openSession } from "./session-link";
|
|
@@ -45,8 +45,8 @@ function Source({ origin }: { origin: ChatOrigin }) {
|
|
|
45
45
|
if (!origin.sessionId) return name;
|
|
46
46
|
const id = origin.sessionId;
|
|
47
47
|
return (
|
|
48
|
-
<
|
|
49
|
-
|
|
48
|
+
<SessionAnchor
|
|
49
|
+
sessionId={id}
|
|
50
50
|
onClick={(e) => {
|
|
51
51
|
e.stopPropagation();
|
|
52
52
|
openSession(e, id);
|
|
@@ -54,7 +54,7 @@ function Source({ origin }: { origin: ChatOrigin }) {
|
|
|
54
54
|
className="hover:text-foreground relative min-w-0 underline-offset-2 before:absolute before:-inset-x-1 before:-inset-y-3.5 hover:underline"
|
|
55
55
|
>
|
|
56
56
|
{name}
|
|
57
|
-
</
|
|
57
|
+
</SessionAnchor>
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -20,7 +20,7 @@ import { cn } from "@/lib/utils";
|
|
|
20
20
|
import { leadingCommand } from "@/lib/chat/commands";
|
|
21
21
|
import { compactedLine } from "@/lib/chat/stale";
|
|
22
22
|
import { splitProse, splitUserText } from "@/lib/chat/user-text";
|
|
23
|
-
import {
|
|
23
|
+
import { inAppSessionRef } from "@/lib/session-url";
|
|
24
24
|
import { CodeBlock } from "./Code";
|
|
25
25
|
import { CopyButton } from "./CopyButton";
|
|
26
26
|
import { FileChip } from "./FileChip";
|
|
@@ -29,6 +29,7 @@ import { Markdown } from "./Markdown";
|
|
|
29
29
|
import { openSessionLink } from "./session-link";
|
|
30
30
|
import { TurnCopy } from "./TurnCopy";
|
|
31
31
|
import { UndoButton } from "./Undo";
|
|
32
|
+
import { ShareFromHere } from "./ShareFromHere";
|
|
32
33
|
import { useRowState } from "./rowState";
|
|
33
34
|
import { useBookmarkChat } from "@/components/Bookmarks/context";
|
|
34
35
|
import { ChatItemContext } from "./itemContext";
|
|
@@ -42,16 +43,16 @@ import {
|
|
|
42
43
|
type Of<K extends ChatItem["kind"]> = Extract<ChatItem, { kind: K }>;
|
|
43
44
|
|
|
44
45
|
function UserLink({ href }: { href: string }) {
|
|
45
|
-
const
|
|
46
|
+
const ref =
|
|
46
47
|
typeof window === "undefined"
|
|
47
48
|
? null
|
|
48
|
-
:
|
|
49
|
+
: inAppSessionRef(href, window.location.origin);
|
|
49
50
|
return (
|
|
50
51
|
<a
|
|
51
52
|
href={href}
|
|
52
|
-
target={
|
|
53
|
-
rel={
|
|
54
|
-
onClick={
|
|
53
|
+
target={ref ? undefined : "_blank"}
|
|
54
|
+
rel={ref ? undefined : "noopener noreferrer"}
|
|
55
|
+
onClick={ref ? (e) => openSessionLink(e, ref) : undefined}
|
|
55
56
|
className="chat-link font-medium"
|
|
56
57
|
>
|
|
57
58
|
{href}
|
|
@@ -280,6 +281,7 @@ export function UserMessage({
|
|
|
280
281
|
<div className="-mr-1.5 flex items-center">
|
|
281
282
|
{onUndo && <UndoButton onClick={onUndo} />}
|
|
282
283
|
<ExportFromHere itemId={item.id} />
|
|
284
|
+
<ShareFromHere itemId={item.id} />
|
|
283
285
|
<BookmarkButton itemId={item.id} />
|
|
284
286
|
</div>
|
|
285
287
|
)}
|
|
@@ -325,6 +327,10 @@ export function AssistantMessage({
|
|
|
325
327
|
<Quote className="h-3.5 w-3.5" />
|
|
326
328
|
</button>
|
|
327
329
|
)}
|
|
330
|
+
<ShareFromHere
|
|
331
|
+
itemId={item.id}
|
|
332
|
+
className="text-muted-foreground hover:text-foreground hover:bg-foreground/[0.06] flex h-9 w-9 items-center justify-center rounded-md md:h-7 md:w-7"
|
|
333
|
+
/>
|
|
328
334
|
</div>
|
|
329
335
|
<BookmarkButton itemId={item.id} />
|
|
330
336
|
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { AnchorHTMLAttributes } from "react";
|
|
4
|
+
import { useSessionHref } from "@/hooks/useSessionHref";
|
|
5
|
+
import { openSessionLink } from "./session-link";
|
|
6
|
+
|
|
7
|
+
type Props = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, "href"> & {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// A link to a session at its readable address. A plain click opens it here;
|
|
12
|
+
// a modified one, a new tab. An own onClick replaces the open.
|
|
13
|
+
export function SessionAnchor({ sessionId, onClick, ...rest }: Props) {
|
|
14
|
+
const href = useSessionHref(sessionId);
|
|
15
|
+
return (
|
|
16
|
+
<a
|
|
17
|
+
href={href}
|
|
18
|
+
onClick={onClick ?? ((e) => openSessionLink(e, sessionId))}
|
|
19
|
+
{...rest}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Share2 } from "lucide-react";
|
|
4
|
+
import { useBookmarkChat } from "@/components/Bookmarks/context";
|
|
5
|
+
import { shareUiActions } from "@/stores/shareUi";
|
|
6
|
+
|
|
7
|
+
// Opens the share dialog on the part of the chat from this message to the latest.
|
|
8
|
+
export function ShareFromHere({
|
|
9
|
+
itemId,
|
|
10
|
+
className,
|
|
11
|
+
}: {
|
|
12
|
+
itemId: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
}) {
|
|
15
|
+
const chat = useBookmarkChat();
|
|
16
|
+
if (!chat) return null;
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
aria-label="Share from here"
|
|
21
|
+
title="Share from here"
|
|
22
|
+
onClick={(e) => {
|
|
23
|
+
e.stopPropagation();
|
|
24
|
+
shareUiActions.open({
|
|
25
|
+
kind: "chat",
|
|
26
|
+
sessionId: chat.sessionId,
|
|
27
|
+
title: "",
|
|
28
|
+
fromId: itemId,
|
|
29
|
+
});
|
|
30
|
+
}}
|
|
31
|
+
className={
|
|
32
|
+
className ??
|
|
33
|
+
"text-muted-foreground hover:text-foreground hover:bg-foreground/[0.06] flex h-11 w-11 shrink-0 items-center justify-center rounded-md opacity-40 group-hover:opacity-100 focus-visible:opacity-100 md:h-7 md:w-7 md:opacity-0"
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
<Share2 className="h-3.5 w-3.5" />
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import type { MouseEvent } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
hrefWithRef,
|
|
4
|
+
hrefWithSession,
|
|
5
|
+
type SessionRef,
|
|
6
|
+
} from "@/lib/session-url";
|
|
3
7
|
|
|
4
|
-
// Opens
|
|
5
|
-
|
|
8
|
+
// Opens a session through its address, the way a shared link does. The
|
|
9
|
+
// address it lands on is rewritten to the session's readable one.
|
|
10
|
+
export function openSessionById(id: string): void {
|
|
11
|
+
go(hrefWithSession(location.href, { id }));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function openSessionLink(
|
|
15
|
+
e: MouseEvent<HTMLAnchorElement>,
|
|
16
|
+
target: string | SessionRef
|
|
17
|
+
) {
|
|
6
18
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
|
|
7
19
|
e.preventDefault();
|
|
8
|
-
|
|
20
|
+
if (typeof target === "string") openSessionById(target);
|
|
21
|
+
else go(hrefWithRef(location.href, target));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function go(href: string): void {
|
|
25
|
+
window.history.pushState(null, "", href);
|
|
9
26
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
10
27
|
}
|
|
@@ -13,10 +13,11 @@ import { CommandsBody } from "./CommandsBody";
|
|
|
13
13
|
import { ConversationSearch } from "./conversations/ConversationSearch";
|
|
14
14
|
|
|
15
15
|
// ⌘K anywhere, or the header button on a phone: every action the open
|
|
16
|
-
// features offer, found by typing,
|
|
16
|
+
// features offer, found by typing, in tabs (Tab cycles them) that include
|
|
17
|
+
// what was said in any chat.
|
|
17
18
|
export function CommandPalette() {
|
|
18
|
-
const { open,
|
|
19
|
-
const wide =
|
|
19
|
+
const { open, tab } = useSnapshot(paletteUi);
|
|
20
|
+
const wide = tab === "conversations";
|
|
20
21
|
return (
|
|
21
22
|
<Dialog open={open} onOpenChange={paletteActions.setOpen}>
|
|
22
23
|
<DialogContent
|
|
@@ -37,7 +38,7 @@ export function CommandPalette() {
|
|
|
37
38
|
? "Search what was said in every conversation"
|
|
38
39
|
: "Search for an action, a session or a conversation"}
|
|
39
40
|
</DialogDescription>
|
|
40
|
-
{/* Mounted per opening and per
|
|
41
|
+
{/* Mounted per opening and per side, so each starts from its own query. */}
|
|
41
42
|
{wide ? (
|
|
42
43
|
<ConversationSearch key="conversations" />
|
|
43
44
|
) : (
|