| 'use client'; |
|
|
| import React, { |
| createContext, |
| FC, |
| useCallback, |
| useMemo, |
| useState, |
| ReactNode, |
| } from 'react'; |
| import clsx from 'clsx'; |
| import useCookie from 'react-use-cookie'; |
| import useSWR from 'swr'; |
| import { orderBy } from 'lodash'; |
| import { SVGLine } from '@gitroom/frontend/components/launches/launches.component'; |
| import ImageWithFallback from '@gitroom/react/helpers/image.with.fallback'; |
| import SafeImage from '@gitroom/react/helpers/safe.image'; |
| import { useFetch } from '@gitroom/helpers/utils/custom.fetch'; |
| import { useWaitForClass } from '@gitroom/helpers/utils/use.wait.for.class'; |
| import { MultiMediaComponent } from '@gitroom/frontend/components/media/media.component'; |
| import { Integration } from '@prisma/client'; |
| import Link from 'next/link'; |
| import { useParams, usePathname, useRouter } from 'next/navigation'; |
| import { useT } from '@gitroom/react/translation/get.transation.service.client'; |
|
|
| export const MediaPortal: FC<{ |
| media: { path: string; id: string }[]; |
| value: string; |
| setMedia: (event: { |
| target: { |
| name: string; |
| value?: { |
| id: string; |
| path: string; |
| alt?: string; |
| thumbnail?: string; |
| thumbnailTimestamp?: number; |
| }[]; |
| }; |
| }) => void; |
| }> = ({ media, setMedia, value }) => { |
| const waitForClass = useWaitForClass('copilotKitMessages'); |
| const t = useT(); |
| if (!waitForClass) return null; |
| return ( |
| <div className="pl-[14px] pr-[24px] whitespace-nowrap editor rm-bg"> |
| <MultiMediaComponent |
| allData={[{ content: value }]} |
| text={value} |
| label={t('attachments', 'Attachments')} |
| description="" |
| value={media} |
| dummy={false} |
| name="image" |
| onChange={setMedia} |
| onOpen={() => {}} |
| onClose={() => {}} |
| /> |
| </div> |
| ); |
| }; |
|
|
| export const AgentList: FC<{ onChange: (arr: any[]) => void }> = ({ |
| onChange, |
| }) => { |
| const fetch = useFetch(); |
| const t = useT(); |
| const [selected, setSelected] = useState([]); |
|
|
| const load = useCallback(async () => { |
| return (await (await fetch('/integrations/list')).json()).integrations; |
| }, []); |
|
|
| const [collapseMenu, setCollapseMenu] = useCookie('collapseMenu', '0'); |
|
|
| const { data } = useSWR('integrations', load, { |
| revalidateOnFocus: false, |
| revalidateOnReconnect: false, |
| revalidateIfStale: false, |
| revalidateOnMount: true, |
| refreshWhenHidden: false, |
| refreshWhenOffline: false, |
| fallbackData: [], |
| }); |
|
|
| const setIntegration = useCallback( |
| (integration: Integration) => () => { |
| if (selected.some((p) => p.id === integration.id)) { |
| onChange(selected.filter((p) => p.id !== integration.id)); |
| setSelected(selected.filter((p) => p.id !== integration.id)); |
| } else { |
| onChange([...selected, integration]); |
| setSelected([...selected, integration]); |
| } |
| }, |
| [selected] |
| ); |
|
|
| const sortedIntegrations = useMemo(() => { |
| return orderBy( |
| data || [], |
| ['type', 'disabled', 'identifier'], |
| ['desc', 'asc', 'asc'] |
| ); |
| }, [data]); |
|
|
| return ( |
| <div |
| className={clsx( |
| 'trz bg-newBgColorInner flex flex-col gap-[15px] transition-all relative', |
| collapseMenu === '1' ? 'group sidebar w-[100px]' : 'w-[260px]' |
| )} |
| > |
| <div className="absolute top-0 start-0 w-full h-full p-[20px] overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor"> |
| <div className="flex items-center"> |
| <h2 className="group-[.sidebar]:hidden flex-1 text-[20px] font-[500] mb-[15px]"> |
| {t('select_channels', 'Select Channels')} |
| </h2> |
| <div |
| onClick={() => setCollapseMenu(collapseMenu === '1' ? '0' : '1')} |
| className="-mt-3 group-[.sidebar]:rotate-[180deg] group-[.sidebar]:mx-auto text-btnText bg-btnSimple rounded-[6px] w-[24px] h-[24px] flex items-center justify-center cursor-pointer select-none" |
| > |
| <svg |
| xmlns="http://www.w3.org/2000/svg" |
| width="7" |
| height="13" |
| viewBox="0 0 7 13" |
| fill="none" |
| > |
| <path |
| d="M6 11.5L1 6.5L6 1.5" |
| stroke="currentColor" |
| strokeWidth="1.5" |
| strokeLinecap="round" |
| strokeLinejoin="round" |
| /> |
| </svg> |
| </div> |
| </div> |
| <div className={clsx('flex flex-col gap-[15px]')}> |
| {sortedIntegrations.map((integration, index) => ( |
| <div |
| onClick={setIntegration(integration)} |
| key={integration.id} |
| className={clsx( |
| 'flex gap-[12px] items-center group/profile justify-center hover:bg-boxHover rounded-e-[8px] hover:opacity-100 cursor-pointer', |
| !selected.some((p) => p.id === integration.id) && 'opacity-20' |
| )} |
| > |
| <div |
| className={clsx( |
| 'relative rounded-full flex justify-center items-center gap-[6px]', |
| integration.disabled && 'opacity-50' |
| )} |
| > |
| {(integration.inBetweenSteps || integration.refreshNeeded) && ( |
| <div className="absolute start-0 top-0 w-[39px] h-[46px] cursor-pointer"> |
| <div className="bg-red-500 w-[15px] h-[15px] rounded-full start-0 -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center"> |
| ! |
| </div> |
| <div className="bg-primary/60 w-[39px] h-[46px] start-0 top-0 absolute rounded-full z-[199]" /> |
| </div> |
| )} |
| <div className="h-full w-[4px] -ms-[12px] rounded-s-[3px] opacity-0 group-hover/profile:opacity-100 transition-opacity"> |
| <SVGLine /> |
| </div> |
| <ImageWithFallback |
| fallbackSrc={`/icons/platforms/${integration.identifier}.png`} |
| src={integration.picture} |
| className="rounded-[8px]" |
| alt={integration.identifier} |
| width={36} |
| height={36} |
| /> |
| <SafeImage |
| src={`/icons/platforms/${integration.identifier}.png`} |
| className="rounded-[8px] absolute z-10 bottom-[5px] -end-[5px] border border-fifth" |
| alt={integration.identifier} |
| width={18.41} |
| height={18.41} |
| /> |
| </div> |
| <div |
| className={clsx( |
| 'flex-1 whitespace-nowrap text-ellipsis overflow-hidden group-[.sidebar]:hidden', |
| integration.disabled && 'opacity-50' |
| )} |
| > |
| {integration.name} |
| </div> |
| </div> |
| ))} |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export const PropertiesContext = createContext({ properties: [] }); |
| export const Agent: FC<{ children: ReactNode }> = ({ children }) => { |
| const [properties, setProperties] = useState([]); |
|
|
| return ( |
| <PropertiesContext.Provider value={{ properties }}> |
| <AgentList onChange={setProperties} /> |
| <div className="bg-newBgColorInner flex flex-1">{children}</div> |
| <Threads /> |
| </PropertiesContext.Provider> |
| ); |
| }; |
|
|
| const Threads: FC = () => { |
| const fetch = useFetch(); |
| const router = useRouter(); |
| const pathname = usePathname(); |
| const t = useT(); |
| const threads = useCallback(async () => { |
| return (await fetch('/copilot/list')).json(); |
| }, []); |
| const { id } = useParams<{ id: string }>(); |
|
|
| const { data } = useSWR('threads', threads); |
|
|
| return ( |
| <div |
| className={clsx( |
| 'trz bg-newBgColorInner flex flex-col gap-[15px] transition-all relative', |
| 'w-[260px]' |
| )} |
| > |
| <div className="absolute top-0 start-0 w-full h-full p-[20px] overflow-auto scrollbar scrollbar-thumb-fifth scrollbar-track-newBgColor"> |
| <div className="mb-[15px] justify-center flex group-[.sidebar]:pb-[15px]"> |
| <Link |
| href={`/agents`} |
| className="text-white whitespace-nowrap flex-1 pt-[12px] pb-[14px] ps-[16px] pe-[20px] group-[.sidebar]:p-0 min-h-[44px] max-h-[44px] rounded-md bg-btnPrimary flex justify-center items-center gap-[5px] outline-none" |
| > |
| <svg |
| xmlns="http://www.w3.org/2000/svg" |
| width="21" |
| height="20" |
| viewBox="0 0 21 20" |
| fill="none" |
| className="min-w-[21px] min-h-[20px]" |
| > |
| <path |
| d="M10.5001 4.16699V15.8337M4.66675 10.0003H16.3334" |
| stroke="white" |
| strokeWidth="1.5" |
| strokeLinecap="round" |
| strokeLinejoin="round" |
| /> |
| </svg> |
| <div className="flex-1 text-start text-[16px] group-[.sidebar]:hidden"> |
| {t('start_a_new_chat', 'Start a new chat')} |
| </div> |
| </Link> |
| </div> |
| <div className="flex flex-col gap-[1px]"> |
| {data?.threads?.map((p: any) => ( |
| <Link |
| className={clsx( |
| 'overflow-ellipsis overflow-hidden whitespace-nowrap hover:bg-newBgColor px-[10px] py-[6px] rounded-[10px] cursor-pointer', |
| p.id === id && 'bg-newBgColor' |
| )} |
| href={`/agents/${p.id}`} |
| key={p.id} |
| > |
| {p.title} |
| </Link> |
| ))} |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|