This is an automated email from the ASF dual-hosted git repository.

EnxDev pushed a commit to branch chat-prototype
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 7b418becc79b2059ddc4ecb093805062e8d71566
Author: Enzo Martellucci <[email protected]>
AuthorDate: Mon May 25 15:04:36 2026 +0200

    feat(extensions): add superset.chatbot contribution point (SIP P1.1)
    
    - Add `app` scope and `AppLocation` type to `ViewContributions` manifest 
schema
    - Add host-internal `getViewProvider` and `getRegisteredViewIds` accessors 
to the views registry
    - Add `getActiveChatbot` resolver with first-to-register fallback policy
    - Mount `ChatbotMount` in the app shell (fixed bottom-right, persists 
across routes
---
 .../superset-core/src/contributions/index.ts       | 51 +-----------
 .../components/ChatbotMount/ChatbotMount.test.tsx  | 91 ++++++++++++++++++++
 .../src/components/ChatbotMount/index.tsx          | 81 ++++++++++++++++++
 superset-frontend/src/core/chatbot/index.test.ts   | 96 ++++++++++++++++++++++
 superset-frontend/src/core/chatbot/index.ts        | 77 +++++++++++++++++
 superset-frontend/src/core/views/index.test.ts     | 63 +++++++++++++-
 superset-frontend/src/core/views/index.ts          | 47 +++++++++++
 superset-frontend/src/views/App.tsx                |  8 ++
 superset-frontend/src/views/contributions.ts       | 31 +++++++
 9 files changed, 497 insertions(+), 48 deletions(-)

diff --git 
a/superset-frontend/packages/superset-core/src/contributions/index.ts 
b/superset-frontend/packages/superset-core/src/contributions/index.ts
index faccbb305dc..787f10261b5 100644
--- a/superset-frontend/packages/superset-core/src/contributions/index.ts
+++ b/superset-frontend/packages/superset-core/src/contributions/index.ts
@@ -17,23 +17,9 @@
  * under the License.
  */
 
-/**
- * @fileoverview Manifest schema for Superset extension contributions.
- *
- * This module defines the aggregate interfaces used by the extension.json
- * manifest and the `superset-extensions` build command. Individual metadata
- * types are defined in their respective namespace modules (commands, views,
- * menus, editors) and re-exported here for the manifest schema.
- */
-
-import { Command } from '../commands';
 import { View } from '../views';
 import { Menu } from '../menus';
-import { Editor } from '../editors';
 
-/**
- * Valid locations within SQL Lab.
- */
 export type SqlLabLocation =
   | 'leftSidebar'
   | 'rightSidebar'
@@ -43,43 +29,14 @@ export type SqlLabLocation =
   | 'results'
   | 'queryHistory';
 
-/**
- * Nested structure for view contributions by scope and location.
- * @example
- * {
- *   sqllab: {
- *     panels: [{ id: "my-ext.panel", name: "My Panel" }],
- *     leftSidebar: [{ id: "my-ext.sidebar", name: "My Sidebar" }]
- *   }
- * }
- */
+/** Valid locations within the app shell (persist across all routes). */
+export type AppLocation = 'chatbot';
+
 export interface ViewContributions {
   sqllab?: Partial<Record<SqlLabLocation, View[]>>;
+  app?: Partial<Record<AppLocation, View[]>>;
 }
 
-/**
- * Nested structure for menu contributions by scope and location.
- * @example
- * {
- *   sqllab: {
- *     editor: { primary: [...], secondary: [...] }
- *   }
- * }
- */
 export interface MenuContributions {
   sqllab?: Partial<Record<SqlLabLocation, Menu>>;
 }
-
-/**
- * Aggregates all contributions (commands, menus, views, and editors) provided 
by an extension or module.
- */
-export interface Contributions {
-  /** List of commands. */
-  commands: Command[];
-  /** Nested mapping of menu contributions by scope and location. */
-  menus: MenuContributions;
-  /** Nested mapping of view contributions by scope and location. */
-  views: ViewContributions;
-  /** List of editors. */
-  editors?: Editor[];
-}
diff --git 
a/superset-frontend/src/components/ChatbotMount/ChatbotMount.test.tsx 
b/superset-frontend/src/components/ChatbotMount/ChatbotMount.test.tsx
new file mode 100644
index 00000000000..ba546005398
--- /dev/null
+++ b/superset-frontend/src/components/ChatbotMount/ChatbotMount.test.tsx
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import { views } from 'src/core';
+import { CHATBOT_LOCATION } from 'src/views/contributions';
+import ChatbotMount from '.';
+
+const disposables: Array<{ dispose: () => void }> = [];
+
+afterEach(() => {
+  disposables.forEach(d => d.dispose());
+  disposables.length = 0;
+});
+
+test('renders nothing when no chatbot extension is registered', () => {
+  render(<ChatbotMount />);
+
+  expect(screen.queryByTestId('chatbot-mount')).not.toBeInTheDocument();
+});
+
+test('renders the registered chatbot inside the fixed mount slot', () => {
+  const provider = () => React.createElement('div', null, 'My Chatbot Bubble');
+  disposables.push(
+    views.registerView(
+      { id: 'superset.chatbot', name: 'Superset Chatbot' },
+      CHATBOT_LOCATION,
+      provider,
+    ),
+  );
+
+  render(<ChatbotMount />);
+
+  expect(screen.getByTestId('chatbot-mount')).toBeInTheDocument();
+  expect(screen.getByText('My Chatbot Bubble')).toBeInTheDocument();
+});
+
+test('renders only the first-to-register chatbot when several are installed', 
() => {
+  const firstProvider = () => React.createElement('div', null, 'First Bubble');
+  const secondProvider = () =>
+    React.createElement('div', null, 'Second Bubble');
+  disposables.push(
+    views.registerView(
+      { id: 'first.chatbot', name: 'First Chatbot' },
+      CHATBOT_LOCATION,
+      firstProvider,
+    ),
+    views.registerView(
+      { id: 'second.chatbot', name: 'Second Chatbot' },
+      CHATBOT_LOCATION,
+      secondProvider,
+    ),
+  );
+
+  render(<ChatbotMount />);
+
+  expect(screen.getByText('First Bubble')).toBeInTheDocument();
+  expect(screen.queryByText('Second Bubble')).not.toBeInTheDocument();
+});
+
+test('isolates a failing chatbot so it does not crash the host', () => {
+  const FailingChatbot = () => {
+    throw new Error('chatbot blew up');
+  };
+  disposables.push(
+    views.registerView(
+      { id: 'superset.chatbot', name: 'Superset Chatbot' },
+      CHATBOT_LOCATION,
+      () => React.createElement(FailingChatbot),
+    ),
+  );
+
+  // The host-owned error boundary catches the failure; render does not throw.
+  expect(() => render(<ChatbotMount />)).not.toThrow();
+});
diff --git a/superset-frontend/src/components/ChatbotMount/index.tsx 
b/superset-frontend/src/components/ChatbotMount/index.tsx
new file mode 100644
index 00000000000..17d16c9e9f3
--- /dev/null
+++ b/superset-frontend/src/components/ChatbotMount/index.tsx
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * @fileoverview Host mount point for the singleton `superset.chatbot`
+ * contribution area.
+ *
+ * The host owns the slot: a fixed bottom-right anchor that persists across all
+ * routes, with a managed z-index. The extension owns everything rendered
+ * inside it — the collapsed bubble, the expanded panel, all open/close state,
+ * animations, and behavior (SIP §3.2 "Component contract").
+ *
+ * Singleton resolution (which of possibly several registered chatbots renders)
+ * is delegated to `getActiveChatbot`. If no chatbot extension is registered,
+ * this component renders nothing and the corner stays empty.
+ */
+
+import { css, useTheme } from '@apache-superset/core/theme';
+import { ErrorBoundary } from 'src/components/ErrorBoundary';
+import { getActiveChatbot } from 'src/core/chatbot';
+
+/**
+ * Margin from the viewport edges for the chatbot anchor, per SIP §3.2.
+ */
+const CHATBOT_EDGE_MARGIN = 24;
+
+/**
+ * Renders the active chatbot extension into a fixed bottom-right slot.
+ *
+ * Mounted once at the app root so the bubble is available consistently across
+ * routes. Renders `null` when no chatbot extension is registered.
+ *
+ * Resolution happens once at mount: chatbot extensions are registered
+ * synchronously during extension loading, before this component renders (it
+ * sits inside `ExtensionsStartup`). Reacting to chatbots that register or
+ * unregister at runtime — deactivate / uninstall / replace — is the extension
+ * lifecycle work tracked separately under SIP §8 phase P1.
+ */
+const ChatbotMount = () => {
+  const theme = useTheme();
+  const activeChatbot = getActiveChatbot();
+
+  if (!activeChatbot) {
+    return null;
+  }
+
+  return (
+    <div
+      data-test="chatbot-mount"
+      css={css`
+        position: fixed;
+        right: ${CHATBOT_EDGE_MARGIN}px;
+        bottom: ${CHATBOT_EDGE_MARGIN}px;
+        /*
+         * Above dashboard content and the toast layer
+         * (toasts sit at zIndexPopupBase + 1), below modal dialogs.
+         */
+        z-index: ${theme.zIndexPopupBase + 2};
+      `}
+    >
+      <ErrorBoundary>{activeChatbot.provider()}</ErrorBoundary>
+    </div>
+  );
+};
+
+export default ChatbotMount;
diff --git a/superset-frontend/src/core/chatbot/index.test.ts 
b/superset-frontend/src/core/chatbot/index.test.ts
new file mode 100644
index 00000000000..0b8d6f2f6ec
--- /dev/null
+++ b/superset-frontend/src/core/chatbot/index.test.ts
@@ -0,0 +1,96 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { views } from 'src/core/views';
+import { CHATBOT_LOCATION } from 'src/views/contributions';
+import { getActiveChatbot } from './index';
+
+const disposables: Array<{ dispose: () => void }> = [];
+
+afterEach(() => {
+  disposables.forEach(d => d.dispose());
+  disposables.length = 0;
+});
+
+test('getActiveChatbot returns undefined when no chatbot is registered', () => 
{
+  expect(getActiveChatbot()).toBeUndefined();
+});
+
+test('getActiveChatbot resolves the single registered chatbot', () => {
+  const provider = () => React.createElement('div', null, 'Chatbot');
+  disposables.push(
+    views.registerView(
+      { id: 'superset.chatbot', name: 'Superset Chatbot' },
+      CHATBOT_LOCATION,
+      provider,
+    ),
+  );
+
+  const active = getActiveChatbot();
+  expect(active).toEqual({ id: 'superset.chatbot', provider });
+});
+
+test('getActiveChatbot picks the first-to-register when multiple are 
installed', () => {
+  const firstProvider = () => React.createElement('div', null, 'First');
+  const secondProvider = () => React.createElement('div', null, 'Second');
+  disposables.push(
+    views.registerView(
+      { id: 'first.chatbot', name: 'First Chatbot' },
+      CHATBOT_LOCATION,
+      firstProvider,
+    ),
+    views.registerView(
+      { id: 'second.chatbot', name: 'Second Chatbot' },
+      CHATBOT_LOCATION,
+      secondProvider,
+    ),
+  );
+
+  const active = getActiveChatbot();
+  expect(active?.id).toBe('first.chatbot');
+  expect(active?.provider).toBe(firstProvider);
+});
+
+test('getActiveChatbot ignores views registered at other locations', () => {
+  const provider = () => React.createElement('div', null, 'Panel');
+  disposables.push(
+    views.registerView(
+      { id: 'some.panel', name: 'Some Panel' },
+      'sqllab.panels',
+      provider,
+    ),
+  );
+
+  expect(getActiveChatbot()).toBeUndefined();
+});
+
+test('getActiveChatbot stops resolving a chatbot once it is disposed', () => {
+  const provider = () => React.createElement('div', null, 'Chatbot');
+  const disposable = views.registerView(
+    { id: 'superset.chatbot', name: 'Superset Chatbot' },
+    CHATBOT_LOCATION,
+    provider,
+  );
+
+  expect(getActiveChatbot()?.id).toBe('superset.chatbot');
+
+  disposable.dispose();
+
+  expect(getActiveChatbot()).toBeUndefined();
+});
diff --git a/superset-frontend/src/core/chatbot/index.ts 
b/superset-frontend/src/core/chatbot/index.ts
new file mode 100644
index 00000000000..4598445ce6f
--- /dev/null
+++ b/superset-frontend/src/core/chatbot/index.ts
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * @fileoverview Host-internal resolver for the exclusive `superset.chatbot`
+ * contribution area.
+ *
+ * `superset.chatbot` is a singleton contribution area: multiple chatbot
+ * extensions may register a view there, but the host renders exactly one.
+ * This module owns the host-side selection policy.
+ *
+ * This is host-internal infrastructure — it is NOT part of the public
+ * `@apache-superset/core` API. Extensions register via the public
+ * `views.registerView()`; only the host resolves which one is active.
+ */
+
+import { ReactElement } from 'react';
+import { CHATBOT_LOCATION } from 'src/views/contributions';
+import { getRegisteredViewIds, getViewProvider } from 'src/core/views';
+
+/**
+ * The resolved active chatbot: a view id paired with its renderable provider.
+ */
+export interface ActiveChatbot {
+  /** The registered view id of the selected chatbot. */
+  id: string;
+  /** The provider that renders the chatbot's React element. */
+  provider: () => ReactElement;
+}
+
+/**
+ * Resolves which single chatbot extension is currently active.
+ *
+ * Selection policy (P1):
+ *  - If no chatbot is registered, returns `undefined` — the corner stays 
empty.
+ *  - If one or more chatbots are registered, the first one to register wins.
+ *
+ * `Set` preserves insertion order, so "first to register" is deterministic.
+ *
+ * This is the P1 fallback policy. P2 introduces an admin "Default chatbot"
+ * setting (SIP §4 option (c)); when that lands, the admin-selected id takes
+ * precedence here and this first-to-register behavior remains only as the
+ * fallback used when no admin setting is configured.
+ *
+ * @returns The active chatbot's id and provider, or `undefined` if none.
+ */
+export const getActiveChatbot = (): ActiveChatbot | undefined => {
+  const registeredIds = getRegisteredViewIds(CHATBOT_LOCATION);
+  if (registeredIds.length === 0) {
+    return undefined;
+  }
+
+  // Deterministic first-to-register fallback. P2 will consult the admin
+  // "Default chatbot" setting before this point.
+  const [selectedId] = registeredIds;
+  const provider = getViewProvider(CHATBOT_LOCATION, selectedId);
+  if (!provider) {
+    return undefined;
+  }
+
+  return { id: selectedId, provider };
+};
diff --git a/superset-frontend/src/core/views/index.test.ts 
b/superset-frontend/src/core/views/index.test.ts
index d98a05b7f4f..16413b109a0 100644
--- a/superset-frontend/src/core/views/index.test.ts
+++ b/superset-frontend/src/core/views/index.test.ts
@@ -17,7 +17,12 @@
  * under the License.
  */
 import React from 'react';
