diff --git a/clients/search-component/README.md b/clients/search-component/README.md index bb439ada1..e93c7760b 100644 --- a/clients/search-component/README.md +++ b/clients/search-component/README.md @@ -99,7 +99,8 @@ declare module "solid-js" { | problemLink | string (example: "mailto:help@trieve.ai?subject=") | null | | responsive | boolean | false | | floatingButtonPosition | "top-left", "top-right", "bottom-left", or "bottom-right" | "bottom-right" | -| showFloatingButton | boolean | false | +| showFloatingButton | boolean | false | +| buttonTriggers | { selector: "query-selector", mode: "chat" | "search" }[] | [] | ### Search Results diff --git a/clients/search-component/package.json b/clients/search-component/package.json index aa457ee1c..ad8652e69 100644 --- a/clients/search-component/package.json +++ b/clients/search-component/package.json @@ -19,7 +19,7 @@ "import": "./dist/vanilla/index.js" } }, - "version": "0.2.26", + "version": "0.2.30", "license": "MIT", "homepage": "https://github.com/devflowinc/trieve/tree/main/clients/search-component", "scripts": { @@ -29,6 +29,7 @@ "lint": "eslint", "build:clean": "rm -rf dist && yarn type:dts && yarn build", "build": "run-s build:src build:css type:dts", + "build:watch": "run-p watch:js build:src build:css type:dts", "build:src": "node ./scripts/build.js", "type:dts": "tsc --emitDeclarationOnly --declarationMap", "build:css": "npx postcss src/app.css -o dist/index.css && cp src/styles.d.ts dist/", @@ -56,7 +57,7 @@ "react-dom": "^18.3.1", "tailwind": "^4.0.0", "tailwindcss": "^3.4.11", - "tailwindcss-scoped-preflight": "^3.4.4", + "tailwindcss-scoped-preflight": "^3.4.10", "typescript": "^5.6.2", "typescript-eslint": "^8.3.0" }, diff --git a/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx b/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx index 42108d648..e60bed62c 100644 --- a/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx +++ b/clients/search-component/src/TrieveModal/Chat/AIInitalMessage.tsx @@ -38,7 +38,7 @@ export const AIInitialMessage = () => { I'm an AI assistant with access to documentation, help articles, and other content.

-

+

Ask me anything about{" "} { const goToPage = (pageIndex: number) => setCurrentPage(pageIndex); return ( -

- )) diff --git a/clients/search-component/src/TrieveModal/index.css b/clients/search-component/src/TrieveModal/index.css index c30e1c21b..d2c496fe6 100644 --- a/clients/search-component/src/TrieveModal/index.css +++ b/clients/search-component/src/TrieveModal/index.css @@ -15,7 +15,7 @@ body { #trieve-search-modal-overlay { - @apply bg-black/60 w-screen fixed inset-0 h-screen animate-overlayShow backdrop-blur-sm z-[998]; + @apply bg-black/60 w-screen fixed inset-0 h-screen animate-overlayShow backdrop-blur-sm; } .close-icon { @@ -23,7 +23,7 @@ body { } #trieve-search-modal { - @apply animate-contentShow scroll-smooth fixed top-[calc(40%-30vh)] sm:top-[calc(25vh-225px)] left-[50%] max-h-[60vh] w-[90vw] sm:max-w-[800px] -translate-x-[50%] rounded-lg shadow-2xl focus:outline-none z-[999] overflow-hidden text-base; + @apply animate-contentShow scroll-smooth fixed top-[calc(40%-30vh)] left-[50%] max-h-[60vh] w-[90vw] sm:max-w-[800px] -translate-x-[50%] rounded-lg shadow-2xl focus:outline-none z-[999] overflow-hidden text-base; color: var(--tv-zinc-950); background-color: var(--tv-zinc-50); @@ -33,6 +33,10 @@ body { @apply pl-2 flex gap-2 w-full overflow-x-auto; } + .suspense-fallback { + @apply hidden; + } + ::-webkit-scrollbar { width: 6px; height: 6px; @@ -56,7 +60,7 @@ body { } &.chat-modal-mobile { - @apply flex flex-col top-0 sm:top-[calc(25vh-225px)] pt-4 max-h-[100vh] w-full sm:w-[90vw] rounded-none sm:rounded-lg; + @apply flex flex-col top-0 sm:top-[calc(40%-30vh)] pt-4 max-h-[100vh] w-full sm:w-[90vw] rounded-none sm:rounded-lg; .chat-outer-wrapper { @apply justify-between w-full h-full mt-10 sm:mt-12; @@ -188,7 +192,7 @@ body { } .trieve-footer { - @apply sticky px-3 items-center bottom-0 flex flex-col; + @apply sticky px-3 items-center bottom-[1px] flex flex-col; background-color: var(--tv-zinc-50); border-color: var(--tv-zinc-200); @@ -309,6 +313,44 @@ body { .additional-image-links { @apply gap-2 mt-4 mb-4 flex flex-row; + .carousel-root { + width: 100%; + overflow: hidden; + } + + .carousel-scroll { + display: flex; + overflow-x: hidden; + scroll-snap-type: x-mandatory; + scroll-behavior: smooth; + width: 100%; + list-style: none; + padding: 0; + margin: 0; + } + + .carousel-item { + flex: 0 0 auto; + padding: 1rem; + scroll-snap-align: start; + box-sizing: border-box; + } + + .carousel-item-hidden { + visibility: hidden; + } + + .carousel-item-visibile { + visibility: visible; + } + + .carousel-controls { + display: flex; + justify-content: center; + align-items: center; + margin-top: 5px; + } + a { @apply hover:bg-zinc-200 text-zinc-700 hover:text-zinc-950 rounded px-2 text-xs py-1 line-clamp-2 overflow-ellipsis grid-rows-2 w-fit; } @@ -498,8 +540,12 @@ body { } } + .brand-paragraph { + @apply leading-8; + } + .brand-name { - @apply text-white px-1.5 py-1 rounded-md font-[500]; + @apply text-white px-1.5 py-1 rounded-md font-[500] whitespace-nowrap text-ellipsis; } } } @@ -529,7 +575,11 @@ body { /* SUGGESTED QUESTIONS */ .system-information-wrapper { - @apply mb-8; + @apply mb-10; + + &.with-group { + @apply mb-24; + } p { @apply mb-6 sm:mb-4; @@ -639,11 +689,14 @@ body { } &:focus { - border: 1.5px solid var(--tv-prop-brand-color); - transform: translateY(-0.1px) scale(1.0006); + border: 1px solid var(--tv-prop-brand-color); } } + input.search-input { + @apply pr-8; + } + input.search-input.ecommerce { @apply rounded-sm; } @@ -745,7 +798,7 @@ body { } &.start-chat { - @apply items-center flex max-w-[calc(90vw-70px)] sm:max-w-none; + @apply items-center flex max-w-[calc(90vw)] sm:max-w-none; h4 { @apply pl-0 pt-0 max-w-[calc(90vw-120px)] sm:max-w-[400px]; diff --git a/clients/search-component/src/TrieveModal/index.tsx b/clients/search-component/src/TrieveModal/index.tsx index 70ce614cb..7566b8e8d 100644 --- a/clients/search-component/src/TrieveModal/index.tsx +++ b/clients/search-component/src/TrieveModal/index.tsx @@ -18,10 +18,45 @@ import { FloatingActionButton } from "./FloatingActionButton"; const Modal = () => { useKeyboardNavigation(); - setClickTriggers(); const { mode, open, setOpen, setMode, props } = useModalState(); const { askQuestion, chatWithGroup } = useChatState(); + useEffect(() => { + setClickTriggers( + setOpen, + setMode, + props + ); + }, []); + + useEffect(() => { + const onViewportResize = () => { + const viewportHeight = window.visualViewport?.height; + const trieveSearchModal = document.getElementById("trieve-search-modal"); + if (trieveSearchModal) { + trieveSearchModal.style.maxHeight = `${viewportHeight}px`; + } + + const chatOuterWrapper = document.querySelector( + ".chat-outer-wrapper" + ); + if (chatOuterWrapper) { + (chatOuterWrapper as HTMLElement).style.maxHeight = + `calc(${viewportHeight}px - 100px)`; + } + if (chatOuterWrapper) { + chatOuterWrapper.scrollTop = + chatOuterWrapper.scrollHeight; + } + }; + + window.addEventListener("resize", onViewportResize); + + return () => { + window.removeEventListener("resize", onViewportResize); + }; + }, [open]); + useEffect(() => { const script = document.createElement("script"); script.src = @@ -77,7 +112,7 @@ const Modal = () => { document.documentElement.style.setProperty( "--tv-prop-brand-font-family", props.brandFontFamily ?? - `Maven Pro, ui-sans-serif, system-ui, sans-serif, + `Maven Pro, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"` ); }, [props.brandColor, props.brandFontFamily]); @@ -99,12 +134,13 @@ const Modal = () => { setOpen(false); }} id="trieve-search-modal-overlay" + style={{ zIndex: props.zIndex ?? 1000 }} >
{props.allowSwitchingModes && }
{ isDoneReading.current = false; + + if (!currentGroup && group) { + chatWithGroup(group); + setCurrentGroup(group); + } + setMessages((m) => [ ...m, [ @@ -274,11 +280,6 @@ function ChatProvider({ children }: { children: React.ReactNode }) { ], ]); - if (!currentGroup && group) { - chatWithGroup(group); - setCurrentGroup(group); - } - if (!currentTopic && !currentGroup && !group) { await createTopic({ question: question || currentQuestion }); } else { diff --git a/clients/search-component/src/utils/hooks/modal-context.tsx b/clients/search-component/src/utils/hooks/modal-context.tsx index 70190a108..39beb6502 100644 --- a/clients/search-component/src/utils/hooks/modal-context.tsx +++ b/clients/search-component/src/utils/hooks/modal-context.tsx @@ -79,7 +79,9 @@ export type ModalProps = { buttonTriggers?: { selector: string; mode: SearchModes; + removeListeners?: boolean; }[]; + zIndex?: number; showFloatingButton?: boolean; floatingButtonPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right"; }; @@ -110,6 +112,7 @@ const defaultProps = { openLinksInNewTab: false, currencyPosition: "before" as currencyPosition, responsive: false, + zIndex: 1000, debounceMs: 0, show: true, position: "bottom-right" as diff --git a/clients/search-component/src/utils/hooks/setClickTriggers.ts b/clients/search-component/src/utils/hooks/setClickTriggers.ts index 2cd040dba..d63434549 100644 --- a/clients/search-component/src/utils/hooks/setClickTriggers.ts +++ b/clients/search-component/src/utils/hooks/setClickTriggers.ts @@ -1,11 +1,32 @@ import { startTransition } from "react"; -import { useModalState } from "./modal-context"; +import { ModalProps, SearchModes } from "./modal-context"; -export const setClickTriggers = () => { - const { setOpen, setMode, props } = useModalState(); +export const setClickTriggers = ( + setOpen: (open: boolean) => void, + setMode: React.Dispatch>, + props: ModalProps +) => { + const removeAllClickListeners = (selector: string): Element | null => { + const element: Element | null = document.querySelector(selector); + if (!element) return null; + // Vue click attributes + element.removeAttribute("@click.prevent"); + element.removeAttribute("@click"); + // @ts-expect-error Property 'href' does not exist on type 'Element'. [2339] + element.href = "#"; + + const newElement = element.cloneNode(true); + element?.parentNode?.replaceChild(newElement, element); + return newElement as unknown as Element; + } props.buttonTriggers?.forEach((trigger) => { - const element = document.querySelector(trigger.selector); + let element: Element | null = document.querySelector(trigger.selector); + if (trigger.removeListeners ?? true) { + element = removeAllClickListeners(trigger.selector); + console.log("Removed click listeners from", trigger.selector); + } + if (element) { element.addEventListener("click", () => { startTransition(() => { diff --git a/clients/search-component/tailwind.config.js b/clients/search-component/tailwind.config.js index edf8a02fb..1542a4676 100644 --- a/clients/search-component/tailwind.config.js +++ b/clients/search-component/tailwind.config.js @@ -17,6 +17,7 @@ export default { "#trieve-search-component", "#trieve-search-modal", "#trieve-search-modal-overlay", + "#open-trieve-modal" ], { rootStyles: true, diff --git a/clients/ts-sdk/openapi.json b/clients/ts-sdk/openapi.json index a36154652..060b29a42 100644 --- a/clients/ts-sdk/openapi.json +++ b/clients/ts-sdk/openapi.json @@ -6967,6 +6967,25 @@ } } }, + "ButtonTrigger": { + "type": "object", + "required": [ + "selector", + "mode" + ], + "properties": { + "mode": { + "type": "string" + }, + "removeTriggers": { + "type": "boolean", + "nullable": true + }, + "selector": { + "type": "string" + } + } + }, "CTRAnalytics": { "oneOf": [ { @@ -12276,6 +12295,13 @@ "type": "string", "nullable": true }, + "buttonTriggers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ButtonTrigger" + }, + "nullable": true + }, "chat": { "type": "boolean", "nullable": true @@ -12425,6 +12451,11 @@ "useGroupSearch": { "type": "boolean", "nullable": true + }, + "zIndex": { + "type": "integer", + "format": "int32", + "nullable": true } } }, diff --git a/clients/ts-sdk/package.json b/clients/ts-sdk/package.json index 8553d7af4..8a07aeeb7 100644 --- a/clients/ts-sdk/package.json +++ b/clients/ts-sdk/package.json @@ -6,7 +6,7 @@ "files": [ "dist" ], - "version": "0.0.44", + "version": "0.0.45", "license": "MIT", "scripts": { "lint": "eslint 'src/**/*.ts'", diff --git a/clients/ts-sdk/src/types.gen.ts b/clients/ts-sdk/src/types.gen.ts index 873629405..4da65f5c7 100644 --- a/clients/ts-sdk/src/types.gen.ts +++ b/clients/ts-sdk/src/types.gen.ts @@ -130,6 +130,12 @@ export type BulkDeleteChunkPayload = { filter: ChunkFilter; }; +export type ButtonTrigger = { + mode: string; + removeTriggers?: (boolean) | null; + selector: string; +}; + export type CTRAnalytics = { filter?: ((SearchAnalyticsFilter) | null); type: 'search_ctr_metrics'; @@ -2045,6 +2051,7 @@ export type PublicPageParameters = { brandFontFamily?: (string) | null; brandLogoImgSrcUrl?: (string) | null; brandName?: (string) | null; + buttonTriggers?: Array | null; chat?: (boolean) | null; creatorLinkedInUrl?: (string) | null; creatorName?: (string) | null; @@ -2075,6 +2082,7 @@ export type PublicPageParameters = { type?: (string) | null; useGroupSearch?: (boolean) | null; videoLink?: (string) | null; + zIndex?: (number) | null; }; export type PublicPageSearchOptions = { diff --git a/frontends/chat/src/components/Layouts/MainLayout.tsx b/frontends/chat/src/components/Layouts/MainLayout.tsx index 02f8e2c7f..69f9a05a9 100644 --- a/frontends/chat/src/components/Layouts/MainLayout.tsx +++ b/frontends/chat/src/components/Layouts/MainLayout.tsx @@ -85,6 +85,9 @@ const MainLayout = (props: LayoutProps) => { null, ); + const [highlightResults, setHighlightResults] = createSignal( + null, + ); const [streamCompletionsFirst, setStreamCompletionsFirst] = createSignal< boolean | null >(null); @@ -244,6 +247,9 @@ const MainLayout = (props: LayoutProps) => { use_group_search: useGroupSearch(), search_type: searchType(), context_options: contextOptions(), + highlight_options: { + highlight_results: highlightResults(), + }, }), signal: completionAbortController().signal, }); @@ -452,6 +458,20 @@ const MainLayout = (props: LayoutProps) => { tabIndex={0} >
+
+ + { + setHighlightResults(e.target.checked); + }} + /> +
-
+
+
+ + } + /> +
+ { + return `${trigger.selector},${trigger.mode}`; + }) ?? [] + } + onChange={(e) => { + setExtraParams( + "buttonTriggers", + e.map((trigger) => { + const [selector, mode, replace] = trigger.split(","); + if (replace) { + return { + selector, + mode, + removeTriggers: replace === "true", + }; + } + return { + selector, + mode, + removeTriggers: false, + }; + }), + ); + }} + addLabel="Add Trigger" + addClass="text-sm" + inputClass="block w-full 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" + /> +
@@ -746,6 +787,54 @@ const PublicPageControls = () => { options={["search", "chat"]} />
+
+
+ + + } + /> +
+ { + setExtraParams( + "zIndex", + parseInt(e.currentTarget.value), + ); + }} + class="block w-full 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" + /> +
diff --git a/frontends/search/src/components/UploadFile.tsx b/frontends/search/src/components/UploadFile.tsx index 5d6f5570a..11922acd3 100644 --- a/frontends/search/src/components/UploadFile.tsx +++ b/frontends/search/src/components/UploadFile.tsx @@ -148,7 +148,7 @@ export const UploadFile = () => { { link: link() === "" ? undefined : link(), tag_set: - tagSet().split(",").length > 0 ? undefined : tagSet().split(","), + tagSet().split(",").length > 0 ? tagSet().split(",") : undefined, split_delimiters: splitDelimiters(), target_splits_per_chunk: targetSplitsPerChunk(), rebalance_chunks: rebalanceChunks(), @@ -168,9 +168,9 @@ export const UploadFile = () => { let base64File = await toBase64(file); base64File = base64File .split(",")[1] - .replace(/\+/g, "-") // Convert '+' to '-' - .replace(/\//g, "_") // Convert '/' to '_' - .replace(/=+$/, ""); // Remove ending '=' + .replace(/\+/g, "-") + .replace(/\//g, "_") + .replace(/=+$/, ""); const requestBody: RequestBody = { ...requestBodyTemplate, base64_file: base64File, diff --git a/frontends/shared/types.ts b/frontends/shared/types.ts index 88fc11650..6d63a44a8 100644 --- a/frontends/shared/types.ts +++ b/frontends/shared/types.ts @@ -638,6 +638,7 @@ export interface RagQueryEvent { note?: string; rating: number; }; + top_score: number; hallucination_score?: number; detected_hallucinations?: string[]; } diff --git a/server/ch_migrations/1734399996_add_top_score_for_rag/down.sql b/server/ch_migrations/1734399996_add_top_score_for_rag/down.sql new file mode 100644 index 000000000..1168fb8ca --- /dev/null +++ b/server/ch_migrations/1734399996_add_top_score_for_rag/down.sql @@ -0,0 +1 @@ +ALTER TABLE rag_queries DROP COLUMN IF EXISTS top_score; \ No newline at end of file diff --git a/server/ch_migrations/1734399996_add_top_score_for_rag/up.sql b/server/ch_migrations/1734399996_add_top_score_for_rag/up.sql new file mode 100644 index 000000000..4548dee24 --- /dev/null +++ b/server/ch_migrations/1734399996_add_top_score_for_rag/up.sql @@ -0,0 +1 @@ +ALTER TABLE rag_queries ADD COLUMN IF NOT EXISTS top_score INT DEFAULT 0; \ No newline at end of file diff --git a/server/src/data/models.rs b/server/src/data/models.rs index 94a9ea677..275c80f97 100644 --- a/server/src/data/models.rs +++ b/server/src/data/models.rs @@ -3153,6 +3153,9 @@ impl DatasetConfigurationDTO { show_floating_button: page_parameters_self .show_floating_button .or(page_parameters_curr.show_floating_button), + button_triggers: page_parameters_self + .button_triggers + .or(page_parameters_curr.button_triggers), debounce_ms: page_parameters_self .debounce_ms .or(page_parameters_curr.debounce_ms), @@ -3195,6 +3198,9 @@ impl DatasetConfigurationDTO { video_link: page_parameters_self .video_link .or(page_parameters_curr.video_link), + z_index: page_parameters_self + .z_index + .or(page_parameters_curr.z_index), }), }, DISABLE_ANALYTICS: self @@ -5109,7 +5115,7 @@ impl RagQueryEventClickhouse { user_message: self.user_message, search_id: uuid::Uuid::from_bytes(*self.search_id.as_bytes()), results, - top_score: 0.0, + top_score: self.top_score, query_rating, dataset_id: uuid::Uuid::from_bytes(*self.dataset_id.as_bytes()), llm_response: self.llm_response, @@ -5131,6 +5137,7 @@ pub struct RagQueryEventClickhouse { pub search_id: uuid::Uuid, pub results: Vec, pub json_results: Vec, + pub top_score: f32, pub query_rating: String, pub llm_response: String, #[serde(with = "clickhouse::serde::uuid")] @@ -5935,6 +5942,7 @@ impl EventTypes { .collect(), query_rating: serde_json::to_string(&query_rating).unwrap_or("".to_string()), llm_response: llm_response.unwrap_or_default(), + top_score: 0.0, dataset_id, created_at: OffsetDateTime::now_utc(), user_id: user_id.unwrap_or_default(), diff --git a/server/src/handlers/chunk_handler.rs b/server/src/handlers/chunk_handler.rs index 1b9971cbf..4de62429a 100644 --- a/server/src/handlers/chunk_handler.rs +++ b/server/src/handlers/chunk_handler.rs @@ -2466,7 +2466,6 @@ pub async fn generate_off_chunks( }; let chunk_ids = data.chunk_ids.clone(); - let prompt = data.prompt.clone(); let stream_response = data.stream_response; let context_options = data.context_options.clone(); @@ -2477,8 +2476,8 @@ pub async fn generate_off_chunks( DatasetConfiguration::from_json(dataset_org_plan_sub.dataset.server_configuration); let base_url = dataset_config.LLM_BASE_URL; - - let default_model = dataset_config.LLM_DEFAULT_MODEL; + let rag_prompt = dataset_config.RAG_PROMPT.clone(); + let chosen_model = dataset_config.LLM_DEFAULT_MODEL; let base_url = if base_url.is_empty() { "https://openrouter.ai/api/v1".into() @@ -2600,7 +2599,8 @@ pub async fn generate_off_chunks( let last_prev_message = prev_messages .last() - .expect("There needs to be at least 1 prior message"); + .expect("There needs to be at least 1 prior message") + .clone(); let mut prev_messages = prev_messages.clone(); @@ -2610,19 +2610,17 @@ pub async fn generate_off_chunks( .iter() .for_each(|message| messages.push(ChatMessage::from(message.clone()))); - let prompt = prompt.unwrap_or("Respond to the question or instruction using the docs and include the doc numbers that you used in square brackets at the end of the sentences that you used the docs for:\n\n".to_string()); - messages.push(ChatMessage::User { content: ChatMessageContent::Text(format!( "{} {}", - prompt, + rag_prompt, last_prev_message.content.clone() )), name: None, }); let parameters = ChatCompletionParameters { - model: default_model, + model: chosen_model, stream: stream_response, messages, top_p: None, @@ -2729,7 +2727,8 @@ pub async fn generate_off_chunks( json.to_string() }) .collect(), - user_message: prompt, + top_score: 0.0, + user_message: format!("{} {}", rag_prompt, last_prev_message.content.clone()), query_rating: String::new(), rag_type: "chosen_chunks".to_string(), llm_response: completion_content.clone(), @@ -2799,7 +2798,8 @@ pub async fn generate_off_chunks( json.to_string() }) .collect(), - user_message: prompt, + top_score: 0.0, + user_message: format!("{} {}", rag_prompt, last_prev_message.content.clone()), rag_type: "chosen_chunks".to_string(), query_rating: String::new(), llm_response: completion, diff --git a/server/src/handlers/page_handler.rs b/server/src/handlers/page_handler.rs index c696c9f02..750c5b47d 100644 --- a/server/src/handlers/page_handler.rs +++ b/server/src/handlers/page_handler.rs @@ -136,6 +136,14 @@ pub struct OpenGraphMetadata { description: Option, } +#[derive(Serialize, Deserialize, Debug, Clone, ToSchema, Default)] +#[serde(rename_all = "camelCase")] +pub struct ButtonTrigger { + selector: String, + mode: String, + remove_triggers: Option, +} + #[derive(Serialize, Deserialize, Debug, Clone, ToSchema, Default)] #[serde(rename_all = "camelCase")] pub struct SingleProductOptions { @@ -217,6 +225,8 @@ pub struct PublicPageParameters { #[serde(skip_serializing_if = "Option::is_none")] pub show_floating_button: Option, #[serde(skip_serializing_if = "Option::is_none")] + pub button_triggers: Option>, + #[serde(skip_serializing_if = "Option::is_none")] pub debounce_ms: Option, #[serde(skip_serializing_if = "Option::is_none")] pub hero_pattern: Option, @@ -236,6 +246,7 @@ pub struct PublicPageParameters { pub brand_font_family: Option, #[serde(skip_serializing_if = "Option::is_none")] pub video_link: Option, + pub z_index: Option, } #[utoipa::path( diff --git a/server/src/lib.rs b/server/src/lib.rs index 1a7367fbf..7c92d751a 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -396,6 +396,7 @@ impl Modify for SecurityAddon { operators::crawl_operator::Metadata, operators::crawl_operator::Sitemap, handlers::stripe_handler::CreateSetupCheckoutSessionResPayload, + handlers::page_handler::ButtonTrigger, handlers::page_handler::PublicPageSearchOptions, handlers::page_handler::OpenGraphMetadata, handlers::page_handler::SingleProductOptions, diff --git a/server/src/middleware/auth_middleware.rs b/server/src/middleware/auth_middleware.rs index 665e12745..cafd7383b 100644 --- a/server/src/middleware/auth_middleware.rs +++ b/server/src/middleware/auth_middleware.rs @@ -121,10 +121,29 @@ where } } - let route = format!("{} {}", req.method(), req.match_info().as_str()); + let curly_matcher = + regex::Regex::new(r"\{[a-zA-Z0-9_-]+\}").expect("Valid regex"); + let route = format!("{} {}", req.method(), req.match_info().as_str()); if let Some(api_key_scopes) = user_api_key.scopes { - if !api_key_scopes.is_empty() && api_key_scopes.contains(&Some(route)) { + if !api_key_scopes.is_empty() + && (api_key_scopes.contains(&Some(route.clone())) + || api_key_scopes + .iter() + .filter_map(|scope| scope.as_ref()) + .any(|scope| { + let wildcard_scope = curly_matcher + .replace_all(scope, "[a-zA-Z0-9_-]+") + .to_string(); + if let Ok(wildcard_scope_regex) = + regex::Regex::new(&wildcard_scope) + { + wildcard_scope_regex.is_match(&route) + } else { + false + } + })) + { if let Some(ref mut user) = user { user.user_orgs.iter_mut().for_each(|org| { if org.organization_id diff --git a/server/src/operators/message_operator.rs b/server/src/operators/message_operator.rs index 4ff9393fb..ce0d83401 100644 --- a/server/src/operators/message_operator.rs +++ b/server/src/operators/message_operator.rs @@ -270,7 +270,7 @@ pub async fn get_rag_chunks_query( pool: web::Data, redis_pool: web::Data, event_queue: web::Data, -) -> Result<(uuid::Uuid, Vec), actix_web::Error> { +) -> Result<(SearchQueryEventClickhouse, Vec), actix_web::Error> { let mut query = if let Some(create_message_query) = create_message_req_payload.search_query.clone() { create_message_query @@ -440,7 +440,7 @@ pub async fn get_rag_chunks_query( .await; } Ok(( - clickhouse_search_event.id, + clickhouse_search_event, result_groups .group_chunks .into_iter() @@ -548,7 +548,7 @@ pub async fn get_rag_chunks_query( .await; } Ok(( - clickhouse_search_event.id, + clickhouse_search_event, result_chunks .score_chunks .iter() @@ -679,7 +679,7 @@ pub async fn stream_response( let rag_prompt = dataset_config.RAG_PROMPT.clone(); let chosen_model = dataset_config.LLM_DEFAULT_MODEL.clone(); - let (search_id, chunk_metadatas) = get_rag_chunks_query( + let (search_event, chunk_metadatas) = get_rag_chunks_query( create_message_req_payload.clone(), dataset_config.clone(), dataset.clone(), @@ -697,7 +697,7 @@ pub async fn stream_response( Bytes::from(create_message_req_payload.no_result_message.unwrap()), )]); return Ok(HttpResponse::Ok() - .insert_header(("TR-QueryID", search_id.to_string())) + .insert_header(("TR-QueryID", search_event.id.to_string())) .streaming(response_stream)); } @@ -953,7 +953,8 @@ pub async fn stream_response( id: query_id, created_at: time::OffsetDateTime::now_utc(), dataset_id: dataset.id, - search_id, + search_id: search_event.id, + top_score: search_event.top_score, results: vec![], json_results: chunk_data, user_message: user_message_query.clone(), @@ -1049,8 +1050,9 @@ pub async fn stream_response( let clickhouse_rag_event = RagQueryEventClickhouse { id: query_id_arb, created_at: time::OffsetDateTime::now_utc(), + search_id: search_event.id, + top_score: search_event.top_score, dataset_id: dataset.id, - search_id, results: vec![], json_results: chunk_data, user_message: user_message_query.clone(), diff --git a/server/src/operators/qdrant_operator.rs b/server/src/operators/qdrant_operator.rs index f8023c8f6..b6ced2d8e 100644 --- a/server/src/operators/qdrant_operator.rs +++ b/server/src/operators/qdrant_operator.rs @@ -529,11 +529,7 @@ pub async fn update_qdrant_point_query( .to_owned() .iter_list() .unwrap() - .map(|id| { - id.to_string() - .parse::() - .expect("group_id must be a valid uuid") - }) + .filter_map(|id| id.to_string().parse::().ok()) .collect::>() } else { vec![] diff --git a/server/src/public/navbar.html b/server/src/public/navbar.html index 9a41de8c8..c22a58411 100644 --- a/server/src/public/navbar.html +++ b/server/src/public/navbar.html @@ -1,6 +1,6 @@