@databiosphere/findable-ui 49.1.0 → 49.2.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.
Files changed (49) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +14 -0
  3. package/lib/views/ExploreView/entityList/filters/components/ToggleButtonGroup/toggleButtonGroup.d.ts +2 -3
  4. package/lib/views/ExploreView/entityList/filters/components/ToggleButtonGroup/toggleButtonGroup.js +3 -4
  5. package/lib/views/ResearchView/assistant/assistant.js +3 -5
  6. package/lib/views/ResearchView/assistant/components/Form/form.d.ts +1 -2
  7. package/lib/views/ResearchView/assistant/components/Form/form.js +5 -18
  8. package/lib/views/ResearchView/assistant/components/Form/types.d.ts +1 -2
  9. package/lib/views/ResearchView/assistant/components/Form/utils.d.ts +1 -1
  10. package/lib/views/ResearchView/assistant/components/Messages/hooks/UseScroll/hook.d.ts +2 -1
  11. package/lib/views/ResearchView/assistant/components/Messages/hooks/UseScroll/hook.js +5 -2
  12. package/lib/views/ResearchView/assistant/components/ToggleButtonGroup/toggleButtonGroup.d.ts +2 -3
  13. package/lib/views/ResearchView/assistant/components/ToggleButtonGroup/toggleButtonGroup.js +3 -4
  14. package/lib/views/ResearchView/assistant/stories/assistant.stories.js +1 -1
  15. package/lib/views/ResearchView/state/provider.d.ts +4 -1
  16. package/lib/views/ResearchView/state/provider.js +5 -2
  17. package/lib/views/ResearchView/state/query/context.d.ts +5 -0
  18. package/lib/views/ResearchView/state/query/context.js +7 -0
  19. package/lib/views/ResearchView/state/query/hooks/UseQuery/hook.d.ts +6 -0
  20. package/lib/views/ResearchView/state/query/hooks/UseQuery/hook.js +9 -0
  21. package/lib/views/ResearchView/state/query/hooks/UseSubmit/hook.d.ts +7 -0
  22. package/lib/views/ResearchView/state/query/hooks/UseSubmit/hook.js +46 -0
  23. package/lib/views/ResearchView/state/query/provider.d.ts +13 -0
  24. package/lib/views/ResearchView/state/query/provider.js +15 -0
  25. package/lib/views/ResearchView/{query → state/query}/types.d.ts +5 -12
  26. package/package.json +1 -1
  27. package/src/views/ExploreView/entityList/filters/components/ToggleButtonGroup/toggleButtonGroup.tsx +8 -4
  28. package/src/views/ResearchView/assistant/assistant.tsx +3 -5
  29. package/src/views/ResearchView/assistant/components/Form/form.tsx +4 -19
  30. package/src/views/ResearchView/assistant/components/Form/types.ts +0 -2
  31. package/src/views/ResearchView/assistant/components/Form/utils.ts +1 -1
  32. package/src/views/ResearchView/assistant/components/Messages/hooks/UseScroll/hook.ts +5 -2
  33. package/src/views/ResearchView/assistant/components/ToggleButtonGroup/toggleButtonGroup.tsx +8 -4
  34. package/src/views/ResearchView/assistant/stories/assistant.stories.tsx +1 -1
  35. package/src/views/ResearchView/state/provider.tsx +8 -1
  36. package/src/views/ResearchView/state/query/context.ts +9 -0
  37. package/src/views/ResearchView/state/query/hooks/UseQuery/hook.ts +11 -0
  38. package/src/views/ResearchView/state/query/hooks/UseSubmit/hook.ts +66 -0
  39. package/src/views/ResearchView/state/query/provider.tsx +27 -0
  40. package/src/views/ResearchView/{query → state/query}/types.ts +9 -15
  41. package/tests/research.queryProvider.test.ts +321 -0
  42. package/lib/views/ResearchView/adapter/useAdapter.d.ts +0 -6
  43. package/lib/views/ResearchView/adapter/useAdapter.js +0 -15
  44. package/lib/views/ResearchView/query/useQuery.d.ts +0 -7
  45. package/lib/views/ResearchView/query/useQuery.js +0 -44
  46. package/src/views/ResearchView/adapter/useAdapter.ts +0 -19
  47. package/src/views/ResearchView/query/useQuery.ts +0 -60
  48. package/tests/research.useQuery.test.ts +0 -165
  49. /package/lib/views/ResearchView/{query → state/query}/types.js +0 -0
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "49.1.0"
2
+ ".": "49.2.0"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [49.2.0](https://github.com/DataBiosphere/findable-ui/compare/v49.1.0...v49.2.0) (2026-03-02)
4
+
5
+
6
+ ### Features
7
+
8
+ * add href to selected toggle buttons for navigation ([#805](https://github.com/DataBiosphere/findable-ui/issues/805)) ([7bacb28](https://github.com/DataBiosphere/findable-ui/commit/7bacb280d4075bb2f403d076e14ee58d26905045))
9
+ * lift query submission into queryprovider ([#803](https://github.com/DataBiosphere/findable-ui/issues/803)) ([466780e](https://github.com/DataBiosphere/findable-ui/commit/466780e12c3218bea29b82cd9759d24334573d91))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * use instant scroll on mount and smooth scroll on subsequent updates ([#806](https://github.com/DataBiosphere/findable-ui/issues/806)) ([30777a7](https://github.com/DataBiosphere/findable-ui/commit/30777a7338f28726959b757ffad8577890c56db0))
15
+ * use instant scroll on mount for messages panel ([#807](https://github.com/DataBiosphere/findable-ui/issues/807)) ([30777a7](https://github.com/DataBiosphere/findable-ui/commit/30777a7338f28726959b757ffad8577890c56db0))
16
+
3
17
  ## [49.1.0](https://github.com/DataBiosphere/findable-ui/compare/v49.0.0...v49.1.0) (2026-02-28)
4
18
 
5
19
 
@@ -1,7 +1,6 @@
1
1
  import { JSX } from "react";
2
2
  /**
3
- * ToggleButtonGroup component for navigating to ResearchView.
4
- * Only navigates to ResearchView when the "Research" button is clicked, otherwise remains on ExploreView.
5
- * @returns ToggleButtonGroup JSX element.
3
+ * ToggleButtonGroup component for navigating between ExploreView and ResearchView.
4
+ * @returns ToggleButtonGroup JSX element, or null if routes are not configured.
6
5
  */
7
6
  export declare const ToggleButtonGroup: () => JSX.Element | null;
@@ -6,13 +6,12 @@ import { useAiRoutes } from "../../../../../../hooks/ai/useAiRoutes/hook";
6
6
  import { StyledToggleButtonGroup } from "./toggleButtonGroup.styles";
7
7
  import { Beta } from "../../../../../../components/common/Chip/components/Beta/beta";
8
8
  /**
9
- * ToggleButtonGroup component for navigating to ResearchView.
10
- * Only navigates to ResearchView when the "Research" button is clicked, otherwise remains on ExploreView.
11
- * @returns ToggleButtonGroup JSX element.
9
+ * ToggleButtonGroup component for navigating between ExploreView and ResearchView.
10
+ * @returns ToggleButtonGroup JSX element, or null if routes are not configured.
12
11
  */
13
12
  export const ToggleButtonGroup = () => {
14
13
  const { routes } = useAiRoutes() || {};
15
14
  if (!routes)
16
15
  return null;
17
- return (_jsx(StyledBox, { children: _jsxs(StyledToggleButtonGroup, { exclusive: true, children: [_jsxs(ToggleButton, { component: Link, href: routes.research, value: "research", children: ["Research ", _jsx(Beta, {})] }), _jsx(ToggleButton, { selected: true, value: "search", children: "Search" })] }) }));
16
+ return (_jsx(StyledBox, { children: _jsxs(StyledToggleButtonGroup, { exclusive: true, children: [_jsxs(ToggleButton, { component: Link, href: routes.research, value: "research", children: ["Research ", _jsx(Beta, {})] }), _jsx(ToggleButton, { component: Link, href: routes.search, selected: true, value: "search", children: "Search" })] }) }));
18
17
  };
@@ -1,18 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useAdapter } from "../adapter/useAdapter";
2
+ import { useChatState } from "../state/hooks/UseChatState/hook";
3
3
  import { Form } from "./components/Form/form";
4
4
  import { Input } from "./components/Input/input";
5
- import { Messages } from "./components/Messages/messages";
6
5
  import { getPlaceholder } from "./components/Input/utils";
6
+ import { Messages } from "./components/Messages/messages";
7
7
  import { Drawer } from "./components/Drawer/drawer";
8
- import { useChatState } from "../state/hooks/UseChatState/hook";
9
8
  import { ToggleButtonGroup } from "./components/ToggleButtonGroup/toggleButtonGroup";
10
9
  /**
11
10
  * Renders the research assistant drawer.
12
11
  * @returns The assistant drawer.
13
12
  */
14
13
  export const Assistant = () => {
15
- const { actions } = useAdapter();
16
14
  const { state } = useChatState();
17
- return (_jsxs(Drawer, { children: [_jsx(ToggleButtonGroup, {}), _jsxs(Form, { actions: actions, status: state.status, children: [_jsx(Messages, { state: state }), _jsx(Input, { disabled: state.status.loading, placeholder: getPlaceholder(state) })] })] }));
15
+ return (_jsxs(Drawer, { children: [_jsx(ToggleButtonGroup, {}), _jsxs(Form, { status: state.status, children: [_jsx(Messages, { state: state }), _jsx(Input, { disabled: state.status.loading, placeholder: getPlaceholder(state) })] })] }));
18
16
  };
@@ -3,10 +3,9 @@ import { FormProps } from "./types";
3
3
  /**
4
4
  * Renders the research form.
5
5
  * @param props - Component props.
6
- * @param props.actions - Form actions.
7
6
  * @param props.children - Form children.
8
7
  * @param props.className - Class name for styling.
9
8
  * @param props.status - Form status.
10
9
  * @returns The research form container element.
11
10
  */
12
- export declare const Form: ({ actions, children, className, status, }: FormProps) => JSX.Element;
11
+ export declare const Form: ({ children, className, status, }: FormProps) => JSX.Element;
@@ -1,39 +1,26 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { TEST_IDS } from "../../../../../tests/testIds";
3
- import { useChatDispatch } from "../../../state/hooks/UseChatDispatch/hook";
3
+ import { useQuery } from "../../../state/query/hooks/UseQuery/hook";
4
4
  import { FIELD_NAME } from "./constants";
5
- import { getPayload } from "./utils";
6
5
  import { StyledForm } from "./form.styles";
6
+ import { getPayload } from "./utils";
7
7
  /**
8
8
  * Renders the research form.
9
9
  * @param props - Component props.
10
- * @param props.actions - Form actions.
11
10
  * @param props.children - Form children.
12
11
  * @param props.className - Class name for styling.
13
12
  * @param props.status - Form status.
14
13
  * @returns The research form container element.
15
14
  */
16
- export const Form = ({ actions, children, className, status, }) => {
17
- const dispatch = useChatDispatch();
15
+ export const Form = ({ children, className, status, }) => {
16
+ const { onSubmit } = useQuery();
18
17
  return (_jsx(StyledForm, { className: className, "data-testid": TEST_IDS.RESEARCH_FORM, onSubmit: async (e) => {
19
- await actions.onSubmit(e, getPayload(e), {
20
- onError: (error) => {
21
- dispatch.onSetError(error.message);
22
- },
23
- onMutate: (form, query) => {
24
- dispatch.onSetQuery(query);
25
- dispatch.onSetStatus(true);
26
- form.reset();
27
- },
18
+ await onSubmit(e, getPayload(e), {
28
19
  onSettled: (form) => {
29
- dispatch.onSetStatus(false);
30
20
  const input = form.elements.namedItem(FIELD_NAME.AI_PROMPT);
31
21
  if (input instanceof HTMLElement)
32
22
  input.focus();
33
23
  },
34
- onSuccess: (data) => {
35
- dispatch.onSetMessage(data);
36
- },
37
24
  status,
38
25
  });
39
26
  }, children: children }));
@@ -1,4 +1,3 @@
1
- import { UseQuery } from "../../../query/types";
2
1
  import { BaseComponentProps, ChildrenProps } from "../../../../../components/types";
3
2
  import { ChatState } from "../../../state/types";
4
- export type FormProps = BaseComponentProps & ChildrenProps & Pick<UseQuery, "actions"> & Pick<ChatState, "status">;
3
+ export type FormProps = BaseComponentProps & ChildrenProps & Pick<ChatState, "status">;
@@ -1,5 +1,5 @@
1
1
  import { FormEvent } from "react";
2
- import { OnSubmitPayload } from "../../../query/types";
2
+ import { OnSubmitPayload } from "../../../state/query/types";
3
3
  /**
4
4
  * Extracts and trims form values from a form element.
5
5
  * @param form - Form element.
@@ -1,6 +1,7 @@
1
1
  import { DependencyList, RefObject } from "react";
2
2
  /**
3
- * Provides a ref that auto-scrolls to the bottom when dependencies change.
3
+ * Provides a ref that scrolls to the bottom when dependencies change.
4
+ * Uses instant scroll on mount and smooth scroll on subsequent updates.
4
5
  * @param deps - Dependency list that triggers scroll on change.
5
6
  * @returns A ref to attach to the scrollable container.
6
7
  */
@@ -1,16 +1,19 @@
1
1
  import { useEffect, useRef } from "react";
2
2
  /**
3
- * Provides a ref that auto-scrolls to the bottom when dependencies change.
3
+ * Provides a ref that scrolls to the bottom when dependencies change.
4
+ * Uses instant scroll on mount and smooth scroll on subsequent updates.
4
5
  * @param deps - Dependency list that triggers scroll on change.
5
6
  * @returns A ref to attach to the scrollable container.
6
7
  */
7
8
  export function useScroll(deps) {
9
+ const behaviorRef = useRef("instant");
8
10
  const ref = useRef(null);
9
11
  useEffect(() => {
10
12
  ref.current?.scrollTo({
11
- behavior: "smooth",
13
+ behavior: behaviorRef.current,
12
14
  top: ref.current.scrollHeight,
13
15
  });
16
+ behaviorRef.current = "smooth";
14
17
  // eslint-disable-next-line react-hooks/exhaustive-deps -- deps are passed in as an argument
15
18
  }, deps);
16
19
  return ref;
@@ -1,7 +1,6 @@
1
1
  import { JSX } from "react";
2
2
  /**
3
- * ToggleButtonGroup component for navigating to ExploreView.
4
- * Only navigates to ExploreView when the "Search" button is clicked, otherwise remains on ResearchView.
5
- * @returns ToggleButtonGroup JSX element.
3
+ * ToggleButtonGroup component for navigating between ResearchView and ExploreView.
4
+ * @returns ToggleButtonGroup JSX element, or null if routes are not configured.
6
5
  */
7
6
  export declare const ToggleButtonGroup: () => JSX.Element | null;
@@ -5,13 +5,12 @@ import Link from "next/link";
5
5
  import { useAiRoutes } from "../../../../../hooks/ai/useAiRoutes/hook";
6
6
  import { Beta } from "../../../../../components/common/Chip/components/Beta/beta";
7
7
  /**
8
- * ToggleButtonGroup component for navigating to ExploreView.
9
- * Only navigates to ExploreView when the "Search" button is clicked, otherwise remains on ResearchView.
10
- * @returns ToggleButtonGroup JSX element.
8
+ * ToggleButtonGroup component for navigating between ResearchView and ExploreView.
9
+ * @returns ToggleButtonGroup JSX element, or null if routes are not configured.
11
10
  */
12
11
  export const ToggleButtonGroup = () => {
13
12
  const { routes } = useAiRoutes() || {};
14
13
  if (!routes)
15
14
  return null;
16
- return (_jsx(StyledBox, { children: _jsxs(StyledToggleButtonGroup, { exclusive: true, children: [_jsxs(ToggleButton, { selected: true, value: "research", children: ["Research ", _jsx(Beta, {})] }), _jsx(ToggleButton, { component: Link, href: routes.search, value: "search", children: "Search" })] }) }));
15
+ return (_jsx(StyledBox, { children: _jsxs(StyledToggleButtonGroup, { exclusive: true, children: [_jsxs(ToggleButton, { component: Link, href: routes.research, selected: true, value: "research", children: ["Research ", _jsx(Beta, {})] }), _jsx(ToggleButton, { component: Link, href: routes.search, value: "search", children: "Search" })] }) }));
17
16
  };
@@ -8,7 +8,7 @@ import { ConfigProvider } from "../../../../providers/config";
8
8
  const meta = {
9
9
  component: Assistant,
10
10
  decorators: [
11
- (Story) => (_jsx(ConfigProvider, { config: INITIAL_CONFIG, children: _jsx(ChatProvider, { initialArgs: INITIAL_ARGS, children: _jsx(Box, { sx: {
11
+ (Story) => (_jsx(ConfigProvider, { config: INITIAL_CONFIG, children: _jsx(ChatProvider, { initialArgs: INITIAL_ARGS, url: "https://api.example.com", children: _jsx(Box, { sx: {
12
12
  backgroundColor: PALETTE.COMMON_WHITE,
13
13
  display: "flex",
14
14
  flexDirection: "column",
@@ -3,14 +3,17 @@ import { InitialArgs } from "./initializer/types";
3
3
  /**
4
4
  * Provider for Chat state.
5
5
  * Manages chat state such as query and response data.
6
+ * Nests QueryProvider to own the fetch lifecycle for query submission.
6
7
  *
7
8
  * @param props - Props.
8
9
  * @param props.children - Children.
9
10
  * @param props.initialArgs - Initial arguments.
11
+ * @param props.url - URL for the query endpoint.
10
12
  *
11
13
  * @returns A context provider wrapping the given children.
12
14
  */
13
- export declare function ChatProvider({ children, initialArgs, }: {
15
+ export declare function ChatProvider({ children, initialArgs, url, }: {
14
16
  children: ReactNode;
15
17
  initialArgs?: InitialArgs;
18
+ url: string;
16
19
  }): JSX.Element;
@@ -1,17 +1,20 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ChatContext } from "./context";
3
3
  import { useChatReducer } from "./hooks/UseChatReducer/hook";
4
+ import { QueryProvider } from "./query/provider";
4
5
  /**
5
6
  * Provider for Chat state.
6
7
  * Manages chat state such as query and response data.
8
+ * Nests QueryProvider to own the fetch lifecycle for query submission.
7
9
  *
8
10
  * @param props - Props.
9
11
  * @param props.children - Children.
10
12
  * @param props.initialArgs - Initial arguments.
13
+ * @param props.url - URL for the query endpoint.
11
14
  *
12
15
  * @returns A context provider wrapping the given children.
13
16
  */
14
- export function ChatProvider({ children, initialArgs, }) {
17
+ export function ChatProvider({ children, initialArgs, url, }) {
15
18
  const reducer = useChatReducer(initialArgs);
16
- return (_jsx(ChatContext.Provider, { value: reducer, children: children }));
19
+ return (_jsx(ChatContext.Provider, { value: reducer, children: _jsx(QueryProvider, { url: url, children: children }) }));
17
20
  }
@@ -0,0 +1,5 @@
1
+ import { QueryContextValue } from "./types";
2
+ /**
3
+ * Context for the query submission provider.
4
+ */
5
+ export declare const QueryContext: import("react").Context<QueryContextValue>;
@@ -0,0 +1,7 @@
1
+ import { createContext } from "react";
2
+ /**
3
+ * Context for the query submission provider.
4
+ */
5
+ export const QueryContext = createContext({
6
+ onSubmit: () => Promise.resolve(),
7
+ });
@@ -0,0 +1,6 @@
1
+ import { QueryContextValue } from "../../types";
2
+ /**
3
+ * Hook to access query submission from the QueryProvider.
4
+ * @returns Query context value with onSubmit.
5
+ */
6
+ export declare const useQuery: () => QueryContextValue;
@@ -0,0 +1,9 @@
1
+ import { useContext } from "react";
2
+ import { QueryContext } from "../../context";
3
+ /**
4
+ * Hook to access query submission from the QueryProvider.
5
+ * @returns Query context value with onSubmit.
6
+ */
7
+ export const useQuery = () => {
8
+ return useContext(QueryContext);
9
+ };
@@ -0,0 +1,7 @@
1
+ import { QueryContextValue } from "../../types";
2
+ /**
3
+ * Hook that manages query submission and abort lifecycle.
4
+ * @param url - The API URL to send queries to.
5
+ * @returns Object containing the onSubmit handler.
6
+ */
7
+ export declare const useSubmit: (url: string) => Pick<QueryContextValue, "onSubmit">;
@@ -0,0 +1,46 @@
1
+ import { useCallback, useRef } from "react";
2
+ import { fetchResponse } from "../../../../query/fetch";
3
+ import { useChatDispatch } from "../../../hooks/UseChatDispatch/hook";
4
+ /**
5
+ * Hook that manages query submission and abort lifecycle.
6
+ * @param url - The API URL to send queries to.
7
+ * @returns Object containing the onSubmit handler.
8
+ */
9
+ export const useSubmit = (url) => {
10
+ const abortRef = useRef(null);
11
+ const dispatch = useChatDispatch();
12
+ const onSubmit = useCallback(async (e, payload, options) => {
13
+ e.preventDefault();
14
+ if (options.status.loading)
15
+ return;
16
+ const { query } = payload;
17
+ if (!query)
18
+ return;
19
+ const form = e.currentTarget;
20
+ // Dispatch query and loading state.
21
+ dispatch.onSetQuery(query);
22
+ dispatch.onSetStatus(true);
23
+ form.reset();
24
+ options.onMutate?.(form, query);
25
+ // Abort any in-flight request.
26
+ abortRef.current?.abort();
27
+ const controller = new AbortController();
28
+ abortRef.current = controller;
29
+ await fetchResponse(url, query, {
30
+ controller,
31
+ onError: (error) => {
32
+ dispatch.onSetError(error.message);
33
+ options.onError?.(error);
34
+ },
35
+ onSettled: () => {
36
+ dispatch.onSetStatus(false);
37
+ options.onSettled?.(form);
38
+ },
39
+ onSuccess: (data) => {
40
+ dispatch.onSetMessage(data);
41
+ options.onSuccess?.(data);
42
+ },
43
+ });
44
+ }, [dispatch, url]);
45
+ return { onSubmit };
46
+ };
@@ -0,0 +1,13 @@
1
+ import { JSX, ReactNode } from "react";
2
+ /**
3
+ * Provider that owns the fetch lifecycle for query submission.
4
+ * Persists across page navigation so in-flight requests are not aborted.
5
+ * @param props - Props.
6
+ * @param props.children - Children.
7
+ * @param props.url - URL for the query endpoint.
8
+ * @returns A context provider wrapping the given children.
9
+ */
10
+ export declare function QueryProvider({ children, url, }: {
11
+ children: ReactNode;
12
+ url: string;
13
+ }): JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { QueryContext } from "./context";
3
+ import { useSubmit } from "./hooks/UseSubmit/hook";
4
+ /**
5
+ * Provider that owns the fetch lifecycle for query submission.
6
+ * Persists across page navigation so in-flight requests are not aborted.
7
+ * @param props - Props.
8
+ * @param props.children - Children.
9
+ * @param props.url - URL for the query endpoint.
10
+ * @returns A context provider wrapping the given children.
11
+ */
12
+ export function QueryProvider({ children, url, }) {
13
+ const { onSubmit } = useSubmit(url);
14
+ return (_jsx(QueryContext.Provider, { value: { onSubmit }, children: children }));
15
+ }
@@ -1,10 +1,5 @@
1
1
  import { FormEvent } from "react";
2
- /**
3
- * Actions returned by the useQuery hook.
4
- */
5
- export interface Actions {
6
- onSubmit: (e: FormEvent<HTMLFormElement>, payload: OnSubmitPayload, options?: OnSubmitOptions) => Promise<void>;
7
- }
2
+ import { Status } from "../types";
8
3
  /**
9
4
  * Options for the onSubmit action.
10
5
  */
@@ -13,9 +8,7 @@ export interface OnSubmitOptions {
13
8
  onMutate?: (form: HTMLFormElement, query: string) => void;
14
9
  onSettled?: (form: HTMLFormElement) => void;
15
10
  onSuccess?: (data: unknown) => void;
16
- status?: {
17
- loading: boolean;
18
- };
11
+ status: Status;
19
12
  }
20
13
  /**
21
14
  * Payload for the onSubmit action.
@@ -24,8 +17,8 @@ export interface OnSubmitPayload {
24
17
  query: string;
25
18
  }
26
19
  /**
27
- * Return type for the useQuery hook.
20
+ * Context value for the QueryProvider.
28
21
  */
29
- export interface UseQuery {
30
- actions: Actions;
22
+ export interface QueryContextValue {
23
+ onSubmit: (e: FormEvent<HTMLFormElement>, payload: OnSubmitPayload, options: OnSubmitOptions) => Promise<void>;
31
24
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@databiosphere/findable-ui",
3
- "version": "49.1.0",
3
+ "version": "49.2.0",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
@@ -7,9 +7,8 @@ import { StyledToggleButtonGroup } from "./toggleButtonGroup.styles";
7
7
  import { Beta } from "../../../../../../components/common/Chip/components/Beta/beta";
8
8
 
9
9
  /**
10
- * ToggleButtonGroup component for navigating to ResearchView.
11
- * Only navigates to ResearchView when the "Research" button is clicked, otherwise remains on ExploreView.
12
- * @returns ToggleButtonGroup JSX element.
10
+ * ToggleButtonGroup component for navigating between ExploreView and ResearchView.
11
+ * @returns ToggleButtonGroup JSX element, or null if routes are not configured.
13
12
  */
14
13
  export const ToggleButtonGroup = (): JSX.Element | null => {
15
14
  const { routes } = useAiRoutes() || {};
@@ -22,7 +21,12 @@ export const ToggleButtonGroup = (): JSX.Element | null => {
22
21
  <ToggleButton component={Link} href={routes.research} value="research">
23
22
  Research <Beta />
24
23
  </ToggleButton>
25
- <ToggleButton selected value="search">
24
+ <ToggleButton
25
+ component={Link}
26
+ href={routes.search}
27
+ selected
28
+ value="search"
29
+ >
26
30
  Search
27
31
  </ToggleButton>
28
32
  </StyledToggleButtonGroup>
@@ -1,11 +1,10 @@
1
1
  import { JSX } from "react";
2
- import { useAdapter } from "../adapter/useAdapter";
2
+ import { useChatState } from "../state/hooks/UseChatState/hook";
3
3
  import { Form } from "./components/Form/form";
4
4
  import { Input } from "./components/Input/input";
5
- import { Messages } from "./components/Messages/messages";
6
5
  import { getPlaceholder } from "./components/Input/utils";
6
+ import { Messages } from "./components/Messages/messages";
7
7
  import { Drawer } from "./components/Drawer/drawer";
8
- import { useChatState } from "../state/hooks/UseChatState/hook";
9
8
  import { ToggleButtonGroup } from "./components/ToggleButtonGroup/toggleButtonGroup";
10
9
 
11
10
  /**
@@ -13,12 +12,11 @@ import { ToggleButtonGroup } from "./components/ToggleButtonGroup/toggleButtonGr
13
12
  * @returns The assistant drawer.
14
13
  */
15
14
  export const Assistant = (): JSX.Element => {
16
- const { actions } = useAdapter();
17
15
  const { state } = useChatState();
18
16
  return (
19
17
  <Drawer>
20
18
  <ToggleButtonGroup />
21
- <Form actions={actions} status={state.status}>
19
+ <Form status={state.status}>
22
20
  <Messages state={state} />
23
21
  <Input
24
22
  disabled={state.status.loading}
@@ -1,50 +1,35 @@
1
1
  import { JSX } from "react";
2
2
  import { TEST_IDS } from "../../../../../tests/testIds";
3
- import { useChatDispatch } from "../../../state/hooks/UseChatDispatch/hook";
4
- import { MessageResponse } from "../../../state/types";
3
+ import { useQuery } from "../../../state/query/hooks/UseQuery/hook";
5
4
  import { FIELD_NAME } from "./constants";
5
+ import { StyledForm } from "./form.styles";
6
6
  import { FormProps } from "./types";
7
7
  import { getPayload } from "./utils";
8
- import { StyledForm } from "./form.styles";
9
8
 
10
9
  /**
11
10
  * Renders the research form.
12
11
  * @param props - Component props.
13
- * @param props.actions - Form actions.
14
12
  * @param props.children - Form children.
15
13
  * @param props.className - Class name for styling.
16
14
  * @param props.status - Form status.
17
15
  * @returns The research form container element.
18
16
  */
19
17
  export const Form = ({
20
- actions,
21
18
  children,
22
19
  className,
23
20
  status,
24
21
  }: FormProps): JSX.Element => {
25
- const dispatch = useChatDispatch();
22
+ const { onSubmit } = useQuery();
26
23
  return (
27
24
  <StyledForm
28
25
  className={className}
29
26
  data-testid={TEST_IDS.RESEARCH_FORM}
30
27
  onSubmit={async (e) => {
31
- await actions.onSubmit(e, getPayload(e), {
32
- onError: (error) => {
33
- dispatch.onSetError(error.message);
34
- },
35
- onMutate: (form, query) => {
36
- dispatch.onSetQuery(query);
37
- dispatch.onSetStatus(true);
38
- form.reset();
39
- },
28
+ await onSubmit(e, getPayload(e), {
40
29
  onSettled: (form) => {
41
- dispatch.onSetStatus(false);
42
30
  const input = form.elements.namedItem(FIELD_NAME.AI_PROMPT);
43
31
  if (input instanceof HTMLElement) input.focus();
44
32
  },
45
- onSuccess: (data) => {
46
- dispatch.onSetMessage(data as MessageResponse);
47
- },
48
33
  status,
49
34
  });
50
35
  }}
@@ -1,4 +1,3 @@
1
- import { UseQuery } from "../../../query/types";
2
1
  import {
3
2
  BaseComponentProps,
4
3
  ChildrenProps,
@@ -7,5 +6,4 @@ import { ChatState } from "../../../state/types";
7
6
 
8
7
  export type FormProps = BaseComponentProps &
9
8
  ChildrenProps &
10
- Pick<UseQuery, "actions"> &
11
9
  Pick<ChatState, "status">;
@@ -1,6 +1,6 @@
1
1
  import { FormEvent } from "react";
2
2
  import { FIELD_NAME } from "./constants";
3
- import { OnSubmitPayload } from "../../../query/types";
3
+ import { OnSubmitPayload } from "../../../state/query/types";
4
4
 
5
5
  /**
6
6
  * Extracts and trims form values from a form element.
@@ -1,20 +1,23 @@
1
1
  import { DependencyList, RefObject, useEffect, useRef } from "react";
2
2
 
3
3
  /**
4
- * Provides a ref that auto-scrolls to the bottom when dependencies change.
4
+ * Provides a ref that scrolls to the bottom when dependencies change.
5
+ * Uses instant scroll on mount and smooth scroll on subsequent updates.
5
6
  * @param deps - Dependency list that triggers scroll on change.
6
7
  * @returns A ref to attach to the scrollable container.
7
8
  */
8
9
  export function useScroll(
9
10
  deps: DependencyList,
10
11
  ): RefObject<HTMLDivElement | null> {
12
+ const behaviorRef = useRef<ScrollBehavior>("instant");
11
13
  const ref = useRef<HTMLDivElement>(null);
12
14
 
13
15
  useEffect(() => {
14
16
  ref.current?.scrollTo({
15
- behavior: "smooth",
17
+ behavior: behaviorRef.current,
16
18
  top: ref.current.scrollHeight,
17
19
  });
20
+ behaviorRef.current = "smooth";
18
21
  // eslint-disable-next-line react-hooks/exhaustive-deps -- deps are passed in as an argument
19
22
  }, deps);
20
23
 
@@ -6,9 +6,8 @@ import { useAiRoutes } from "../../../../../hooks/ai/useAiRoutes/hook";
6
6
  import { Beta } from "../../../../../components/common/Chip/components/Beta/beta";
7
7
 
8
8
  /**
9
- * ToggleButtonGroup component for navigating to ExploreView.
10
- * Only navigates to ExploreView when the "Search" button is clicked, otherwise remains on ResearchView.
11
- * @returns ToggleButtonGroup JSX element.
9
+ * ToggleButtonGroup component for navigating between ResearchView and ExploreView.
10
+ * @returns ToggleButtonGroup JSX element, or null if routes are not configured.
12
11
  */
13
12
  export const ToggleButtonGroup = (): JSX.Element | null => {
14
13
  const { routes } = useAiRoutes() || {};
@@ -18,7 +17,12 @@ export const ToggleButtonGroup = (): JSX.Element | null => {
18
17
  return (
19
18
  <StyledBox>
20
19
  <StyledToggleButtonGroup exclusive>
21
- <ToggleButton selected value="research">
20
+ <ToggleButton
21
+ component={Link}
22
+ href={routes.research}
23
+ selected
24
+ value="research"
25
+ >
22
26
  Research <Beta />
23
27
  </ToggleButton>
24
28
  <ToggleButton component={Link} href={routes.search} value="search">
@@ -12,7 +12,7 @@ const meta: Meta<typeof Assistant> = {
12
12
  decorators: [
13
13
  (Story): JSX.Element => (
14
14
  <ConfigProvider config={INITIAL_CONFIG}>
15
- <ChatProvider initialArgs={INITIAL_ARGS}>
15
+ <ChatProvider initialArgs={INITIAL_ARGS} url="https://api.example.com">
16
16
  <Box
17
17
  sx={{
18
18
  backgroundColor: PALETTE.COMMON_WHITE,