-import { views, resolveView } from './index';
+import {
+  views,
+  resolveView,
+  getViewProvider,
+  getRegisteredViewIds,
+} from './index';
 
 const disposables: Array<{ dispose: () => void }> = [];
 
@@ -110,3 +115,59 @@ test('dispose removes the view registration', () => {
 
   expect(views.getViews('sqllab.panels')).toBeUndefined();
 });
+
+test('getViewProvider returns the registered provider for a matching 
location', () => {
+  const provider = () => React.createElement('div', null, 'Test');
+  disposables.push(
+    views.registerView(
+      { id: 'test.provider', name: 'Test Provider' },
+      'superset.chatbot',
+      provider,
+    ),
+  );
+
+  expect(getViewProvider('superset.chatbot', 'test.provider')).toBe(provider);
+});
+
+test('getViewProvider returns undefined when the location does not match', () 
=> {
+  const provider = () => React.createElement('div', null, 'Test');
+  disposables.push(
+    views.registerView(
+      { id: 'test.provider', name: 'Test Provider' },
+      'sqllab.panels',
+      provider,
+    ),
+  );
+
+  // Registered, but at a different location.
+  expect(getViewProvider('superset.chatbot', 'test.provider')).toBeUndefined();
+});
+
+test('getViewProvider returns undefined for an unknown id', () => {
+  expect(getViewProvider('superset.chatbot', 'nonexistent')).toBeUndefined();
+});
+
+test('getRegisteredViewIds returns ids in registration order', () => {
+  const provider = () => React.createElement('div', null, 'Test');
+  disposables.push(
+    views.registerView(
+      { id: 'first.chatbot', name: 'First' },
+      'superset.chatbot',
+      provider,
+    ),
+    views.registerView(
+      { id: 'second.chatbot', name: 'Second' },
+      'superset.chatbot',
+      provider,
+    ),
+  );
+
+  expect(getRegisteredViewIds('superset.chatbot')).toEqual([
+    'first.chatbot',
+    'second.chatbot',
+  ]);
+});
+
+test('getRegisteredViewIds returns an empty array for an unused location', () 
=> {
+  expect(getRegisteredViewIds('superset.chatbot')).toEqual([]);
+});
diff --git a/superset-frontend/src/core/views/index.ts 
b/superset-frontend/src/core/views/index.ts
index 5bed7d10910..6f29143d453 100644
--- a/superset-frontend/src/core/views/index.ts
+++ b/superset-frontend/src/core/views/index.ts
@@ -77,6 +77,53 @@ const getViews: typeof viewsApi.getViews = (
     .filter((c): c is View => !!c);
 };
 
