handoff-app 2.0.0-8 → 2.0.0
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/{Changelog.md → CHANGELOG.md} +85 -1
- package/{License.md → LICENSE.md} +1 -1
- package/README.md +816 -393
- package/RECIPES.md +975 -0
- package/UPGRADE.md +57 -9
- package/components.json +20 -20
- package/config/docs/foundations/colors/guidelines.md +0 -1
- package/config/docs/foundations/colors.md +0 -1
- package/config/docs/foundations/effects/guidelines.md +0 -1
- package/config/docs/foundations/effects/resources.md +0 -1
- package/config/docs/foundations/effects.md +0 -1
- package/config/docs/foundations/fonts.md +0 -1
- package/config/docs/foundations/grid.md +0 -1
- package/config/docs/foundations/icon.md +0 -1
- package/config/docs/foundations/icons/guidelines.md +0 -1
- package/config/docs/foundations/icons.md +0 -1
- package/config/docs/foundations/images/guidelines.md +0 -1
- package/config/docs/foundations/images/resources.md +0 -1
- package/config/docs/foundations/images.md +0 -1
- package/config/docs/foundations/logo/resources.md +0 -1
- package/config/docs/foundations/logo.md +0 -1
- package/config/docs/foundations/typography/guidelines.md +0 -1
- package/config/docs/foundations/typography/resources.md +0 -1
- package/config/docs/foundations/typography.md +0 -1
- package/config/docs/foundations.md +0 -1
- package/config/docs/guidelines.md +0 -1
- package/config/docs/index.md +0 -1
- package/config/docs/system/tokens/foundations/colors.md +0 -4
- package/config/docs/system/tokens/foundations/effects.md +0 -4
- package/config/docs/system/tokens/foundations/typography.md +0 -2
- package/config/docs/system.md +0 -2
- package/config/templates/page.md +2 -1
- package/config/templates/page.mdx +2 -1
- package/dist/ai/connections.d.ts +66 -0
- package/dist/ai/connections.js +149 -0
- package/dist/ai/define.d.ts +10 -0
- package/dist/ai/define.js +13 -0
- package/dist/ai/types.d.ts +26 -0
- package/dist/ai/types.js +10 -0
- package/dist/app-builder/build.d.ts +14 -7
- package/dist/app-builder/build.js +195 -66
- package/dist/app-builder/client-config.js +3 -2
- package/dist/app-builder/paths.d.ts +0 -12
- package/dist/app-builder/paths.js +3 -20
- package/dist/app-builder/static-export.js +1 -1
- package/dist/app-builder/vercel-output.d.ts +1 -1
- package/dist/app-builder/vercel-output.js +5 -4
- package/dist/cache/build-cache.d.ts +27 -4
- package/dist/cache/build-cache.js +114 -47
- package/dist/cache/build-inputs.d.ts +13 -0
- package/dist/cache/build-inputs.js +19 -0
- package/dist/cache/index.d.ts +2 -1
- package/dist/cache/index.js +5 -1
- package/dist/catalog/normalize.js +3 -0
- package/dist/catalog/previews.js +5 -4
- package/dist/catalog/types.d.ts +8 -0
- package/dist/cli/create.js +1 -2
- package/dist/commands/build/index.d.ts +1 -1
- package/dist/commands/build/index.js +1 -1
- package/dist/commands/utils.d.ts +3 -3
- package/dist/config/defaults.js +7 -4
- package/dist/config/from-env.d.ts +4 -0
- package/dist/config/from-env.js +38 -1
- package/dist/config/helpers.js +2 -2
- package/dist/config/index.d.ts +0 -1
- package/dist/config/index.js +1 -4
- package/dist/config/loader.js +28 -2
- package/dist/config/resolve-env.d.ts +2 -1
- package/dist/config/resolve-env.js +28 -12
- package/dist/config/runtime.js +9 -11
- package/dist/handlebars.d.ts +10 -2
- package/dist/index.d.ts +12 -7
- package/dist/index.js +18 -22
- package/dist/nav/index.d.ts +2 -2
- package/dist/nav/registry.d.ts +3 -3
- package/dist/nav/registry.js +1 -1
- package/dist/nav/workspace.d.ts +3 -3
- package/dist/nav/workspace.js +1 -1
- package/dist/pipeline/figma.d.ts +13 -2
- package/dist/pipeline/figma.js +86 -46
- package/dist/pipeline/styles.js +1 -1
- package/dist/pipeline/validation.d.ts +1 -1
- package/dist/pipeline/validation.js +3 -3
- package/dist/providers.d.ts +10 -0
- package/dist/providers.js +13 -0
- package/dist/registry/asset-storage/define.d.ts +5 -5
- package/dist/registry/asset-storage/define.js +4 -4
- package/dist/registry/asset-storage/resolve.d.ts +9 -19
- package/dist/registry/asset-storage/resolve.js +33 -21
- package/dist/registry/asset-storage/types.d.ts +37 -17
- package/dist/registry/asset-storage/types.js +5 -5
- package/dist/registry/auth/ai-keys.d.ts +32 -0
- package/dist/registry/auth/ai-keys.js +69 -0
- package/dist/registry/auth/crypto.d.ts +17 -0
- package/dist/registry/auth/crypto.js +61 -1
- package/dist/registry/auth/index.d.ts +2 -1
- package/dist/registry/auth/index.js +11 -2
- package/dist/registry/checkout/index.d.ts +2 -1
- package/dist/registry/checkout/index.js +57 -127
- package/dist/registry/client.js +53 -17
- package/dist/registry/db/driver.js +3 -3
- package/dist/registry/db/schema.d.ts +261 -3
- package/dist/registry/db/schema.js +29 -4
- package/dist/registry/email/define.d.ts +10 -0
- package/dist/registry/email/define.js +13 -0
- package/dist/registry/email/providers/smtp.d.ts +10 -0
- package/dist/registry/email/providers/smtp.js +73 -0
- package/dist/registry/email/resolve.d.ts +28 -0
- package/dist/registry/email/resolve.js +74 -0
- package/dist/registry/email/types.d.ts +24 -0
- package/dist/registry/email/types.js +6 -0
- package/dist/registry/path.d.ts +2 -0
- package/dist/registry/path.js +11 -1
- package/dist/registry/publish/package.js +8 -0
- package/dist/registry/transfer.d.ts +4 -0
- package/dist/store/registry.d.ts +3 -3
- package/dist/store/registry.js +4 -4
- package/dist/transformers/plugins/ssr-render.js +40 -15
- package/dist/transformers/preview/component/builder.d.ts +1 -9
- package/dist/transformers/preview/component/builder.js +17 -17
- package/dist/transformers/preview/component/css.d.ts +3 -1
- package/dist/transformers/preview/component/css.js +92 -51
- package/dist/transformers/preview/component/html.d.ts +3 -1
- package/dist/transformers/preview/component/html.js +6 -2
- package/dist/transformers/preview/component/javascript.d.ts +2 -1
- package/dist/transformers/preview/component/javascript.js +10 -4
- package/dist/transformers/preview/pattern/html.js +3 -1
- package/dist/transformers/preview/types.d.ts +13 -0
- package/dist/transformers/utils/build.d.ts +24 -0
- package/dist/transformers/utils/build.js +100 -1
- package/dist/transformers/utils/module.js +2 -1
- package/dist/types/config.d.ts +135 -18
- package/dist/utils/path.d.ts +1 -5
- package/dist/utils/path.js +6 -23
- package/docs/api.md +6 -4
- package/docs/cli.md +275 -54
- package/drizzle/0007_add_ai_keys.sql +11 -0
- package/drizzle/0008_add_source_dir.sql +2 -0
- package/drizzle/meta/0007_snapshot.json +2013 -0
- package/drizzle/meta/0008_snapshot.json +2025 -0
- package/drizzle/meta/_journal.json +14 -0
- package/package.json +19 -8
- package/src/ai/README.md +42 -0
- package/src/ai/connections.ts +189 -0
- package/src/ai/define.ts +15 -0
- package/src/ai/types.ts +30 -0
- package/src/app/components/AiAssistant/AiAssistantPanel.tsx +144 -0
- package/src/app/components/AiAssistant/AiAssistantProvider.tsx +215 -0
- package/src/app/components/AiAssistant/AiConversation.tsx +737 -0
- package/src/app/components/AiAssistant/AiToolResult.tsx +99 -0
- package/src/app/components/AiAssistant/dockState.ts +86 -0
- package/src/app/components/AiAssistant/toolLabel.ts +24 -0
- package/src/app/components/AiAssistant/useAiChat.ts +147 -0
- package/src/app/components/AiAssistant/useAiConnections.ts +86 -0
- package/src/app/components/AiAssistant/usePageContext.ts +63 -0
- package/src/app/components/Auth/AccountLayout.tsx +58 -19
- package/src/app/components/Auth/AuthShell.tsx +27 -33
- package/src/app/components/Auth/RegistryInstallGate.tsx +4 -8
- package/src/app/components/Auth/installStatus.ts +29 -0
- package/src/app/components/Component/PageSliceResolver.tsx +1 -0
- package/src/app/components/Component/Preview.tsx +383 -189
- package/src/app/components/Component/slices/ComponentDisplaySlice.tsx +4 -0
- package/src/app/components/Foundations/ColorGrid.tsx +13 -14
- package/src/app/components/Layout/Header.tsx +21 -15
- package/src/app/components/Layout/Main.tsx +2 -2
- package/src/app/components/Markdown/MarkdownComponents.tsx +30 -32
- package/src/app/components/McpIntegration/McpConfigDialog.tsx +12 -51
- package/src/app/components/Navigation/MobileNav.tsx +2 -2
- package/src/app/components/Navigation/SideNav.tsx +1 -15
- package/src/app/components/context/NavProvider.tsx +7 -3
- package/src/app/components/ui/attachment.tsx +50 -0
- package/src/app/components/ui/bubble.tsx +57 -0
- package/src/app/components/ui/button.tsx +3 -1
- package/src/app/components/ui/confirm-dialog.tsx +87 -0
- package/src/app/components/ui/dialog.tsx +7 -2
- package/src/app/components/ui/marker.tsx +56 -0
- package/src/app/components/ui/sidebar.tsx +3 -1
- package/src/app/components/util/index.ts +3 -12
- package/src/app/css/index.css +64 -0
- package/src/app/lib/ai/auth.ts +33 -0
- package/src/app/lib/ai/context.ts +99 -0
- package/src/app/lib/ai/model.ts +79 -0
- package/src/app/lib/ai/prompt.ts +36 -0
- package/src/app/lib/ai/resolve.ts +97 -0
- package/src/app/lib/ai/tools.ts +120 -0
- package/src/app/lib/api/methods.ts +13 -0
- package/src/app/lib/asset-storage.ts +35 -36
- package/src/app/lib/auth/api.ts +4 -12
- package/src/app/lib/auth/email.ts +96 -23
- package/src/app/lib/docs-api/page-rendering.ts +1 -1
- package/src/app/lib/docs-api/registry.ts +2 -3
- package/src/app/lib/docs-api/runtime-config.ts +47 -21
- package/src/app/lib/mcp/components.ts +523 -0
- package/src/app/lib/mcp/page-url.ts +24 -0
- package/src/app/lib/mcp/pages.ts +84 -0
- package/src/app/lib/mcp/result.ts +33 -0
- package/src/app/lib/mcp/server.ts +39 -318
- package/src/app/lib/mcp/tokens.ts +334 -0
- package/src/app/lib/mcp/tool.ts +19 -0
- package/src/app/lib/registry-api/asset-transfer.ts +119 -11
- package/src/app/lib/registry-api/errors.ts +2 -0
- package/src/app/lib/registry-api/handler.ts +7 -1
- package/src/app/lib/registry-api/transfer.ts +28 -6
- package/src/app/lib/server-module.ts +34 -0
- package/src/app/lib/utils.ts +20 -0
- package/src/app/next.config.mjs +26 -24
- package/src/app/pages/[...slug]/index.tsx +2 -1
- package/src/app/pages/_app.tsx +6 -2
- package/src/app/pages/_document.tsx +6 -0
- package/src/app/pages/account/ai.tsx +183 -0
- package/src/app/pages/account/index.tsx +68 -68
- package/src/app/pages/account/tokens.tsx +37 -7
- package/src/app/pages/account/users.tsx +75 -15
- package/src/app/pages/api/account/ai/keys/[connectionId].ts +60 -0
- package/src/app/pages/api/account/ai/keys/index.ts +20 -0
- package/src/app/pages/api/admin/users/[id]/[action].ts +80 -11
- package/src/app/pages/api/admin/users/index.ts +2 -2
- package/src/app/pages/api/ai/chat.ts +115 -0
- package/src/app/pages/api/ai/connections.ts +23 -0
- package/src/app/pages/api/auth/request-reset.ts +14 -16
- package/src/app/pages/api/docs/nav.json.ts +1 -1
- package/src/app/pages/api/install.ts +3 -3
- package/src/app/pages/api/mcp.ts +6 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/complete.ts +11 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash]/upload.ts +12 -0
- package/src/app/pages/api/registry/transfer/assets/blobs/[hash].ts +1 -1
- package/src/app/pages/cli/device.tsx +10 -5
- package/src/app/pages/foundations/colors.tsx +9 -8
- package/src/app/pages/foundations/index.tsx +3 -3
- package/src/app/pages/index.tsx +2 -2
- package/src/app/pages/install.tsx +7 -2
- package/src/app/pages/login.tsx +32 -27
- package/src/app/pages/reset-password.tsx +21 -20
- package/src/app/pages/system/component/[component]/index.tsx +2 -5
- package/src/app/pages/system/index.tsx +11 -11
- package/src/app/pages/system/pattern/[pattern]/index.tsx +2 -2
- package/src/app/pages/system/tokens/components/[component]/index.tsx +0 -4
- package/src/app-builder/README.md +2 -1
- package/src/app-builder/build.ts +218 -73
- package/src/app-builder/client-config.ts +3 -2
- package/src/app-builder/paths.ts +3 -22
- package/src/app-builder/static-export.ts +1 -1
- package/src/app-builder/vercel-output.ts +5 -4
- package/src/cache/README.md +4 -1
- package/src/cache/build-cache.ts +128 -43
- package/src/cache/build-inputs.ts +28 -0
- package/src/cache/index.ts +4 -0
- package/src/catalog/README.md +3 -0
- package/src/catalog/normalize.ts +2 -0
- package/src/catalog/previews.ts +6 -3
- package/src/catalog/types.ts +8 -0
- package/src/cli/create.ts +1 -2
- package/src/commands/build/index.ts +1 -1
- package/src/config/README.md +10 -9
- package/src/config/defaults.ts +7 -4
- package/src/config/from-env.ts +33 -0
- package/src/config/helpers.ts +1 -3
- package/src/config/index.ts +0 -3
- package/src/config/loader.ts +27 -3
- package/src/config/resolve-env.ts +28 -15
- package/src/config/runtime.ts +10 -5
- package/src/handlebars.ts +8 -2
- package/src/index.ts +33 -29
- package/src/nav/index.ts +2 -2
- package/src/nav/registry.ts +3 -3
- package/src/nav/workspace.ts +3 -3
- package/src/pipeline/README.md +1 -1
- package/src/pipeline/figma.ts +98 -49
- package/src/pipeline/styles.ts +1 -1
- package/src/pipeline/validation.ts +3 -3
- package/src/providers.ts +18 -0
- package/src/registry/asset-storage/define.ts +6 -6
- package/src/registry/asset-storage/resolve.ts +38 -37
- package/src/registry/asset-storage/types.ts +39 -17
- package/src/registry/auth/ai-keys.ts +62 -0
- package/src/registry/auth/crypto.ts +63 -1
- package/src/registry/auth/index.ts +11 -1
- package/src/registry/checkout/index.ts +60 -138
- package/src/registry/client.ts +60 -20
- package/src/registry/db/driver.ts +2 -2
- package/src/registry/db/schema.ts +49 -22
- package/src/registry/email/define.ts +15 -0
- package/src/registry/email/providers/smtp.ts +39 -0
- package/src/registry/email/resolve.ts +94 -0
- package/src/registry/email/types.ts +28 -0
- package/src/registry/path.ts +11 -0
- package/src/registry/publish/package.ts +9 -0
- package/src/registry/transfer.ts +4 -0
- package/src/store/registry.ts +7 -7
- package/src/transformers/plugins/ssr-render.ts +53 -16
- package/src/transformers/preview/component/builder.ts +19 -25
- package/src/transformers/preview/component/css.ts +101 -53
- package/src/transformers/preview/component/html.ts +6 -2
- package/src/transformers/preview/component/javascript.ts +14 -4
- package/src/transformers/preview/pattern/html.ts +3 -1
- package/src/transformers/preview/types.ts +14 -0
- package/src/transformers/utils/build.ts +93 -0
- package/src/transformers/utils/module.ts +4 -2
- package/src/types/config.ts +148 -20
- package/src/utils/README.md +1 -1
- package/src/utils/path.ts +6 -29
- package/templates/default/handoff.config.js.tpl +10 -2
- package/templates/default/handoff.config.ts.tpl +10 -2
- package/templates/default/handoff.config.with-examples.js.tpl +10 -2
- package/templates/default/handoff.config.with-examples.ts.tpl +10 -2
- package/templates/default/package.json.tpl +2 -2
- package/.editorconfig +0 -19
- package/.eslintrc.json +0 -4
- package/.prettierignore +0 -0
- package/.prettierrc.json +0 -8
- package/.vscode/settings.json +0 -17
- package/dist/config/validator.d.ts +0 -9
- package/dist/config/validator.js +0 -24
- package/dist/registry/asset-storage/adapters/vercel-blob.d.ts +0 -17
- package/dist/registry/asset-storage/adapters/vercel-blob.js +0 -87
- package/src/app/components/old/Header.tsx +0 -89
- package/src/app/lib/mcp/shape.ts +0 -596
- package/src/app/pages/assets/fonts.tsx +0 -129
- package/src/app/pages/assets/index.tsx +0 -91
- package/src/app/pages/assets/logos.tsx +0 -75
- package/src/config/validator.ts +0 -22
- package/src/registry/asset-storage/adapters/vercel-blob.ts +0 -51
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Sparkles } from 'lucide-react';
|
|
4
|
+
import { useSession } from 'next-auth/react';
|
|
5
|
+
import { useRouter } from 'next/router';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
|
|
8
|
+
import { useIsMobile } from '../../hooks/use-mobile';
|
|
9
|
+
import { SheetClose } from '../ui/sheet';
|
|
10
|
+
import { AiAssistantPanel } from './AiAssistantPanel';
|
|
11
|
+
import { DEFAULT_DOCK, DOCK_WIDTH_VAR, clampWidth, dockWidth, isAiAssistantRoute, readDock, writeDock, type DockState } from './dockState';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Availability and dock state for the docs assistant.
|
|
15
|
+
*
|
|
16
|
+
* Availability comes from values baked in at build time (the `env` block in `next.config.mjs`): a
|
|
17
|
+
* static export has no API routes, and a build without `runtime.ai` has no chat route. The triggers
|
|
18
|
+
* are gated on the same values, so such a build shows no affordance rather than one that fails.
|
|
19
|
+
*
|
|
20
|
+
* The header hides its controls below the `@2xl` container width, so the mobile nav needs a trigger
|
|
21
|
+
* of its own. Both reach the one panel this provider renders, which is also what `⌘K` (`Ctrl K`
|
|
22
|
+
* away from Apple keyboards) toggles.
|
|
23
|
+
*
|
|
24
|
+
* Registry mode adds a second condition: the chat route authorizes on the session, so a signed-out
|
|
25
|
+
* reader sees no control rather than one that answers 401.
|
|
26
|
+
*
|
|
27
|
+
* Mounted in `_app`, above the page tree. That position is what makes the pinned panel possible:
|
|
28
|
+
* every page builds its own `Layout`, so anything inside one is unmounted by the next soft
|
|
29
|
+
* navigation, along with the conversation in it.
|
|
30
|
+
*/
|
|
31
|
+
const isAvailable = process.env.HANDOFF_AI_ENABLED === 'true' && process.env.HANDOFF_BUILD_TARGET !== 'static';
|
|
32
|
+
const isRegistryRuntime = process.env.HANDOFF_RUNTIME_MODE === 'registry';
|
|
33
|
+
|
|
34
|
+
interface AiAssistantApi {
|
|
35
|
+
isOpen: boolean;
|
|
36
|
+
open: () => void;
|
|
37
|
+
toggle: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const AiAssistantContext = React.createContext<AiAssistantApi | null>(null);
|
|
41
|
+
|
|
42
|
+
/** The opener, or `null` where this build has no assistant. */
|
|
43
|
+
export const useAiAssistant = (): AiAssistantApi | null => React.useContext(AiAssistantContext);
|
|
44
|
+
|
|
45
|
+
export const AiAssistantProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
46
|
+
const router = useRouter();
|
|
47
|
+
const visible = isAiAssistantRoute(router.pathname);
|
|
48
|
+
const page = <div className={visible ? 'ai-dock-inset' : undefined}>{children}</div>;
|
|
49
|
+
|
|
50
|
+
return isAvailable && isRegistryRuntime ? (
|
|
51
|
+
<RegistryAiAssistantProvider visible={visible}>{page}</RegistryAiAssistantProvider>
|
|
52
|
+
) : (
|
|
53
|
+
<AiAssistant available={isAvailable} visible={visible}>
|
|
54
|
+
{page}
|
|
55
|
+
</AiAssistant>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `useSession` needs the registry session provider, which only registry mode mounts.
|
|
61
|
+
*
|
|
62
|
+
* A loading session reports `undefined` and not `false`: the inline script may already have reserved
|
|
63
|
+
* the dock's column, and a `false` here would take it back and give it again once the session lands.
|
|
64
|
+
*/
|
|
65
|
+
const RegistryAiAssistantProvider: React.FC<{ children: React.ReactNode; visible: boolean }> = ({ children, visible }) => {
|
|
66
|
+
const { data: session, status } = useSession();
|
|
67
|
+
return (
|
|
68
|
+
<AiAssistant available={status === 'loading' ? undefined : Boolean(session?.user)} visible={visible}>
|
|
69
|
+
{children}
|
|
70
|
+
</AiAssistant>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const AiAssistant: React.FC<{ children: React.ReactNode; available: boolean | undefined; visible: boolean }> = ({
|
|
75
|
+
children,
|
|
76
|
+
available,
|
|
77
|
+
visible,
|
|
78
|
+
}) => {
|
|
79
|
+
const isMobile = useIsMobile();
|
|
80
|
+
const [dock, setDock] = React.useState<DockState>(DEFAULT_DOCK);
|
|
81
|
+
// Read after mount: the server cannot see it, and a first render that disagreed would fail
|
|
82
|
+
// hydration.
|
|
83
|
+
const [restored, setRestored] = React.useState(false);
|
|
84
|
+
|
|
85
|
+
const change = React.useCallback((next: Partial<DockState>) => {
|
|
86
|
+
setDock((current) => ({ ...current, ...next, width: clampWidth(next.width ?? current.width) }));
|
|
87
|
+
}, []);
|
|
88
|
+
|
|
89
|
+
const value = React.useMemo<AiAssistantApi>(
|
|
90
|
+
() => ({
|
|
91
|
+
isOpen: dock.open,
|
|
92
|
+
open: () => change({ open: true }),
|
|
93
|
+
toggle: () => setDock((current) => ({ ...current, open: !current.open })),
|
|
94
|
+
}),
|
|
95
|
+
[change, dock.open]
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
if (available !== true) return;
|
|
100
|
+
setDock(readDock());
|
|
101
|
+
setRestored(true);
|
|
102
|
+
}, [available]);
|
|
103
|
+
|
|
104
|
+
React.useEffect(() => {
|
|
105
|
+
if (restored) writeDock(dock);
|
|
106
|
+
}, [restored, dock]);
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The column the page leaves free.
|
|
110
|
+
*
|
|
111
|
+
* Nothing is written while the stored state is still out: the inline script in `_document` has
|
|
112
|
+
* already put the right value there, and an intermediate `0px` would reflow the page. A reader
|
|
113
|
+
* with no assistant must take the column back, because the script cannot see a signed-out session.
|
|
114
|
+
*/
|
|
115
|
+
React.useEffect(() => {
|
|
116
|
+
if (!visible) {
|
|
117
|
+
const root = document.documentElement;
|
|
118
|
+
root.style.setProperty(DOCK_WIDTH_VAR, '0px');
|
|
119
|
+
delete root.dataset.aiDockOpen;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (available === undefined) return;
|
|
123
|
+
if (available && !restored) return;
|
|
124
|
+
const root = document.documentElement;
|
|
125
|
+
root.style.setProperty(DOCK_WIDTH_VAR, `${available && !isMobile ? dockWidth(dock) : 0}px`);
|
|
126
|
+
if (available && dock.open) root.dataset.aiDockOpen = 'true';
|
|
127
|
+
else delete root.dataset.aiDockOpen;
|
|
128
|
+
}, [available, restored, isMobile, dock, visible]);
|
|
129
|
+
|
|
130
|
+
React.useEffect(() => {
|
|
131
|
+
if (available !== true || !visible) return;
|
|
132
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
133
|
+
if (event.key.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
|
|
134
|
+
event.preventDefault();
|
|
135
|
+
value.toggle();
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
window.addEventListener('keydown', onKeyDown);
|
|
139
|
+
return () => window.removeEventListener('keydown', onKeyDown);
|
|
140
|
+
}, [available, value, visible]);
|
|
141
|
+
|
|
142
|
+
if (available !== true || !visible) return <>{children}</>;
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<AiAssistantContext.Provider value={value}>
|
|
146
|
+
{children}
|
|
147
|
+
{restored && <AiAssistantPanel state={dock} isMobile={isMobile} onChange={change} />}
|
|
148
|
+
</AiAssistantContext.Provider>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The keyboard shortcut label, written for the keyboard in front of the reader.
|
|
154
|
+
*
|
|
155
|
+
* The value must settle after mount, not during render. This app is prerendered, so a value read
|
|
156
|
+
* from `navigator` during render disagrees with the markup that React hydrates. The prerendered
|
|
157
|
+
* HTML carries the Apple form, and every other platform corrects it in the first effect.
|
|
158
|
+
*/
|
|
159
|
+
const useShortcutLabel = (): string => {
|
|
160
|
+
const [label, setLabel] = React.useState('⌘K');
|
|
161
|
+
|
|
162
|
+
React.useEffect(() => {
|
|
163
|
+
if (!/mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent)) setLabel('Ctrl K');
|
|
164
|
+
}, []);
|
|
165
|
+
|
|
166
|
+
return label;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The header control. It must not look like a search field: it opens a conversation, not a result
|
|
171
|
+
* list.
|
|
172
|
+
*/
|
|
173
|
+
export function AiAssistantTrigger() {
|
|
174
|
+
const assistant = useAiAssistant();
|
|
175
|
+
const shortcut = useShortcutLabel();
|
|
176
|
+
if (!assistant) return null;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<span className="ai-assistant-trigger" data-open={assistant.isOpen} inert={assistant.isOpen}>
|
|
180
|
+
<button
|
|
181
|
+
type="button"
|
|
182
|
+
onClick={assistant.toggle}
|
|
183
|
+
title="Ask the design system"
|
|
184
|
+
aria-expanded={assistant.isOpen}
|
|
185
|
+
className="outline-hidden inline-flex h-8 shrink-0 items-center gap-2 rounded-full border bg-background px-3 shadow-sm transition-colors hover:bg-muted focus-visible:ring-1 focus-visible:ring-ring"
|
|
186
|
+
>
|
|
187
|
+
<Sparkles className="h-4 w-4 text-primary" />
|
|
188
|
+
<span className="text-sm font-medium">Ask</span>
|
|
189
|
+
<kbd className="rounded-sm border bg-muted px-1 font-mono text-[10px] leading-[14px] text-muted-foreground">{shortcut}</kbd>
|
|
190
|
+
</button>
|
|
191
|
+
</span>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* The same entry point for the mobile nav. It must sit inside that nav's sheet: the assistant is a
|
|
197
|
+
* sheet of its own on a narrow viewport, and two open sheets fight over focus.
|
|
198
|
+
*/
|
|
199
|
+
export function AiAssistantMobileTrigger() {
|
|
200
|
+
const assistant = useAiAssistant();
|
|
201
|
+
if (!assistant) return null;
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<SheetClose asChild>
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
onClick={assistant.open}
|
|
208
|
+
className="outline-hidden flex w-full items-center gap-2.5 rounded-lg border px-3 py-2.5 text-left transition-colors hover:bg-muted focus-visible:ring-1 focus-visible:ring-ring"
|
|
209
|
+
>
|
|
210
|
+
<Sparkles className="h-4 w-4 text-primary" />
|
|
211
|
+
<span className="text-sm font-medium">Ask</span>
|
|
212
|
+
</button>
|
|
213
|
+
</SheetClose>
|
|
214
|
+
);
|
|
215
|
+
}
|