mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-03-17 21:32:36 +01:00
parent
42d6d935ae
commit
9bdb581220
@ -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
|
||||
|
@ -357,6 +357,7 @@ class ConnectorCredentialPairDescriptor(BaseModel):
|
||||
name: str | None = None
|
||||
connector: ConnectorSnapshot
|
||||
credential: CredentialSnapshot
|
||||
access_type: AccessType
|
||||
|
||||
|
||||
class RunConnectorRequest(BaseModel):
|
||||
|
@ -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
183
package-lock.json
generated
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"react-datepicker": "^7.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-datepicker": "^6.2.0"
|
||||
}
|
||||
}
|
132
web/package-lock.json
generated
132
web/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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 "Still need help?"
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -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 "Still need help?" 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>
|
||||
);
|
||||
}
|
@ -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 />
|
||||
|
@ -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} />}
|
||||
|
@ -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";
|
||||
|
@ -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}
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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)}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
47
web/src/components/ui/RadioGroupItemField.tsx
Normal file
47
web/src/components/ui/RadioGroupItemField.tsx
Normal 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>
|
||||
);
|
||||
};
|
@ -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",
|
||||
},
|
||||
|
12
web/src/components/ui/collapsible.tsx
Normal file
12
web/src/components/ui/collapsible.tsx
Normal 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 };
|
@ -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}
|
||||
|
@ -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 `,
|
||||
|
@ -201,6 +201,7 @@ export interface CCPairDescriptor<ConnectorType, CredentialType> {
|
||||
name: string | null;
|
||||
connector: Connector<ConnectorType>;
|
||||
credential: Credential<CredentialType>;
|
||||
access_type: AccessType;
|
||||
}
|
||||
|
||||
export interface DocumentSet {
|
||||
|
Loading…
x
Reference in New Issue
Block a user