@marimo-team/islands 0.19.8-dev19 → 0.19.8-dev23
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/package.json
CHANGED
|
@@ -609,9 +609,13 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
|
|
|
609
609
|
const isDuplicate =
|
|
610
610
|
KNOWN_PROVIDERS.includes(normalizedName as KnownProviderId) ||
|
|
611
611
|
(customProviders && Object.keys(customProviders).includes(normalizedName));
|
|
612
|
+
const hasInvalidChars = normalizedName.includes(".");
|
|
612
613
|
|
|
613
614
|
const hasValidValues =
|
|
614
|
-
normalizedName.trim() &&
|
|
615
|
+
normalizedName.trim() &&
|
|
616
|
+
newProviderBaseUrl.trim() &&
|
|
617
|
+
!isDuplicate &&
|
|
618
|
+
!hasInvalidChars;
|
|
615
619
|
|
|
616
620
|
const resetForm = () => {
|
|
617
621
|
setNewProviderName("");
|
|
@@ -669,7 +673,12 @@ export const CustomProvidersConfig: React.FC<AiConfigProps> = ({
|
|
|
669
673
|
A provider with this name already exists.
|
|
670
674
|
</p>
|
|
671
675
|
)}
|
|
672
|
-
{
|
|
676
|
+
{hasInvalidChars && (
|
|
677
|
+
<p className="text-xs text-destructive">
|
|
678
|
+
Provider names cannot contain '.' characters.
|
|
679
|
+
</p>
|
|
680
|
+
)}
|
|
681
|
+
{newProviderName && !hasInvalidChars && (
|
|
673
682
|
<p className="text-xs text-muted-secondary">
|
|
674
683
|
Use models with prefix:{" "}
|
|
675
684
|
<Kbd className="inline text-xs">{normalizedName}/</Kbd>
|
|
@@ -33,6 +33,15 @@ const tabTarget = (path: string): string => {
|
|
|
33
33
|
return `${getSessionId()}-${encodeURIComponent(path)}`;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
const isHttpsUrl = (value: string): boolean => {
|
|
37
|
+
try {
|
|
38
|
+
const url = new URL(value);
|
|
39
|
+
return url.protocol === "https:";
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
36
45
|
const SEARCH_THRESHOLD = 10;
|
|
37
46
|
|
|
38
47
|
const GalleryPage: React.FC = () => {
|
|
@@ -43,10 +52,10 @@ const GalleryPage: React.FC = () => {
|
|
|
43
52
|
[],
|
|
44
53
|
);
|
|
45
54
|
const workspace = response.data;
|
|
46
|
-
const files = workspace?.files ?? [];
|
|
47
|
-
const root = workspace?.root ?? "";
|
|
48
55
|
|
|
49
56
|
const formattedFiles = useMemo(() => {
|
|
57
|
+
const files = workspace?.files ?? [];
|
|
58
|
+
const root = workspace?.root ?? "";
|
|
50
59
|
return files
|
|
51
60
|
.filter((file) => !file.isDirectory)
|
|
52
61
|
.map((file) => {
|
|
@@ -54,17 +63,28 @@ const GalleryPage: React.FC = () => {
|
|
|
54
63
|
root && Paths.isAbsolute(file.path) && file.path.startsWith(root)
|
|
55
64
|
? Paths.rest(file.path, root)
|
|
56
65
|
: file.path;
|
|
57
|
-
const title =
|
|
66
|
+
const title =
|
|
67
|
+
file.opengraph?.title ?? titleCase(Paths.basename(relativePath));
|
|
58
68
|
const subtitle = titleCase(Paths.dirname(relativePath));
|
|
69
|
+
const description = file.opengraph?.description ?? "";
|
|
70
|
+
const opengraphImage = file.opengraph?.image;
|
|
71
|
+
const thumbnailUrl =
|
|
72
|
+
opengraphImage && isHttpsUrl(opengraphImage)
|
|
73
|
+
? opengraphImage
|
|
74
|
+
: asURL(
|
|
75
|
+
`/og/thumbnail?file=${encodeURIComponent(relativePath)}`,
|
|
76
|
+
).toString();
|
|
59
77
|
return {
|
|
60
78
|
...file,
|
|
61
79
|
relativePath,
|
|
62
80
|
title,
|
|
63
81
|
subtitle,
|
|
82
|
+
description,
|
|
83
|
+
thumbnailUrl,
|
|
64
84
|
};
|
|
65
85
|
})
|
|
66
86
|
.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
67
|
-
}, [files, root]);
|
|
87
|
+
}, [workspace?.files, workspace?.root]);
|
|
68
88
|
|
|
69
89
|
const filteredFiles = useMemo(() => {
|
|
70
90
|
if (!searchQuery) {
|
|
@@ -130,8 +150,14 @@ const GalleryPage: React.FC = () => {
|
|
|
130
150
|
target={tabTarget(file.path)}
|
|
131
151
|
className="no-underline"
|
|
132
152
|
>
|
|
133
|
-
<Card className="h-full hover:bg-accent/20 transition-colors">
|
|
134
|
-
<
|
|
153
|
+
<Card className="h-full overflow-hidden hover:bg-accent/20 transition-colors">
|
|
154
|
+
<img
|
|
155
|
+
src={file.thumbnailUrl}
|
|
156
|
+
alt={file.title}
|
|
157
|
+
loading="lazy"
|
|
158
|
+
className="w-full aspect-1200/630 object-cover border-b border-border/60"
|
|
159
|
+
/>
|
|
160
|
+
<CardContent className="p-6 pt-4">
|
|
135
161
|
<div className="flex flex-col gap-1">
|
|
136
162
|
{file.subtitle && (
|
|
137
163
|
<div className="text-sm font-semibold text-muted-foreground">
|
|
@@ -141,6 +167,11 @@ const GalleryPage: React.FC = () => {
|
|
|
141
167
|
<div className="text-lg font-medium">
|
|
142
168
|
{file.title}
|
|
143
169
|
</div>
|
|
170
|
+
{file.description && (
|
|
171
|
+
<div className="text-sm text-muted-foreground line-clamp-3 mt-1">
|
|
172
|
+
{file.description}
|
|
173
|
+
</div>
|
|
174
|
+
)}
|
|
144
175
|
</div>
|
|
145
176
|
</CardContent>
|
|
146
177
|
</Card>
|
|
@@ -76,4 +76,26 @@ describe("parseContent", () => {
|
|
|
76
76
|
url: "https://avatars.githubusercontent.com/u/123",
|
|
77
77
|
});
|
|
78
78
|
});
|
|
79
|
+
|
|
80
|
+
it("preserves newlines between URLs", () => {
|
|
81
|
+
const parts = parseContent("https://marimo.io\nhttps://github.com\n");
|
|
82
|
+
expect(parts).toEqual([
|
|
83
|
+
{ type: "url", url: "https://marimo.io" },
|
|
84
|
+
{ type: "text", value: "\n" },
|
|
85
|
+
{ type: "url", url: "https://github.com" },
|
|
86
|
+
{ type: "text", value: "\n" },
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("preserves whitespace in mixed content", () => {
|
|
91
|
+
const parts = parseContent(
|
|
92
|
+
"Line 1: https://marimo.io\nLine 2: https://github.com",
|
|
93
|
+
);
|
|
94
|
+
expect(parts).toEqual([
|
|
95
|
+
{ type: "text", value: "Line 1: " },
|
|
96
|
+
{ type: "url", url: "https://marimo.io" },
|
|
97
|
+
{ type: "text", value: "\nLine 2: " },
|
|
98
|
+
{ type: "url", url: "https://github.com" },
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
79
101
|
});
|
package/src/utils/url-parser.ts
CHANGED
|
@@ -19,7 +19,7 @@ export function parseContent(text: string): ContentPart[] {
|
|
|
19
19
|
return [{ type: "image", url: text }];
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const parts = text.split(urlRegex).filter((part) => part
|
|
22
|
+
const parts = text.split(urlRegex).filter((part) => part !== "");
|
|
23
23
|
return parts.map((part) => {
|
|
24
24
|
const isUrl = urlRegex.test(part);
|
|
25
25
|
if (isUrl) {
|