@webiny/app-admin-ui 6.6.0-alpha.4 → 6.6.0-alpha.5

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,6 @@
1
1
  import react, { useCallback, useEffect, useMemo, useRef } from "react";
2
2
  import { Command } from "cmdk";
3
- import { AiChatFeature, CommandPaletteFeature, createReactiveComponent, useAdminConfig } from "@webiny/app-admin";
3
+ import { AdminAssistantFeature, CommandPaletteFeature, createReactiveComponent, useAdminConfig, useFeatureFlags } from "@webiny/app-admin";
4
4
  import { useContainer, useFeature } from "@webiny/app";
5
5
  import { RouterGateway } from "@webiny/app/features/router/abstractions.js";
6
6
  import { Icon } from "@webiny/admin-ui";
@@ -40,10 +40,11 @@ const CommandPaletteBase = ()=>{
40
40
  const inputRef = useRef(null);
41
41
  const scrollRef = useRef(null);
42
42
  const { vm } = presenter;
43
- const { presenter: aiChat } = useFeature(AiChatFeature);
44
- const aiMode = useMemo(()=>createAiMode(aiChat), [
45
- aiChat
43
+ const { presenter: assistant } = useFeature(AdminAssistantFeature);
44
+ const aiMode = useMemo(()=>createAiMode(assistant), [
45
+ assistant
46
46
  ]);
47
+ const aiEnabled = useFeatureFlags().isEnabled("aiPowerups.adminAssistant");
47
48
  useEffect(()=>{
48
49
  presenter.init();
49
50
  }, [
@@ -72,7 +73,8 @@ const CommandPaletteBase = ()=>{
72
73
  ]);
73
74
  usePaletteHotkeys({
74
75
  presenter,
75
- close
76
+ close,
77
+ aiEnabled
76
78
  });
77
79
  const groups = useMemo(()=>{
78
80
  const result = [];
@@ -81,14 +83,16 @@ const CommandPaletteBase = ()=>{
81
83
  title: NAVIGATION_GROUP,
82
84
  rows: navigationRows
83
85
  });
84
- const commandGroups = commandVmsToGroups(vm.commands, (name)=>presenter.useCommand(name));
86
+ const commands = aiEnabled ? vm.commands : vm.commands.filter((cmd)=>!cmd.entersAiMode);
87
+ const commandGroups = commandVmsToGroups(commands, (name)=>presenter.useCommand(name));
85
88
  result.push(...commandGroups);
86
89
  return result;
87
90
  }, [
88
91
  menus,
89
92
  vm.commands,
90
93
  navigateTo,
91
- presenter
94
+ presenter,
95
+ aiEnabled
92
96
  ]);
93
97
  useEffect(()=>{
94
98
  if (!vm.aiModeActive) return;
@@ -122,7 +126,7 @@ const CommandPaletteBase = ()=>{
122
126
  setQuery: (q)=>presenter.setQuery(q),
123
127
  exit: exitAiMode
124
128
  });
125
- if (" " === e.key && "" === vm.query) {
129
+ if (" " === e.key && "" === vm.query && aiEnabled) {
126
130
  e.preventDefault();
127
131
  presenter.enterAiMode();
128
132
  }
@@ -182,7 +186,7 @@ const CommandPaletteBase = ()=>{
182
186
  }
183
187
  }, appearance ? aiMode.body : /*#__PURE__*/ react.createElement(Command.List, null, /*#__PURE__*/ react.createElement(Command.Empty, null, /*#__PURE__*/ react.createElement(NoResults, {
184
188
  query: vm.query,
185
- onAskAi: askAiFromQuery
189
+ onAskAi: aiEnabled ? askAiFromQuery : void 0
186
190
  })), groups.map((group)=>/*#__PURE__*/ react.createElement(Command.Group, {
187
191
  key: group.title,
188
192
  heading: /*#__PURE__*/ react.createElement(GroupHeading, {
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPalette/CommandPalette.js","sources":["../../src/CommandPalette/CommandPalette.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { Command } from \"cmdk\";\nimport {\n AiChatFeature,\n CommandPaletteFeature,\n createReactiveComponent,\n useAdminConfig\n} from \"@webiny/app-admin\";\nimport { useContainer, useFeature } from \"@webiny/app\";\nimport { RouterGateway } from \"@webiny/app/features/router/abstractions.js\";\nimport { Icon } from \"@webiny/admin-ui\";\nimport { ReactComponent as SearchIcon } from \"@webiny/icons/search.svg\";\nimport { ReactComponent as ReturnIcon } from \"@webiny/icons/keyboard_return.svg\";\nimport { ReactComponent as ArrowUpIcon } from \"@webiny/icons/keyboard_arrow_up.svg\";\nimport { ReactComponent as ArrowDownIcon } from \"@webiny/icons/keyboard_arrow_down.svg\";\nimport { NAVIGATION_GROUP } from \"./constants.js\";\nimport type { CommandGroup } from \"./types.js\";\nimport { commandVmsToGroups, deriveNavigationRows } from \"./deriveRows.js\";\nimport {\n CommandDetail,\n CommandItemRow,\n GroupHeading,\n HintIcon,\n Kbd,\n NoResults,\n PaletteFooter,\n type Hint\n} from \"./components/index.js\";\nimport { createAiMode } from \"./modes/index.js\";\nimport { usePaletteHotkeys } from \"./usePaletteHotkeys.js\";\n\nconst COMMAND_HINTS: Hint[] = [\n {\n keys: (\n <>\n <HintIcon element={<ArrowUpIcon />} />\n <HintIcon element={<ArrowDownIcon />} />\n </>\n ),\n label: \"Navigate\"\n },\n { keys: <HintIcon element={<ReturnIcon />} />, label: \"Select\" },\n { keys: \"space\", label: \"Ask AI\" }\n];\n\nconst CommandPaletteBase = () => {\n const { presenter } = useFeature(CommandPaletteFeature);\n const { menus } = useAdminConfig();\n const container = useContainer();\n const inputRef = useRef<HTMLInputElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n const { vm } = presenter;\n\n /*\n * One mode today. The palette talks to it only through `PaletteMode`, so everything the\n * assistant needs lives in `createAiMode` rather than here. A second mode turns this into a\n * registry; nothing above this line has to change for that.\n */\n const { presenter: aiChat } = useFeature(AiChatFeature);\n const aiMode = useMemo(() => createAiMode(aiChat), [aiChat]);\n\n useEffect(() => {\n presenter.init();\n }, [presenter]);\n\n const close = useCallback(() => {\n presenter.close();\n aiMode.reset();\n }, [presenter, aiMode]);\n\n const exitAiMode = useCallback(() => {\n presenter.exitAiMode();\n aiMode.reset();\n }, [presenter, aiMode]);\n\n const navigateTo = useCallback(\n (to: string) => {\n container.resolve(RouterGateway).pushState(to);\n close();\n },\n [container, close]\n );\n\n usePaletteHotkeys({ presenter, close });\n\n const groups = useMemo<CommandGroup[]>(() => {\n const result: CommandGroup[] = [];\n const navigationRows = deriveNavigationRows(menus, navigateTo);\n if (navigationRows.length > 0) {\n result.push({ title: NAVIGATION_GROUP, rows: navigationRows });\n }\n const commandGroups = commandVmsToGroups(vm.commands, name => presenter.useCommand(name));\n result.push(...commandGroups);\n return result;\n }, [menus, vm.commands, navigateTo, presenter]);\n\n /*\n * The input is shared across modes, so focus has to be restored after the surrounding tree swaps.\n * Driven by the presenter's state rather than by each entry point, since a mode can now also be\n * entered by selecting the command, which never reaches this component.\n */\n useEffect(() => {\n if (!vm.aiModeActive) {\n return;\n }\n requestAnimationFrame(() => inputRef.current?.focus());\n }, [vm.aiModeActive]);\n\n /*\n * Let the active mode keep its own content in view. Runs on every render while a mode is showing,\n * because the body is what changes and the palette cannot tell what inside it moved.\n */\n useEffect(() => {\n if (!vm.aiModeActive || !scrollRef.current) {\n return;\n }\n aiMode.afterRender?.(scrollRef.current);\n });\n\n if (!vm.isOpen) {\n return null;\n }\n\n const active = vm.activeCommand;\n\n /* The one seeded entry: a search that found nothing becomes the question as-is. */\n const askAiFromQuery = () => {\n // Read before entering, because entering clears the query.\n const seed = vm.query;\n presenter.enterAiMode();\n aiMode.enter(seed);\n };\n\n /* Null while the command list is showing, which is what every `appearance ?` below tests for. */\n const appearance = vm.aiModeActive ? aiMode.appearance : null;\n const placeholder = appearance ? appearance.placeholder : \"Search for pages and actions…\";\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n if (active) {\n presenter.cancelCommand();\n } else if (vm.aiModeActive) {\n exitAiMode();\n } else {\n close();\n }\n return;\n }\n\n if (vm.aiModeActive) {\n // The mode decides what its keys mean; anything it declines is simply ignored here.\n aiMode.handleKey(e, {\n query: vm.query,\n setQuery: q => presenter.setQuery(q),\n exit: exitAiMode\n });\n return;\n }\n\n // Space on an EMPTY query enters the aiMode. Gated on `query === \"\"` so space stays an ordinary\n // character the moment there is anything to search — \"new entry\" must keep working.\n if (e.key === \" \" && vm.query === \"\") {\n e.preventDefault();\n presenter.enterAiMode();\n }\n };\n\n return (\n <div\n role=\"presentation\"\n onClick={close}\n className=\"fixed inset-0 z-overlay flex animate-in items-start justify-center bg-neutral-dark/50 fade-in duration-150\"\n style={{ padding: \"13vh 16px 16px\", backdropFilter: \"blur(2px)\" }}\n >\n <div\n onClick={e => e.stopPropagation()}\n onKeyDown={onKeyDown}\n className=\"flex w-full animate-in flex-col overflow-hidden rounded-lg border border-neutral-dimmed bg-neutral-base shadow-xxl duration-150 zoom-in-95 slide-in-from-top-2\"\n style={{\n maxWidth: 680,\n maxHeight: \"70vh\",\n height: appearance?.tall ? \"70vh\" : \"45vh\"\n }}\n >\n {active ? (\n <CommandDetail\n active={active}\n onBack={() => presenter.cancelCommand()}\n onClose={close}\n />\n ) : (\n <Command\n label=\"Command palette\"\n // cmdk filtering is for the command list; a mode renders its own body.\n shouldFilter={!appearance}\n style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minHeight: 0 }}\n >\n {/* Input row — the same slot in every mode, so switching never moves it. */}\n <div className=\"flex flex-none items-center gap-sm border-b border-neutral-subtle px-md py-sm-plus\">\n <Icon\n icon={appearance ? appearance.icon : <SearchIcon />}\n color={appearance ? appearance.iconColor : \"neutral-light\"}\n size={\"md\"}\n label={appearance ? appearance.iconLabel : \"Search\"}\n />\n {appearance?.badge ?? null}\n <Command.Input\n ref={inputRef}\n autoFocus\n value={vm.query}\n onValueChange={q => presenter.setQuery(q)}\n spellCheck={false}\n placeholder={placeholder}\n className=\"min-w-0 flex-1 border-0 bg-transparent text-lg text-neutral-primary outline-none\"\n />\n <Kbd>esc</Kbd>\n </div>\n\n <div\n ref={scrollRef}\n className=\"p-xs-plus\"\n style={{ flex: 1, minHeight: 0, overflowY: \"auto\" }}\n >\n {appearance ? (\n aiMode.body\n ) : (\n <Command.List>\n <Command.Empty>\n <NoResults query={vm.query} onAskAi={askAiFromQuery} />\n </Command.Empty>\n\n {groups.map(group => (\n <Command.Group\n key={group.title}\n heading={<GroupHeading title={group.title} />}\n >\n {group.rows.map(row => (\n <CommandItemRow key={row.key} row={row} />\n ))}\n </Command.Group>\n ))}\n </Command.List>\n )}\n </div>\n\n <PaletteFooter\n label={appearance ? appearance.footerLabel : \"Webiny command palette\"}\n hints={appearance ? appearance.hints : COMMAND_HINTS}\n />\n </Command>\n )}\n </div>\n </div>\n );\n};\n\nexport const CommandPalette = createReactiveComponent(CommandPaletteBase);\n"],"names":["COMMAND_HINTS","HintIcon","ArrowUpIcon","ArrowDownIcon","ReturnIcon","CommandPaletteBase","presenter","useFeature","CommandPaletteFeature","menus","useAdminConfig","container","useContainer","inputRef","useRef","scrollRef","vm","aiChat","AiChatFeature","aiMode","useMemo","createAiMode","useEffect","close","useCallback","exitAiMode","navigateTo","to","RouterGateway","usePaletteHotkeys","groups","result","navigationRows","deriveNavigationRows","NAVIGATION_GROUP","commandGroups","commandVmsToGroups","name","requestAnimationFrame","active","askAiFromQuery","seed","appearance","placeholder","onKeyDown","e","q","CommandDetail","Command","Icon","SearchIcon","Kbd","NoResults","group","GroupHeading","row","CommandItemRow","PaletteFooter","CommandPalette","createReactiveComponent"],"mappings":"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"}
1
+ {"version":3,"file":"CommandPalette/CommandPalette.js","sources":["../../src/CommandPalette/CommandPalette.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { Command } from \"cmdk\";\nimport {\n AdminAssistantFeature,\n CommandPaletteFeature,\n createReactiveComponent,\n useAdminConfig,\n useFeatureFlags\n} from \"@webiny/app-admin\";\nimport { useContainer, useFeature } from \"@webiny/app\";\nimport { RouterGateway } from \"@webiny/app/features/router/abstractions.js\";\nimport { Icon } from \"@webiny/admin-ui\";\nimport { ReactComponent as SearchIcon } from \"@webiny/icons/search.svg\";\nimport { ReactComponent as ReturnIcon } from \"@webiny/icons/keyboard_return.svg\";\nimport { ReactComponent as ArrowUpIcon } from \"@webiny/icons/keyboard_arrow_up.svg\";\nimport { ReactComponent as ArrowDownIcon } from \"@webiny/icons/keyboard_arrow_down.svg\";\nimport { NAVIGATION_GROUP } from \"./constants.js\";\nimport type { CommandGroup } from \"./types.js\";\nimport { commandVmsToGroups, deriveNavigationRows } from \"./deriveRows.js\";\nimport {\n CommandDetail,\n CommandItemRow,\n GroupHeading,\n HintIcon,\n Kbd,\n NoResults,\n PaletteFooter,\n type Hint\n} from \"./components/index.js\";\nimport { createAiMode } from \"./modes/index.js\";\nimport { usePaletteHotkeys } from \"./usePaletteHotkeys.js\";\n\nconst COMMAND_HINTS: Hint[] = [\n {\n keys: (\n <>\n <HintIcon element={<ArrowUpIcon />} />\n <HintIcon element={<ArrowDownIcon />} />\n </>\n ),\n label: \"Navigate\"\n },\n { keys: <HintIcon element={<ReturnIcon />} />, label: \"Select\" },\n { keys: \"space\", label: \"Ask AI\" }\n];\n\nconst CommandPaletteBase = () => {\n const { presenter } = useFeature(CommandPaletteFeature);\n const { menus } = useAdminConfig();\n const container = useContainer();\n const inputRef = useRef<HTMLInputElement>(null);\n const scrollRef = useRef<HTMLDivElement>(null);\n const { vm } = presenter;\n\n /*\n * One mode today. The palette talks to it only through `PaletteMode`, so everything the\n * assistant needs lives in `createAiMode` rather than here. A second mode turns this into a\n * registry; nothing above this line has to change for that.\n */\n const { presenter: assistant } = useFeature(AdminAssistantFeature);\n const aiMode = useMemo(() => createAiMode(assistant), [assistant]);\n\n /*\n * The assistant is licensed separately, and the api registers no route without it. Checked here\n * rather than at registration because admin flags are fetched, so they are not known when the\n * container is built.\n */\n const aiEnabled = useFeatureFlags().isEnabled(\"aiPowerups.adminAssistant\");\n\n useEffect(() => {\n presenter.init();\n }, [presenter]);\n\n const close = useCallback(() => {\n presenter.close();\n aiMode.reset();\n }, [presenter, aiMode]);\n\n const exitAiMode = useCallback(() => {\n presenter.exitAiMode();\n aiMode.reset();\n }, [presenter, aiMode]);\n\n const navigateTo = useCallback(\n (to: string) => {\n container.resolve(RouterGateway).pushState(to);\n close();\n },\n [container, close]\n );\n\n usePaletteHotkeys({ presenter, close, aiEnabled });\n\n const groups = useMemo<CommandGroup[]>(() => {\n const result: CommandGroup[] = [];\n const navigationRows = deriveNavigationRows(menus, navigateTo);\n if (navigationRows.length > 0) {\n result.push({ title: NAVIGATION_GROUP, rows: navigationRows });\n }\n // Filtered on `entersAiMode` rather than on a command name, so a second mode needs no edit.\n const commands = aiEnabled ? vm.commands : vm.commands.filter(cmd => !cmd.entersAiMode);\n const commandGroups = commandVmsToGroups(commands, name => presenter.useCommand(name));\n result.push(...commandGroups);\n return result;\n }, [menus, vm.commands, navigateTo, presenter, aiEnabled]);\n\n /*\n * The input is shared across modes, so focus has to be restored after the surrounding tree swaps.\n * Driven by the presenter's state rather than by each entry point, since a mode can now also be\n * entered by selecting the command, which never reaches this component.\n */\n useEffect(() => {\n if (!vm.aiModeActive) {\n return;\n }\n requestAnimationFrame(() => inputRef.current?.focus());\n }, [vm.aiModeActive]);\n\n /*\n * Let the active mode keep its own content in view. Runs on every render while a mode is showing,\n * because the body is what changes and the palette cannot tell what inside it moved.\n */\n useEffect(() => {\n if (!vm.aiModeActive || !scrollRef.current) {\n return;\n }\n aiMode.afterRender?.(scrollRef.current);\n });\n\n if (!vm.isOpen) {\n return null;\n }\n\n const active = vm.activeCommand;\n\n /* The one seeded entry: a search that found nothing becomes the question as-is. */\n const askAiFromQuery = () => {\n // Read before entering, because entering clears the query.\n const seed = vm.query;\n presenter.enterAiMode();\n aiMode.enter(seed);\n };\n\n /* Null while the command list is showing, which is what every `appearance ?` below tests for. */\n const appearance = vm.aiModeActive ? aiMode.appearance : null;\n const placeholder = appearance ? appearance.placeholder : \"Search for pages and actions…\";\n\n const onKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n if (active) {\n presenter.cancelCommand();\n } else if (vm.aiModeActive) {\n exitAiMode();\n } else {\n close();\n }\n return;\n }\n\n if (vm.aiModeActive) {\n // The mode decides what its keys mean; anything it declines is simply ignored here.\n aiMode.handleKey(e, {\n query: vm.query,\n setQuery: q => presenter.setQuery(q),\n exit: exitAiMode\n });\n return;\n }\n\n // Space on an EMPTY query enters the aiMode. Gated on `query === \"\"` so space stays an ordinary\n // character the moment there is anything to search — \"new entry\" must keep working.\n if (e.key === \" \" && vm.query === \"\" && aiEnabled) {\n e.preventDefault();\n presenter.enterAiMode();\n }\n };\n\n return (\n <div\n role=\"presentation\"\n onClick={close}\n className=\"fixed inset-0 z-overlay flex animate-in items-start justify-center bg-neutral-dark/50 fade-in duration-150\"\n style={{ padding: \"13vh 16px 16px\", backdropFilter: \"blur(2px)\" }}\n >\n <div\n onClick={e => e.stopPropagation()}\n onKeyDown={onKeyDown}\n className=\"flex w-full animate-in flex-col overflow-hidden rounded-lg border border-neutral-dimmed bg-neutral-base shadow-xxl duration-150 zoom-in-95 slide-in-from-top-2\"\n style={{\n maxWidth: 680,\n maxHeight: \"70vh\",\n height: appearance?.tall ? \"70vh\" : \"45vh\"\n }}\n >\n {active ? (\n <CommandDetail\n active={active}\n onBack={() => presenter.cancelCommand()}\n onClose={close}\n />\n ) : (\n <Command\n label=\"Command palette\"\n // cmdk filtering is for the command list; a mode renders its own body.\n shouldFilter={!appearance}\n style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minHeight: 0 }}\n >\n {/* Input row — the same slot in every mode, so switching never moves it. */}\n <div className=\"flex flex-none items-center gap-sm border-b border-neutral-subtle px-md py-sm-plus\">\n <Icon\n icon={appearance ? appearance.icon : <SearchIcon />}\n color={appearance ? appearance.iconColor : \"neutral-light\"}\n size={\"md\"}\n label={appearance ? appearance.iconLabel : \"Search\"}\n />\n {appearance?.badge ?? null}\n <Command.Input\n ref={inputRef}\n autoFocus\n value={vm.query}\n onValueChange={q => presenter.setQuery(q)}\n spellCheck={false}\n placeholder={placeholder}\n className=\"min-w-0 flex-1 border-0 bg-transparent text-lg text-neutral-primary outline-none\"\n />\n <Kbd>esc</Kbd>\n </div>\n\n <div\n ref={scrollRef}\n className=\"p-xs-plus\"\n style={{ flex: 1, minHeight: 0, overflowY: \"auto\" }}\n >\n {appearance ? (\n aiMode.body\n ) : (\n <Command.List>\n <Command.Empty>\n <NoResults\n query={vm.query}\n onAskAi={aiEnabled ? askAiFromQuery : undefined}\n />\n </Command.Empty>\n\n {groups.map(group => (\n <Command.Group\n key={group.title}\n heading={<GroupHeading title={group.title} />}\n >\n {group.rows.map(row => (\n <CommandItemRow key={row.key} row={row} />\n ))}\n </Command.Group>\n ))}\n </Command.List>\n )}\n </div>\n\n <PaletteFooter\n label={appearance ? appearance.footerLabel : \"Webiny command palette\"}\n hints={appearance ? appearance.hints : COMMAND_HINTS}\n />\n </Command>\n )}\n </div>\n </div>\n );\n};\n\nexport const CommandPalette = createReactiveComponent(CommandPaletteBase);\n"],"names":["COMMAND_HINTS","HintIcon","ArrowUpIcon","ArrowDownIcon","ReturnIcon","CommandPaletteBase","presenter","useFeature","CommandPaletteFeature","menus","useAdminConfig","container","useContainer","inputRef","useRef","scrollRef","vm","assistant","AdminAssistantFeature","aiMode","useMemo","createAiMode","aiEnabled","useFeatureFlags","useEffect","close","useCallback","exitAiMode","navigateTo","to","RouterGateway","usePaletteHotkeys","groups","result","navigationRows","deriveNavigationRows","NAVIGATION_GROUP","commands","cmd","commandGroups","commandVmsToGroups","name","requestAnimationFrame","active","askAiFromQuery","seed","appearance","placeholder","onKeyDown","e","q","CommandDetail","Command","Icon","SearchIcon","Kbd","NoResults","undefined","group","GroupHeading","row","CommandItemRow","PaletteFooter","CommandPalette","createReactiveComponent"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import React from "react";
2
- import type { AiChatPendingApproval } from "@webiny/app-admin";
2
+ import type { AdminAssistantPendingApproval } from "@webiny/app-admin";
3
3
  export interface ApprovalPlanProps {
4
- approvals: AiChatPendingApproval[];
4
+ approvals: AdminAssistantPendingApproval[];
5
5
  busy: boolean;
6
6
  onApprove: () => void;
7
7
  onReject: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPalette/components/ApprovalPlan.js","sources":["../../../src/CommandPalette/components/ApprovalPlan.tsx"],"sourcesContent":["import React from \"react\";\nimport { Button } from \"@webiny/admin-ui\";\nimport { cn } from \"@webiny/admin-ui\";\nimport { Icon } from \"@webiny/admin-ui\";\nimport { Text } from \"@webiny/admin-ui\";\nimport { ReactComponent as BoltIcon } from \"@webiny/icons/bolt.svg\";\nimport { ReactComponent as WarningIcon } from \"@webiny/icons/warning.svg\";\nimport type { AiChatPendingApproval } from \"@webiny/app-admin\";\n\nexport interface ApprovalPlanProps {\n approvals: AiChatPendingApproval[];\n busy: boolean;\n onApprove: () => void;\n onReject: () => void;\n}\n\n/**\n * The confirm gate for a proposed change.\n *\n * Arguments are shown as raw JSON on purpose. A prose summary would be the assistant describing its\n * own request — and the assistant is exactly what is not yet trusted here. What the user approves has\n * to be what actually runs.\n */\nexport const ApprovalPlan = ({ approvals, busy, onApprove, onReject }: ApprovalPlanProps) => {\n if (approvals.length === 0) {\n return null;\n }\n\n const destructive = approvals.some(approval => approval.destructive);\n\n let heading = `Approve ${approvals.length} changes`;\n if (approvals.length === 1) {\n heading = `Approve: ${approvals[0].title ?? approvals[0].toolName}`;\n }\n\n return (\n <div\n className={cn(\n \"mt-sm overflow-hidden rounded-md border border-neutral-dimmed\",\n destructive ? \"bg-destructive-subtle\" : \"bg-warning-subtle\"\n )}\n >\n <div className=\"flex items-center gap-sm border-b border-neutral-dimmed px-sm py-xs-plus\">\n <Icon\n icon={destructive ? <WarningIcon /> : <BoltIcon />}\n size=\"sm\"\n label=\"\"\n color=\"inherit\"\n className={destructive ? \"text-destructive-primary\" : undefined}\n />\n <Text size=\"sm\" className=\"font-semibold text-neutral-primary\">\n {heading}\n </Text>\n {destructive ? (\n <Text size=\"sm\" className=\"ml-auto text-destructive-primary\">\n Cannot be undone\n </Text>\n ) : null}\n </div>\n\n <div className=\"bg-neutral-base px-sm py-xs-plus\">\n {approvals.map(approval => (\n <div key={approval.approvalId} className=\"mb-xs\">\n <Text as=\"div\" size=\"sm\" className=\"font-mono text-neutral-strong\">\n {approval.toolName}\n </Text>\n <pre className=\"mt-xxs overflow-x-auto rounded bg-neutral-subtle p-sm text-xs text-neutral-strong\">\n {JSON.stringify(approval.input, null, 2)}\n </pre>\n </div>\n ))}\n\n <div className=\"mt-sm flex items-center gap-sm\">\n <Button variant=\"primary\" text=\"Run\" disabled={busy} onClick={onApprove} />\n <Button variant=\"secondary\" text=\"Reject\" disabled={busy} onClick={onReject} />\n <Text size=\"sm\" className=\"text-neutral-muted\">\n Nothing runs until you confirm.\n </Text>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["ApprovalPlan","approvals","busy","onApprove","onReject","destructive","approval","heading","cn","Icon","WarningIcon","BoltIcon","undefined","Text","JSON","Button"],"mappings":";;;;AAuBO,MAAMA,eAAe,CAAC,EAAEC,SAAS,EAAEC,IAAI,EAAEC,SAAS,EAAEC,QAAQ,EAAqB;IACpF,IAAIH,AAAqB,MAArBA,UAAU,MAAM,EAChB,OAAO;IAGX,MAAMI,cAAcJ,UAAU,IAAI,CAACK,CAAAA,WAAYA,SAAS,WAAW;IAEnE,IAAIC,UAAU,CAAC,QAAQ,EAAEN,UAAU,MAAM,CAAC,QAAQ,CAAC;IACnD,IAAIA,AAAqB,MAArBA,UAAU,MAAM,EAChBM,UAAU,CAAC,SAAS,EAAEN,SAAS,CAAC,EAAE,CAAC,KAAK,IAAIA,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE;IAGvE,OAAO,WAAP,GACI,oBAAC;QACG,WAAWO,GACP,iEACAH,cAAc,0BAA0B;qBAG5C,oBAAC;QAAI,WAAU;qBACX,oBAACI,MAAIA;QACD,MAAMJ,cAAc,WAAdA,GAAc,oBAACK,4BAAWA,QAAAA,WAAAA,GAAM,oBAACC,gBAAQA;QAC/C,MAAK;QACL,OAAM;QACN,OAAM;QACN,WAAWN,cAAc,6BAA6BO;sBAE1D,oBAACC,MAAIA;QAAC,MAAK;QAAK,WAAU;OACrBN,UAEJF,cAAc,WAAdA,GACG,oBAACQ,MAAIA;QAAC,MAAK;QAAK,WAAU;OAAmC,sBAG7D,qBAGR,oBAAC;QAAI,WAAU;OACVZ,UAAU,GAAG,CAACK,CAAAA,WAAAA,WAAAA,GACX,oBAAC;YAAI,KAAKA,SAAS,UAAU;YAAE,WAAU;yBACrC,oBAACO,MAAIA;YAAC,IAAG;YAAM,MAAK;YAAK,WAAU;WAC9BP,SAAS,QAAQ,iBAEtB,oBAAC;YAAI,WAAU;WACVQ,KAAK,SAAS,CAACR,SAAS,KAAK,EAAE,MAAM,qBAKlD,oBAAC;QAAI,WAAU;qBACX,oBAACS,QAAMA;QAAC,SAAQ;QAAU,MAAK;QAAM,UAAUb;QAAM,SAASC;sBAC9D,oBAACY,QAAMA;QAAC,SAAQ;QAAY,MAAK;QAAS,UAAUb;QAAM,SAASE;sBACnE,oBAACS,MAAIA;QAAC,MAAK;QAAK,WAAU;OAAqB;AAOnE"}
1
+ {"version":3,"file":"CommandPalette/components/ApprovalPlan.js","sources":["../../../src/CommandPalette/components/ApprovalPlan.tsx"],"sourcesContent":["import React from \"react\";\nimport { Button } from \"@webiny/admin-ui\";\nimport { cn } from \"@webiny/admin-ui\";\nimport { Icon } from \"@webiny/admin-ui\";\nimport { Text } from \"@webiny/admin-ui\";\nimport { ReactComponent as BoltIcon } from \"@webiny/icons/bolt.svg\";\nimport { ReactComponent as WarningIcon } from \"@webiny/icons/warning.svg\";\nimport type { AdminAssistantPendingApproval } from \"@webiny/app-admin\";\n\nexport interface ApprovalPlanProps {\n approvals: AdminAssistantPendingApproval[];\n busy: boolean;\n onApprove: () => void;\n onReject: () => void;\n}\n\n/**\n * The confirm gate for a proposed change.\n *\n * Arguments are shown as raw JSON on purpose. A prose summary would be the assistant describing its\n * own request — and the assistant is exactly what is not yet trusted here. What the user approves has\n * to be what actually runs.\n */\nexport const ApprovalPlan = ({ approvals, busy, onApprove, onReject }: ApprovalPlanProps) => {\n if (approvals.length === 0) {\n return null;\n }\n\n const destructive = approvals.some(approval => approval.destructive);\n\n let heading = `Approve ${approvals.length} changes`;\n if (approvals.length === 1) {\n heading = `Approve: ${approvals[0].title ?? approvals[0].toolName}`;\n }\n\n return (\n <div\n className={cn(\n \"mt-sm overflow-hidden rounded-md border border-neutral-dimmed\",\n destructive ? \"bg-destructive-subtle\" : \"bg-warning-subtle\"\n )}\n >\n <div className=\"flex items-center gap-sm border-b border-neutral-dimmed px-sm py-xs-plus\">\n <Icon\n icon={destructive ? <WarningIcon /> : <BoltIcon />}\n size=\"sm\"\n label=\"\"\n color=\"inherit\"\n className={destructive ? \"text-destructive-primary\" : undefined}\n />\n <Text size=\"sm\" className=\"font-semibold text-neutral-primary\">\n {heading}\n </Text>\n {destructive ? (\n <Text size=\"sm\" className=\"ml-auto text-destructive-primary\">\n Cannot be undone\n </Text>\n ) : null}\n </div>\n\n <div className=\"bg-neutral-base px-sm py-xs-plus\">\n {approvals.map(approval => (\n <div key={approval.approvalId} className=\"mb-xs\">\n <Text as=\"div\" size=\"sm\" className=\"font-mono text-neutral-strong\">\n {approval.toolName}\n </Text>\n <pre className=\"mt-xxs overflow-x-auto rounded bg-neutral-subtle p-sm text-xs text-neutral-strong\">\n {JSON.stringify(approval.input, null, 2)}\n </pre>\n </div>\n ))}\n\n <div className=\"mt-sm flex items-center gap-sm\">\n <Button variant=\"primary\" text=\"Run\" disabled={busy} onClick={onApprove} />\n <Button variant=\"secondary\" text=\"Reject\" disabled={busy} onClick={onReject} />\n <Text size=\"sm\" className=\"text-neutral-muted\">\n Nothing runs until you confirm.\n </Text>\n </div>\n </div>\n </div>\n );\n};\n"],"names":["ApprovalPlan","approvals","busy","onApprove","onReject","destructive","approval","heading","cn","Icon","WarningIcon","BoltIcon","undefined","Text","JSON","Button"],"mappings":";;;;AAuBO,MAAMA,eAAe,CAAC,EAAEC,SAAS,EAAEC,IAAI,EAAEC,SAAS,EAAEC,QAAQ,EAAqB;IACpF,IAAIH,AAAqB,MAArBA,UAAU,MAAM,EAChB,OAAO;IAGX,MAAMI,cAAcJ,UAAU,IAAI,CAACK,CAAAA,WAAYA,SAAS,WAAW;IAEnE,IAAIC,UAAU,CAAC,QAAQ,EAAEN,UAAU,MAAM,CAAC,QAAQ,CAAC;IACnD,IAAIA,AAAqB,MAArBA,UAAU,MAAM,EAChBM,UAAU,CAAC,SAAS,EAAEN,SAAS,CAAC,EAAE,CAAC,KAAK,IAAIA,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE;IAGvE,OAAO,WAAP,GACI,oBAAC;QACG,WAAWO,GACP,iEACAH,cAAc,0BAA0B;qBAG5C,oBAAC;QAAI,WAAU;qBACX,oBAACI,MAAIA;QACD,MAAMJ,cAAc,WAAdA,GAAc,oBAACK,4BAAWA,QAAAA,WAAAA,GAAM,oBAACC,gBAAQA;QAC/C,MAAK;QACL,OAAM;QACN,OAAM;QACN,WAAWN,cAAc,6BAA6BO;sBAE1D,oBAACC,MAAIA;QAAC,MAAK;QAAK,WAAU;OACrBN,UAEJF,cAAc,WAAdA,GACG,oBAACQ,MAAIA;QAAC,MAAK;QAAK,WAAU;OAAmC,sBAG7D,qBAGR,oBAAC;QAAI,WAAU;OACVZ,UAAU,GAAG,CAACK,CAAAA,WAAAA,WAAAA,GACX,oBAAC;YAAI,KAAKA,SAAS,UAAU;YAAE,WAAU;yBACrC,oBAACO,MAAIA;YAAC,IAAG;YAAM,MAAK;YAAK,WAAU;WAC9BP,SAAS,QAAQ,iBAEtB,oBAAC;YAAI,WAAU;WACVQ,KAAK,SAAS,CAACR,SAAS,KAAK,EAAE,MAAM,qBAKlD,oBAAC;QAAI,WAAU;qBACX,oBAACS,QAAMA;QAAC,SAAQ;QAAU,MAAK;QAAM,UAAUb;QAAM,SAASC;sBAC9D,oBAACY,QAAMA;QAAC,SAAQ;QAAY,MAAK;QAAS,UAAUb;QAAM,SAASE;sBACnE,oBAACS,MAAIA;QAAC,MAAK;QAAK,WAAU;OAAqB;AAOnE"}
@@ -2,9 +2,12 @@ import React from "react";
2
2
  /**
3
3
  * A dead end is the best moment to offer the assistant: the user has already expressed intent in
4
4
  * words, so hand that exact query straight to AI rather than making them retype it.
5
+ *
6
+ * `onAskAi` is optional because the assistant is licensed separately. Without it this is a plain
7
+ * "nothing matched" state rather than an offer the project cannot accept.
5
8
  */
6
9
  export interface NoResultsProps {
7
10
  query: string;
8
- onAskAi: () => void;
11
+ onAskAi?: () => void;
9
12
  }
10
13
  export declare const NoResults: ({ query, onAskAi }: NoResultsProps) => React.JSX.Element;
@@ -17,7 +17,7 @@ const NoResults = ({ query, onAskAi })=>/*#__PURE__*/ react.createElement("div",
17
17
  as: "div",
18
18
  size: "sm",
19
19
  className: "mb-md mt-xxs text-neutral-muted"
20
- }, "No page or action matches. Ask the assistant instead."), /*#__PURE__*/ react.createElement(Button, {
20
+ }, onAskAi ? "No page or action matches. Ask the assistant instead." : "No page or action matches."), onAskAi ? /*#__PURE__*/ react.createElement(Button, {
21
21
  variant: "secondary",
22
22
  text: "Ask AI",
23
23
  icon: /*#__PURE__*/ react.createElement(Icon, {
@@ -27,7 +27,7 @@ const NoResults = ({ query, onAskAi })=>/*#__PURE__*/ react.createElement("div",
27
27
  color: "inherit"
28
28
  }),
29
29
  onClick: onAskAi
30
- }));
30
+ }) : null);
31
31
  export { NoResults };
32
32
 
33
33
  //# sourceMappingURL=NoResults.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPalette/components/NoResults.js","sources":["../../../src/CommandPalette/components/NoResults.tsx"],"sourcesContent":["import React from \"react\";\nimport { Button, Icon, Text } from \"@webiny/admin-ui\";\nimport { ReactComponent as SearchOffIcon } from \"@webiny/icons/search_off.svg\";\nimport { ReactComponent as AiIcon } from \"@webiny/icons/auto_awesome.svg\";\n\n/**\n * A dead end is the best moment to offer the assistant: the user has already expressed intent in\n * words, so hand that exact query straight to AI rather than making them retype it.\n */\nexport interface NoResultsProps {\n query: string;\n onAskAi: () => void;\n}\n\nexport const NoResults = ({ query, onAskAi }: NoResultsProps) => (\n <div className=\"flex flex-col items-center justify-center px-lg py-xxl text-center\">\n <Icon icon={<SearchOffIcon />} size=\"lg\" label=\"\" color=\"neutral-strong-transparent\" />\n <Text as=\"div\" size=\"lg\" className=\"mt-sm font-semibold text-neutral-strong\">\n {`No results for “${query}”`}\n </Text>\n <Text as=\"div\" size=\"sm\" className=\"mb-md mt-xxs text-neutral-muted\">\n No page or action matches. Ask the assistant instead.\n </Text>\n <Button\n variant=\"secondary\"\n text=\"Ask AI\"\n icon={<Icon icon={<AiIcon />} size=\"sm\" label=\"\" color=\"inherit\" />}\n onClick={onAskAi}\n />\n </div>\n);\n"],"names":["NoResults","query","onAskAi","Icon","SearchOffIcon","Text","Button","AiIcon"],"mappings":";;;;AAcO,MAAMA,YAAY,CAAC,EAAEC,KAAK,EAAEC,OAAO,EAAkB,iBACxD,oBAAC;QAAI,WAAU;qBACX,oBAACC,MAAIA;QAAC,oBAAM,oBAACC,gBAAaA;QAAK,MAAK;QAAK,OAAM;QAAG,OAAM;sBACxD,oBAACC,MAAIA;QAAC,IAAG;QAAM,MAAK;QAAK,WAAU;OAC9B,CAAC,gBAAgB,EAAEJ,MAAM,CAAC,CAAC,iBAEhC,oBAACI,MAAIA;QAAC,IAAG;QAAM,MAAK;QAAK,WAAU;OAAkC,wEAGrE,oBAACC,QAAMA;QACH,SAAQ;QACR,MAAK;QACL,oBAAM,oBAACH,MAAIA;YAAC,oBAAM,oBAACI,iCAAMA;YAAK,MAAK;YAAK,OAAM;YAAG,OAAM;;QACvD,SAASL"}
1
+ {"version":3,"file":"CommandPalette/components/NoResults.js","sources":["../../../src/CommandPalette/components/NoResults.tsx"],"sourcesContent":["import React from \"react\";\nimport { Button, Icon, Text } from \"@webiny/admin-ui\";\nimport { ReactComponent as SearchOffIcon } from \"@webiny/icons/search_off.svg\";\nimport { ReactComponent as AiIcon } from \"@webiny/icons/auto_awesome.svg\";\n\n/**\n * A dead end is the best moment to offer the assistant: the user has already expressed intent in\n * words, so hand that exact query straight to AI rather than making them retype it.\n *\n * `onAskAi` is optional because the assistant is licensed separately. Without it this is a plain\n * \"nothing matched\" state rather than an offer the project cannot accept.\n */\nexport interface NoResultsProps {\n query: string;\n onAskAi?: () => void;\n}\n\nexport const NoResults = ({ query, onAskAi }: NoResultsProps) => (\n <div className=\"flex flex-col items-center justify-center px-lg py-xxl text-center\">\n <Icon icon={<SearchOffIcon />} size=\"lg\" label=\"\" color=\"neutral-strong-transparent\" />\n <Text as=\"div\" size=\"lg\" className=\"mt-sm font-semibold text-neutral-strong\">\n {`No results for “${query}”`}\n </Text>\n <Text as=\"div\" size=\"sm\" className=\"mb-md mt-xxs text-neutral-muted\">\n {onAskAi\n ? \"No page or action matches. Ask the assistant instead.\"\n : \"No page or action matches.\"}\n </Text>\n {onAskAi ? (\n <Button\n variant=\"secondary\"\n text=\"Ask AI\"\n icon={<Icon icon={<AiIcon />} size=\"sm\" label=\"\" color=\"inherit\" />}\n onClick={onAskAi}\n />\n ) : null}\n </div>\n);\n"],"names":["NoResults","query","onAskAi","Icon","SearchOffIcon","Text","Button","AiIcon"],"mappings":";;;;AAiBO,MAAMA,YAAY,CAAC,EAAEC,KAAK,EAAEC,OAAO,EAAkB,iBACxD,oBAAC;QAAI,WAAU;qBACX,oBAACC,MAAIA;QAAC,oBAAM,oBAACC,gBAAaA;QAAK,MAAK;QAAK,OAAM;QAAG,OAAM;sBACxD,oBAACC,MAAIA;QAAC,IAAG;QAAM,MAAK;QAAK,WAAU;OAC9B,CAAC,gBAAgB,EAAEJ,MAAM,CAAC,CAAC,iBAEhC,oBAACI,MAAIA;QAAC,IAAG;QAAM,MAAK;QAAK,WAAU;OAC9BH,UACK,0DACA,+BAETA,UAAU,WAAVA,GACG,oBAACI,QAAMA;QACH,SAAQ;QACR,MAAK;QACL,oBAAM,oBAACH,MAAIA;YAAC,oBAAM,oBAACI,iCAAMA;YAAK,MAAK;YAAK,OAAM;YAAG,OAAM;;QACvD,SAASL;SAEb"}
@@ -1,13 +1,13 @@
1
- import type { AiChatPresenter } from "@webiny/app-admin";
1
+ import type { AdminAssistantPresenter } from "@webiny/app-admin";
2
2
  import type { PaletteMode } from "./PaletteMode.js";
3
3
  /**
4
4
  * The assistant, as a palette mode.
5
5
  *
6
6
  * Everything specific to it lives here: what the input row shows, what Enter and Backspace do, and
7
- * what fills the panel. The conversation itself belongs to `AiChatPresenter`.
7
+ * what fills the panel. The conversation itself belongs to `AdminAssistantPresenter`.
8
8
  *
9
9
  * A plain object rather than a hook. `appearance` and `body` are getters, so reading them during a
10
10
  * reactive component's render tracks the presenter's observables and re-renders on a new turn, with
11
11
  * no dependency arrays to keep honest.
12
12
  */
13
- export declare const createAiMode: (presenter: AiChatPresenter.Interface) => PaletteMode;
13
+ export declare const createAiMode: (presenter: AdminAssistantPresenter.Interface) => PaletteMode;
@@ -29,7 +29,7 @@ const createAiMode = (presenter)=>({
29
29
  iconColor: "accent",
30
30
  iconLabel: "Ask AI",
31
31
  badge: /*#__PURE__*/ react.createElement(AiModeBadge, null),
32
- placeholder: started ? "Ask a follow-up…" : "Ask about your content…",
32
+ placeholder: started ? "Ask a follow-up…" : "Ask about this project…",
33
33
  footerLabel: "Webiny AI",
34
34
  hints: HINTS,
35
35
  tall: true
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPalette/modes/createAiMode.js","sources":["../../../src/CommandPalette/modes/createAiMode.tsx"],"sourcesContent":["import React from \"react\";\nimport type { AiChatPresenter } from \"@webiny/app-admin\";\nimport { ReactComponent as AiIcon } from \"@webiny/icons/auto_awesome.svg\";\nimport { ReactComponent as ReturnIcon } from \"@webiny/icons/keyboard_return.svg\";\nimport { ReactComponent as BackspaceIcon } from \"@webiny/icons/backspace.svg\";\nimport { AiModeBadge, AiSuggestions, AiTurn, HintIcon, type Hint } from \"../components/index.js\";\nimport type { PaletteMode, PaletteModeKeyContext } from \"./PaletteMode.js\";\n\nconst HINTS: Hint[] = [\n { keys: <HintIcon element={<ReturnIcon />} />, label: \"Ask\" },\n { keys: <HintIcon element={<BackspaceIcon />} />, label: \"Commands\" },\n { keys: \"esc\", label: \"Close\" }\n];\n\n/**\n * The assistant, as a palette mode.\n *\n * Everything specific to it lives here: what the input row shows, what Enter and Backspace do, and\n * what fills the panel. The conversation itself belongs to `AiChatPresenter`.\n *\n * A plain object rather than a hook. `appearance` and `body` are getters, so reading them during a\n * reactive component's render tracks the presenter's observables and re-renders on a new turn, with\n * no dependency arrays to keep honest.\n */\nexport const createAiMode = (presenter: AiChatPresenter.Interface): PaletteMode => ({\n get appearance() {\n const started = presenter.vm.turns.length > 0;\n\n return {\n icon: <AiIcon />,\n iconColor: \"accent\" as const,\n iconLabel: \"Ask AI\",\n badge: <AiModeBadge />,\n placeholder: started ? \"Ask a follow-up…\" : \"Ask about your content…\",\n footerLabel: \"Webiny AI\",\n hints: HINTS,\n tall: true\n };\n },\n\n get body() {\n const { turns, busy } = presenter.vm;\n\n if (turns.length === 0) {\n return <AiSuggestions onAsk={question => presenter.ask(question)} />;\n }\n\n return turns.map((turn, index) => (\n <AiTurn\n key={index}\n turn={turn}\n initials=\"You\"\n busy={busy}\n onApprove={() => presenter.decide(index, true)}\n onReject={() => presenter.decide(index, false)}\n />\n ));\n },\n\n // A seed only arrives from the no-results \"Ask AI\" button; see `PaletteMode.enter`.\n enter(seed?: string) {\n if (seed?.trim()) {\n presenter.ask(seed);\n }\n },\n\n reset() {\n presenter.reset();\n },\n\n handleKey(event: React.KeyboardEvent, context: PaletteModeKeyContext) {\n if (event.key === \"Enter\") {\n event.preventDefault();\n presenter.ask(context.query);\n context.setQuery(\"\");\n return true;\n }\n\n // Backspace on an empty input leaves the mode, mirroring how space entered it.\n if (event.key === \"Backspace\" && context.query === \"\") {\n event.preventDefault();\n context.exit();\n return true;\n }\n\n return false;\n },\n\n // Answers are long enough to push earlier turns off-screen, so keep the newest one visible.\n afterRender(container: HTMLElement) {\n container.scrollTo({ top: container.scrollHeight });\n }\n});\n"],"names":["HINTS","HintIcon","ReturnIcon","BackspaceIcon","createAiMode","presenter","started","AiIcon","AiModeBadge","turns","busy","AiSuggestions","question","turn","index","AiTurn","seed","event","context","container"],"mappings":";;;;;AAQA,MAAMA,QAAgB;IAClB;QAAE,MAAM,WAAN,GAAM,oBAACC,UAAQA;YAAC,uBAAS,oBAACC,oCAAUA;;QAAS,OAAO;IAAM;IAC5D;QAAE,MAAM,WAAN,GAAM,oBAACD,UAAQA;YAAC,uBAAS,oBAACE,8BAAaA;;QAAS,OAAO;IAAW;IACpE;QAAE,MAAM;QAAO,OAAO;IAAQ;CACjC;AAYM,MAAMC,eAAe,CAACC,YAAuD;QAChF,IAAI,cAAa;YACb,MAAMC,UAAUD,UAAU,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG;YAE5C,OAAO;gBACH,MAAM,WAAN,GAAM,oBAACE,gBAAMA;gBACb,WAAW;gBACX,WAAW;gBACX,OAAO,WAAP,GAAO,oBAACC,aAAWA;gBACnB,aAAaF,UAAU,qBAAqB;gBAC5C,aAAa;gBACb,OAAON;gBACP,MAAM;YACV;QACJ;QAEA,IAAI,QAAO;YACP,MAAM,EAAES,KAAK,EAAEC,IAAI,EAAE,GAAGL,UAAU,EAAE;YAEpC,IAAII,AAAiB,MAAjBA,MAAM,MAAM,EACZ,OAAO,WAAP,GAAO,oBAACE,eAAaA;gBAAC,OAAOC,CAAAA,WAAYP,UAAU,GAAG,CAACO;;YAG3D,OAAOH,MAAM,GAAG,CAAC,CAACI,MAAMC,QAAAA,WAAAA,GACpB,oBAACC,QAAMA;oBACH,KAAKD;oBACL,MAAMD;oBACN,UAAS;oBACT,MAAMH;oBACN,WAAW,IAAML,UAAU,MAAM,CAACS,OAAO;oBACzC,UAAU,IAAMT,UAAU,MAAM,CAACS,OAAO;;QAGpD;QAGA,OAAME,IAAa;YACf,IAAIA,MAAM,QACNX,UAAU,GAAG,CAACW;QAEtB;QAEA;YACIX,UAAU,KAAK;QACnB;QAEA,WAAUY,KAA0B,EAAEC,OAA8B;YAChE,IAAID,AAAc,YAAdA,MAAM,GAAG,EAAc;gBACvBA,MAAM,cAAc;gBACpBZ,UAAU,GAAG,CAACa,QAAQ,KAAK;gBAC3BA,QAAQ,QAAQ,CAAC;gBACjB,OAAO;YACX;YAGA,IAAID,AAAc,gBAAdA,MAAM,GAAG,IAAoBC,AAAkB,OAAlBA,QAAQ,KAAK,EAAS;gBACnDD,MAAM,cAAc;gBACpBC,QAAQ,IAAI;gBACZ,OAAO;YACX;YAEA,OAAO;QACX;QAGA,aAAYC,SAAsB;YAC9BA,UAAU,QAAQ,CAAC;gBAAE,KAAKA,UAAU,YAAY;YAAC;QACrD;IACJ"}
1
+ {"version":3,"file":"CommandPalette/modes/createAiMode.js","sources":["../../../src/CommandPalette/modes/createAiMode.tsx"],"sourcesContent":["import React from \"react\";\nimport type { AdminAssistantPresenter } from \"@webiny/app-admin\";\nimport { ReactComponent as AiIcon } from \"@webiny/icons/auto_awesome.svg\";\nimport { ReactComponent as ReturnIcon } from \"@webiny/icons/keyboard_return.svg\";\nimport { ReactComponent as BackspaceIcon } from \"@webiny/icons/backspace.svg\";\nimport { AiModeBadge, AiSuggestions, AiTurn, HintIcon, type Hint } from \"../components/index.js\";\nimport type { PaletteMode, PaletteModeKeyContext } from \"./PaletteMode.js\";\n\nconst HINTS: Hint[] = [\n { keys: <HintIcon element={<ReturnIcon />} />, label: \"Ask\" },\n { keys: <HintIcon element={<BackspaceIcon />} />, label: \"Commands\" },\n { keys: \"esc\", label: \"Close\" }\n];\n\n/**\n * The assistant, as a palette mode.\n *\n * Everything specific to it lives here: what the input row shows, what Enter and Backspace do, and\n * what fills the panel. The conversation itself belongs to `AdminAssistantPresenter`.\n *\n * A plain object rather than a hook. `appearance` and `body` are getters, so reading them during a\n * reactive component's render tracks the presenter's observables and re-renders on a new turn, with\n * no dependency arrays to keep honest.\n */\nexport const createAiMode = (presenter: AdminAssistantPresenter.Interface): PaletteMode => ({\n get appearance() {\n const started = presenter.vm.turns.length > 0;\n\n return {\n icon: <AiIcon />,\n iconColor: \"accent\" as const,\n iconLabel: \"Ask AI\",\n badge: <AiModeBadge />,\n placeholder: started ? \"Ask a follow-up…\" : \"Ask about this project…\",\n footerLabel: \"Webiny AI\",\n hints: HINTS,\n tall: true\n };\n },\n\n get body() {\n const { turns, busy } = presenter.vm;\n\n if (turns.length === 0) {\n return <AiSuggestions onAsk={question => presenter.ask(question)} />;\n }\n\n return turns.map((turn, index) => (\n <AiTurn\n key={index}\n turn={turn}\n initials=\"You\"\n busy={busy}\n onApprove={() => presenter.decide(index, true)}\n onReject={() => presenter.decide(index, false)}\n />\n ));\n },\n\n // A seed only arrives from the no-results \"Ask AI\" button; see `PaletteMode.enter`.\n enter(seed?: string) {\n if (seed?.trim()) {\n presenter.ask(seed);\n }\n },\n\n reset() {\n presenter.reset();\n },\n\n handleKey(event: React.KeyboardEvent, context: PaletteModeKeyContext) {\n if (event.key === \"Enter\") {\n event.preventDefault();\n presenter.ask(context.query);\n context.setQuery(\"\");\n return true;\n }\n\n // Backspace on an empty input leaves the mode, mirroring how space entered it.\n if (event.key === \"Backspace\" && context.query === \"\") {\n event.preventDefault();\n context.exit();\n return true;\n }\n\n return false;\n },\n\n // Answers are long enough to push earlier turns off-screen, so keep the newest one visible.\n afterRender(container: HTMLElement) {\n container.scrollTo({ top: container.scrollHeight });\n }\n});\n"],"names":["HINTS","HintIcon","ReturnIcon","BackspaceIcon","createAiMode","presenter","started","AiIcon","AiModeBadge","turns","busy","AiSuggestions","question","turn","index","AiTurn","seed","event","context","container"],"mappings":";;;;;AAQA,MAAMA,QAAgB;IAClB;QAAE,MAAM,WAAN,GAAM,oBAACC,UAAQA;YAAC,uBAAS,oBAACC,oCAAUA;;QAAS,OAAO;IAAM;IAC5D;QAAE,MAAM,WAAN,GAAM,oBAACD,UAAQA;YAAC,uBAAS,oBAACE,8BAAaA;;QAAS,OAAO;IAAW;IACpE;QAAE,MAAM;QAAO,OAAO;IAAQ;CACjC;AAYM,MAAMC,eAAe,CAACC,YAA+D;QACxF,IAAI,cAAa;YACb,MAAMC,UAAUD,UAAU,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG;YAE5C,OAAO;gBACH,MAAM,WAAN,GAAM,oBAACE,gBAAMA;gBACb,WAAW;gBACX,WAAW;gBACX,OAAO,WAAP,GAAO,oBAACC,aAAWA;gBACnB,aAAaF,UAAU,qBAAqB;gBAC5C,aAAa;gBACb,OAAON;gBACP,MAAM;YACV;QACJ;QAEA,IAAI,QAAO;YACP,MAAM,EAAES,KAAK,EAAEC,IAAI,EAAE,GAAGL,UAAU,EAAE;YAEpC,IAAII,AAAiB,MAAjBA,MAAM,MAAM,EACZ,OAAO,WAAP,GAAO,oBAACE,eAAaA;gBAAC,OAAOC,CAAAA,WAAYP,UAAU,GAAG,CAACO;;YAG3D,OAAOH,MAAM,GAAG,CAAC,CAACI,MAAMC,QAAAA,WAAAA,GACpB,oBAACC,QAAMA;oBACH,KAAKD;oBACL,MAAMD;oBACN,UAAS;oBACT,MAAMH;oBACN,WAAW,IAAML,UAAU,MAAM,CAACS,OAAO;oBACzC,UAAU,IAAMT,UAAU,MAAM,CAACS,OAAO;;QAGpD;QAGA,OAAME,IAAa;YACf,IAAIA,MAAM,QACNX,UAAU,GAAG,CAACW;QAEtB;QAEA;YACIX,UAAU,KAAK;QACnB;QAEA,WAAUY,KAA0B,EAAEC,OAA8B;YAChE,IAAID,AAAc,YAAdA,MAAM,GAAG,EAAc;gBACvBA,MAAM,cAAc;gBACpBZ,UAAU,GAAG,CAACa,QAAQ,KAAK;gBAC3BA,QAAQ,QAAQ,CAAC;gBACjB,OAAO;YACX;YAGA,IAAID,AAAc,gBAAdA,MAAM,GAAG,IAAoBC,AAAkB,OAAlBA,QAAQ,KAAK,EAAS;gBACnDD,MAAM,cAAc;gBACpBC,QAAQ,IAAI;gBACZ,OAAO;YACX;YAEA,OAAO;QACX;QAGA,aAAYC,SAAsB;YAC9BA,UAAU,QAAQ,CAAC;gBAAE,KAAKA,UAAU,YAAY;YAAC;QACrD;IACJ"}
@@ -2,6 +2,8 @@ import { type CommandPalettePresenter } from "@webiny/app-admin";
2
2
  interface PaletteHotkeysParams {
3
3
  presenter: CommandPalettePresenter.Interface;
4
4
  close: () => void;
5
+ /** False when the license blocks the assistant, so mod+k must not cycle into it. */
6
+ aiEnabled: boolean;
5
7
  }
6
8
  /**
7
9
  * The palette's global keys: the ones that work while it is closed, or while focus is not in the
@@ -10,5 +12,5 @@ interface PaletteHotkeysParams {
10
12
  * Reads the view model during render, so a reactive component calling this still re-renders when
11
13
  * the palette opens, a detail view appears, or AI mode is entered.
12
14
  */
13
- export declare const usePaletteHotkeys: ({ presenter, close }: PaletteHotkeysParams) => void;
15
+ export declare const usePaletteHotkeys: ({ presenter, close, aiEnabled }: PaletteHotkeysParams) => void;
14
16
  export {};
@@ -1,18 +1,20 @@
1
1
  import { useMemo } from "react";
2
2
  import { useHotkeys } from "@webiny/app-admin";
3
3
  import { PALETTE_HOTKEY_ZINDEX } from "./constants.js";
4
- const usePaletteHotkeys = ({ presenter, close })=>{
4
+ const usePaletteHotkeys = ({ presenter, close, aiEnabled })=>{
5
5
  const { isOpen, activeCommand, aiModeActive } = presenter.vm;
6
6
  const keys = useMemo(()=>({
7
7
  "mod+k": (e)=>{
8
8
  e.preventDefault();
9
9
  if (!isOpen) return void presenter.open();
10
10
  if (activeCommand) return void presenter.cancelCommand();
11
- if (!aiModeActive) return void presenter.enterAiMode();
11
+ if (aiEnabled && !aiModeActive) return void presenter.enterAiMode();
12
12
  close();
13
13
  },
14
14
  backspace: (e)=>{
15
- if (e.target instanceof HTMLInputElement) return;
15
+ const target = e.target;
16
+ const editable = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target?.isContentEditable;
17
+ if (!isOpen || !activeCommand || editable) return;
16
18
  e.preventDefault();
17
19
  presenter.cancelCommand();
18
20
  },
@@ -23,7 +25,8 @@ const usePaletteHotkeys = ({ presenter, close })=>{
23
25
  isOpen,
24
26
  activeCommand,
25
27
  aiModeActive,
26
- close
28
+ close,
29
+ aiEnabled
27
30
  ]);
28
31
  useHotkeys({
29
32
  zIndex: PALETTE_HOTKEY_ZINDEX,
@@ -1 +1 @@
1
- {"version":3,"file":"CommandPalette/usePaletteHotkeys.js","sources":["../../src/CommandPalette/usePaletteHotkeys.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useHotkeys, type CommandPalettePresenter } from \"@webiny/app-admin\";\nimport { PALETTE_HOTKEY_ZINDEX } from \"./constants.js\";\n\ninterface PaletteHotkeysParams {\n presenter: CommandPalettePresenter.Interface;\n /* Not `presenter.close`: closing also resets the mode, which is the palette's business. */\n close: () => void;\n}\n\n/**\n * The palette's global keys: the ones that work while it is closed, or while focus is not in the\n * input. Keys that only mean something while typing stay on the input's `onKeyDown`.\n *\n * Reads the view model during render, so a reactive component calling this still re-renders when\n * the palette opens, a detail view appears, or AI mode is entered.\n */\nexport const usePaletteHotkeys = ({ presenter, close }: PaletteHotkeysParams) => {\n const { isOpen, activeCommand, aiModeActive } = presenter.vm;\n\n /*\n * mod+k cycles closed -> commands -> AI -> closed, so the same key that opens the palette also\n * reaches the assistant without the user having to know about the space shortcut. Closing from AI\n * mode keeps mod+k a way OUT of the palette, which is what it does everywhere else.\n * Backspace backs out of a detail view; command shortcuts run directly.\n */\n const keys = useMemo(\n () => ({\n \"mod+k\": (e: KeyboardEvent) => {\n e.preventDefault();\n\n if (!isOpen) {\n presenter.open();\n return;\n }\n\n /*\n * A detail view renders in place of the command list, so switching to AI mode from\n * there would put the user in a mode they cannot see. Back out to the list instead.\n */\n if (activeCommand) {\n presenter.cancelCommand();\n return;\n }\n\n if (!aiModeActive) {\n presenter.enterAiMode();\n return;\n }\n\n close();\n },\n backspace: (e: KeyboardEvent) => {\n if (e.target instanceof HTMLInputElement) {\n return;\n }\n e.preventDefault();\n presenter.cancelCommand();\n },\n ...presenter.shortcutKeys\n }),\n [presenter, presenter.shortcutKeys, isOpen, activeCommand, aiModeActive, close]\n );\n\n useHotkeys({ zIndex: PALETTE_HOTKEY_ZINDEX, keys });\n};\n"],"names":["usePaletteHotkeys","presenter","close","isOpen","activeCommand","aiModeActive","keys","useMemo","e","HTMLInputElement","useHotkeys","PALETTE_HOTKEY_ZINDEX"],"mappings":";;;AAiBO,MAAMA,oBAAoB,CAAC,EAAEC,SAAS,EAAEC,KAAK,EAAwB;IACxE,MAAM,EAAEC,MAAM,EAAEC,aAAa,EAAEC,YAAY,EAAE,GAAGJ,UAAU,EAAE;IAQ5D,MAAMK,OAAOC,QACT,IAAO;YACH,SAAS,CAACC;gBACNA,EAAE,cAAc;gBAEhB,IAAI,CAACL,QAAQ,YACTF,UAAU,IAAI;gBAQlB,IAAIG,eAAe,YACfH,UAAU,aAAa;gBAI3B,IAAI,CAACI,cAAc,YACfJ,UAAU,WAAW;gBAIzBC;YACJ;YACA,WAAW,CAACM;gBACR,IAAIA,EAAE,MAAM,YAAYC,kBACpB;gBAEJD,EAAE,cAAc;gBAChBP,UAAU,aAAa;YAC3B;YACA,GAAGA,UAAU,YAAY;QAC7B,IACA;QAACA;QAAWA,UAAU,YAAY;QAAEE;QAAQC;QAAeC;QAAcH;KAAM;IAGnFQ,WAAW;QAAE,QAAQC;QAAuBL;IAAK;AACrD"}
1
+ {"version":3,"file":"CommandPalette/usePaletteHotkeys.js","sources":["../../src/CommandPalette/usePaletteHotkeys.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useHotkeys, type CommandPalettePresenter } from \"@webiny/app-admin\";\nimport { PALETTE_HOTKEY_ZINDEX } from \"./constants.js\";\n\ninterface PaletteHotkeysParams {\n presenter: CommandPalettePresenter.Interface;\n /* Not `presenter.close`: closing also resets the mode, which is the palette's business. */\n close: () => void;\n /** False when the license blocks the assistant, so mod+k must not cycle into it. */\n aiEnabled: boolean;\n}\n\n/**\n * The palette's global keys: the ones that work while it is closed, or while focus is not in the\n * input. Keys that only mean something while typing stay on the input's `onKeyDown`.\n *\n * Reads the view model during render, so a reactive component calling this still re-renders when\n * the palette opens, a detail view appears, or AI mode is entered.\n */\nexport const usePaletteHotkeys = ({ presenter, close, aiEnabled }: PaletteHotkeysParams) => {\n const { isOpen, activeCommand, aiModeActive } = presenter.vm;\n\n /*\n * mod+k cycles closed -> commands -> AI -> closed, so the same key that opens the palette also\n * reaches the assistant without the user having to know about the space shortcut. Closing from AI\n * mode keeps mod+k a way OUT of the palette, which is what it does everywhere else.\n * Backspace backs out of a detail view; command shortcuts run directly.\n */\n const keys = useMemo(\n () => ({\n \"mod+k\": (e: KeyboardEvent) => {\n e.preventDefault();\n\n if (!isOpen) {\n presenter.open();\n return;\n }\n\n /*\n * A detail view renders in place of the command list, so switching to AI mode from\n * there would put the user in a mode they cannot see. Back out to the list instead.\n */\n if (activeCommand) {\n presenter.cancelCommand();\n return;\n }\n\n if (aiEnabled && !aiModeActive) {\n presenter.enterAiMode();\n return;\n }\n\n close();\n },\n backspace: (e: KeyboardEvent) => {\n /*\n * This listener is global and stays registered while the palette is closed, so it\n * must leave Backspace alone unless there is a detail view to back out of, and never\n * touch it inside an input, textarea or rich text editor.\n */\n const target = e.target as HTMLElement | null;\n const editable =\n target instanceof HTMLInputElement ||\n target instanceof HTMLTextAreaElement ||\n target?.isContentEditable;\n if (!isOpen || !activeCommand || editable) {\n return;\n }\n e.preventDefault();\n presenter.cancelCommand();\n },\n ...presenter.shortcutKeys\n }),\n [presenter, presenter.shortcutKeys, isOpen, activeCommand, aiModeActive, close, aiEnabled]\n );\n\n useHotkeys({ zIndex: PALETTE_HOTKEY_ZINDEX, keys });\n};\n"],"names":["usePaletteHotkeys","presenter","close","aiEnabled","isOpen","activeCommand","aiModeActive","keys","useMemo","e","target","editable","HTMLInputElement","HTMLTextAreaElement","useHotkeys","PALETTE_HOTKEY_ZINDEX"],"mappings":";;;AAmBO,MAAMA,oBAAoB,CAAC,EAAEC,SAAS,EAAEC,KAAK,EAAEC,SAAS,EAAwB;IACnF,MAAM,EAAEC,MAAM,EAAEC,aAAa,EAAEC,YAAY,EAAE,GAAGL,UAAU,EAAE;IAQ5D,MAAMM,OAAOC,QACT,IAAO;YACH,SAAS,CAACC;gBACNA,EAAE,cAAc;gBAEhB,IAAI,CAACL,QAAQ,YACTH,UAAU,IAAI;gBAQlB,IAAII,eAAe,YACfJ,UAAU,aAAa;gBAI3B,IAAIE,aAAa,CAACG,cAAc,YAC5BL,UAAU,WAAW;gBAIzBC;YACJ;YACA,WAAW,CAACO;gBAMR,MAAMC,SAASD,EAAE,MAAM;gBACvB,MAAME,WACFD,kBAAkBE,oBAClBF,kBAAkBG,uBAClBH,QAAQ;gBACZ,IAAI,CAACN,UAAU,CAACC,iBAAiBM,UAC7B;gBAEJF,EAAE,cAAc;gBAChBR,UAAU,aAAa;YAC3B;YACA,GAAGA,UAAU,YAAY;QAC7B,IACA;QAACA;QAAWA,UAAU,YAAY;QAAEG;QAAQC;QAAeC;QAAcJ;QAAOC;KAAU;IAG9FW,WAAW;QAAE,QAAQC;QAAuBR;IAAK;AACrD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/app-admin-ui",
3
- "version": "6.6.0-alpha.4",
3
+ "version": "6.6.0-alpha.5",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -14,10 +14,10 @@
14
14
  "license": "MIT",
15
15
  "dependencies": {
16
16
  "@types/react": "18.3.31",
17
- "@webiny/admin-ui": "6.6.0-alpha.4",
18
- "@webiny/app": "6.6.0-alpha.4",
19
- "@webiny/app-admin": "6.6.0-alpha.4",
20
- "@webiny/icons": "6.6.0-alpha.4",
17
+ "@webiny/admin-ui": "6.6.0-alpha.5",
18
+ "@webiny/app": "6.6.0-alpha.5",
19
+ "@webiny/app-admin": "6.6.0-alpha.5",
20
+ "@webiny/icons": "6.6.0-alpha.5",
21
21
  "cmdk": "1.1.1",
22
22
  "react": "18.3.1",
23
23
  "react-dom": "18.3.1",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/react-helmet": "6.1.11",
28
- "@webiny/build-tools": "6.6.0-alpha.4",
28
+ "@webiny/build-tools": "6.6.0-alpha.5",
29
29
  "rimraf": "6.1.3",
30
30
  "typescript": "7.0.2"
31
31
  },