diff --git a/frontend/src/lib/components/details/CopyableCodeBlock.svelte b/frontend/src/lib/components/details/CopyableCodeBlock.svelte index 74efc33451..e99089bbbd 100644 --- a/frontend/src/lib/components/details/CopyableCodeBlock.svelte +++ b/frontend/src/lib/components/details/CopyableCodeBlock.svelte @@ -9,9 +9,19 @@ language: LanguageType disabled?: boolean wrap?: boolean + copyOnClick?: boolean } - let { code, language, disabled = false, wrap = false }: Props = $props() + let { code, language, disabled = false, wrap = false, copyOnClick = true }: Props = $props() + + function copyCode(event?: MouseEvent) { + if (disabled) { + return + } + event?.preventDefault() + event?.stopPropagation() + copyToClipboard(code) + } @@ -19,22 +29,24 @@
{ - if (disabled) { - return + if (copyOnClick) { + copyCode(e) } - e.preventDefault() - copyToClipboard(code) }} > -
- +
+
+ +
diff --git a/frontend/src/lib/components/home/HomeConnectDrawer.svelte b/frontend/src/lib/components/home/HomeConnectDrawer.svelte new file mode 100644 index 0000000000..e7e4a306d9 --- /dev/null +++ b/frontend/src/lib/components/home/HomeConnectDrawer.svelte @@ -0,0 +1,133 @@ + + + + +
+
+
+ + + + {#snippet content()} +
+ +
+
+
+

Local setup

+

+ Run this in your local repo to bind the current workspace, create + wmill.yaml, and pull the latest files. +

+
+ + +
+ + + +

+ wmill workspace add + will handle authentication, + wmill init + bootstraps the local config, and + wmill sync pull + fetches the workspace content. +

+
+
+ + +
+
+
+

MCP URL

+

+ Generate an MCP server URL for the current workspace and choose which + scripts, flows, and endpoints the client can access. +

+
+ + +
+ + {#key openVersion} + + {/key} +
+
+
+ {/snippet} +
+
+
+
+
+
diff --git a/frontend/src/lib/components/settings/CreateToken.svelte b/frontend/src/lib/components/settings/CreateToken.svelte index b4f273869b..ad54ddad82 100644 --- a/frontend/src/lib/components/settings/CreateToken.svelte +++ b/frontend/src/lib/components/settings/CreateToken.svelte @@ -15,6 +15,9 @@ interface Props { showMcpMode?: boolean openWithMcpMode?: boolean + mcpOnly?: boolean + lockWorkspace?: boolean + title?: string newTokenLabel?: string defaultNewTokenWorkspace?: string scopes?: string[] @@ -24,6 +27,10 @@ let { showMcpMode = false, + openWithMcpMode = false, + mcpOnly = false, + lockWorkspace = false, + title = 'Add a new token', defaultNewTokenWorkspace, scopes, onTokenCreated, @@ -37,6 +44,8 @@ let newTokenWorkspace = $state(untrack(() => defaultNewTokenWorkspace)) let mcpCreationMode = $state(false) let mcpScope = $state('mcp:favorites') + let lastRequestedMcpMode = $state(undefined) + let mcpLabelAutofilled = $state(false) let customScopes = $state([]) let showCustomScopes = $state(false) @@ -55,6 +64,31 @@ return [{ id: currentWorkspace, name: currentWorkspace }, ...workspacesList] } + function enterMcpMode() { + mcpCreationMode = true + newTokenExpiration = undefined + newTokenWorkspace = defaultNewTokenWorkspace ?? $workspaceStore + newToken = undefined + newMcpToken = undefined + if (!newTokenLabel) { + newTokenLabel = 'MCP token' + mcpLabelAutofilled = true + } else { + mcpLabelAutofilled = false + } + } + + function exitMcpMode() { + mcpCreationMode = false + newTokenExpiration = undefined + newTokenWorkspace = defaultNewTokenWorkspace + newMcpToken = undefined + if (mcpLabelAutofilled) { + newTokenLabel = undefined + } + mcpLabelAutofilled = false + } + async function createToken(mcpMode: boolean = false): Promise { try { let date: Date | undefined @@ -79,13 +113,17 @@ }) if (mcpMode) { + newToken = undefined newMcpToken = `${createdToken}` } else { + newMcpToken = undefined newToken = `${createdToken}` } - onTokenCreated(newToken ?? newMcpToken ?? '') - mcpCreationMode = false + onTokenCreated(`${createdToken}`) + if (!mcpOnly) { + mcpCreationMode = false + } } catch (err) { console.error('Failed to create token:', err) } @@ -93,13 +131,34 @@ const workspaces = $derived(ensureCurrentWorkspaceIncluded($userWorkspaces, $workspaceStore)) const mcpBaseUrl = $derived(`${window.location.origin}/api/mcp/w/${newTokenWorkspace}/mcp?token=`) + + $effect(() => { + const requestedMcpMode = mcpOnly || openWithMcpMode + if (requestedMcpMode === lastRequestedMcpMode) { + return + } + + if (requestedMcpMode) { + enterMcpMode() + } else { + exitMcpMode() + } + + lastRequestedMcpMode = requestedMcpMode + }) + + $effect(() => { + if (mcpLabelAutofilled && newTokenLabel !== 'MCP token') { + mcpLabelAutofilled = false + } + })
-

Add a new token

+

{title}

- {#if showMcpMode} + {#if showMcpMode && !mcpOnly}
{ - mcpCreationMode = e.detail if (e.detail) { - newTokenLabel = 'MCP token' - newTokenExpiration = undefined - newTokenWorkspace = $workspaceStore + enterMcpMode() } else { - newTokenLabel = undefined - newTokenExpiration = undefined - newTokenWorkspace = defaultNewTokenWorkspace + exitMcpMode() } }} checked={mcpCreationMode} @@ -162,70 +216,76 @@ {/if}
- {#if mcpCreationMode} -
- -
+ {#if mcpCreationMode} +
+ +
-
- Workspace - ({ label: w.name, value: w.id, subtitle: w.id }))} + /> +
+ {/if} + {/if} -
- Label (optional) - -
+ {#if !mcpOnly} +
+ Label (optional) + +
+ {/if} - {#if !mcpCreationMode} -
- Expires In (optional) - +
+ {/if}
- + {#if !mcpOnly} + + {/if}
diff --git a/frontend/src/routes/(root)/(logged)/+page.svelte b/frontend/src/routes/(root)/(logged)/+page.svelte index 9f7c9b493c..b2c53164e3 100644 --- a/frontend/src/routes/(root)/(logged)/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/+page.svelte @@ -13,6 +13,7 @@ import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte' import PickHubFlow from '$lib/components/flows/pickers/PickHubFlow.svelte' import HighlightCode from '$lib/components/HighlightCode.svelte' + import HomeConnectDrawer from '$lib/components/home/HomeConnectDrawer.svelte' import { Building, ExternalLink, @@ -20,7 +21,8 @@ Globe2, Loader2, Code, - LayoutDashboard + LayoutDashboard, + PlugZap } from 'lucide-svelte' import { hubBaseUrlStore } from '$lib/stores' import { base } from '$lib/base' @@ -98,6 +100,7 @@ } let workspaceTutorials: WorkspaceTutorials | undefined = $state(undefined) + let homeConnectDrawer: HomeConnectDrawer | undefined = $state(undefined) // Provide workspaceTutorials to child components via a reactive wrapper let workspaceTutorialsContext = $derived(workspaceTutorials) @@ -283,8 +286,18 @@ title="Home" childrenWrapperDivClasses="flex-1 flex flex-row gap-4 flex-wrap justify-end items-center" > + {#if $userStore?.operator} + + {/if} {#if !$userStore?.operator && showCreateButtons} - Create a {#if HOME_SHOW_CREATE_FLOW}{/if} {#if HOME_SHOW_CREATE_APP}{/if} @@ -296,13 +309,25 @@ showCreateButtons = v}/> {#if !$userStore?.operator} -
- - - {#if HOME_SHOW_HUB} - - {/if} - +
+
+ + + {#if HOME_SHOW_HUB} + + {/if} + +
+ +
{/if} {#if tab == 'hub'} @@ -374,3 +399,4 @@
+