This commit is contained in:
Benjamin Shafii
2026-03-27 07:39:12 -07:00
parent f2e5c4c9cb
commit 7fd45ff871
11 changed files with 468 additions and 58 deletions

View File

@@ -4159,6 +4159,30 @@ export default function App() {
}
};
const createWorkspaceFromBundle = async (
bundle: SharedWorkspaceProfileBundleV1,
folder: string | null,
defaultPreset = defaultPresetFromTemplateBundle(bundle),
) => {
const request = {
bundleUrl: "",
intent: "new_worker" as const,
source: "cloud-template" as const,
label: bundle.name,
};
const ok = await workspaceStore.createWorkspaceFlow(defaultPreset, folder);
if (!ok) return false;
return importSharedBundleIntoActiveWorker(
request,
{
localRoot: workspaceStore.selectedWorkspaceRoot().trim(),
},
bundle,
);
};
const importSharedSkillIntoWorkspace = async (workspaceId: string) => {
if (sharedSkillDestinationBusyId()) return;
const destination = sharedSkillDestinationRequest();
@@ -4793,6 +4817,34 @@ export default function App() {
});
};
const startWorkspaceFromCloudTemplate = async (input: {
name: string;
templateData: unknown;
folder: string | null;
preset?: WorkspacePreset;
}) => {
const bundle = parseSharedBundle(input.templateData);
if (bundle.type !== "workspace-profile") {
throw new Error("Only workspace templates can start a new workspace.");
}
setError(null);
setSharedSkillDestinationRequest(null);
setSharedBundleImportChoice(null);
setSharedBundleImportError(null);
setSharedBundleCreateWorkerRequest(null);
setSharedTemplateStartRequest(null);
const imported = await createWorkspaceFromBundle(
bundle,
input.folder,
input.preset ?? defaultPresetFromTemplateBundle(bundle),
);
if (!imported) {
throw new Error(`Failed to create ${input.name} from template.`);
}
};
const sharedBundleImportCopy = createMemo(() => {
const choice = sharedBundleImportChoice();
if (!choice) return null;
@@ -8774,6 +8826,14 @@ export default function App() {
}}
onPickFolder={workspaceStore.pickWorkspaceFolder}
defaultPreset={createWorkspaceDefaultPreset()}
onConfirmTemplate={(template, preset, folder) =>
startWorkspaceFromCloudTemplate({
name: template.name,
templateData: template.templateData,
folder,
preset,
})
}
onConfirm={async (preset, folder) => {
const request = sharedBundleCreateWorkerRequest();
const ok = await workspaceStore.createWorkspaceFlow(preset, folder);

View File

@@ -1,8 +1,10 @@
import { For, Show, createEffect, createMemo, createSignal, onCleanup } from "solid-js";
import { FolderPlus, Loader2, X, XCircle } from "lucide-solid";
import { Boxes, FolderPlus, Loader2, X, XCircle } from "lucide-solid";
import { t, currentLocale } from "../../i18n";
import type { WorkspacePreset } from "../types";
import { type DenTemplate, readDenSettings } from "../lib/den";
import { loadDenTemplateCache, readDenTemplateCacheSnapshot } from "../lib/den-template-cache";
import Button from "./button";
@@ -29,6 +31,7 @@ export default function CreateWorkspaceModal(props: {
onWorkerRetry?: () => void;
workerDebugLines?: string[];
workerSubmitting?: boolean;
onConfirmTemplate?: (template: DenTemplate, preset: WorkspacePreset, folder: string | null) => Promise<void> | void;
submittingProgress?: {
runId: string;
startedAt: number;
@@ -46,14 +49,27 @@ export default function CreateWorkspaceModal(props: {
const [pickingFolder, setPickingFolder] = createSignal(false);
const [showProgressDetails, setShowProgressDetails] = createSignal(false);
const [now, setNow] = createSignal(Date.now());
const [cloudSettings, setCloudSettings] = createSignal(readDenSettings());
const [selectedTemplateId, setSelectedTemplateId] = createSignal<string | null>(null);
const [templateError, setTemplateError] = createSignal<string | null>(null);
createEffect(() => {
if (props.open) {
setPreset(props.defaultPreset ?? "starter");
setCloudSettings(readDenSettings());
setSelectedTemplateId(null);
setTemplateError(null);
requestAnimationFrame(() => pickFolderRef?.focus());
}
});
createEffect(() => {
if (!props.open && !isInline()) return;
const handler = () => setCloudSettings(readDenSettings());
window.addEventListener("openwork-den-session-updated", handler as EventListener);
onCleanup(() => window.removeEventListener("openwork-den-session-updated", handler as EventListener));
});
const handlePickFolder = async () => {
if (pickingFolder()) return;
setPickingFolder(true);
@@ -81,6 +97,34 @@ export default function CreateWorkspaceModal(props: {
const showWorkerCallout = () => Boolean(props.onConfirmWorker && workerDisabled() && workerDisabledReason());
const workerDebugLines = createMemo(() => (props.workerDebugLines ?? []).map((line) => line.trim()).filter(Boolean));
const hasSelectedFolder = createMemo(() => Boolean(selectedFolder()?.trim()));
const templateCacheSnapshot = createMemo(() =>
readDenTemplateCacheSnapshot({
baseUrl: cloudSettings().baseUrl,
token: cloudSettings().authToken,
orgSlug: cloudSettings().activeOrgSlug,
}),
);
const cloudWorkspaceTemplates = createMemo(() =>
templateCacheSnapshot().templates.filter((template) => {
const payload = template.templateData;
return Boolean(payload && typeof payload === "object" && (payload as { type?: unknown }).type === "workspace-profile");
}),
);
const showTemplateSection = createMemo(
() => Boolean(props.onConfirmTemplate && cloudSettings().authToken?.trim() && cloudSettings().activeOrgSlug?.trim()),
);
createEffect(() => {
if (!showTemplateSection() || (!props.open && !isInline())) return;
void loadDenTemplateCache(
{
baseUrl: cloudSettings().baseUrl,
token: cloudSettings().authToken,
orgSlug: cloudSettings().activeOrgSlug,
},
{ force: true },
).catch(() => undefined);
});
createEffect(() => {
if (!submitting()) {
@@ -98,6 +142,42 @@ export default function CreateWorkspaceModal(props: {
return Math.max(0, Math.floor((now() - current.startedAt) / 1000));
});
const formatTemplateTimestamp = (value: string | null) => {
if (!value) return "Recently updated";
const date = new Date(value);
if (Number.isNaN(date.getTime())) return "Recently updated";
return new Intl.DateTimeFormat(undefined, {
month: "short",
day: "numeric",
year: "numeric",
}).format(date);
};
const templateCreatorLabel = (template: DenTemplate) => {
const creator = template.creator;
if (!creator) return "Unknown creator";
return creator.name?.trim() || creator.email?.trim() || "Unknown creator";
};
const selectedTemplate = createMemo(
() => cloudWorkspaceTemplates().find((template) => template.id === selectedTemplateId()) ?? null,
);
const handleSubmit = async () => {
const template = selectedTemplate();
if (template && props.onConfirmTemplate) {
try {
setTemplateError(null);
await props.onConfirmTemplate(template, preset(), selectedFolder());
} catch (error) {
setTemplateError(error instanceof Error ? error.message : `Failed to create ${template.name}.`);
}
return;
}
props.onConfirm(preset(), selectedFolder());
};
const content = (
<div class="ow-soft-shell flex max-h-[90vh] w-full max-w-[500px] flex-col overflow-hidden rounded-[24px] bg-[#fbfbfc]">
<div class="flex items-start justify-between gap-4 px-6 py-5">
@@ -140,6 +220,75 @@ export default function CreateWorkspaceModal(props: {
{hasSelectedFolder() ? translate("dashboard.change") : "Select folder"}
</button>
</div>
<Show when={showTemplateSection()}>
<div class="mt-4 ow-soft-card p-5">
<div class="flex items-start justify-between gap-3">
<div>
<div class="flex items-center gap-2 text-[15px] font-semibold text-dls-text">
<Boxes size={16} class="text-dls-secondary" />
Team templates
</div>
<div class="mt-1 text-[13px] text-gray-11">
Start from a template shared with {cloudSettings().activeOrgName?.trim() || "your org"}.
</div>
</div>
<Show when={templateCacheSnapshot().busy}>
<div class="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 text-[11px] font-medium text-dls-secondary shadow-[0_0_0_1px_rgba(0,0,0,0.04)]">
<Loader2 size={12} class="animate-spin" />
Syncing
</div>
</Show>
</div>
<Show when={templateError() || templateCacheSnapshot().error}>
{(value) => (
<div class="mt-4 rounded-xl border border-red-7/20 bg-red-2/30 px-3 py-2 text-xs text-red-11">
{value()}
</div>
)}
</Show>
<Show when={cloudWorkspaceTemplates().length > 0} fallback={
<div class="mt-4 rounded-xl border border-dashed border-dls-border bg-white/60 px-4 py-4 text-sm text-dls-secondary">
No shared workspace templates found for this org yet.
</div>
}>
<div class="mt-4 space-y-2">
<For each={cloudWorkspaceTemplates()}>
{(template) => {
const selected = () => selectedTemplateId() === template.id;
return (
<button
type="button"
class={`flex w-full items-center justify-between gap-3 rounded-2xl px-4 py-3 text-left transition-all ${selected() ? "bg-[#eef4ff] shadow-[0_0_0_2px_rgba(59,130,246,0.18)]" : "bg-white/70 shadow-[0_0_0_1px_rgba(0,0,0,0.04)] hover:bg-white"}`}
onClick={() => {
setTemplateError(null);
setSelectedTemplateId((current) => (current === template.id ? null : template.id));
}}
>
<div class="min-w-0">
<div class="flex items-center gap-2">
<div class="truncate text-sm font-medium text-dls-text">{template.name}</div>
<Show when={selected()}>
<span class="rounded-full bg-[#dbeafe] px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-[#1d4ed8]">
Selected
</span>
</Show>
</div>
<div class="mt-1 truncate text-[11px] text-dls-secondary">
by {templateCreatorLabel(template)} · {formatTemplateTimestamp(template.updatedAt ?? template.createdAt)}
</div>
</div>
<div class={`h-4 w-4 shrink-0 rounded-full border ${selected() ? "border-[#2563eb] bg-[#2563eb] shadow-[inset_0_0_0_3px_white]" : "border-dls-border bg-white"}`} />
</button>
);
}}
</For>
</div>
</Show>
</div>
</Show>
</div>
<div class="flex flex-col gap-3 px-6 py-5">
@@ -288,7 +437,7 @@ export default function CreateWorkspaceModal(props: {
</Show>
<button
type="button"
onClick={() => props.onConfirm(preset(), selectedFolder())}
onClick={() => void handleSubmit()}
disabled={!selectedFolder() || submitting()}
title={!selectedFolder() ? translate("dashboard.choose_folder_continue") : undefined}
class="ow-button-primary px-6 py-2 text-xs disabled:cursor-not-allowed disabled:opacity-50"

View File

@@ -15,6 +15,11 @@ import {
resolveDenBaseUrls,
writeDenSettings,
} from "../lib/den";
import {
clearDenTemplateCache,
loadDenTemplateCache,
readDenTemplateCacheSnapshot,
} from "../lib/den-template-cache";
import { usePlatform } from "../context/platform";
type DenSettingsPanelProps = {
@@ -86,7 +91,6 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
const [sessionBusy, setSessionBusy] = createSignal(false);
const [orgsBusy, setOrgsBusy] = createSignal(false);
const [workersBusy, setWorkersBusy] = createSignal(false);
const [templatesBusy, setTemplatesBusy] = createSignal(false);
const [openingWorkerId, setOpeningWorkerId] = createSignal<string | null>(null);
const [openingTemplateId, setOpeningTemplateId] = createSignal<string | null>(null);
const [user, setUser] = createSignal<{
@@ -108,12 +112,11 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
createdAt: string | null;
}>
>([]);
const [templates, setTemplates] = createSignal<DenTemplate[]>([]);
const [statusMessage, setStatusMessage] = createSignal<string | null>(null);
const [authError, setAuthError] = createSignal<string | null>(null);
const [orgsError, setOrgsError] = createSignal<string | null>(null);
const [workersError, setWorkersError] = createSignal<string | null>(null);
const [templatesError, setTemplatesError] = createSignal<string | null>(null);
const [templateActionError, setTemplateActionError] = createSignal<string | null>(null);
const activeOrg = createMemo(() => orgs().find((org) => org.id === activeOrgId()) ?? null);
const client = createMemo(() =>
@@ -121,6 +124,18 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
);
const isSignedIn = createMemo(() => Boolean(user() && authToken().trim()));
const activeOrgName = createMemo(() => activeOrg()?.name || "No org selected");
const templateCacheSnapshot = createMemo(() =>
readDenTemplateCacheSnapshot({
baseUrl: baseUrl(),
token: authToken(),
orgSlug: activeOrg()?.slug ?? null,
}),
);
const templatesBusy = createMemo(() => templateCacheSnapshot().busy);
const templates = createMemo(() => templateCacheSnapshot().templates);
const templatesError = createMemo(
() => templateActionError() ?? templateCacheSnapshot().error,
);
const summaryTone = createMemo(() => {
if (authError() || workersError() || orgsError() || templatesError()) return "error" as const;
@@ -260,15 +275,15 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
setUser(null);
setOrgs([]);
setWorkers([]);
setTemplates([]);
setActiveOrgId("");
setOrgsError(null);
setWorkersError(null);
setTemplatesError(null);
setTemplateActionError(null);
};
const clearSignedInState = (message?: string | null) => {
clearDenSession({ includeBaseUrls: !props.developerMode });
clearDenTemplateCache();
if (!props.developerMode) {
setBaseUrl(DEFAULT_DEN_BASE_URL);
setBaseUrlDraft(DEFAULT_DEN_BASE_URL);
@@ -368,16 +383,20 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
const refreshTemplates = async (quiet = false) => {
const orgSlug = activeOrg()?.slug?.trim() ?? "";
if (!authToken().trim() || !orgSlug) {
setTemplates([]);
return;
}
setTemplatesBusy(true);
if (!quiet) setTemplatesError(null);
setTemplateActionError(null);
try {
const nextTemplates = await client().listTemplates(orgSlug);
setTemplates(nextTemplates);
const nextTemplates = await loadDenTemplateCache(
{
baseUrl: baseUrl(),
token: authToken(),
orgSlug,
},
{ force: true },
);
if (!quiet) {
setStatusMessage(
nextTemplates.length > 0
@@ -386,9 +405,9 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
);
}
} catch (error) {
setTemplatesError(error instanceof Error ? error.message : "Failed to load team templates.");
} finally {
setTemplatesBusy(false);
if (!quiet) {
setTemplateActionError(error instanceof Error ? error.message : "Failed to load team templates.");
}
}
};
@@ -551,7 +570,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
if (openingTemplateId()) return;
setOpeningTemplateId(template.id);
setTemplatesError(null);
setTemplateActionError(null);
try {
await props.openCloudTemplate({
@@ -562,7 +581,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
});
setStatusMessage(`Opened ${template.name} from ${activeOrg()?.name ?? "team templates"}.`);
} catch (error) {
setTemplatesError(error instanceof Error ? error.message : `Failed to open ${template.name}.`);
setTemplateActionError(error instanceof Error ? error.message : `Failed to open ${template.name}.`);
} finally {
setOpeningTemplateId(null);
}
@@ -592,11 +611,13 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
const headerBadgeClass =
"inline-flex min-h-8 items-center gap-2 rounded-xl bg-[#f3f4f6] px-3 text-[13px] font-medium text-dls-text";
const headerStatusBadgeClass =
"inline-flex h-8 items-center justify-center gap-2 rounded-xl bg-[#f3f4f6] px-3 text-[13px] leading-none font-medium text-dls-secondary";
"inline-flex min-h-10 min-w-[132px] items-center justify-center gap-2 rounded-2xl bg-[#f3f4f6] px-4 text-center text-sm font-medium text-dls-text";
const sectionPillClass =
"inline-flex items-center gap-1.5 rounded-full bg-[#f3f4f6] px-2.5 py-1 text-[11px] font-medium text-gray-11";
const softNoticeClass =
"rounded-xl bg-[#f8fafc] px-3 py-2 text-xs text-gray-11";
const quietControlClass =
"bg-white/90 text-dls-text border border-black/8 shadow-[0_1px_2px_rgba(17,24,39,0.06)]";
return (
<div class="space-y-6">
@@ -782,7 +803,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
</div>
<Button
variant="outline"
class="h-8 px-3 text-xs shrink-0"
class={`h-10 px-4 text-sm shrink-0 ${quietControlClass}`}
onClick={() => void signOut()}
disabled={authBusy() || sessionBusy()}
>
@@ -800,7 +821,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
</div>
<div class="flex items-center gap-2 shrink-0">
<select
class="ow-input max-w-[220px] px-3 py-1.5 text-xs text-dls-text"
class={`ow-input h-10 max-w-[260px] rounded-xl px-4 py-2 text-sm font-medium text-dls-text ${quietControlClass}`}
value={activeOrgId()}
onChange={(event) => {
const nextId = event.currentTarget.value;
@@ -829,7 +850,7 @@ export default function DenSettingsPanel(props: DenSettingsPanelProps) {
</select>
<Button
variant="outline"
class="h-8 px-3 text-xs"
class={`h-10 px-4 text-sm ${quietControlClass}`}
onClick={() => void refreshOrgs()}
disabled={orgsBusy()}
>

View File

@@ -61,7 +61,7 @@ type Props = {
};
const MAX_SESSIONS_PREVIEW = 6;
const COLLAPSED_SESSIONS_PREVIEW = 1;
const COLLAPSED_SESSIONS_PREVIEW = MAX_SESSIONS_PREVIEW;
type SessionListItem = WorkspaceSessionGroup["sessions"][number];
type FlattenedSessionRow = { session: SessionListItem; depth: number };
@@ -729,29 +729,52 @@ export default function WorkspaceSessionList(props: Props) {
<div class="mt-3 px-1 pb-1">
<div class="relative flex flex-col gap-1 pl-2.5 before:absolute before:bottom-2 before:left-0 before:top-2 before:w-[2px] before:bg-gray-3 before:content-['']">
<Show
when={isWorkspaceExpanded(workspace().id)}
fallback={
<Show when={group.sessions.length > 0}>
<For
each={previewSessions(
workspace().id,
group.sessions,
tree,
forcedExpandedSessionIds,
)}
>
{(row) =>
renderSessionRow(
<Show
when={isWorkspaceExpanded(workspace().id)}
fallback={
<Show when={group.sessions.length > 0}>
<For
each={previewSessions(
workspace().id,
row,
group.sessions,
tree,
forcedExpandedSessionIds,
)}
</For>
</Show>
}
>
>
{(row) =>
renderSessionRow(
workspace().id,
row,
tree,
forcedExpandedSessionIds,
)}
</For>
<Show
when={
getRootSessions(group.sessions).length >
previewCount(workspace().id)
}
>
<button
type="button"
class="w-full rounded-[15px] border border-transparent px-3 py-2.5 text-left text-[11px] text-gray-10 transition-colors hover:bg-gray-2/60 hover:text-gray-11"
onClick={() =>
showMoreSessions(
workspace().id,
getRootSessions(group.sessions).length,
)
}
>
{showMoreLabel(
workspace().id,
getRootSessions(group.sessions).length,
)}
</button>
</Show>
</Show>
}
>
<Show
when={
group.status === "loading" &&

View File

@@ -0,0 +1,131 @@
import { createSignal } from "solid-js";
import { createDenClient, type DenTemplate } from "./den";
type DenTemplateCacheKeyInput = {
baseUrl?: string | null;
token?: string | null;
orgSlug?: string | null;
};
type DenTemplateCacheEntry = {
templates: DenTemplate[];
busy: boolean;
error: string | null;
loadedAt: number | null;
promise: Promise<DenTemplate[]> | null;
};
const templateCache = new Map<string, DenTemplateCacheEntry>();
const [templateCacheVersion, setTemplateCacheVersion] = createSignal(0);
function getCacheKey(input: DenTemplateCacheKeyInput): string | null {
const baseUrl = input.baseUrl?.trim() ?? "";
const token = input.token?.trim() ?? "";
const orgSlug = input.orgSlug?.trim() ?? "";
if (!baseUrl || !token || !orgSlug) return null;
return `${baseUrl}::${orgSlug}::${token}`;
}
function readEntry(key: string | null): DenTemplateCacheEntry {
if (!key) {
return {
templates: [],
busy: false,
error: null,
loadedAt: null,
promise: null,
};
}
return (
templateCache.get(key) ?? {
templates: [],
busy: false,
error: null,
loadedAt: null,
promise: null,
}
);
}
function writeEntry(key: string, next: DenTemplateCacheEntry) {
templateCache.set(key, next);
setTemplateCacheVersion((value) => value + 1);
}
function toMessage(error: unknown, fallback: string) {
return error instanceof Error ? error.message : fallback;
}
export function readDenTemplateCacheSnapshot(input: DenTemplateCacheKeyInput) {
templateCacheVersion();
const key = getCacheKey(input);
const entry = readEntry(key);
return {
key,
templates: entry.templates,
busy: entry.busy,
error: entry.error,
loadedAt: entry.loadedAt,
};
}
export async function loadDenTemplateCache(
input: DenTemplateCacheKeyInput,
options: { force?: boolean } = {},
): Promise<DenTemplate[]> {
const key = getCacheKey(input);
if (!key) return [];
const current = readEntry(key);
if (current.promise) {
return current.promise;
}
if (!options.force && current.loadedAt && !current.error) {
return current.templates;
}
const request = createDenClient({
baseUrl: input.baseUrl?.trim() ?? "",
token: input.token?.trim() ?? "",
})
.listTemplates(input.orgSlug?.trim() ?? "")
.then((templates) => {
writeEntry(key, {
templates,
busy: false,
error: null,
loadedAt: Date.now(),
promise: null,
});
return templates;
})
.catch((error) => {
const latest = readEntry(key);
writeEntry(key, {
templates: latest.templates,
busy: false,
error: toMessage(error, "Failed to load team templates."),
loadedAt: latest.loadedAt,
promise: null,
});
throw error;
});
writeEntry(key, {
templates: current.templates,
busy: true,
error: null,
loadedAt: current.loadedAt,
promise: request,
});
return request;
}
export function clearDenTemplateCache() {
if (templateCache.size === 0) return;
templateCache.clear();
setTemplateCacheVersion((value) => value + 1);
}

View File

@@ -170,7 +170,7 @@ export function AuthScreen() {
return (
<section className="den-page flex w-full items-center py-4 lg:min-h-[calc(100vh-2.5rem)]">
<div className="grid w-full gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(360px,440px)]">
<div className="flex flex-col gap-6">
<div className="order-2 flex flex-col gap-6 lg:order-1">
<div className="relative min-h-[300px] overflow-hidden rounded-[32px] border border-gray-100 px-7 py-8 md:px-10 md:py-10">
<div className="absolute inset-0 z-0">
<Dithering
@@ -191,7 +191,7 @@ export function AuthScreen() {
grainMixer={0}
grainOverlay={0}
frame={176868.9}
colors={["#E0FCFF", "#3B82F6", "#7C3AED", "#50F7D4"]}
colors={["#0F172A", "#1E40AF", "#4C1D95", "#0F766E"]}
style={{ width: "100%", height: "100%" }}
/>
</Dithering>
@@ -199,7 +199,7 @@ export function AuthScreen() {
<div className="relative z-10 flex h-full flex-col justify-between gap-10">
<div className="flex items-center gap-3">
<img src="/openwork-mark.svg" alt="OpenWork" className="h-9 w-auto" />
<img src="/openwork-logo-transparent.svg" alt="OpenWork" className="h-9 w-auto" />
<span className="text-[13px] font-medium text-white/80">OpenWork Cloud</span>
</div>
@@ -223,23 +223,24 @@ export function AuthScreen() {
body="Package skills, MCPs, plugins, and config once so the whole org can use the same setup."
/>
<FeatureCard
title="Shared workspaces"
title="Cloud Hosted Agents"
body="Keep selected workflows running in the cloud without asking each teammate to run them locally."
/>
<FeatureCard
title="Provider control"
body="Roll into standardized model access and billing controls as your team grows into Cloud."
title="Custom LLM Providers"
body="Whether you want to use LiteLLM, Azure, or any other provider, you can use OpenWork to provision your team."
/>
</div>
</div>
{hasResolvedSession ? (
<LoadingPanel
title="Redirecting to your workspace."
body="We found your account and are sending you to the right Cloud destination now."
/>
) : (
<div className="rounded-[28px] border border-gray-100 bg-white p-6 shadow-[0_10px_30px_-24px_rgba(15,23,42,0.22)] md:p-7">
<div className="order-1 lg:order-2">
{hasResolvedSession ? (
<LoadingPanel
title="Redirecting to your workspace."
body="We found your account and are sending you to the right Cloud destination now."
/>
) : (
<div className="rounded-[28px] border border-gray-100 bg-white p-6 shadow-[0_10px_30px_-24px_rgba(15,23,42,0.22)] md:p-7">
<div className="grid gap-2">
<p className="text-[11px] font-semibold uppercase tracking-[0.18em] text-gray-400">
Account
@@ -445,7 +446,8 @@ export function AuthScreen() {
</div>
) : null}
</div>
)}
)}
</div>
</div>
</section>
);

View File

@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@@ -5,7 +5,7 @@ const POSTHOG_PROXY_PATH = "/ow";
const POSTHOG_API_HOST = "us.i.posthog.com";
const POSTHOG_ASSETS_HOST = "us-assets.i.posthog.com";
export function middleware(request: NextRequest) {
export function proxy(request: NextRequest) {
const url = request.nextUrl.clone();
const { pathname } = url;

View File

@@ -0,0 +1,12 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_182_19)">
<path d="M612.283 48.1887C625.496 46.0953 647.01 49.7669 659.498 54.1301C674.701 59.4419 689.252 67.9192 703.298 75.7141C718.677 84.3343 734.102 92.872 749.572 101.326C768.927 111.916 788.228 122.606 807.473 133.396C840.683 151.785 870.254 166.532 883.97 205.384C893.264 231.712 891.051 259.314 891.044 286.774L891.035 375.676L891.027 510.719C891.027 548.514 892.711 592.842 885.28 629.363C877.45 668.196 860.873 704.734 836.807 736.199C819.076 759.099 797.811 779.03 773.812 795.243C753.081 809.457 728.663 821.54 706.376 833.38L617.657 880.867L519.187 933.624C490.696 948.991 465.95 966.39 433.129 970.741C420.834 972.69 402.72 969.583 391.229 965.263C381.115 961.46 370.688 955.51 361.175 950.28L324.758 930.253L256.121 892.792C236.955 882.325 217.038 872.18 199.807 858.867C174.606 839.396 159.573 811.138 156.147 779.58C153.961 759.436 154.618 735.47 154.621 714.854L154.558 621.205L154.822 470.301C155.178 436.128 153.699 403.213 160.327 369.35C166.568 336.451 180.643 305.541 201.361 279.235C215.839 261.25 232.92 245.526 252.038 232.581C269.567 220.687 292.408 208.988 311.365 198.724L398.702 151.378L502.977 95.0881L543.077 73.4368C567.921 60.1065 583.278 51.3671 612.283 48.1887ZM833.058 194.304C823.696 186.764 810.462 183.336 798.57 184.177C771.778 187.62 742.31 206.444 718.286 219.565L628.893 268.315C589.049 289.644 549.367 311.277 509.854 333.213C493.187 342.415 468.999 354.487 453.64 364.719C437.593 375.25 423.409 388.377 411.669 403.562C393.449 427.391 381.387 455.347 376.551 484.951C373.498 502.539 373.265 522.833 373.19 540.702L373.144 586.463L373.108 737.774L373.06 834.782C373.036 850.93 372.755 867.066 373.301 883.201C374.493 918.481 396.493 935.629 430.624 933.974C454.078 931.219 483.649 912.278 504.852 900.772L597.104 850.665L686.12 802.92C707.445 791.522 728.945 780.569 749.24 767.331C798.823 734.99 835.505 681.466 848.284 623.706C855.778 589.832 854.001 552.144 854.009 517.346L854.081 399.85C854.507 364.925 854.64 329.996 854.48 295.069C854.474 284.199 854.406 273.352 854.338 262.486C854.188 238.496 853.425 210.709 833.058 194.304ZM699.028 114.926C675.263 101.784 646.691 83.1971 618.776 85.2073C589.573 87.7692 569.517 101.526 544.458 115.132L465.686 158.248L345.258 223.919C316.332 239.825 284.352 255.692 258.192 275.931C226.008 300.832 206.21 336.477 198.533 376.138C192.736 406.084 194.036 436.541 193.35 466.938C192.258 519.066 193.754 571.457 193.968 623.546L194.003 719.207C194.002 757.248 188.736 801.436 221.267 828.343C231.847 837.093 242.715 842.79 254.608 849.344L288.732 868.112L319.865 885.469C324.589 888.118 331.038 892.047 335.685 894.323L335.066 888.161C333.868 859.228 334.498 827.775 334.504 798.64L334.711 648.9L334.819 558.867C334.801 526.582 333.303 498.295 340.325 466.563C348.141 430.493 365.381 397.139 390.288 369.904C418.661 339.049 446.341 325.558 482.31 306.174L539.163 275.25L694.589 190.489L740.329 165.625C748.779 161.046 757.55 156.593 765.789 151.719L699.028 114.926Z" fill="white"/>
<path d="M612.283 48.1887C625.496 46.0953 647.01 49.7669 659.498 54.1301C674.701 59.4419 689.252 67.9192 703.298 75.7141C718.677 84.3343 734.102 92.872 749.572 101.326C768.927 111.916 788.228 122.606 807.473 133.396C840.683 151.785 870.254 166.532 883.97 205.384C893.264 231.712 891.051 259.314 891.044 286.774L891.035 375.676L891.027 510.719C891.027 548.514 892.711 592.842 885.28 629.363C877.45 668.196 860.873 704.734 836.807 736.199C819.076 759.099 797.811 779.03 773.812 795.243C753.081 809.457 728.663 821.54 706.376 833.38L617.657 880.867L519.187 933.624C490.696 948.991 465.95 966.39 433.129 970.741C420.834 972.69 402.72 969.583 391.229 965.263C381.115 961.46 370.688 955.51 361.175 950.28L324.758 930.253L256.121 892.792C236.955 882.325 217.038 872.18 199.807 858.867C174.606 839.396 159.573 811.138 156.147 779.58C153.961 759.436 154.618 735.47 154.621 714.854L154.558 621.205L154.822 470.301C155.178 436.128 153.699 403.213 160.327 369.35C166.568 336.451 180.643 305.541 201.361 279.235C215.839 261.25 232.92 245.526 252.038 232.581C269.567 220.687 292.408 208.988 311.365 198.724L398.702 151.378L502.977 95.0881L543.077 73.4368C567.921 60.1065 583.278 51.3671 612.283 48.1887ZM833.058 194.304C823.696 186.764 810.462 183.336 798.57 184.177C771.778 187.62 742.31 206.444 718.286 219.565L628.893 268.315C589.049 289.644 549.367 311.277 509.854 333.213C493.187 342.415 468.999 354.487 453.64 364.719C437.593 375.25 423.409 388.377 411.669 403.562C393.449 427.391 381.387 455.347 376.551 484.951C373.498 502.539 373.265 522.833 373.19 540.702L373.144 586.463L373.108 737.774L373.06 834.782C373.036 850.93 372.755 867.066 373.301 883.201C374.493 918.481 396.493 935.629 430.624 933.974C454.078 931.219 483.649 912.278 504.852 900.772L597.104 850.665L686.12 802.92C707.445 791.522 728.945 780.569 749.24 767.331C798.823 734.99 835.505 681.466 848.284 623.706C855.778 589.832 854.001 552.144 854.009 517.346L854.081 399.85C854.507 364.925 854.64 329.996 854.48 295.069C854.474 284.199 854.406 273.352 854.338 262.486C854.188 238.496 853.425 210.709 833.058 194.304ZM699.028 114.926C675.263 101.784 646.691 83.1971 618.776 85.2073C589.573 87.7692 569.517 101.526 544.458 115.132L465.686 158.248L345.258 223.919C316.332 239.825 284.352 255.692 258.192 275.931C226.008 300.832 206.21 336.477 198.533 376.138C192.736 406.084 194.036 436.541 193.35 466.938C192.258 519.066 193.754 571.457 193.968 623.546L194.003 719.207C194.002 757.248 188.736 801.436 221.267 828.343C231.847 837.093 242.715 842.79 254.608 849.344L288.732 868.112L319.865 885.469C324.589 888.118 331.038 892.047 335.685 894.323L335.066 888.161C333.868 859.228 334.498 827.775 334.504 798.64L334.711 648.9L334.819 558.867C334.801 526.582 333.303 498.295 340.325 466.563C348.141 430.493 365.381 397.139 390.288 369.904C418.661 339.049 446.341 325.558 482.31 306.174L539.163 275.25L694.589 190.489L740.329 165.625C748.779 161.046 757.55 156.593 765.789 151.719L699.028 114.926Z" stroke="black"/>
<path d="M707.136 316.628C721.059 315.692 731.348 318.341 738.451 324.629C745.562 330.925 749.575 340.951 750.75 354.977C752.107 371.153 751.571 386.95 751.565 403.22L751.606 477.994L751.608 538.359V538.36C751.633 572.695 751.503 598.89 737.268 631.095C728.146 651.702 714.527 670.004 697.41 684.661L697.409 684.662C676.943 702.314 659.598 710.334 635.98 722.932L635.978 722.933L586.056 749.815L544.081 772.435C534.514 777.579 525.114 783.188 515.06 787.067C513.944 787.441 512.739 787.881 511.654 788.086C499.816 790.33 491.415 788.801 485.444 784.882C479.468 780.96 475.838 774.585 473.666 766.955C471.493 759.325 470.794 750.489 470.643 741.717C470.492 732.925 470.89 724.301 470.896 716.971L470.927 647.705L470.959 556.256L470.955 552.466C470.837 513.478 469.04 479.71 492.344 445.513C510.52 418.843 533.546 407.36 560.949 392.612L560.951 392.61L602.102 370.225H602.103L660.639 338.258C667.632 334.456 675.45 329.791 683.469 325.685C691.468 321.589 699.592 318.09 707.136 316.628ZM713.285 496.62C644.143 531.96 575.912 571.385 507.254 607.595L506.988 607.736V608.036C506.983 613.716 506.787 647.908 506.725 681.064C506.694 697.638 506.696 713.95 506.774 726.3C506.813 732.475 506.871 737.661 506.952 741.396C506.992 743.262 507.039 744.77 507.092 745.858C507.119 746.401 507.147 746.845 507.178 747.178C507.194 747.344 507.21 747.488 507.228 747.606C507.244 747.713 507.266 747.832 507.301 747.929L507.353 748.069L507.472 748.158L507.937 748.503L508.195 748.695L508.477 748.54L607.909 693.874L607.91 693.873C618.778 687.883 631.73 681.28 644.136 674.15C656.55 667.015 668.482 659.315 677.361 651.088C692.481 637.21 703.503 619.447 709.22 599.735C711.523 591.803 713.05 583.665 713.78 575.437C714.03 572.658 714.449 554.214 714.645 535.938C714.743 526.793 714.786 517.678 714.724 510.568C714.693 507.013 714.635 503.956 714.545 501.646C714.501 500.491 714.448 499.519 714.385 498.762C714.324 498.021 714.251 497.445 714.156 497.112L714.125 497.002L714.049 496.917L713.886 496.734L713.629 496.444L713.285 496.62ZM711.494 352.851C709.637 352.076 708.003 352.436 706.092 352.578L706.015 352.583L705.944 352.612C698.889 355.425 686.232 362.852 679.152 366.748H679.151L629.312 394.263L569.9 426.668C547.09 439.062 525.912 446.909 515.215 472.603C506.341 493.917 506.996 512.768 506.979 535.002L506.955 567.674V568.538L507.704 568.109L514.16 564.408L651.968 489.63H651.969L694.011 466.743C699.714 463.658 709.097 458.251 714.832 455.792L715.136 455.662L715.135 455.33C715.038 434.764 715.049 414.198 715.169 393.633V393.63C715.172 389.759 715.464 380.757 715.231 372.222C715.115 367.951 714.866 363.773 714.382 360.388C714.14 358.696 713.837 357.189 713.458 355.96C713.082 354.746 712.613 353.744 712.004 353.12L711.945 353.06L711.868 353.022L711.494 352.851Z" fill="white" stroke="white"/>
</g>
<defs>
<clipPath id="clip0_182_19">
<rect width="1024" height="1024" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,12 @@
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_182_19)">
<path d="M612.283 48.1887C625.496 46.0953 647.01 49.7669 659.498 54.1301C674.701 59.4419 689.252 67.9192 703.298 75.7141C718.677 84.3343 734.102 92.872 749.572 101.326C768.927 111.916 788.228 122.606 807.473 133.396C840.683 151.785 870.254 166.532 883.97 205.384C893.264 231.712 891.051 259.314 891.044 286.774L891.035 375.676L891.027 510.719C891.027 548.514 892.711 592.842 885.28 629.363C877.45 668.196 860.873 704.734 836.807 736.199C819.076 759.099 797.811 779.03 773.812 795.243C753.081 809.457 728.663 821.54 706.376 833.38L617.657 880.867L519.187 933.624C490.696 948.991 465.95 966.39 433.129 970.741C420.834 972.69 402.72 969.583 391.229 965.263C381.115 961.46 370.688 955.51 361.175 950.28L324.758 930.253L256.121 892.792C236.955 882.325 217.038 872.18 199.807 858.867C174.606 839.396 159.573 811.138 156.147 779.58C153.961 759.436 154.618 735.47 154.621 714.854L154.558 621.205L154.822 470.301C155.178 436.128 153.699 403.213 160.327 369.35C166.568 336.451 180.643 305.541 201.361 279.235C215.839 261.25 232.92 245.526 252.038 232.581C269.567 220.687 292.408 208.988 311.365 198.724L398.702 151.378L502.977 95.0881L543.077 73.4368C567.921 60.1065 583.278 51.3671 612.283 48.1887ZM833.058 194.304C823.696 186.764 810.462 183.336 798.57 184.177C771.778 187.62 742.31 206.444 718.286 219.565L628.893 268.315C589.049 289.644 549.367 311.277 509.854 333.213C493.187 342.415 468.999 354.487 453.64 364.719C437.593 375.25 423.409 388.377 411.669 403.562C393.449 427.391 381.387 455.347 376.551 484.951C373.498 502.539 373.265 522.833 373.19 540.702L373.144 586.463L373.108 737.774L373.06 834.782C373.036 850.93 372.755 867.066 373.301 883.201C374.493 918.481 396.493 935.629 430.624 933.974C454.078 931.219 483.649 912.278 504.852 900.772L597.104 850.665L686.12 802.92C707.445 791.522 728.945 780.569 749.24 767.331C798.823 734.99 835.505 681.466 848.284 623.706C855.778 589.832 854.001 552.144 854.009 517.346L854.081 399.85C854.507 364.925 854.64 329.996 854.48 295.069C854.474 284.199 854.406 273.352 854.338 262.486C854.188 238.496 853.425 210.709 833.058 194.304ZM699.028 114.926C675.263 101.784 646.691 83.1971 618.776 85.2073C589.573 87.7692 569.517 101.526 544.458 115.132L465.686 158.248L345.258 223.919C316.332 239.825 284.352 255.692 258.192 275.931C226.008 300.832 206.21 336.477 198.533 376.138C192.736 406.084 194.036 436.541 193.35 466.938C192.258 519.066 193.754 571.457 193.968 623.546L194.003 719.207C194.002 757.248 188.736 801.436 221.267 828.343C231.847 837.093 242.715 842.79 254.608 849.344L288.732 868.112L319.865 885.469C324.589 888.118 331.038 892.047 335.685 894.323L335.066 888.161C333.868 859.228 334.498 827.775 334.504 798.64L334.711 648.9L334.819 558.867C334.801 526.582 333.303 498.295 340.325 466.563C348.141 430.493 365.381 397.139 390.288 369.904C418.661 339.049 446.341 325.558 482.31 306.174L539.163 275.25L694.589 190.489L740.329 165.625C748.779 161.046 757.55 156.593 765.789 151.719L699.028 114.926Z" fill="black"/>
<path d="M612.283 48.1887C625.496 46.0953 647.01 49.7669 659.498 54.1301C674.701 59.4419 689.252 67.9192 703.298 75.7141C718.677 84.3343 734.102 92.872 749.572 101.326C768.927 111.916 788.228 122.606 807.473 133.396C840.683 151.785 870.254 166.532 883.97 205.384C893.264 231.712 891.051 259.314 891.044 286.774L891.035 375.676L891.027 510.719C891.027 548.514 892.711 592.842 885.28 629.363C877.45 668.196 860.873 704.734 836.807 736.199C819.076 759.099 797.811 779.03 773.812 795.243C753.081 809.457 728.663 821.54 706.376 833.38L617.657 880.867L519.187 933.624C490.696 948.991 465.95 966.39 433.129 970.741C420.834 972.69 402.72 969.583 391.229 965.263C381.115 961.46 370.688 955.51 361.175 950.28L324.758 930.253L256.121 892.792C236.955 882.325 217.038 872.18 199.807 858.867C174.606 839.396 159.573 811.138 156.147 779.58C153.961 759.436 154.618 735.47 154.621 714.854L154.558 621.205L154.822 470.301C155.178 436.128 153.699 403.213 160.327 369.35C166.568 336.451 180.643 305.541 201.361 279.235C215.839 261.25 232.92 245.526 252.038 232.581C269.567 220.687 292.408 208.988 311.365 198.724L398.702 151.378L502.977 95.0881L543.077 73.4368C567.921 60.1065 583.278 51.3671 612.283 48.1887ZM833.058 194.304C823.696 186.764 810.462 183.336 798.57 184.177C771.778 187.62 742.31 206.444 718.286 219.565L628.893 268.315C589.049 289.644 549.367 311.277 509.854 333.213C493.187 342.415 468.999 354.487 453.64 364.719C437.593 375.25 423.409 388.377 411.669 403.562C393.449 427.391 381.387 455.347 376.551 484.951C373.498 502.539 373.265 522.833 373.19 540.702L373.144 586.463L373.108 737.774L373.06 834.782C373.036 850.93 372.755 867.066 373.301 883.201C374.493 918.481 396.493 935.629 430.624 933.974C454.078 931.219 483.649 912.278 504.852 900.772L597.104 850.665L686.12 802.92C707.445 791.522 728.945 780.569 749.24 767.331C798.823 734.99 835.505 681.466 848.284 623.706C855.778 589.832 854.001 552.144 854.009 517.346L854.081 399.85C854.507 364.925 854.64 329.996 854.48 295.069C854.474 284.199 854.406 273.352 854.338 262.486C854.188 238.496 853.425 210.709 833.058 194.304ZM699.028 114.926C675.263 101.784 646.691 83.1971 618.776 85.2073C589.573 87.7692 569.517 101.526 544.458 115.132L465.686 158.248L345.258 223.919C316.332 239.825 284.352 255.692 258.192 275.931C226.008 300.832 206.21 336.477 198.533 376.138C192.736 406.084 194.036 436.541 193.35 466.938C192.258 519.066 193.754 571.457 193.968 623.546L194.003 719.207C194.002 757.248 188.736 801.436 221.267 828.343C231.847 837.093 242.715 842.79 254.608 849.344L288.732 868.112L319.865 885.469C324.589 888.118 331.038 892.047 335.685 894.323L335.066 888.161C333.868 859.228 334.498 827.775 334.504 798.64L334.711 648.9L334.819 558.867C334.801 526.582 333.303 498.295 340.325 466.563C348.141 430.493 365.381 397.139 390.288 369.904C418.661 339.049 446.341 325.558 482.31 306.174L539.163 275.25L694.589 190.489L740.329 165.625C748.779 161.046 757.55 156.593 765.789 151.719L699.028 114.926Z" stroke="black"/>
<path d="M707.136 316.628C721.059 315.692 731.348 318.341 738.451 324.629C745.562 330.925 749.575 340.951 750.75 354.977C752.107 371.153 751.571 386.95 751.565 403.22L751.606 477.994L751.608 538.359V538.36C751.633 572.695 751.503 598.89 737.268 631.095C728.146 651.702 714.527 670.004 697.41 684.661L697.409 684.662C676.943 702.314 659.598 710.334 635.98 722.932L635.978 722.933L586.056 749.815L544.081 772.435C534.514 777.579 525.114 783.188 515.06 787.067C513.944 787.441 512.739 787.881 511.654 788.086C499.816 790.33 491.415 788.801 485.444 784.882C479.468 780.96 475.838 774.585 473.666 766.955C471.493 759.325 470.794 750.489 470.643 741.717C470.492 732.925 470.89 724.301 470.896 716.971L470.927 647.705L470.959 556.256L470.955 552.466C470.837 513.478 469.04 479.71 492.344 445.513C510.52 418.843 533.546 407.36 560.949 392.612L560.951 392.61L602.102 370.225H602.103L660.639 338.258C667.632 334.456 675.45 329.791 683.469 325.685C691.468 321.589 699.592 318.09 707.136 316.628ZM713.285 496.62C644.143 531.96 575.912 571.385 507.254 607.595L506.988 607.736V608.036C506.983 613.716 506.787 647.908 506.725 681.064C506.694 697.638 506.696 713.95 506.774 726.3C506.813 732.475 506.871 737.661 506.952 741.396C506.992 743.262 507.039 744.77 507.092 745.858C507.119 746.401 507.147 746.845 507.178 747.178C507.194 747.344 507.21 747.488 507.228 747.606C507.244 747.713 507.266 747.832 507.301 747.929L507.353 748.069L507.472 748.158L507.937 748.503L508.195 748.695L508.477 748.54L607.909 693.874L607.91 693.873C618.778 687.883 631.73 681.28 644.136 674.15C656.55 667.015 668.482 659.315 677.361 651.088C692.481 637.21 703.503 619.447 709.22 599.735C711.523 591.803 713.05 583.665 713.78 575.437C714.03 572.658 714.449 554.214 714.645 535.938C714.743 526.793 714.786 517.678 714.724 510.568C714.693 507.013 714.635 503.956 714.545 501.646C714.501 500.491 714.448 499.519 714.385 498.762C714.324 498.021 714.251 497.445 714.156 497.112L714.125 497.002L714.049 496.917L713.886 496.734L713.629 496.444L713.285 496.62ZM711.494 352.851C709.637 352.076 708.003 352.436 706.092 352.578L706.015 352.583L705.944 352.612C698.889 355.425 686.232 362.852 679.152 366.748H679.151L629.312 394.263L569.9 426.668C547.09 439.062 525.912 446.909 515.215 472.603C506.341 493.917 506.996 512.768 506.979 535.002L506.955 567.674V568.538L507.704 568.109L514.16 564.408L651.968 489.63H651.969L694.011 466.743C699.714 463.658 709.097 458.251 714.832 455.792L715.136 455.662L715.135 455.33C715.038 434.764 715.049 414.198 715.169 393.633V393.63C715.172 389.759 715.464 380.757 715.231 372.222C715.115 367.951 714.866 363.773 714.382 360.388C714.14 358.696 713.837 357.189 713.458 355.96C713.082 354.746 712.613 353.744 712.004 353.12L711.945 353.06L711.868 353.022L711.494 352.851Z" fill="black" stroke="black"/>
</g>
<defs>
<clipPath id="clip0_182_19">
<rect width="1024" height="1024" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB