Update slack configs (#3776)

* update

* fix build
This commit is contained in:
pablonyx 2025-01-28 13:10:09 -08:00 committed by GitHub
parent 42d6d935ae
commit 9bdb581220
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 996 additions and 612 deletions

View File

@ -58,6 +58,7 @@ class UserGroup(BaseModel):
credential=CredentialSnapshot.from_credential_db_model(
cc_pair_relationship.cc_pair.credential
),
access_type=cc_pair_relationship.cc_pair.access_type,
)
for cc_pair_relationship in user_group_model.cc_pair_relationships
if cc_pair_relationship.is_current

View File

@ -357,6 +357,7 @@ class ConnectorCredentialPairDescriptor(BaseModel):
name: str | None = None
connector: ConnectorSnapshot
credential: CredentialSnapshot
access_type: AccessType
class RunConnectorRequest(BaseModel):

View File

@ -68,6 +68,7 @@ class DocumentSet(BaseModel):
credential=CredentialSnapshot.from_credential_db_model(
cc_pair.credential
),
access_type=cc_pair.access_type,
)
for cc_pair in document_set_model.connector_credential_pairs
],

183
package-lock.json generated
View File

@ -1,183 +0,0 @@
{
"name": "onyx",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"react-datepicker": "^7.6.0"
},
"devDependencies": {
"@types/react-datepicker": "^6.2.0"
}
},
"node_modules/@floating-ui/core": {
"version": "1.6.9",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.6.13",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.6.0",
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/react": {
"version": "0.27.3",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz",
"integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==",
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.1.2",
"@floating-ui/utils": "^0.2.9",
"tabbable": "^6.0.0"
},
"peerDependencies": {
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
}
},
"node_modules/@floating-ui/react-dom": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
"integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "19.0.4",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.4.tgz",
"integrity": "sha512-3O4QisJDYr1uTUMZHA2YswiQZRq+Pd8D+GdVFYikTutYsTz+QZgWkAPnP7rx9txoI6EXKcPiluMqWPFV3tT9Wg==",
"dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-datepicker": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.2.0.tgz",
"integrity": "sha512-+JtO4Fm97WLkJTH8j8/v3Ldh7JCNRwjMYjRaKh4KHH0M3jJoXtwiD3JBCsdlg3tsFIw9eQSqyAPeVDN2H2oM9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.26.2",
"@types/react": "*",
"date-fns": "^3.3.1"
}
},
"node_modules/@types/react-datepicker/node_modules/@floating-ui/react": {
"version": "0.26.28",
"resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
"integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@floating-ui/react-dom": "^2.1.2",
"@floating-ui/utils": "^0.2.8",
"tabbable": "^6.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT"
},
"node_modules/date-fns": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/react": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-datepicker": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.6.0.tgz",
"integrity": "sha512-9cQH6Z/qa4LrGhzdc3XoHbhrxNcMi9MKjZmYgF/1MNNaJwvdSjv3Xd+jjvrEEbKEf71ZgCA3n7fQbdwd70qCRw==",
"license": "MIT",
"dependencies": {
"@floating-ui/react": "^0.27.0",
"clsx": "^2.1.1",
"date-fns": "^3.6.0"
},
"peerDependencies": {
"react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc",
"react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc"
}
},
"node_modules/react-dom": {
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.25.0"
},
"peerDependencies": {
"react": "^19.0.0"
}
},
"node_modules/scheduler": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
"license": "MIT",
"peer": true
},
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
"license": "MIT"
}
}
}

View File

@ -1,8 +0,0 @@
{
"dependencies": {
"react-datepicker": "^7.6.0"
},
"devDependencies": {
"@types/react-datepicker": "^6.2.0"
}
}

132
web/package-lock.json generated
View File

@ -16,6 +16,7 @@
"@headlessui/tailwindcss": "^0.2.1",
"@phosphor-icons/react": "^2.0.8",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
@ -3507,6 +3508,137 @@
}
}
},
"node_modules/@radix-ui/react-collapsible": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.2.tgz",
"integrity": "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.1",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
"license": "MIT"
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-context": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-presence": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-primitive": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-slot": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-compose-refs": "1.1.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",

View File

@ -19,6 +19,7 @@
"@headlessui/tailwindcss": "^0.2.1",
"@phosphor-icons/react": "^2.0.8",
"@radix-ui/react-checkbox": "^1.1.2",
"@radix-ui/react-collapsible": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",

View File

@ -40,14 +40,7 @@ import * as Yup from "yup";
import CollapsibleSection from "./CollapsibleSection";
import { SuccessfulPersonaUpdateRedirectType } from "./enums";
import { Persona, PersonaLabel, StarterMessage } from "./interfaces";
import {
createPersonaLabel,
PersonaUpsertParameters,
createPersona,
deletePersonaLabel,
updatePersonaLabel,
updatePersona,
} from "./lib";
import { PersonaUpsertParameters, createPersona, updatePersona } from "./lib";
import {
CameraIcon,
GroupsIconSkeleton,

View File

@ -1,38 +1,21 @@
"use client";
import { ArrayHelpers, FieldArray, Form, Formik } from "formik";
import React, { useMemo } from "react";
import { Formik } from "formik";
import * as Yup from "yup";
import { usePopup } from "@/components/admin/connectors/Popup";
import { DocumentSet, SlackChannelConfig } from "@/lib/types";
import {
BooleanFormField,
Label,
SelectorFormField,
SubLabel,
TextArrayField,
TextFormField,
} from "@/components/admin/connectors/Field";
import {
createSlackChannelConfig,
isPersonaASlackBotPersona,
updateSlackChannelConfig,
} from "../lib";
import CardSection from "@/components/admin/CardSection";
import { Button } from "@/components/ui/button";
import { useRouter } from "next/navigation";
import { Persona } from "@/app/admin/assistants/interfaces";
import { useState } from "react";
import { AdvancedOptionsToggle } from "@/components/AdvancedOptionsToggle";
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
import CollapsibleSection from "@/app/admin/assistants/CollapsibleSection";
import { StandardAnswerCategoryResponse } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE";
import { StandardAnswerCategoryDropdownField } from "@/components/standardAnswers/StandardAnswerCategoryDropdown";
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
} from "@/components/ui/fully_wrapped_tabs";
import { SEARCH_TOOL_NAME } from "@/app/chat/tools/constants";
import { SlackChannelConfigFormFields } from "./SlackChannelConfigFormFields";
export const SlackChannelConfigCreationForm = ({
slack_bot_id,
@ -47,353 +30,179 @@ export const SlackChannelConfigCreationForm = ({
standardAnswerCategoryResponse: StandardAnswerCategoryResponse;
existingSlackChannelConfig?: SlackChannelConfig;
}) => {
const isUpdate = existingSlackChannelConfig !== undefined;
const { popup, setPopup } = usePopup();
const router = useRouter();
const isUpdate = Boolean(existingSlackChannelConfig);
const existingSlackBotUsesPersona = existingSlackChannelConfig?.persona
? !isPersonaASlackBotPersona(existingSlackChannelConfig.persona)
: false;
const [usingPersonas, setUsingPersonas] = useState(
existingSlackBotUsesPersona
);
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const knowledgePersona = personas.find((persona) => persona.id === 0);
const documentSetContainsSync = (documentSet: DocumentSet) =>
documentSet.cc_pair_descriptors.some(
(descriptor) => descriptor.access_type === "sync"
);
const searchEnabledAssistants = useMemo(() => {
return personas.filter((persona) => {
return persona.tools.some((tool) => tool.name == SEARCH_TOOL_NAME);
});
}, [personas]);
return (
<div>
<CardSection>
{popup}
<Formik
initialValues={{
slack_bot_id: slack_bot_id,
channel_name:
existingSlackChannelConfig?.channel_config.channel_name,
answer_validity_check_enabled: (
existingSlackChannelConfig?.channel_config?.answer_filters || []
).includes("well_answered_postfilter"),
questionmark_prefilter_enabled: (
existingSlackChannelConfig?.channel_config?.answer_filters || []
).includes("questionmark_prefilter"),
respond_tag_only:
existingSlackChannelConfig?.channel_config?.respond_tag_only ||
false,
respond_to_bots:
existingSlackChannelConfig?.channel_config?.respond_to_bots ||
false,
show_continue_in_web_ui:
// If we're updating, we want to keep the existing value
// Otherwise, we want to default to true
existingSlackChannelConfig?.channel_config
?.show_continue_in_web_ui ?? !isUpdate,
enable_auto_filters:
existingSlackChannelConfig?.enable_auto_filters || false,
respond_member_group_list:
existingSlackChannelConfig?.channel_config
?.respond_member_group_list ?? [],
still_need_help_enabled:
existingSlackChannelConfig?.channel_config?.follow_up_tags !==
undefined,
follow_up_tags:
existingSlackChannelConfig?.channel_config?.follow_up_tags,
<CardSection className="max-w-4xl">
{popup}
<Formik
initialValues={{
slack_bot_id: slack_bot_id,
channel_name:
existingSlackChannelConfig?.channel_config.channel_name || "",
answer_validity_check_enabled: (
existingSlackChannelConfig?.channel_config?.answer_filters || []
).includes("well_answered_postfilter"),
questionmark_prefilter_enabled: (
existingSlackChannelConfig?.channel_config?.answer_filters || []
).includes("questionmark_prefilter"),
respond_tag_only:
existingSlackChannelConfig?.channel_config?.respond_tag_only ||
false,
respond_to_bots:
existingSlackChannelConfig?.channel_config?.respond_to_bots ||
false,
show_continue_in_web_ui:
existingSlackChannelConfig?.channel_config
?.show_continue_in_web_ui ?? !isUpdate,
enable_auto_filters:
existingSlackChannelConfig?.enable_auto_filters || false,
respond_member_group_list:
existingSlackChannelConfig?.channel_config
?.respond_member_group_list || [],
still_need_help_enabled:
existingSlackChannelConfig?.channel_config?.follow_up_tags !==
undefined,
follow_up_tags:
existingSlackChannelConfig?.channel_config?.follow_up_tags ||
undefined,
document_sets:
existingSlackChannelConfig && existingSlackChannelConfig.persona
? existingSlackChannelConfig.persona.document_sets.map(
(documentSet) => documentSet.id
)
: ([] as number[]),
persona_id:
existingSlackChannelConfig?.persona &&
!isPersonaASlackBotPersona(existingSlackChannelConfig.persona)
? existingSlackChannelConfig.persona.id
: null,
response_type:
existingSlackChannelConfig?.response_type || "citations",
standard_answer_categories:
existingSlackChannelConfig?.standard_answer_categories || [],
knowledge_source: existingSlackBotUsesPersona
? "assistant"
: existingSlackChannelConfig?.persona
? "document_sets"
: "all_public",
}}
validationSchema={Yup.object().shape({
slack_bot_id: Yup.number().required(),
channel_name: Yup.string().required("Channel Name is required"),
response_type: Yup.string()
.oneOf(["quotes", "citations"])
.required("Response type is required"),
answer_validity_check_enabled: Yup.boolean().required(),
questionmark_prefilter_enabled: Yup.boolean().required(),
respond_tag_only: Yup.boolean().required(),
respond_to_bots: Yup.boolean().required(),
show_continue_in_web_ui: Yup.boolean().required(),
enable_auto_filters: Yup.boolean().required(),
respond_member_group_list: Yup.array().of(Yup.string()).required(),
still_need_help_enabled: Yup.boolean().required(),
follow_up_tags: Yup.array().of(Yup.string()),
document_sets: Yup.array()
.of(Yup.number())
.when("knowledge_source", {
is: "document_sets",
then: (schema) =>
schema.min(
1,
"At least one Document Set is required when using the 'Document Sets' knowledge source"
),
}),
persona_id: Yup.number()
.nullable()
.when("knowledge_source", {
is: "assistant",
then: (schema) =>
schema.required(
"A persona is required when using the'Assistant' knowledge source"
),
}),
standard_answer_categories: Yup.array(),
knowledge_source: Yup.string()
.oneOf(["all_public", "document_sets", "assistant"])
.required(),
})}
onSubmit={async (values, formikHelpers) => {
formikHelpers.setSubmitting(true);
const cleanedValues = {
...values,
slack_bot_id,
channel_name: values.channel_name,
respond_member_group_list: values.respond_member_group_list,
usePersona: values.knowledge_source === "assistant",
document_sets:
existingSlackChannelConfig && existingSlackChannelConfig.persona
? existingSlackChannelConfig.persona.document_sets.map(
(documentSet) => documentSet.id
)
: ([] as number[]),
// prettier-ignore
values.knowledge_source === "document_sets"
? values.document_sets
: [],
persona_id:
existingSlackChannelConfig?.persona &&
!isPersonaASlackBotPersona(existingSlackChannelConfig.persona)
? existingSlackChannelConfig.persona.id
: knowledgePersona?.id ?? null,
response_type:
existingSlackChannelConfig?.response_type || "citations",
standard_answer_categories: existingSlackChannelConfig
? existingSlackChannelConfig.standard_answer_categories
: [],
}}
validationSchema={Yup.object().shape({
slack_bot_id: Yup.number().required(),
channel_name: Yup.string(),
response_type: Yup.string()
.oneOf(["quotes", "citations"])
.required(),
answer_validity_check_enabled: Yup.boolean().required(),
questionmark_prefilter_enabled: Yup.boolean().required(),
respond_tag_only: Yup.boolean().required(),
respond_to_bots: Yup.boolean().required(),
show_continue_in_web_ui: Yup.boolean().required(),
enable_auto_filters: Yup.boolean().required(),
respond_member_group_list: Yup.array().of(Yup.string()).required(),
still_need_help_enabled: Yup.boolean().required(),
follow_up_tags: Yup.array().of(Yup.string()),
document_sets: Yup.array().of(Yup.number()),
persona_id: Yup.number().nullable(),
standard_answer_categories: Yup.array(),
})}
onSubmit={async (values, formikHelpers) => {
formikHelpers.setSubmitting(true);
values.knowledge_source === "assistant"
? values.persona_id
: null,
standard_answer_categories: values.standard_answer_categories.map(
(category: any) => category.id
),
};
const cleanedValues = {
...values,
slack_bot_id: slack_bot_id,
channel_name: values.channel_name!,
respond_member_group_list: values.respond_member_group_list,
usePersona: usingPersonas,
standard_answer_categories: values.standard_answer_categories.map(
(category) => category.id
),
};
if (!cleanedValues.still_need_help_enabled) {
cleanedValues.follow_up_tags = undefined;
} else {
if (!cleanedValues.follow_up_tags) {
cleanedValues.follow_up_tags = [];
}
if (!cleanedValues.still_need_help_enabled) {
cleanedValues.follow_up_tags = undefined;
} else {
if (!cleanedValues.follow_up_tags) {
cleanedValues.follow_up_tags = [];
}
let response;
if (isUpdate) {
response = await updateSlackChannelConfig(
existingSlackChannelConfig.id,
}
const response = isUpdate
? await updateSlackChannelConfig(
existingSlackChannelConfig!.id,
cleanedValues
);
} else {
response = await createSlackChannelConfig(cleanedValues);
}
formikHelpers.setSubmitting(false);
if (response.ok) {
router.push(`/admin/bots/${slack_bot_id}`);
} else {
const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({
message: isUpdate
? `Error updating OnyxBot config - ${errorMsg}`
: `Error creating OnyxBot config - ${errorMsg}`,
type: "error",
});
}
}}
>
{({ isSubmitting, values, setFieldValue }) => (
<Form>
<div className="px-6 pb-6 pt-4 w-full">
<TextFormField
name="channel_name"
label="Slack Channel Name:"
/>
)
: await createSlackChannelConfig(cleanedValues);
<div className="mt-6">
<Label>Knowledge Sources</Label>
<SubLabel>
Controls which information OnyxBot will pull from when
answering questions.
</SubLabel>
<Tabs
defaultValue="document_sets"
className="w-full mt-4"
value={usingPersonas ? "assistants" : "document_sets"}
onValueChange={(value) =>
setUsingPersonas(value === "assistants")
}
>
<TabsList>
<TabsTrigger value="document_sets">
Document Sets
</TabsTrigger>
<TabsTrigger value="assistants">Assistants</TabsTrigger>
</TabsList>
<TabsContent value="assistants">
<SubLabel>
Select the assistant OnyxBot will use while answering
questions in Slack.
</SubLabel>
<SelectorFormField
name="persona_id"
options={personas.map((persona) => {
return {
name: persona.name,
value: persona.id,
};
})}
/>
</TabsContent>
<TabsContent value="document_sets">
<SubLabel>
Select the document sets OnyxBot will use while
answering questions in Slack.
</SubLabel>
<SubLabel>
Note: If No Document Sets are selected, OnyxBot will
search through all connected documents.
</SubLabel>
<FieldArray
name="document_sets"
render={(arrayHelpers: ArrayHelpers) => (
<div>
<div className="mb-3 mt-2 flex gap-2 flex-wrap text-sm">
{documentSets.map((documentSet) => {
const ind = values.document_sets.indexOf(
documentSet.id
);
const isSelected = ind !== -1;
return (
<DocumentSetSelectable
key={documentSet.id}
documentSet={documentSet}
isSelected={isSelected}
onSelect={() => {
if (isSelected) {
arrayHelpers.remove(ind);
} else {
arrayHelpers.push(documentSet.id);
}
}}
/>
);
})}
</div>
<div></div>
</div>
)}
/>
</TabsContent>
</Tabs>
</div>
<div className="mt-6">
<AdvancedOptionsToggle
showAdvancedOptions={showAdvancedOptions}
setShowAdvancedOptions={setShowAdvancedOptions}
/>
</div>
{showAdvancedOptions && (
<div className="mt-4">
<div className="w-64 mb-4">
<SelectorFormField
name="response_type"
label="Answer Type"
tooltip="Controls the format of OnyxBot's responses."
options={[
{ name: "Standard", value: "citations" },
{ name: "Detailed", value: "quotes" },
]}
/>
</div>
<BooleanFormField
name="show_continue_in_web_ui"
removeIndent
label="Show Continue in Web UI button"
tooltip="If set, will show a button at the bottom of the response that allows the user to continue the conversation in the Onyx Web UI"
/>
<div className="flex flex-col space-y-3 mt-2">
<BooleanFormField
name="still_need_help_enabled"
removeIndent
label={'Give a "Still need help?" button'}
tooltip={`OnyxBot's response will include a button at the bottom
of the response that asks the user if they still need help.`}
/>
{values.still_need_help_enabled && (
<CollapsibleSection prompt="Configure Still Need Help Button">
<TextArrayField
name="follow_up_tags"
label="(Optional) Users / Groups to Tag"
values={values}
subtext={
<div>
The Slack users / groups we should tag if the
user clicks the &quot;Still need help?&quot;
button. If no emails are provided, we will not
tag anyone and will just react with a 🆘 emoji
to the original message.
</div>
}
placeholder="User email or user group name..."
/>
</CollapsibleSection>
)}
<BooleanFormField
name="answer_validity_check_enabled"
removeIndent
label="Only respond if citations found"
tooltip="If set, will only answer questions where the model successfully produces citations"
/>
<BooleanFormField
name="questionmark_prefilter_enabled"
removeIndent
label="Only respond to questions"
tooltip="If set, will only respond to messages that contain a question mark"
/>
<BooleanFormField
name="respond_tag_only"
removeIndent
label="Respond to @OnyxBot Only"
tooltip="If set, OnyxBot will only respond when directly tagged"
/>
<BooleanFormField
name="respond_to_bots"
removeIndent
label="Respond to Bot messages"
tooltip="If not set, OnyxBot will always ignore messages from Bots"
/>
<BooleanFormField
name="enable_auto_filters"
removeIndent
label="Enable LLM Autofiltering"
tooltip="If set, the LLM will generate source and time filters based on the user's query"
/>
<div className="mt-12">
<TextArrayField
name="respond_member_group_list"
label="(Optional) Respond to Certain Users / Groups"
subtext={
"If specified, OnyxBot responses will only " +
"be visible to the members or groups in this list."
}
values={values}
placeholder="User email or user group name..."
/>
</div>
</div>
<StandardAnswerCategoryDropdownField
standardAnswerCategoryResponse={
standardAnswerCategoryResponse
}
categories={values.standard_answer_categories}
setCategories={(categories) =>
setFieldValue("standard_answer_categories", categories)
}
/>
</div>
)}
<div className="flex">
<Button
type="submit"
variant="submit"
disabled={isSubmitting || !values.channel_name}
className="mx-auto w-64"
>
{isUpdate ? "Update!" : "Create!"}
</Button>
</div>
</div>
</Form>
)}
</Formik>
</CardSection>
</div>
formikHelpers.setSubmitting(false);
if (response.ok) {
router.push(`/admin/bots/${slack_bot_id}`);
} else {
const responseJson = await response.json();
const errorMsg = responseJson.detail || responseJson.message;
setPopup({
message: `Error ${
isUpdate ? "updating" : "creating"
} OnyxBot config - ${errorMsg}`,
type: "error",
});
}
}}
>
<SlackChannelConfigFormFields
isUpdate={isUpdate}
documentSets={documentSets}
searchEnabledAssistants={searchEnabledAssistants}
standardAnswerCategoryResponse={standardAnswerCategoryResponse}
setPopup={setPopup}
/>
</Formik>
</CardSection>
);
};

View File

@ -0,0 +1,529 @@
"use client";
import React, { useState, useEffect, useMemo } from "react";
import { FieldArray, Form, useFormikContext, ErrorMessage } from "formik";
import { CCPairDescriptor, DocumentSet } from "@/lib/types";
import {
BooleanFormField,
Label,
SelectorFormField,
SubLabel,
TextArrayField,
TextFormField,
} from "@/components/admin/connectors/Field";
import { Button } from "@/components/ui/button";
import { Persona } from "@/app/admin/assistants/interfaces";
import { AdvancedOptionsToggle } from "@/components/AdvancedOptionsToggle";
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
import CollapsibleSection from "@/app/admin/assistants/CollapsibleSection";
import { StandardAnswerCategoryResponse } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE";
import { StandardAnswerCategoryDropdownField } from "@/components/standardAnswers/StandardAnswerCategoryDropdown";
import { RadioGroup } from "@/components/ui/radio-group";
import { RadioGroupItemField } from "@/components/ui/RadioGroupItemField";
import { AlertCircle, View } from "lucide-react";
import { useRouter } from "next/navigation";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { SourceIcon } from "@/components/SourceIcon";
import Link from "next/link";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
interface SlackChannelConfigFormFieldsProps {
isUpdate: boolean;
documentSets: DocumentSet[];
searchEnabledAssistants: Persona[];
standardAnswerCategoryResponse: StandardAnswerCategoryResponse;
setPopup: (popup: {
message: string;
type: "error" | "success" | "warning";
}) => void;
}
export function SlackChannelConfigFormFields({
isUpdate,
documentSets,
searchEnabledAssistants,
standardAnswerCategoryResponse,
setPopup,
}: SlackChannelConfigFormFieldsProps) {
const router = useRouter();
const { values, setFieldValue, isSubmitting } = useFormikContext<any>();
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
const [viewUnselectableSets, setViewUnselectableSets] = useState(false);
const [viewSyncEnabledAssistants, setViewSyncEnabledAssistants] =
useState(false);
const documentSetContainsSync = (documentSet: DocumentSet) =>
documentSet.cc_pair_descriptors.some(
(descriptor) => descriptor.access_type === "sync"
);
const [syncEnabledAssistants, availableAssistants] = useMemo(() => {
const sync: Persona[] = [];
const available: Persona[] = [];
searchEnabledAssistants.forEach((persona) => {
const hasSyncSet = persona.document_sets.some(documentSetContainsSync);
if (hasSyncSet) {
sync.push(persona);
} else {
available.push(persona);
}
});
return [sync, available];
}, [searchEnabledAssistants]);
const unselectableSets = useMemo(() => {
return documentSets.filter((ds) =>
ds.cc_pair_descriptors.some(
(descriptor) => descriptor.access_type === "sync"
)
);
}, [documentSets]);
const memoizedPrivateConnectors = useMemo(() => {
const uniqueDescriptors = new Map();
documentSets.forEach((ds) => {
ds.cc_pair_descriptors.forEach((descriptor) => {
if (
descriptor.access_type === "private" &&
!uniqueDescriptors.has(descriptor.id)
) {
uniqueDescriptors.set(descriptor.id, descriptor);
}
});
});
return Array.from(uniqueDescriptors.values());
}, [documentSets]);
useEffect(() => {
const invalidSelected = values.document_sets.filter((dsId: number) =>
unselectableSets.some((us) => us.id === dsId)
);
if (invalidSelected.length > 0) {
setFieldValue(
"document_sets",
values.document_sets.filter(
(dsId: number) => !invalidSelected.includes(dsId)
)
);
setPopup({
message:
"We removed one or more document sets from your selection because they are no longer valid. Please review and update your configuration.",
type: "warning",
});
}
}, [unselectableSets, values.document_sets, setFieldValue, setPopup]);
const documentSetContainsPrivate = (documentSet: DocumentSet) => {
return documentSet.cc_pair_descriptors.some(
(descriptor) => descriptor.access_type === "private"
);
};
const shouldShowPrivacyAlert = useMemo(() => {
if (values.knowledge_source === "document_sets") {
const selectedSets = documentSets.filter((ds) =>
values.document_sets.includes(ds.id)
);
return selectedSets.some((ds) => documentSetContainsPrivate(ds));
} else if (values.knowledge_source === "assistant") {
const chosenAssistant = searchEnabledAssistants.find(
(p) => p.id == values.persona_id
);
return chosenAssistant?.document_sets.some((ds) =>
documentSetContainsPrivate(ds)
);
}
return false;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [values.knowledge_source, values.document_sets, values.persona_id]);
const selectableSets = useMemo(() => {
return documentSets.filter(
(ds) =>
!ds.cc_pair_descriptors.some(
(descriptor) => descriptor.access_type === "sync"
)
);
}, [documentSets]);
return (
<Form className="px-6 max-w-4xl">
<div className="pt-4 w-full">
<TextFormField name="channel_name" label="Slack Channel Name:" />
<div className="space-y-2 mt-4">
<Label>Knowledge Source</Label>
<RadioGroup
className="flex flex-col gap-y-4"
value={values.knowledge_source}
onValueChange={(value: string) => {
setFieldValue("knowledge_source", value);
}}
>
<RadioGroupItemField
value="all_public"
id="all_public"
label="All Public Knowledge"
sublabel="Let OnyxBot respond based on information from all public connectors "
/>
{selectableSets.length + unselectableSets.length > 0 && (
<RadioGroupItemField
value="document_sets"
id="document_sets"
label="Specific Document Sets"
sublabel="Control which documents to use for answering questions"
/>
)}
<RadioGroupItemField
value="assistant"
id="assistant"
label="Specific Assistant"
sublabel="Control both the documents and the prompt to use for answering questions"
/>
</RadioGroup>
</div>
{values.knowledge_source === "document_sets" &&
documentSets.length > 0 && (
<div className="mt-4">
<SubLabel>
<>
Select the document sets OnyxBot will use while answering
questions in Slack.
<br />
{unselectableSets.length > 0 ? (
<span>
Some incompatible document sets are{" "}
{viewUnselectableSets ? "visible" : "hidden"}.{" "}
<button
type="button"
onClick={() =>
setViewUnselectableSets(
(viewUnselectableSets) => !viewUnselectableSets
)
}
className="text-sm text-link"
>
{viewUnselectableSets
? "Hide un-selectable "
: "View all "}
document sets
</button>
</span>
) : (
""
)}
</>
</SubLabel>
<FieldArray
name="document_sets"
render={(arrayHelpers) => (
<>
{selectableSets.length > 0 && (
<div className="mb-3 mt-2 flex gap-2 flex-wrap text-sm">
{selectableSets.map((documentSet) => {
const selectedIndex = values.document_sets.indexOf(
documentSet.id
);
const isSelected = selectedIndex !== -1;
return (
<DocumentSetSelectable
key={documentSet.id}
documentSet={documentSet}
isSelected={isSelected}
onSelect={() => {
if (isSelected) {
arrayHelpers.remove(selectedIndex);
} else {
arrayHelpers.push(documentSet.id);
}
}}
/>
);
})}
</div>
)}
{viewUnselectableSets && unselectableSets.length > 0 && (
<div className="mt-4">
<p className="text-sm text-text-dark/80">
These document sets cannot be attached as they have
auto-synced docs:
</p>
<div className="mb-3 mt-2 flex gap-2 flex-wrap text-sm">
{unselectableSets.map((documentSet) => (
<DocumentSetSelectable
key={documentSet.id}
documentSet={documentSet}
disabled
disabledTooltip="Unable to use this document set because it contains a connector with auto-sync permissions. OnyxBot's responses in this channel are visible to all Slack users, so mirroring the asker's permissions could inadvertently expose private information."
isSelected={false}
onSelect={() => {}}
/>
))}
</div>
</div>
)}
<ErrorMessage
className="text-red-500 text-sm mt-1"
name="document_sets"
component="div"
/>
</>
)}
/>
</div>
)}
{values.knowledge_source === "assistant" && (
<div className="mt-4">
<SubLabel>
<>
Select the search-enabled assistant OnyxBot will use while
answering questions in Slack.
{syncEnabledAssistants.length > 0 && (
<>
<br />
<span className="text-sm text-text-dark/80">
Note: Some of your assistants have auto-synced connectors
in their document sets. You cannot select these assistants
as they will not be able to answer questions in Slack.{" "}
<button
type="button"
onClick={() =>
setViewSyncEnabledAssistants(
(viewSyncEnabledAssistants) =>
!viewSyncEnabledAssistants
)
}
className="text-sm text-link"
>
{viewSyncEnabledAssistants
? "Hide un-selectable "
: "View all "}
assistants
</button>
</span>
</>
)}
</>
</SubLabel>
<SelectorFormField
name="persona_id"
options={availableAssistants.map((persona) => ({
name: persona.name,
value: persona.id,
}))}
/>
{viewSyncEnabledAssistants && syncEnabledAssistants.length > 0 && (
<div className="mt-4">
<p className="text-sm text-text-dark/80">
Un-selectable assistants:
</p>
<div className="mb-3 mt-2 flex gap-2 flex-wrap text-sm">
{syncEnabledAssistants.map((persona: Persona) => (
<button
type="button"
onClick={() =>
router.push(`/admin/assistants/${persona.id}`)
}
key={persona.id}
className="p-2 bg-background-100 cursor-pointer rounded-md flex items-center gap-2"
>
<AssistantIcon
assistant={persona}
size={16}
className="flex-none"
/>
{persona.name}
</button>
))}
</div>
</div>
)}
</div>
)}
</div>
<div className="mt-2">
<AdvancedOptionsToggle
showAdvancedOptions={showAdvancedOptions}
setShowAdvancedOptions={setShowAdvancedOptions}
/>
</div>
{showAdvancedOptions && (
<div className="mt-4">
<div className="w-64 mb-4">
<SelectorFormField
name="response_type"
label="Answer Type"
tooltip="Controls the format of OnyxBot's responses."
options={[
{ name: "Standard", value: "citations" },
{ name: "Detailed", value: "quotes" },
]}
/>
</div>
<BooleanFormField
name="show_continue_in_web_ui"
removeIndent
label="Show Continue in Web UI button"
tooltip="If set, will show a button at the bottom of the response that allows the user to continue the conversation in the Onyx Web UI"
/>
<div className="flex flex-col space-y-3 mt-2">
<BooleanFormField
name="still_need_help_enabled"
removeIndent
onChange={(checked: boolean) => {
setFieldValue("still_need_help_enabled", checked);
if (!checked) {
setFieldValue("follow_up_tags", []);
}
}}
label={'Give a "Still need help?" button'}
tooltip={`OnyxBot's response will include a button at the bottom
of the response that asks the user if they still need help.`}
/>
{values.still_need_help_enabled && (
<CollapsibleSection prompt="Configure Still Need Help Button">
<TextArrayField
name="follow_up_tags"
label="(Optional) Users / Groups to Tag"
values={values}
subtext={
<div>
The Slack users / groups we should tag if the user clicks
the &quot;Still need help?&quot; button. If no emails are
provided, we will not tag anyone and will just react with
a 🆘 emoji to the original message.
</div>
}
placeholder="User email or user group name..."
/>
</CollapsibleSection>
)}
<BooleanFormField
name="answer_validity_check_enabled"
removeIndent
label="Only respond if citations found"
tooltip="If set, will only answer questions where the model successfully produces citations"
/>
<BooleanFormField
name="questionmark_prefilter_enabled"
removeIndent
label="Only respond to questions"
tooltip="If set, OnyxBot will only respond to messages that contain a question mark"
/>
<BooleanFormField
name="respond_tag_only"
removeIndent
label="Respond to @OnyxBot Only"
tooltip="If set, OnyxBot will only respond when directly tagged"
/>
<BooleanFormField
name="respond_to_bots"
removeIndent
label="Respond to Bot messages"
tooltip="If not set, OnyxBot will always ignore messages from Bots"
/>
<BooleanFormField
name="enable_auto_filters"
removeIndent
label="Enable LLM Autofiltering"
tooltip="If set, the LLM will generate source and time filters based on the user's query"
/>
<div className="mt-12">
<TextArrayField
name="respond_member_group_list"
label="(Optional) Respond to Certain Users / Groups"
subtext={
"If specified, OnyxBot responses will only " +
"be visible to the members or groups in this list."
}
values={values}
placeholder="User email or user group name..."
/>
</div>
</div>
<StandardAnswerCategoryDropdownField
standardAnswerCategoryResponse={standardAnswerCategoryResponse}
categories={values.standard_answer_categories}
setCategories={(categories: any) =>
setFieldValue("standard_answer_categories", categories)
}
/>
</div>
)}
<div className="flex mt-2 gap-x-2 w-full justify-end flex">
{shouldShowPrivacyAlert && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="flex hover:bg-background-150 cursor-pointer p-2 rounded-lg items-center">
<AlertCircle className="h-5 w-5 text-alert" />
</div>
</TooltipTrigger>
<TooltipContent side="top" className="bg-white p-4 w-80">
<Label className="text-text mb-2 font-semibold">
Privacy Alert
</Label>
<p className="text-sm text-text-darker mb-4">
Please note that at least one of the documents accessible by
your OnyxBot is marked as private and may contain sensitive
information. These documents will be accessible to all users
of this OnyxBot. Ensure this aligns with your intended
document sharing policy.
</p>
<div className="space-y-2">
<h4 className="text-sm text-text font-medium">
Relevant Connectors:
</h4>
<div className="max-h-40 overflow-y-auto border-t border-text-subtle flex-col gap-y-2">
{memoizedPrivateConnectors.map(
(ccpairinfo: CCPairDescriptor<any, any>) => (
<Link
key={ccpairinfo.id}
href={`/admin/connector/${ccpairinfo.id}`}
className="flex items-center p-2 rounded-md hover:bg-gray-100 transition-colors"
>
<div className="mr-2">
<SourceIcon
iconSize={16}
sourceType={ccpairinfo.connector.source}
/>
</div>
<span className="text-sm text-text-darker font-medium">
{ccpairinfo.name}
</span>
</Link>
)
)}
</div>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<Button onClick={() => {}} type="submit">
{isUpdate ? "Update" : "Create"}
</Button>
<Button type="button" variant="outline" onClick={() => router.back()}>
Cancel
</Button>
</div>
</Form>
);
}

View File

@ -67,7 +67,8 @@ async function EditslackChannelConfigPage(props: {
/>
);
}
const documentSets = (await documentSetsResponse.json()) as DocumentSet[];
const response = await documentSetsResponse.json();
const documentSets = response as DocumentSet[];
if (assistantsFetchError) {
return (
@ -79,7 +80,7 @@ async function EditslackChannelConfigPage(props: {
}
return (
<div className="container mx-auto">
<div className="max-w-4xl container mx-auto">
<InstantSSRAutoRefresh />
<BackButton />

View File

@ -56,7 +56,7 @@ async function NewChannelConfigPage(props: {
}
return (
<div className="container mx-auto">
<div className="container max-w-4xl mx-auto">
<BackButton />
<AdminPageTitle
icon={<SourceIcon iconSize={32} sourceType={ValidSources.Slack} />}

View File

@ -1,3 +1,3 @@
export const SEARCH_TOOL_NAME = "run_search";
export const SEARCH_TOOL_NAME = "SearchTool";
export const INTERNET_SEARCH_TOOL_NAME = "run_internet_search";
export const IMAGE_GENERATION_TOOL_NAME = "run_image_generation";

View File

@ -1,27 +1,42 @@
import { CheckmarkIcon } from "./icons/icons";
import { FaXRay } from "react-icons/fa";
import { CheckmarkIcon, XIcon } from "./icons/icons";
export const CustomCheckbox = ({
checked,
onChange,
disabled,
}: {
checked: boolean;
onChange?: () => void;
disabled?: boolean;
}) => {
return (
<label className="flex items-center cursor-pointer">
<label
className={`flex items-center cursor-pointer ${
disabled ? "opacity-50" : ""
}`}
>
<input
type="checkbox"
className="hidden"
checked={checked}
onChange={onChange}
readOnly={onChange ? false : true}
disabled={disabled}
/>
<span className="relative">
<span
className={`block w-3 h-3 border border-border-strong rounded ${
checked ? "bg-green-700" : "bg-background"
} transition duration-300`}
checked ? "bg-green-700" : disabled ? "bg-error" : "bg-background"
} transition duration-300 ${disabled ? "bg-background" : ""}`}
>
{disabled && (
<XIcon
size={12}
className="absolute z-[1000] top-0 left-0 fill-current text-inverted"
/>
)}
{checked && (
<CheckmarkIcon
size={12}

View File

@ -9,7 +9,12 @@ export default function CardSection({
className?: string;
}) {
return (
<div className={cn("p-6 border border-border-strong/80", className)}>
<div
className={cn(
"p-6 border bg-white rounded border-border-strong/80",
className
)}
>
{children}
</div>
);

View File

@ -27,7 +27,6 @@ import ReactMarkdown from "react-markdown";
import { FaMarkdown } from "react-icons/fa";
import { useRef, useState, useCallback } from "react";
import remarkGfm from "remark-gfm";
import { EditIcon } from "@/components/icons/icons";
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { CheckboxField } from "@/components/ui/checkbox";
@ -77,9 +76,7 @@ export function LabelWithTooltip({
}
export function SubLabel({ children }: { children: string | JSX.Element }) {
return (
<div className="text-xs text-subtle whitespace-pre-line">{children}</div>
);
return <div className="text-sm text-text-dark/80 mb-2">{children}</div>;
}
export function ManualErrorMessage({ children }: { children: string }) {
@ -228,11 +225,7 @@ export function TextFormField({
} gap-x-2 items-start`}
>
<div className="flex gap-x-2 items-center">
{!removeLabel && (
<Label className={sizeClass.label} small={small}>
{label}
</Label>
)}
{!removeLabel && <Label small={false}>{label}</Label>}
{optional ? <span>(optional) </span> : ""}
{tooltip && <ToolTipDetails>{tooltip}</ToolTipDetails>}
</div>
@ -450,6 +443,7 @@ interface BooleanFormFieldProps {
optional?: boolean;
tooltip?: string;
disabledTooltip?: string;
onChange?: (checked: boolean) => void;
}
export const BooleanFormField = ({
@ -463,6 +457,7 @@ export const BooleanFormField = ({
disabled,
tooltip,
disabledTooltip,
onChange,
}: BooleanFormFieldProps) => {
const { setFieldValue } = useFormikContext<any>();
@ -471,8 +466,11 @@ export const BooleanFormField = ({
if (!disabled) {
setFieldValue(name, checked);
}
if (onChange) {
onChange(checked === true);
}
},
[disabled, name, setFieldValue]
[disabled, name, setFieldValue, onChange]
);
return (
@ -734,6 +732,7 @@ export function SelectorFormField({
) : (
options.map((option) => (
<SelectItem
hideCheck
icon={option.icon}
key={option.value}
value={String(option.value)}

View File

@ -1,54 +1,83 @@
"use client";
import { DocumentSet, ValidSources } from "@/lib/types";
import { CustomCheckbox } from "../CustomCheckbox";
import { SourceIcon } from "../SourceIcon";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
export function DocumentSetSelectable({
documentSet,
isSelected,
onSelect,
disabled,
disabledTooltip,
}: {
documentSet: DocumentSet;
isSelected: boolean;
onSelect: () => void;
disabled?: boolean;
disabledTooltip?: string;
}) {
// Collect unique connector sources
const uniqueSources = new Set<ValidSources>();
documentSet.cc_pair_descriptors.forEach((ccPairDescriptor) => {
uniqueSources.add(ccPairDescriptor.connector.source);
});
return (
<div
key={documentSet.id}
className={
`
w-72
px-3
py-1
rounded-lg
border
border-border
flex
cursor-pointer ` +
(isSelected ? " bg-hover" : " bg-background hover:bg-hover-light")
}
onClick={onSelect}
>
<div className="flex w-full">
<div className="flex flex-col h-full">
<div className="font-bold">{documentSet.name}</div>
<div className="text-xs">{documentSet.description}</div>
<div className="flex gap-x-2 pt-1 mt-auto mb-1">
{Array.from(uniqueSources).map((source) => (
<SourceIcon key={source} sourceType={source} iconSize={16} />
))}
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<div
className={`
w-72
px-3
py-1
rounded-lg
border
border-border
${disabled ? "bg-background" : ""}
flex
cursor-pointer
${isSelected ? "bg-hover" : "bg-background hover:bg-hover-light"}
`}
onClick={disabled ? undefined : onSelect}
>
<div className="flex w-full">
<div className="flex flex-col h-full">
<div className="font-bold">{documentSet.name}</div>
<div className="text-xs">{documentSet.description}</div>
<div className="flex gap-x-2 pt-1 mt-auto mb-1">
{Array.from(uniqueSources).map((source) => (
<SourceIcon
key={source}
sourceType={source}
iconSize={16}
/>
))}
</div>
</div>
<div className="ml-auto my-auto pl-1">
<CustomCheckbox
checked={isSelected}
onChange={() => null}
disabled={disabled}
/>
</div>
</div>
</div>
</div>
<div className="ml-auto my-auto">
<div className="pl-1">
<CustomCheckbox checked={isSelected} onChange={() => null} />
</div>
</div>
</div>
</div>
</TooltipTrigger>
{disabled && disabledTooltip && (
<TooltipContent>
<p>{disabledTooltip}</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
);
}

View File

@ -5,7 +5,6 @@ import MultiSelectDropdown from "../MultiSelectDropdown";
import { StandardAnswerCategory } from "@/lib/types";
import { ErrorCallout } from "../ErrorCallout";
import { LoadingAnimation } from "../Loading";
import { Separator } from "@/components/ui/separator";
interface StandardAnswerCategoryDropdownFieldProps {
standardAnswerCategoryResponse: StandardAnswerCategoryResponse;
@ -37,7 +36,7 @@ export const StandardAnswerCategoryDropdownField: FC<
<>
<div>
<Label>Standard Answer Categories</Label>
<div className="w-4/12">
<div className="w-64">
<MultiSelectDropdown
name="standard_answer_categories"
label=""
@ -64,8 +63,6 @@ export const StandardAnswerCategoryDropdownField: FC<
/>
</div>
</div>
<Separator />
</>
);
};

View File

@ -0,0 +1,47 @@
import React from "react";
import { RadioGroupItem } from "@/components/ui/radio-group";
import { ErrorMessage } from "formik";
interface RadioGroupItemFieldProps {
value: string;
id: string;
label: string;
sublabel?: string;
}
export const RadioGroupItemField: React.FC<RadioGroupItemFieldProps> = ({
value,
id,
label,
sublabel,
}) => {
const handleClick = () => {
const radio = document.getElementById(id) as HTMLInputElement;
if (radio) {
radio.checked = true;
radio.dispatchEvent(new Event("change", { bubbles: true }));
}
};
return (
<div className="flex items-start space-x-2">
<RadioGroupItem value={value} id={id} className="mt-1" />
<div className="flex flex-col">
<label
htmlFor={id}
className="flex flex-col cursor-pointer"
onClick={handleClick}
>
<span className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70">
{label}
</span>
{sublabel && (
<span className="text-sm text-muted-foreground mt-1">
{sublabel}
</span>
)}
</label>
</div>
</div>
);
};

View File

@ -8,8 +8,10 @@ const alertVariants = cva(
{
variants: {
variant: {
info: "border-black/50 dark:border-black dark:border-black/50 dark:dark:border-black",
default:
"bg-white text-neutral-950 dark:bg-neutral-950 dark:text-neutral-50",
"bg-background-50 text-text-darker dark:bg-background-950 dark:text-text",
destructive:
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
},

View File

@ -0,0 +1,12 @@
"use client";
import * as React from "react";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@ -28,7 +28,7 @@ const RadioGroupItem = React.forwardRef<
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-neutral-200 border-neutral-900 text-neutral-900 ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-800 dark:border-neutral-50 dark:text-neutral-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300",
"aspect-square h-3.5 w-3.5 rounded-full border border-neutral-200 border-neutral-900 text-neutral-900 ring-offset-white focus:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-800 dark:border-neutral-50 dark:text-neutral-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300",
className
)}
{...props}

View File

@ -29,7 +29,7 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
`z-50 overflow-hidden rounded-md text-white ${
backgroundColor || "bg-background-900"
backgroundColor || "bg-black"
}
${width || "max-w-sm"}
px-2 py-1.5 text-xs shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 `,

View File

@ -201,6 +201,7 @@ export interface CCPairDescriptor<ConnectorType, CredentialType> {
name: string | null;
connector: Connector<ConnectorType>;
credential: Credential<CredentialType>;
access_type: AccessType;
}
export interface DocumentSet {