feat: copilotkit

This commit is contained in:
Nevo David 2024-05-24 17:14:06 +07:00
parent e4eff984f8
commit 498e688332
17 changed files with 4264 additions and 348 deletions

View File

@ -25,6 +25,7 @@ import { MessagesController } from '@gitroom/backend/api/routes/messages.control
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';
import { CodesService } from '@gitroom/nestjs-libraries/services/codes.service';
import { CopilotController } from '@gitroom/backend/api/routes/copilot.controller';
const authenticatedController = [
UsersController,
@ -37,7 +38,7 @@ const authenticatedController = [
BillingController,
NotificationsController,
MarketplaceController,
MessagesController
MessagesController,
];
@Module({
imports: [
@ -58,7 +59,12 @@ const authenticatedController = [
]
: []),
],
controllers: [StripeController, AuthController, ...authenticatedController],
controllers: [
StripeController,
AuthController,
CopilotController,
...authenticatedController,
],
providers: [
AuthService,
StripeService,

View File

@ -0,0 +1,15 @@
import { Controller, Post, Req, Res } from '@nestjs/common';
import { CopilotRuntime, OpenAIAdapter } from '@copilotkit/backend';
@Controller('/copilot')
export class CopilotController {
@Post('/chat')
chat(@Req() req: Request, @Res() res: Response) {
const copilotKit = new CopilotRuntime({});
return copilotKit.streamHttpServerResponse(
req,
res,
new OpenAIAdapter({ model: 'gpt-4o' })
);
}
}

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="#fff" xmlns="http://www.w3.org/2000/svg">
<path d="M3.84453 3.84453C2.71849 4.97056 2.71849 6.79623 3.84453 7.92226L5.43227 9.51C5.44419 9.49622 5.45669 9.48276 5.46978 9.46967L9.46978 5.46967C9.48284 5.45662 9.49625 5.44415 9.50999 5.43226L7.92226 3.84453C6.79623 2.71849 4.97056 2.71849 3.84453 3.84453Z" fill="#fff"/>
<path d="M10.5679 6.49012C10.556 6.50386 10.5435 6.51728 10.5304 6.53033L6.53044 10.5303C6.51735 10.5434 6.5039 10.5559 6.49011 10.5678L16.0777 20.1555C17.2038 21.2815 19.0294 21.2815 20.1555 20.1555C21.2815 19.0294 21.2815 17.2038 20.1555 16.0777L10.5679 6.49012Z" fill="#fff"/>
<path d="M16.1 2.30719C16.261 1.8976 16.8385 1.8976 16.9994 2.30719L17.4298 3.40247C17.479 3.52752 17.5776 3.62651 17.7022 3.67583L18.7934 4.1078C19.2015 4.26934 19.2015 4.849 18.7934 5.01054L17.7022 5.44252C17.5776 5.49184 17.479 5.59082 17.4298 5.71587L16.9995 6.81115C16.8385 7.22074 16.261 7.22074 16.1 6.81116L15.6697 5.71587C15.6205 5.59082 15.5219 5.49184 15.3973 5.44252L14.3061 5.01054C13.898 4.849 13.898 4.26934 14.3061 4.1078L15.3973 3.67583C15.5219 3.62651 15.6205 3.52752 15.6697 3.40247L16.1 2.30719Z" fill="#fff"/>
<path d="M19.9672 9.12945C20.1281 8.71987 20.7057 8.71987 20.8666 9.12945L21.0235 9.5288C21.0727 9.65385 21.1713 9.75284 21.2959 9.80215L21.6937 9.95965C22.1018 10.1212 22.1018 10.7009 21.6937 10.8624L21.2959 11.0199C21.1713 11.0692 21.0727 11.1682 21.0235 11.2932L20.8666 11.6926C20.7057 12.1022 20.1281 12.1022 19.9672 11.6926L19.8103 11.2932C19.7611 11.1682 19.6625 11.0692 19.5379 11.0199L19.14 10.8624C18.732 10.7009 18.732 10.1212 19.14 9.95965L19.5379 9.80215C19.6625 9.75284 19.7611 9.65385 19.8103 9.5288L19.9672 9.12945Z" fill="#fff"/>
<path d="M5.1332 15.3072C5.29414 14.8976 5.87167 14.8976 6.03261 15.3072L6.18953 15.7065C6.23867 15.8316 6.33729 15.9306 6.46188 15.9799L6.85975 16.1374C7.26783 16.2989 7.26783 16.8786 6.85975 17.0401L6.46188 17.1976C6.33729 17.2469 6.23867 17.3459 6.18953 17.471L6.03261 17.8703C5.87167 18.2799 5.29414 18.2799 5.1332 17.8703L4.97628 17.471C4.92714 17.3459 4.82852 17.2469 4.70393 17.1976L4.30606 17.0401C3.89798 16.8786 3.89798 16.2989 4.30606 16.1374L4.70393 15.9799C4.82852 15.9306 4.92714 15.8316 4.97628 15.7065L5.1332 15.3072Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -287,4 +287,44 @@ html {
:empty + .existing-empty {
display: none;
}
:root {
--copilot-kit-primary-color: #612AD5 !important;
--copilot-kit-background-color: #0B0F1C !important;
--copilot-kit-separator-color: #1F2941 !important;
--copilot-kit-contrast-color: #FFFFFF !important;
--copilot-kit-secondary-contrast-color: #FFFFFF !important;
--copilot-kit-secondary-color: #000 !important;
--copilot-kit-response-button-background-color: #000 !important;;
--copilot-kit-response-button-color: #fff !important;;
}
.copilotKitWindow {
background-color: #0B0F1C !important;
}
.copilotKitButtonIconOpen svg {
display: none !important;
}
.copilotKitButtonIconOpen:after {
content: "";
display: block;
position: relative;
z-index: 1;
object-fit: contain;
color: white;
background: url("/magic.svg") no-repeat center center / contain;
width: 30px;
height: 30px;
}
.copilotKitPopup {
right: -2rem !important;
bottom: 2rem !important;
}
.copilotKitWindow {
/*right: -5rem !important;*/
}

View File

@ -3,6 +3,7 @@ import interClass from '@gitroom/react/helpers/inter.font';
export const dynamic = 'force-dynamic';
import './global.css';
import 'react-tooltip/dist/react-tooltip.css';
import "@copilotkit/react-ui/styles.css";
import LayoutContext from '@gitroom/frontend/components/layout/layout.context';
import { ReactNode } from 'react';

View File

@ -12,7 +12,7 @@ import React, {
import dayjs from 'dayjs';
import { Integrations } from '@gitroom/frontend/components/launches/calendar.context';
import clsx from 'clsx';
import MDEditor, { commands } from '@uiw/react-md-editor';
import { commands } from '@uiw/react-md-editor';
import { usePreventWindowUnload } from '@gitroom/react/helpers/use.prevent.window.unload';
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
import { useModals } from '@mantine/modals';
@ -45,6 +45,12 @@ import {
PostToOrganization,
} from '@gitroom/frontend/components/launches/post.to.organization';
import { Submitted } from '@gitroom/frontend/components/launches/submitted';
import { supportEmitter } from '@gitroom/frontend/components/layout/support';
import { Editor } from '@gitroom/frontend/components/launches/editor';
import { AddPostButton } from '@gitroom/frontend/components/launches/add.post.button';
import { useStateCallback } from '@gitroom/react/helpers/use.state.callback';
import { CopilotPopup } from '@copilotkit/react-ui';
import { useUser } from '@gitroom/frontend/components/layout/user.context';
export const AddEditModal: FC<{
date: dayjs.Dayjs;
@ -59,7 +65,7 @@ export const AddEditModal: FC<{
const modal = useModals();
// selected integrations to allow edit
const [selectedIntegrations, setSelectedIntegrations] = useState<
const [selectedIntegrations, setSelectedIntegrations] = useStateCallback<
Integrations[]
>([]);
@ -74,6 +80,8 @@ export const AddEditModal: FC<{
const fetch = useFetch();
const user = useUser();
const updateOrder = useCallback(() => {
modal.closeAll();
reopenModal();
@ -111,7 +119,10 @@ export const AddEditModal: FC<{
// if the user exit the popup we reset the global variable with all the values
useEffect(() => {
supportEmitter.emit('change', false);
return () => {
supportEmitter.emit('change', true);
resetValues();
};
}, []);
@ -317,6 +328,17 @@ export const AddEditModal: FC<{
return (
<>
{user?.tier.ai && (
<CopilotPopup
hitEscapeToClose={false}
clickOutsideToClose={true}
labels={{
title: 'AI Content Assistant',
}}
className="!z-[499]"
instructions="You are an assistant that help the user to schedule their social media posts"
/>
)}
<div className={clsx('flex gap-[20px] bg-black')}>
<div
className={clsx(
@ -344,9 +366,13 @@ export const AddEditModal: FC<{
selectedIntegrations={[]}
singleSelect={false}
onChange={setSelectedIntegrations}
isMain={true}
/>
)}
<div id="renderEditor" className={clsx(!showHide.hideTopEditor && 'hidden')} />
<div
id="renderEditor"
className={clsx(!showHide.hideTopEditor && 'hidden')}
/>
{!existingData.integration && !showHide.hideTopEditor ? (
<>
<div>You are in global editing mode</div>
@ -355,7 +381,8 @@ export const AddEditModal: FC<{
<div>
<div className="flex gap-[4px]">
<div className="flex-1 editor text-white">
<MDEditor
<Editor
order={index}
height={value.length > 1 ? 150 : 250}
commands={[
...commands
@ -426,26 +453,7 @@ export const AddEditModal: FC<{
</div>
</div>
<div>
<Button
onClick={addValue(index)}
className="!h-[24px] rounded-[3px] flex gap-[4px] w-[102px] text-[12px] font-[500]"
>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
>
<path
d="M7 1.3125C5.87512 1.3125 4.7755 1.64607 3.8402 2.27102C2.90489 2.89597 2.17591 3.78423 1.74544 4.82349C1.31496 5.86274 1.20233 7.00631 1.42179 8.10958C1.64124 9.21284 2.18292 10.2263 2.97833 11.0217C3.77374 11.8171 4.78716 12.3588 5.89043 12.5782C6.99369 12.7977 8.13726 12.685 9.17651 12.2546C10.2158 11.8241 11.104 11.0951 11.729 10.1598C12.3539 9.2245 12.6875 8.12488 12.6875 7C12.6859 5.49207 12.0862 4.04636 11.0199 2.98009C9.95365 1.91382 8.50793 1.31409 7 1.3125ZM7 11.8125C6.04818 11.8125 5.11773 11.5303 4.32632 11.0014C3.53491 10.4726 2.91808 9.72103 2.55383 8.84166C2.18959 7.96229 2.09428 6.99466 2.27997 6.06113C2.46566 5.12759 2.92401 4.27009 3.59705 3.59705C4.27009 2.92401 5.1276 2.46566 6.06113 2.27997C6.99466 2.09428 7.9623 2.18958 8.84167 2.55383C9.72104 2.91808 10.4726 3.53491 11.0015 4.32632C11.5303 5.11773 11.8125 6.04818 11.8125 7C11.8111 8.27591 11.3036 9.49915 10.4014 10.4014C9.49915 11.3036 8.27591 11.8111 7 11.8125ZM9.625 7C9.625 7.11603 9.57891 7.22731 9.49686 7.30936C9.41481 7.39141 9.30353 7.4375 9.1875 7.4375H7.4375V9.1875C7.4375 9.30353 7.39141 9.41481 7.30936 9.49686C7.22731 9.57891 7.11603 9.625 7 9.625C6.88397 9.625 6.77269 9.57891 6.69064 9.49686C6.6086 9.41481 6.5625 9.30353 6.5625 9.1875V7.4375H4.8125C4.69647 7.4375 4.58519 7.39141 4.50314 7.30936C4.4211 7.22731 4.375 7.11603 4.375 7C4.375 6.88397 4.4211 6.77269 4.50314 6.69064C4.58519 6.60859 4.69647 6.5625 4.8125 6.5625H6.5625V4.8125C6.5625 4.69647 6.6086 4.58519 6.69064 4.50314C6.77269 4.42109 6.88397 4.375 7 4.375C7.11603 4.375 7.22731 4.42109 7.30936 4.50314C7.39141 4.58519 7.4375 4.69647 7.4375 4.8125V6.5625H9.1875C9.30353 6.5625 9.41481 6.60859 9.49686 6.69064C9.57891 6.77269 9.625 6.88397 9.625 7Z"
fill="white"
/>
</svg>
</div>
<div>Add post</div>
</Button>
<AddPostButton num={index} onClick={addValue(index)} />
</div>
</Fragment>
))}

View File

@ -0,0 +1,40 @@
import { Button } from '@gitroom/react/form/button';
import React, { FC } from 'react';
import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';
export const AddPostButton: FC<{ onClick: () => void; num: number }> = (
props
) => {
const { onClick, num } = props;
useCopilotAction({
name: 'addPost_' + num,
description: 'Add a post after the post number ' + (num + 1),
handler: () => {
onClick();
},
});
return (
<Button
onClick={onClick}
className="!h-[24px] rounded-[3px] flex gap-[4px] w-[102px] text-[12px] font-[500]"
>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
>
<path
d="M7 1.3125C5.87512 1.3125 4.7755 1.64607 3.8402 2.27102C2.90489 2.89597 2.17591 3.78423 1.74544 4.82349C1.31496 5.86274 1.20233 7.00631 1.42179 8.10958C1.64124 9.21284 2.18292 10.2263 2.97833 11.0217C3.77374 11.8171 4.78716 12.3588 5.89043 12.5782C6.99369 12.7977 8.13726 12.685 9.17651 12.2546C10.2158 11.8241 11.104 11.0951 11.729 10.1598C12.3539 9.2245 12.6875 8.12488 12.6875 7C12.6859 5.49207 12.0862 4.04636 11.0199 2.98009C9.95365 1.91382 8.50793 1.31409 7 1.3125ZM7 11.8125C6.04818 11.8125 5.11773 11.5303 4.32632 11.0014C3.53491 10.4726 2.91808 9.72103 2.55383 8.84166C2.18959 7.96229 2.09428 6.99466 2.27997 6.06113C2.46566 5.12759 2.92401 4.27009 3.59705 3.59705C4.27009 2.92401 5.1276 2.46566 6.06113 2.27997C6.99466 2.09428 7.9623 2.18958 8.84167 2.55383C9.72104 2.91808 10.4726 3.53491 11.0015 4.32632C11.5303 5.11773 11.8125 6.04818 11.8125 7C11.8111 8.27591 11.3036 9.49915 10.4014 10.4014C9.49915 11.3036 8.27591 11.8111 7 11.8125ZM9.625 7C9.625 7.11603 9.57891 7.22731 9.49686 7.30936C9.41481 7.39141 9.30353 7.4375 9.1875 7.4375H7.4375V9.1875C7.4375 9.30353 7.39141 9.41481 7.30936 9.49686C7.22731 9.57891 7.11603 9.625 7 9.625C6.88397 9.625 6.77269 9.57891 6.69064 9.49686C6.6086 9.41481 6.5625 9.30353 6.5625 9.1875V7.4375H4.8125C4.69647 7.4375 4.58519 7.39141 4.50314 7.30936C4.4211 7.22731 4.375 7.11603 4.375 7C4.375 6.88397 4.4211 6.77269 4.50314 6.69064C4.58519 6.60859 4.69647 6.5625 4.8125 6.5625H6.5625V4.8125C6.5625 4.69647 6.6086 4.58519 6.69064 4.50314C6.77269 4.42109 6.88397 4.375 7 4.375C7.11603 4.375 7.22731 4.42109 7.30936 4.50314C7.39141 4.58519 7.4375 4.69647 7.4375 4.8125V6.5625H9.1875C9.30353 6.5625 9.41481 6.60859 9.49686 6.69064C9.57891 6.77269 9.625 6.88397 9.625 7Z"
fill="white"
/>
</svg>
</div>
<div>Add post</div>
</Button>
);
};

View File

@ -334,9 +334,9 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
<ExistingDataContextProvider value={data}>
<AddEditModal
reopenModal={editPost(post)}
integrations={integrations.filter(
integrations={integrations.slice(0).filter(
(f) => f.id === data.integration
)}
).map(p => ({ ...p, picture: data.integrationPicture }))}
date={getDate}
/>
</ExistingDataContextProvider>
@ -345,7 +345,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
title: ``,
});
},
[]
[integrations]
);
const addModal = useCallback(() => {
@ -358,7 +358,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
},
children: (
<AddEditModal
integrations={integrations}
integrations={integrations.slice(0).map(p => ({ ...p }))}
date={getDate}
reopenModal={() => ({})}
/>
@ -366,7 +366,7 @@ const CalendarColumnRender: FC<{ day: number; hour: string }> = (props) => {
size: '80%',
// title: `Adding posts for ${getDate.format('DD/MM/YYYY HH:mm')}`,
});
}, []);
}, [integrations]);
const addProvider = useAddProvider();

View File

@ -0,0 +1,36 @@
import { forwardRef } from 'react';
import type { MDEditorProps } from '@uiw/react-md-editor/src/Types';
import { RefMDEditor } from '@uiw/react-md-editor/src/Editor';
import MDEditor from '@uiw/react-md-editor';
import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';
export const Editor = forwardRef<
RefMDEditor,
MDEditorProps & { order: number, currentWatching: string, isGlobal: boolean}
>(
(
props: MDEditorProps & { order: number, currentWatching: string, isGlobal: boolean },
ref: React.ForwardedRef<RefMDEditor>
) => {
useCopilotReadable({
description: 'Content of the post number ' + (props.order + 1),
value: props.content,
});
useCopilotAction({
name: 'editPost_' + props.order,
description: `Edit the content of post number ${props.order + 1}`,
parameters: [
{
name: 'content',
type: 'string',
},
],
handler: ({ content }) => {
props?.onChange?.(content);
},
});
return <MDEditor {...props} ref={ref} />;
}
);

View File

@ -4,22 +4,26 @@ import { useMoveToIntegrationListener } from '@gitroom/frontend/components/launc
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
import clsx from 'clsx';
import Image from 'next/image';
import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';
import { useStateCallback } from '@gitroom/react/helpers/use.state.callback';
export const PickPlatforms: FC<{
integrations: Integrations[];
selectedIntegrations: Integrations[];
onChange: (integrations: Integrations[]) => void;
onChange: (integrations: Integrations[], callback: () => void) => void;
singleSelect: boolean;
hide?: boolean;
isMain: boolean;
}> = (props) => {
const { hide, integrations, selectedIntegrations, onChange } = props;
const { hide, isMain, integrations, selectedIntegrations, onChange } = props;
const ref = useRef<HTMLDivElement>(null);
const [isLeft, setIsLeft] = useState(false);
const [isRight, setIsRight] = useState(false);
const [selectedAccounts, setSelectedAccounts] =
useState<Integrations[]>(selectedIntegrations);
const [selectedAccounts, setSelectedAccounts] = useStateCallback<
Integrations[]
>(selectedIntegrations.slice(0).map((p) => ({ ...p })));
useEffect(() => {
if (
@ -56,21 +60,36 @@ export const PickPlatforms: FC<{
checkLeftRight();
}, [selectedIntegrations, integrations]);
useMoveToIntegrationListener([integrations], props.singleSelect, (identifier) => {
const findIntegration = integrations.find(
(p) => p.id === identifier
);
useMoveToIntegrationListener(
[integrations],
props.singleSelect,
(identifier) => {
const findIntegration = integrations.find((p) => p.id === identifier);
if (findIntegration) {
addPlatform(findIntegration)();
if (findIntegration) {
addPlatform(findIntegration)();
}
}
});
);
const addPlatform = useCallback(
(integration: Integrations) => async () => {
const promises = [];
if (props.singleSelect) {
onChange([integration]);
setSelectedAccounts([integration]);
promises.push(
new Promise((res) => {
onChange([integration], () => {
res('');
});
})
);
promises.push(
new Promise((res) => {
setSelectedAccounts([integration], () => {
res('');
});
})
);
return;
}
if (selectedAccounts.includes(integration)) {
@ -86,23 +105,83 @@ export const PickPlatforms: FC<{
) {
return;
}
onChange(changedIntegrations);
setSelectedAccounts(changedIntegrations);
promises.push(
new Promise((res) => {
onChange(changedIntegrations, () => {
res('');
});
})
);
promises.push(
new Promise((res) => {
setSelectedAccounts(changedIntegrations, () => {
res('');
});
})
);
} else {
const changedIntegrations = [...selectedAccounts, integration];
onChange(changedIntegrations);
setSelectedAccounts(changedIntegrations);
promises.push(
new Promise((res) => {
onChange(changedIntegrations, () => {
res('');
});
})
);
promises.push(
new Promise((res) => {
setSelectedAccounts(changedIntegrations, () => {
res('');
});
})
);
}
await Promise.all(promises);
},
[selectedAccounts]
);
const handler = useCallback(
async ({ integrationId }: { integrationId: string }) => {
const findIntegration = integrations.find((p) => p.id === integrationId)!;
await addPlatform(findIntegration)();
},
[selectedAccounts, integrations, selectedAccounts]
);
useCopilotReadable({
description: isMain ? 'All available platforms channels' : 'Possible platforms channels to edit',
value: JSON.stringify(integrations),
});
useCopilotAction(
{
name: isMain ? `addOrRemovePlatform` : 'setSelectedIntegration',
description: isMain
? `Add or remove one channel to schedule your post to, always pass the id`
: 'Set selected integration',
parameters: [
{
name: 'integrationId',
type: 'string',
description: 'The id of the integration',
required: true,
},
],
handler,
},
[addPlatform, selectedAccounts, integrations]
);
if (hide) {
return null;
}
return (
<div className={clsx('flex select-none', props.singleSelect && 'gap-[10px]')}>
<div
className={clsx('flex select-none', props.singleSelect && 'gap-[10px]')}
>
{props.singleSelect && (
<div className="flex items-center">
{isLeft && (
@ -138,75 +217,77 @@ export const PickPlatforms: FC<{
>
<div className="innerComponent">
<div className="flex">
{integrations.filter(f => !f.inBetweenSteps).map((integration) =>
!props.singleSelect ? (
<div
key={integration.id}
className="flex gap-[8px] items-center mr-[10px]"
>
{integrations
.filter((f) => !f.inBetweenSteps)
.map((integration) =>
!props.singleSelect ? (
<div
onClick={addPlatform(integration)}
className={clsx(
'cursor-pointer relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth filter transition-all duration-500',
selectedAccounts.findIndex(
(p) => p.id === integration.id
) === -1
? 'opacity-40'
: ''
)}
key={integration.id}
className="flex gap-[8px] items-center mr-[10px]"
>
<img
src={integration.picture}
className="rounded-full"
alt={integration.identifier}
width={32}
height={32}
/>
<Image
src={`/icons/platforms/${integration.identifier}.png`}
className="rounded-full absolute z-10 -bottom-[5px] -right-[5px] border border-fifth"
alt={integration.identifier}
width={20}
height={20}
/>
</div>
</div>
) : (
<div key={integration.id} className="">
<div
onClick={addPlatform(integration)}
className={clsx(
'cursor-pointer rounded-[50px] w-[200px] relative h-[40px] flex justify-center items-center bg-fifth filter transition-all duration-500',
selectedAccounts.findIndex(
(p) => p.id === integration.id
) === -1
? 'bg-third border border-third'
: 'bg-[#291259] border border-[#5826C2]'
)}
>
<div className="flex items-center justify-center gap-[10px]">
<div className="relative">
<img
src={integration.picture}
className="rounded-full"
alt={integration.identifier}
width={24}
height={24}
/>
<Image
src={`/icons/platforms/${integration.identifier}.png`}
className="rounded-full absolute z-10 -bottom-[5px] -right-[5px] border border-fifth"
alt={integration.identifier}
width={15}
height={15}
/>
</div>
<div>{integration.name}</div>
<div
onClick={addPlatform(integration)}
className={clsx(
'cursor-pointer relative w-[34px] h-[34px] rounded-full flex justify-center items-center bg-fifth filter transition-all duration-500',
selectedAccounts.findIndex(
(p) => p.id === integration.id
) === -1
? 'opacity-40'
: ''
)}
>
<img
src={integration.picture}
className="rounded-full"
alt={integration.identifier}
width={32}
height={32}
/>
<Image
src={`/icons/platforms/${integration.identifier}.png`}
className="rounded-full absolute z-10 -bottom-[5px] -right-[5px] border border-fifth"
alt={integration.identifier}
width={20}
height={20}
/>
</div>
</div>
</div>
)
)}
) : (
<div key={integration.id} className="">
<div
onClick={addPlatform(integration)}
className={clsx(
'cursor-pointer rounded-[50px] w-[200px] relative h-[40px] flex justify-center items-center bg-fifth filter transition-all duration-500',
selectedAccounts.findIndex(
(p) => p.id === integration.id
) === -1
? 'bg-third border border-third'
: 'bg-[#291259] border border-[#5826C2]'
)}
>
<div className="flex items-center justify-center gap-[10px]">
<div className="relative">
<img
src={integration.picture}
className="rounded-full"
alt={integration.identifier}
width={24}
height={24}
/>
<Image
src={`/icons/platforms/${integration.identifier}.png`}
className="rounded-full absolute z-10 -bottom-[5px] -right-[5px] border border-fifth"
alt={integration.identifier}
width={15}
height={15}
/>
</div>
<div>{integration.name}</div>
</div>
</div>
</div>
)
)}
</div>
</div>
</div>

View File

@ -60,9 +60,14 @@ export const LaunchesComponent = () => {
}
}, []);
const continueIntegration = useCallback((integration: any) => async () => {
router.push(`/launches?added=${integration.identifier}&continue=${integration.id}`);
}, []);
const continueIntegration = useCallback(
(integration: any) => async () => {
router.push(
`/launches?added=${integration.identifier}&continue=${integration.id}`
);
},
[]
);
useEffect(() => {
if (typeof window !== 'undefined' && window.opener) {
@ -74,6 +79,7 @@ export const LaunchesComponent = () => {
return <LoadingComponent />;
}
// @ts-ignore
return (
<CalendarWeekProvider integrations={sortedIntegrations}>
<div className="flex flex-1 flex-col">
@ -97,7 +103,10 @@ export const LaunchesComponent = () => {
)}
>
{integration.inBetweenSteps && (
<div className="absolute left-0 top-0 w-[39px] h-[46px] cursor-pointer" onClick={continueIntegration(integration)}>
<div
className="absolute left-0 top-0 w-[39px] h-[46px] cursor-pointer"
onClick={continueIntegration(integration)}
>
<div className="bg-red-500 w-[15px] h-[15px] rounded-full -left-[5px] -top-[5px] absolute z-[200] text-[10px] flex justify-center items-center">
!
</div>
@ -148,7 +157,7 @@ export const LaunchesComponent = () => {
))}
</div>
<AddProviderButton update={() => update(true)} />
{sortedIntegrations?.length > 0 && <GeneratorComponent />}
{sortedIntegrations?.length > 0 && user?.tier.ai && <GeneratorComponent />}
</div>
<div className="flex-1 flex flex-col gap-[14px]">
<Filters />

View File

@ -4,6 +4,7 @@ import {PickPlatforms} from "@gitroom/frontend/components/launches/helpers/pick.
import {IntegrationContext} from "@gitroom/frontend/components/launches/helpers/use.integration";
import {ShowAllProviders} from "@gitroom/frontend/components/launches/providers/show.all.providers";
import dayjs from "dayjs";
import { useStateCallback } from '@gitroom/react/helpers/use.state.callback';
export const ProvidersOptions: FC<{
integrations: Integrations[];
@ -11,7 +12,7 @@ export const ProvidersOptions: FC<{
date: dayjs.Dayjs;
}> = (props) => {
const { integrations, editorValue, date } = props;
const [selectedIntegrations, setSelectedIntegrations] = useState([
const [selectedIntegrations, setSelectedIntegrations] = useStateCallback([
integrations[0],
]);
@ -28,6 +29,7 @@ export const ProvidersOptions: FC<{
onChange={setSelectedIntegrations}
singleSelect={true}
hide={integrations.length === 1}
isMain={false}
/>
<IntegrationContext.Provider
value={{ value: editorValue, integration: selectedIntegrations?.[0], date }}

View File

@ -29,6 +29,9 @@ import { postSelector } from '@gitroom/frontend/components/post-url-selector/pos
import { UpDownArrow } from '@gitroom/frontend/components/launches/up.down.arrow';
import { arrayMoveImmutable } from 'array-move';
import { linkedinCompany } from '@gitroom/frontend/components/launches/helpers/linkedin.component';
import { Editor } from '@gitroom/frontend/components/launches/editor';
import { useCopilotAction, useCopilotReadable } from '@copilotkit/react-core';
import { AddPostButton } from '@gitroom/frontend/components/launches/add.post.button';
// Simple component to change back to settings on after changing tab
export const SetTab: FC<{ changeTab: () => void }> = (props) => {
@ -81,6 +84,10 @@ export const withProvider = (
}) => {
const existingData = useExistingData();
const { integration, date } = useIntegration();
useCopilotReadable({
description: integration?.type === 'social' ? 'force content always in MD format' : 'force content always to be fit to social media',
value: ''
});
const [editInPlace, setEditInPlace] = useState(!!existingData.integration);
const [InPlaceValue, setInPlaceValue] = useState<
Array<{
@ -220,6 +227,14 @@ export const withProvider = (
);
}, [props.value, editInPlace]);
useCopilotAction({
name: editInPlace ? 'switchToGlobalEdit' : `editInPlace_${integration?.identifier}`,
description: editInPlace ? 'Switch to global editing' : `Edit only ${integration?.identifier} this, if you want a different identifier, you have to use setSelectedIntegration first`,
handler: async () => {
await changeToEditor();
},
});
// this is a trick to prevent the data from being deleted, yet we don't render the elements
if (!props.show) {
return null;
@ -279,7 +294,8 @@ export const withProvider = (
<div>
<div className="flex gap-[4px]">
<div className="flex-1 text-white editor">
<MDEditor
<Editor
order={index}
height={InPlaceValue.length > 1 ? 200 : 250}
value={val.content}
commands={[
@ -353,26 +369,7 @@ export const withProvider = (
</div>
</div>
<div>
<Button
onClick={addValue(index)}
className="!h-[24px] rounded-[3px] flex gap-[4px] w-[102px] text-[12px] font-[500]"
>
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
>
<path
d="M7 1.3125C5.87512 1.3125 4.7755 1.64607 3.8402 2.27102C2.90489 2.89597 2.17591 3.78423 1.74544 4.82349C1.31496 5.86274 1.20233 7.00631 1.42179 8.10958C1.64124 9.21284 2.18292 10.2263 2.97833 11.0217C3.77374 11.8171 4.78716 12.3588 5.89043 12.5782C6.99369 12.7977 8.13726 12.685 9.17651 12.2546C10.2158 11.8241 11.104 11.0951 11.729 10.1598C12.3539 9.2245 12.6875 8.12488 12.6875 7C12.6859 5.49207 12.0862 4.04636 11.0199 2.98009C9.95365 1.91382 8.50793 1.31409 7 1.3125ZM7 11.8125C6.04818 11.8125 5.11773 11.5303 4.32632 11.0014C3.53491 10.4726 2.91808 9.72103 2.55383 8.84166C2.18959 7.96229 2.09428 6.99466 2.27997 6.06113C2.46566 5.12759 2.92401 4.27009 3.59705 3.59705C4.27009 2.92401 5.1276 2.46566 6.06113 2.27997C6.99466 2.09428 7.9623 2.18958 8.84167 2.55383C9.72104 2.91808 10.4726 3.53491 11.0015 4.32632C11.5303 5.11773 11.8125 6.04818 11.8125 7C11.8111 8.27591 11.3036 9.49915 10.4014 10.4014C9.49915 11.3036 8.27591 11.8111 7 11.8125ZM9.625 7C9.625 7.11603 9.57891 7.22731 9.49686 7.30936C9.41481 7.39141 9.30353 7.4375 9.1875 7.4375H7.4375V9.1875C7.4375 9.30353 7.39141 9.41481 7.30936 9.49686C7.22731 9.57891 7.11603 9.625 7 9.625C6.88397 9.625 6.77269 9.57891 6.69064 9.49686C6.6086 9.41481 6.5625 9.30353 6.5625 9.1875V7.4375H4.8125C4.69647 7.4375 4.58519 7.39141 4.50314 7.30936C4.4211 7.22731 4.375 7.11603 4.375 7C4.375 6.88397 4.4211 6.77269 4.50314 6.69064C4.58519 6.60859 4.69647 6.5625 4.8125 6.5625H6.5625V4.8125C6.5625 4.69647 6.6086 4.58519 6.69064 4.50314C6.77269 4.42109 6.88397 4.375 7 4.375C7.11603 4.375 7.22731 4.42109 7.30936 4.50314C7.39141 4.58519 7.4375 4.69647 7.4375 4.8125V6.5625H9.1875C9.30353 6.5625 9.41481 6.60859 9.49686 6.69064C9.57891 6.77269 9.625 6.88397 9.625 7Z"
fill="white"
/>
</svg>
</div>
<div>Add post</div>
</Button>
<AddPostButton onClick={addValue(index)} num={index} />
</div>
</Fragment>
))}

View File

@ -26,6 +26,7 @@ import { Onboarding } from '@gitroom/frontend/components/onboarding/onboarding';
import { Support } from '@gitroom/frontend/components/layout/support';
import { ContinueProvider } from '@gitroom/frontend/components/layout/continue.provider';
import { isGeneral } from '@gitroom/react/helpers/is.general';
import { CopilotKit } from '@copilotkit/react-core';
dayjs.extend(utc);
dayjs.extend(weekOfYear);
@ -48,38 +49,44 @@ export const LayoutSettings = ({ children }: { children: ReactNode }) => {
return (
<ContextWrapper user={user}>
<MantineWrapper>
<ToolTip />
<ShowMediaBoxModal />
<ShowLinkedinCompany />
<Toaster />
<ShowPostSelector />
<Onboarding />
<Support />
<ContinueProvider />
<div className="min-h-[100vh] w-full max-w-[1440px] mx-auto bg-primary px-[12px] text-white flex flex-col">
<div className="px-[23px] flex h-[80px] items-center justify-between z-[200] sticky top-0 bg-primary">
<Link href="/" className="text-2xl flex items-center gap-[10px]">
<div>
<Image src="/logo.svg" width={55} height={53} alt="Logo" />
<CopilotKit
runtimeUrl={process.env.NEXT_PUBLIC_BACKEND_URL + '/copilot/chat'}
>
<MantineWrapper>
<ToolTip />
<ShowMediaBoxModal />
<ShowLinkedinCompany />
<Toaster />
<ShowPostSelector />
<Onboarding />
<Support />
<ContinueProvider />
<div className="min-h-[100vh] w-full max-w-[1440px] mx-auto bg-primary px-[12px] text-white flex flex-col">
<div className="px-[23px] flex h-[80px] items-center justify-between z-[200] sticky top-0 bg-primary">
<Link href="/" className="text-2xl flex items-center gap-[10px]">
<div>
<Image src="/logo.svg" width={55} height={53} alt="Logo" />
</div>
<div className="mt-[12px]">
{isGeneral() ? 'Postiz' : 'Gitroom'}
</div>
</Link>
{user?.orgId ? <TopMenu /> : <div />}
<div className="flex items-center gap-[8px]">
<SettingsComponent />
<NotificationComponent />
<OrganizationSelector />
</div>
</div>
<div className="flex-1 flex">
<div className="flex-1 rounded-3xl px-[23px] py-[17px] flex flex-col">
<Title />
<div className="flex flex-1 flex-col">{children}</div>
</div>
<div className="mt-[12px]">{isGeneral() ? 'Postiz' : 'Gitroom'}</div>
</Link>
{user?.orgId ? <TopMenu /> : <div />}
<div className="flex items-center gap-[8px]">
<SettingsComponent />
<NotificationComponent />
<OrganizationSelector />
</div>
</div>
<div className="flex-1 flex">
<div className="flex-1 rounded-3xl px-[23px] py-[17px] flex flex-col">
<Title />
<div className="flex flex-1 flex-col">{children}</div>
</div>
</div>
</div>
</MantineWrapper>
</MantineWrapper>
</CopilotKit>
</ContextWrapper>
);
};

View File

@ -1,8 +1,22 @@
'use client';
import { EventEmitter } from 'events';
import { useEffect, useState } from 'react';
export const supportEmitter = new EventEmitter();
export const Support = () => {
if (!process.env.NEXT_PUBLIC_DISCORD_SUPPORT) return null
const [show, setShow] = useState(true);
useEffect(() => {
supportEmitter.on('change', setShow);
return () => {
supportEmitter.off('state', setShow);
}
}, []);
if (!process.env.NEXT_PUBLIC_DISCORD_SUPPORT || !show) return null
return (
<div className="bg-[#612AD5] fixed right-[20px] bottom-[20px] z-[500] p-[20px] text-white rounded-[20px] cursor-pointer" onClick={() => window.open(process.env.NEXT_PUBLIC_DISCORD_SUPPORT)}>Discord Support</div>
<div className="bg-[#612AD5] fixed right-[15px] bottom-[15px] z-[500] p-[20px] text-white rounded-[20px] cursor-pointer" onClick={() => window.open(process.env.NEXT_PUBLIC_DISCORD_SUPPORT)}>Discord Support</div>
)
}

4000
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,10 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.529.1",
"@casl/ability": "^6.5.0",
"@copilotkit/backend": "^0.9.0",
"@copilotkit/react-core": "^0.25.0",
"@copilotkit/react-textarea": "^0.35.0",
"@copilotkit/react-ui": "^0.22.0",
"@hookform/resolvers": "^3.3.4",
"@mantine/core": "^5.10.5",
"@mantine/dates": "^5.10.5",