+/**
+ * Host-internal accessor that returns the registered `provider` for a view id
+ * at a given location.
+ *
+ * This is deliberately NOT part of the public `@apache-superset/core` `views`
+ * API. The public `getViews` returns descriptors only (`id`/`name`/...), so an
+ * extension can discover what is registered but cannot obtain — and therefore
+ * cannot render — another extension's view outside the host's mount point,
+ * lifecycle, and fault-isolation boundary.
+ *
+ * The host uses this accessor to render exclusive (singleton) contribution
+ * areas such as `superset.chatbot`, where it must enumerate the candidates and
+ * then render exactly one. See `getActiveChatbot` in `src/core/chatbot`.
+ *
+ * @param location The contribution location (e.g. `superset.chatbot`).
+ * @param id The registered view id.
+ * @returns The provider function, or undefined if no matching view is
+ *   registered at that location.
+ */
+export const getViewProvider = (
+  location: string,
+  id: string,
+): (() => ReactElement) | undefined => {
+  const entry = viewRegistry.get(id);
+  if (entry?.location !== location) {
+    return undefined;
+  }
+  return entry.provider;
+};
+
+/**
+ * Host-internal accessor that returns the ordered list of view ids registered
+ * at a location, in registration order.
+ *
+ * Registration order is meaningful for exclusive locations: the host's
+ * deterministic fallback policy ("first to register wins") relies on it.
+ * Like {@link getViewProvider}, this is host-internal and not part of the
+ * public API.
+ *
+ * @param location The contribution location.
+ * @returns View ids in registration order, or an empty array if none.
+ */
+export const getRegisteredViewIds = (location: string): string[] => {
+  const ids = locationIndex.get(location);
+  return ids ? Array.from(ids) : [];
+};
+
 export const views: typeof viewsApi = {
   registerView,
   getViews,
diff --git a/superset-frontend/src/views/App.tsx 
b/superset-frontend/src/views/App.tsx
index 4f30a552e94..d84b111ca84 100644
--- a/superset-frontend/src/views/App.tsx
+++ b/superset-frontend/src/views/App.tsx
@@ -39,6 +39,7 @@ import setupCodeOverrides from 'src/setup/setupCodeOverrides';
 import { logEvent } from 'src/logger/actions';
 import { store } from 'src/views/store';
 import ExtensionsStartup from 'src/extensions/ExtensionsStartup';
+import ChatbotMount from 'src/components/ChatbotMount';
 import { RootContextProviders } from './RootContextProviders';
 import { ScrollToTop } from './ScrollToTop';
 
@@ -112,6 +113,13 @@ const App = () => (
             </Route>
           ))}
         </Switch>
+        {/*
+          The singleton chatbot bubble. Rendered as a sibling of the route
+          Switch — inside ExtensionsStartup so chatbot extensions have been
+          loaded and registered, but outside the Switch so the bubble persists
+          across route changes (SIP §3.2).
+        */}
+        <ChatbotMount />
       </ExtensionsStartup>
       <ToastContainer />
     </RootContextProviders>
diff --git a/superset-frontend/src/views/contributions.ts 
b/superset-frontend/src/views/contributions.ts
new file mode 100644
index 00000000000..ec075222b23
--- /dev/null
+++ b/superset-frontend/src/views/contributions.ts
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+/**
+ * View locations for app-shell extension integration.
+ *
+ * These define locations that persist across all routes, mirroring the `app`
+ * scope of the `ViewContributions` manifest schema.
+ */
+export const AppViewLocations = {
+  app: {
+    chatbot: 'superset.chatbot',
+  },
+} as const;
+
+export const CHATBOT_LOCATION = AppViewLocations.app.chatbot;

Reply via email to