@saadnvd1/agent-os 0.3.580 → 0.3.581
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.
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import type { MouseEvent } from "react";
|
|
4
3
|
import {
|
|
5
4
|
Bot,
|
|
6
5
|
CalendarClock,
|
|
@@ -15,6 +14,7 @@ import { eventParts, originOf } from "@/lib/chat/origin";
|
|
|
15
14
|
import { hrefWithSession } from "@/lib/session-url";
|
|
16
15
|
import { cn } from "@/lib/utils";
|
|
17
16
|
import { UserMessage } from "./Messages";
|
|
17
|
+
import { openSessionLink as openSession } from "./session-link";
|
|
18
18
|
import { useRowState } from "./rowState";
|
|
19
19
|
|
|
20
20
|
type UserItem = Extract<ChatItem, { kind: "user" }>;
|
|
@@ -34,14 +34,6 @@ const SHORT = 32;
|
|
|
34
34
|
export const clock = (at: number) =>
|
|
35
35
|
new Date(at).toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
36
36
|
|
|
37
|
-
// Opens the session through its address, the way a shared link does.
|
|
38
|
-
function openSession(e: MouseEvent<HTMLAnchorElement>, id: string) {
|
|
39
|
-
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
|
|
40
|
-
e.preventDefault();
|
|
41
|
-
window.history.pushState(null, "", hrefWithSession(location.href, id));
|
|
42
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
37
|
function Source({ origin }: { origin: ChatOrigin }) {
|
|
46
38
|
const name = (
|
|
47
39
|
<span className="text-foreground/80 block truncate font-medium">
|
|
@@ -12,27 +12,49 @@ import type { ChatItem } from "@/lib/chat/events";
|
|
|
12
12
|
import { cn } from "@/lib/utils";
|
|
13
13
|
import { leadingCommand } from "@/lib/chat/commands";
|
|
14
14
|
import { compactedLine } from "@/lib/chat/stale";
|
|
15
|
-
import {
|
|
15
|
+
import { splitProse, splitUserText } from "@/lib/chat/user-text";
|
|
16
|
+
import { inAppSessionId } from "@/lib/session-url";
|
|
16
17
|
import { CodeBlock } from "./Code";
|
|
17
18
|
import { CopyButton } from "./CopyButton";
|
|
18
19
|
import { ImageThumb } from "./ImageThumb";
|
|
19
20
|
import { Markdown } from "./Markdown";
|
|
21
|
+
import { openSessionLink } from "./session-link";
|
|
20
22
|
import { UndoButton } from "./Undo";
|
|
21
23
|
import { useRowState } from "./rowState";
|
|
22
24
|
|
|
23
25
|
type Of<K extends ChatItem["kind"]> = Extract<ChatItem, { kind: K }>;
|
|
24
26
|
|
|
27
|
+
function UserLink({ href }: { href: string }) {
|
|
28
|
+
const id =
|
|
29
|
+
typeof window === "undefined"
|
|
30
|
+
? null
|
|
31
|
+
: inAppSessionId(href, window.location.origin);
|
|
32
|
+
return (
|
|
33
|
+
<a
|
|
34
|
+
href={href}
|
|
35
|
+
target={id ? undefined : "_blank"}
|
|
36
|
+
rel={id ? undefined : "noopener noreferrer"}
|
|
37
|
+
onClick={id ? (e) => openSessionLink(e, id) : undefined}
|
|
38
|
+
className="font-medium [overflow-wrap:anywhere] underline underline-offset-2"
|
|
39
|
+
>
|
|
40
|
+
{href}
|
|
41
|
+
</a>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
function Prose({ text }: { text: string }) {
|
|
26
46
|
return (
|
|
27
47
|
<>
|
|
28
|
-
{
|
|
29
|
-
p.code ? (
|
|
48
|
+
{splitProse(text).map((p, i) =>
|
|
49
|
+
p.kind === "code" ? (
|
|
30
50
|
<code
|
|
31
51
|
key={i}
|
|
32
52
|
className="bg-foreground/[0.06] rounded px-1 py-0.5 font-mono text-[0.9em]"
|
|
33
53
|
>
|
|
34
54
|
{p.text}
|
|
35
55
|
</code>
|
|
56
|
+
) : p.kind === "link" ? (
|
|
57
|
+
<UserLink key={i} href={p.text} />
|
|
36
58
|
) : (
|
|
37
59
|
<span key={i}>{p.text}</span>
|
|
38
60
|
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MouseEvent } from "react";
|
|
2
|
+
import { hrefWithSession } from "@/lib/session-url";
|
|
3
|
+
|
|
4
|
+
// Opens the session through its address, the way a shared link does.
|
|
5
|
+
export function openSessionLink(e: MouseEvent<HTMLAnchorElement>, id: string) {
|
|
6
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
|
|
7
|
+
e.preventDefault();
|
|
8
|
+
window.history.pushState(null, "", hrefWithSession(location.href, id));
|
|
9
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
10
|
+
}
|
package/lib/chat/user-text.ts
CHANGED
|
@@ -62,3 +62,55 @@ export function splitInlineCode(
|
|
|
62
62
|
if (last < text.length) out.push({ code: false, text: text.slice(last) });
|
|
63
63
|
return out;
|
|
64
64
|
}
|
|
65
|
+
|
|
66
|
+
export type ProsePart =
|
|
67
|
+
| { kind: "text"; text: string }
|
|
68
|
+
| { kind: "code"; text: string }
|
|
69
|
+
| { kind: "link"; text: string };
|
|
70
|
+
|
|
71
|
+
const URL_RE = /https?:\/\/[^\s<>"'`]+/gi;
|
|
72
|
+
const TRAILING = /[.,;:!?]$/;
|
|
73
|
+
|
|
74
|
+
// Punctuation that ends a sentence, or a closing paren with no opener in the
|
|
75
|
+
// URL, belongs to the text around it.
|
|
76
|
+
function trimUrl(url: string): string {
|
|
77
|
+
for (;;) {
|
|
78
|
+
if (TRAILING.test(url)) url = url.slice(0, -1);
|
|
79
|
+
else if (url.endsWith(")") && count(url, ")") > count(url, "("))
|
|
80
|
+
url = url.slice(0, -1);
|
|
81
|
+
else return url;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function count(s: string, ch: string): number {
|
|
86
|
+
return s.split(ch).length - 1;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// http(s) URLs in plain text, split out so they can be links.
|
|
90
|
+
export function splitLinks(text: string): { link: boolean; text: string }[] {
|
|
91
|
+
const out: { link: boolean; text: string }[] = [];
|
|
92
|
+
let last = 0;
|
|
93
|
+
for (const m of text.matchAll(URL_RE)) {
|
|
94
|
+
const url = trimUrl(m[0]);
|
|
95
|
+
if (!/^https?:\/\/[^/?#]+/i.test(url)) continue;
|
|
96
|
+
if (m.index > last)
|
|
97
|
+
out.push({ link: false, text: text.slice(last, m.index) });
|
|
98
|
+
out.push({ link: true, text: url });
|
|
99
|
+
last = m.index + url.length;
|
|
100
|
+
}
|
|
101
|
+
if (last < text.length) out.push({ link: false, text: text.slice(last) });
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Prose split into code spans, links and the text between. Links are only
|
|
106
|
+
// looked for outside code spans.
|
|
107
|
+
export function splitProse(text: string): ProsePart[] {
|
|
108
|
+
return splitInlineCode(text).flatMap((p): ProsePart[] =>
|
|
109
|
+
p.code
|
|
110
|
+
? [{ kind: "code", text: p.text }]
|
|
111
|
+
: splitLinks(p.text).map((l) => ({
|
|
112
|
+
kind: l.link ? "link" : "text",
|
|
113
|
+
text: l.text,
|
|
114
|
+
}))
|
|
115
|
+
);
|
|
116
|
+
}
|
package/lib/session-url.ts
CHANGED
|
@@ -26,6 +26,18 @@ export function hrefWithSession(href: string, id: string | null): string {
|
|
|
26
26
|
return url.pathname + url.search + url.hash;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** The session a link to this same instance opens, or null for any other link. */
|
|
30
|
+
export function inAppSessionId(href: string, origin: string): string | null {
|
|
31
|
+
let url: URL;
|
|
32
|
+
try {
|
|
33
|
+
url = new URL(href);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (url.origin !== origin || url.pathname !== "/") return null;
|
|
38
|
+
return sessionIdFromHref(href);
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
export function sessionLink(origin: string, id: string): string {
|
|
30
42
|
return `${origin}/?${SESSION_PARAM}=${encodeURIComponent(id)}`;
|
|
31
43
|
}
|