diff --git a/clients/ts-sdk/openapi.json b/clients/ts-sdk/openapi.json index bddfffda0e..78b0c44062 100644 --- a/clients/ts-sdk/openapi.json +++ b/clients/ts-sdk/openapi.json @@ -11809,6 +11809,13 @@ "type": "boolean", "nullable": true }, + "tabMessages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PublicPageTabMessage" + }, + "nullable": true + }, "theme": { "allOf": [ { @@ -11962,6 +11969,25 @@ "search_type": "semantic" } }, + "PublicPageTabMessage": { + "type": "object", + "required": [ + "title", + "tabInnerHtml", + "showComponentCode" + ], + "properties": { + "showComponentCode": { + "type": "boolean" + }, + "tabInnerHtml": { + "type": "string" + }, + "title": { + "type": "string" + } + } + }, "PublicPageTheme": { "type": "string", "enum": [ @@ -15015,6 +15041,12 @@ ], "nullable": true }, + "recency_bias": { + "type": "number", + "format": "float", + "description": "Recency Bias lets you determine how much of an effect the recency of chunks will have on the search results. If not specified, this defaults to 0.0. We recommend setting this to 1.0 for a gentle reranking of the results, >3.0 for a strong reranking of the results.", + "nullable": true + }, "sort_by": { "allOf": [ { diff --git a/clients/ts-sdk/src/types.gen.ts b/clients/ts-sdk/src/types.gen.ts index ced2853a2d..01c57c94e4 100644 --- a/clients/ts-sdk/src/types.gen.ts +++ b/clients/ts-sdk/src/types.gen.ts @@ -1863,6 +1863,7 @@ export type PublicPageParameters = { responsive?: (boolean) | null; searchOptions?: ((PublicPageSearchOptions) | null); suggestedQueries?: (boolean) | null; + tabMessages?: Array | null; theme?: ((PublicPageTheme) | null); type?: (string) | null; useGroupSearch?: (boolean) | null; @@ -1916,6 +1917,12 @@ export type PublicPageSearchOptions = { user_id?: (string) | null; }; +export type PublicPageTabMessage = { + showComponentCode: boolean; + tabInnerHtml: string; + title: string; +}; + export type PublicPageTheme = 'light' | 'dark'; export type QdrantChunkMetadata = { @@ -2720,6 +2727,10 @@ export type SortBySearchType = { */ export type SortOptions = { location_bias?: ((GeoInfoWithBias) | null); + /** + * Recency Bias lets you determine how much of an effect the recency of chunks will have on the search results. If not specified, this defaults to 0.0. We recommend setting this to 1.0 for a gentle reranking of the results, >3.0 for a strong reranking of the results. + */ + recency_bias?: (number) | null; sort_by?: ((QdrantSortBy) | null); /** * Tag weights is a JSON object which can be used to boost the ranking of chunks with certain tags. This is useful for when you want to be able to bias towards chunks with a certain tag on the fly. The keys are the tag names and the values are the weights. diff --git a/frontends/dashboard/src/hooks/usePublicPageSettings.tsx b/frontends/dashboard/src/hooks/usePublicPageSettings.tsx new file mode 100644 index 0000000000..d8df445db5 --- /dev/null +++ b/frontends/dashboard/src/hooks/usePublicPageSettings.tsx @@ -0,0 +1,227 @@ +import { createSignal, createEffect, useContext, createMemo } from "solid-js"; +import { createStore } from "solid-js/store"; +import { Dataset, PublicPageParameters } from "trieve-ts-sdk"; +import { createQuery } from "@tanstack/solid-query"; +import { DatasetContext } from "../contexts/DatasetContext"; +import { UserContext } from "../contexts/UserContext"; +import { useTrieve } from "./useTrieve"; +import { createToast } from "../components/ShowToasts"; +import { ApiRoutes } from "../components/Routes"; +import { HeroPatterns } from "../pages/dataset/HeroPatterns"; +import { createInitializedContext } from "../utils/initialize"; + +export type DatasetWithPublicPage = Dataset & { + server_configuration: { + PUBLIC_DATASET?: { + extra_params: PublicPageParameters; + enabled: boolean; + }; + }; +}; + +export const { use: usePublicPage, provider: PublicPageProvider } = + createInitializedContext("public-page-settings", () => { + const [extraParams, setExtraParams] = createStore({}); + const [searchOptionsError, setSearchOptionsError] = createSignal< + string | null + >(null); + + const [isPublic, setisPublic] = createSignal(false); + const [hasLoaded, setHasLoaded] = createSignal(false); + + const { datasetId } = useContext(DatasetContext); + const { selectedOrg } = useContext(UserContext); + + const trieve = useTrieve(); + + createEffect(() => { + void ( + trieve.fetch<"eject">("/api/dataset/{dataset_id}", "get", { + datasetId: datasetId(), + }) as Promise + ).then((dataset) => { + setisPublic(!!dataset.server_configuration?.PUBLIC_DATASET?.enabled); + setExtraParams( + dataset?.server_configuration?.PUBLIC_DATASET?.extra_params || {}, + ); + + setHasLoaded(true); + }); + }); + + const crawlSettingsQuery = createQuery(() => ({ + queryKey: ["crawl-settings", datasetId()], + queryFn: async () => { + const result = await trieve.fetch( + "/api/dataset/crawl_options/{dataset_id}", + "get", + { + datasetId: datasetId(), + }, + ); + return result.crawl_options ?? null; + }, + })); + + // If the useGroupSearch has not been manually set, + // set to true if shopify scraping is enabled + createEffect(() => { + if ( + crawlSettingsQuery.data && + crawlSettingsQuery.data.scrape_options?.type === "shopify" + ) { + if ( + extraParams.useGroupSearch === null || + extraParams.useGroupSearch === undefined + ) { + setExtraParams("useGroupSearch", true); + } + } + }); + + // manually set the array for rolemessages to simplify logic + // context blocks until it's set + createEffect(() => { + if ( + extraParams.tabMessages === undefined || + extraParams.tabMessages === null + ) { + setExtraParams("tabMessages", []); + } + }); + + // Selecting blank as the hero pattern should reset everything else + // Selecting another pattern builds the svg field + createEffect(() => { + const pattern = extraParams.heroPattern?.heroPatternName; + const foreground = extraParams.heroPattern?.foregroundColor; + if (hasLoaded()) { + if (pattern == "Blank" || !pattern) { + setExtraParams("heroPattern", { + heroPatternName: "Blank", + heroPatternSvg: "", + foregroundColor: "#ffffff", + foregroundOpacity: 0.5, + backgroundColor: "#f3f3f3", + }); + } else if (pattern == "Solid") { + setExtraParams("heroPattern", (prev) => ({ + ...prev, + backgroundColor: foreground, + })); + } else { + setExtraParams("heroPattern", (prev) => ({ + ...prev, + heroPatternSvg: HeroPatterns[pattern]( + prev?.foregroundColor || "#ffffff", + prev?.foregroundOpacity || 0.5, + ), + })); + } + } + }); + + const unpublishDataset = async () => { + await trieve.fetch("/api/dataset", "put", { + organizationId: selectedOrg().id, + data: { + dataset_id: datasetId(), + server_configuration: { + PUBLIC_DATASET: { + enabled: false, + }, + }, + }, + }); + + createToast({ + type: "info", + title: `Made dataset ${datasetId()} private`, + }); + + setisPublic(false); + }; + + const publishDataset = async () => { + const name = `${datasetId()}-pregenerated-search-component`; + if (!isPublic()) { + const response = await trieve.fetch( + "/api/organization/api_key", + "post", + { + data: { + name: name, + role: 0, + dataset_ids: [datasetId()], + scopes: ApiRoutes["Search Component Routes"], + }, + organizationId: selectedOrg().id, + }, + ); + + await trieve.fetch("/api/dataset", "put", { + organizationId: selectedOrg().id, + data: { + dataset_id: datasetId(), + server_configuration: { + PUBLIC_DATASET: { + enabled: true, + // @ts-expect-error Object literal may only specify known properties, and 'api_key' does not exist in type 'PublicDatasetOptions'. [2353] + api_key: response.api_key, + extra_params: { + ...extraParams, + }, + }, + }, + }, + }); + + createToast({ + type: "info", + title: `Created API key for ${datasetId()} named ${name}`, + }); + } else { + await trieve.fetch("/api/dataset", "put", { + organizationId: selectedOrg().id, + data: { + dataset_id: datasetId(), + server_configuration: { + PUBLIC_DATASET: { + enabled: true, + extra_params: { + ...extraParams, + }, + }, + }, + }, + }); + + createToast({ + type: "info", + title: `Updated Public settings for ${name}`, + }); + } + + setExtraParams(extraParams); + setisPublic(true); + }; + + const apiHost = import.meta.env.VITE_API_HOST as unknown as string; + const publicUrl = createMemo(() => { + return `${apiHost.slice(0, -4)}/public_page/${datasetId()}`; + }); + + return { + extraParams, + setExtraParams, + searchOptionsError, + setSearchOptionsError, + isPublic, + publicUrl, + unpublishDataset, + publishDataset, + get ready() { + return hasLoaded() && !!extraParams.tabMessages; + }, + }; + }); diff --git a/frontends/dashboard/src/index.css b/frontends/dashboard/src/index.css index 954b2ca9cb..62fcb49862 100644 --- a/frontends/dashboard/src/index.css +++ b/frontends/dashboard/src/index.css @@ -69,6 +69,10 @@ pre.shiki { } } +.jsoneditor.jsoneditor-mode-code { + @apply border-neutral-300; +} + @keyframes fadeIn { from { opacity: 0; diff --git a/frontends/dashboard/src/index.tsx b/frontends/dashboard/src/index.tsx index 93f60ec33d..9983658dcb 100644 --- a/frontends/dashboard/src/index.tsx +++ b/frontends/dashboard/src/index.tsx @@ -37,7 +37,7 @@ import { RecommendationsTablePage } from "./analytics/pages/tablePages/Recommend import { SingleRecommendationQueryPage } from "./analytics/pages/SingleRecommendationQueryPage.tsx"; import { EventsTablePage } from "./analytics/pages/tablePages/EventsTablePage.tsx"; import { SingleEventQueryPage } from "./analytics/pages/SingleEventQueryPage.tsx"; -import { PublicPageSettings } from "./pages/dataset/PublicPageSettings.tsx"; +import { PublicPageSettingsPage } from "./pages/dataset/PublicPageSettings.tsx"; if (!DEV) { Sentry.init({ @@ -151,7 +151,7 @@ const routes: RouteDefinition[] = [ }, { path: "/public-page", - component: PublicPageSettings, + component: PublicPageSettingsPage, }, { path: "/analytics", diff --git a/frontends/dashboard/src/pages/dataset/PublicPageSettings.tsx b/frontends/dashboard/src/pages/dataset/PublicPageSettings.tsx index 5b2ec67ca7..eed20f8c8d 100644 --- a/frontends/dashboard/src/pages/dataset/PublicPageSettings.tsx +++ b/frontends/dashboard/src/pages/dataset/PublicPageSettings.tsx @@ -1,217 +1,19 @@ -import { createSignal, createEffect, Show, useContext } from "solid-js"; -import { createToast } from "../../components/ShowToasts"; -import { ApiRoutes } from "../../components/Routes"; -import { DatasetContext } from "../../contexts/DatasetContext"; -import { UserContext } from "../../contexts/UserContext"; -import { useTrieve } from "../../hooks/useTrieve"; -import { createMemo } from "solid-js"; +import { createEffect, createSignal, For, Show } from "solid-js"; import { CopyButton } from "../../components/CopyButton"; import { FaRegularCircleQuestion } from "solid-icons/fa"; import { JsonInput, MultiStringInput, Select, Tooltip } from "shared/ui"; -import { createStore } from "solid-js/store"; -import { Dataset, PublicPageParameters } from "trieve-ts-sdk"; import { publicPageSearchOptionsSchema } from "../../analytics/utils/schemas/autocomplete"; -import { FiExternalLink } from "solid-icons/fi"; -import { createQuery } from "@tanstack/solid-query"; -import { HeroPatterns } from "./HeroPatterns"; - -export type DatasetWithPublicPage = Dataset & { - server_configuration: { - PUBLIC_DATASET?: { - extra_params: PublicPageParameters; - enabled: boolean; - }; - }; -}; - -export const PublicPageSettings = () => { - const apiHost = import.meta.env.VITE_API_HOST as unknown as string; - - const [extraParams, setExtraParams] = createStore({}); - const [searchOptionsError, setSearchOptionsError] = createSignal< - string | null - >(null); - const [isPublic, setisPublic] = createSignal(false); - const [hasLoaded, setHasLoaded] = createSignal(false); - - const { datasetId } = useContext(DatasetContext); - const { selectedOrg } = useContext(UserContext); - - const publicUrl = createMemo(() => { - return `${apiHost.slice(0, -4)}/public_page/${datasetId()}`; - }); - - const [heroPattern, setHeroPattern] = createSignal("Blank"); - const [foregroundColor, setForegroundColor] = createSignal("#ffffff"); - const [foregroundOpacity, setForegroundOpacity] = createSignal(50); - const [backgroundColor, setBackgroundColor] = createSignal("#ffffff"); - - const trieve = useTrieve(); - - createEffect(() => { - void ( - trieve.fetch<"eject">("/api/dataset/{dataset_id}", "get", { - datasetId: datasetId(), - }) as Promise - ).then((dataset) => { - setisPublic(!!dataset.server_configuration?.PUBLIC_DATASET?.enabled); - setExtraParams( - dataset?.server_configuration?.PUBLIC_DATASET?.extra_params || {}, - ); - - const params = - dataset?.server_configuration?.PUBLIC_DATASET?.extra_params; - setHeroPattern(params?.heroPattern?.heroPatternName || "Blank"); - setForegroundColor(params?.heroPattern?.foregroundColor || "#000000"); - setForegroundOpacity( - (params?.heroPattern?.foregroundOpacity || 0.5) * 100, - ); - setBackgroundColor(params?.heroPattern?.backgroundColor || "#000000"); - - setHasLoaded(true); - }); - }); - - const crawlSettingsQuery = createQuery(() => ({ - queryKey: ["crawl-settings", datasetId()], - queryFn: async () => { - const result = await trieve.fetch( - "/api/dataset/crawl_options/{dataset_id}", - "get", - { - datasetId: datasetId(), - }, - ); - - return result.crawl_options ?? null; - }, - })); - - // If the useGroupSearch has not been manually set, - // set to true if shopify scraping is enabled - createEffect(() => { - if ( - crawlSettingsQuery.data && - crawlSettingsQuery.data.scrape_options?.type === "shopify" - ) { - if ( - extraParams.useGroupSearch === null || - extraParams.useGroupSearch === undefined - ) { - setExtraParams("useGroupSearch", true); - } - } - }); - - const unpublishDataset = async () => { - await trieve.fetch("/api/dataset", "put", { - organizationId: selectedOrg().id, - data: { - dataset_id: datasetId(), - server_configuration: { - PUBLIC_DATASET: { - enabled: false, - }, - }, - }, - }); - - createToast({ - type: "info", - title: `Made dataset ${datasetId()} private`, - }); - - setisPublic(false); - }; +import { FiExternalLink, FiPlus, FiTrash } from "solid-icons/fi"; - createEffect(() => { - const pattern = heroPattern(); - const color = foregroundColor(); - const opacity = foregroundOpacity() / 100; - - if (hasLoaded()) { - if (pattern === "Blank") { - setExtraParams("heroPattern", { - heroPatternSvg: "", - heroPatternName: "", - foregroundColor: "#ffffff", - foregroundOpacity: 0.5, - backgroundColor: "#ffffff", - }); - } else { - const heroPattern = { - heroPatternSvg: HeroPatterns[pattern](color, opacity), - heroPatternName: pattern, - foregroundColor: color, - foregroundOpacity: opacity, - backgroundColor: backgroundColor(), - }; - - setExtraParams("heroPattern", heroPattern); - } - } - }); - - const publishDataset = async () => { - const name = `${datasetId()}-pregenerated-search-component`; - if (!isPublic()) { - const response = await trieve.fetch("/api/organization/api_key", "post", { - data: { - name: name, - role: 0, - dataset_ids: [datasetId()], - scopes: ApiRoutes["Search Component Routes"], - }, - organizationId: selectedOrg().id, - }); - - await trieve.fetch("/api/dataset", "put", { - organizationId: selectedOrg().id, - data: { - dataset_id: datasetId(), - server_configuration: { - PUBLIC_DATASET: { - enabled: true, - // @ts-expect-error Object literal may only specify known properties, and 'api_key' does not exist in type 'PublicDatasetOptions'. [2353] - api_key: response.api_key, - extra_params: { - ...extraParams, - }, - }, - }, - }, - }); - - createToast({ - type: "info", - title: `Created API key for ${datasetId()} named ${name}`, - }); - } else { - await trieve.fetch("/api/dataset", "put", { - organizationId: selectedOrg().id, - data: { - dataset_id: datasetId(), - server_configuration: { - PUBLIC_DATASET: { - enabled: true, - extra_params: { - ...extraParams, - }, - }, - }, - }, - }); - - createToast({ - type: "info", - title: `Updated Public settings for ${name}`, - }); - } - - setExtraParams(extraParams); - setisPublic(true); - }; +import { + PublicPageProvider, + usePublicPage, +} from "../../hooks/usePublicPageSettings"; +import { HeroPatterns } from "./HeroPatterns"; +import { createStore } from "solid-js/store"; +import { PublicPageTabMessage } from "trieve-ts-sdk"; +export const PublicPageSettingsPage = () => { return (
@@ -224,6 +26,26 @@ export const PublicPageSettings = () => {

+ + + + + ); +}; + +const PublicPageControls = () => { + const { + extraParams, + setExtraParams, + isPublic, + publishDataset, + unpublishDataset, + publicUrl, + searchOptionsError, + } = usePublicPage(); + + return ( + <>
- + { setExtraParams("analytics", e.currentTarget.checked); @@ -479,35 +301,7 @@ export const PublicPageSettings = () => { - -
-
Search Options
- { - const result = publicPageSearchOptionsSchema.safeParse(value); - - if (result.success) { - setExtraParams("searchOptions", result.data); - setSearchOptionsError(null); - } else { - setSearchOptionsError( - result.error.errors.at(0)?.message || - "Invalid Search Options", - ); - } - }} - value={() => { - return extraParams?.searchOptions || {}; - }} - onError={(message) => { - setSearchOptionsError(message); - }} - /> - -
{searchOptionsError()}
-
-
- +
@@ -583,15 +377,15 @@ export const PublicPageSettings = () => { { - setForegroundColor(e.currentTarget.value); + setExtraParams( + "heroPattern", + "foregroundColor", + e.currentTarget.value, + ); }} - value={foregroundColor()} + value={extraParams.heroPattern?.foregroundColor || "#ffffff"} />
@@ -614,14 +412,23 @@ export const PublicPageSettings = () => { min="0" max="100" onChange={(e) => { - setForegroundOpacity(parseInt(e.currentTarget.value)); + setExtraParams( + "heroPattern", + "foregroundOpacity", + parseInt(e.currentTarget.value) / 100, + ); }} - value={foregroundOpacity()} + value={ + (extraParams.heroPattern?.foregroundOpacity || 0.5) * 100 + } />
@@ -793,7 +604,9 @@ export const PublicPageSettings = () => {
-
+ + +
+ + ); +}; + +export const TabOptions = () => { + const { extraParams: params } = usePublicPage(); + + // We know params.tabMessages is an array because of effect in hook + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const [messages, setMessages] = createStore(params.tabMessages!); + + const [selectedTabIndex, setSelectedTabIndex] = createSignal( + null, + ); + + createEffect(() => { + if (messages.length > 0 && selectedTabIndex() === null) { + setSelectedTabIndex(0); + } + }); + + const TabConfig = (props: { + index: number; + message: PublicPageTabMessage; + }) => { + const [nameRequiredWarning, setNameRequiredWarning] = createSignal(false); + return ( + <> + +
+
+ + { + if (e.currentTarget.value === "") { + setNameRequiredWarning(true); + } + }} + placeholder={`Tab ${props.index + 1}`} + class="block w-full max-w-md rounded border border-neutral-300 px-3 py-1.5 shadow-sm placeholder:text-neutral-400 focus:outline-magenta-500 sm:text-sm sm:leading-6" + value={props.message.title || ""} + onInput={(e) => { + setMessages(props.index, { + ...props.message, + title: e.currentTarget.value, + }); + }} + /> + +
Tab name is required
+
+
+
+ + { + setMessages(props.index, { + ...props.message, + showComponentCode: e.currentTarget.checked, + }); + }} + /> +
+
+ + { + setMessages(props.index, { + ...props.message, + tabInnerHtml: value, + }); + }} + /> + + ); + }; + + return ( +
0}> + Tab Messages +
+ + {(message, index) => ( +
+ +
+ )} +
+ +
+ {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} + +
+ +
+
+
+ ); +}; + +export const SearchOptions = () => { + const { + extraParams, + setExtraParams, + searchOptionsError, + setSearchOptionsError, + } = usePublicPage(); + return ( +
+ + { + const result = publicPageSearchOptionsSchema.safeParse(value); + + if (result.success) { + setExtraParams("searchOptions", result.data); + setSearchOptionsError(null); + } else { + setSearchOptionsError( + result.error.errors.at(0)?.message || "Invalid Search Options", + ); + } + }} + value={() => { + return extraParams?.searchOptions || {}; + }} + onError={(message) => { + setSearchOptionsError(message); + }} + /> + +
{searchOptionsError()}
+
); }; + +// Text area switches between preview and input +const HtmlEditor = (props: { + value: string; + onValueChange: (value: string) => void; +}) => { + return ( +