Skip to content

Commit

Permalink
Fixing linter complains
Browse files Browse the repository at this point in the history
  • Loading branch information
jespino committed Jan 10, 2025
1 parent 63f67e6 commit d842868
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 99 deletions.
9 changes: 4 additions & 5 deletions webapp/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,16 +816,15 @@ export async function getTeamTopPlaybooks(timeRange: string, page: number, perPa
return data as InsightsResponse;
}


export async function generateStatusUpdate(playbookRunID: string, botId: string, instructions: string[], messages: string[]) {
const url = `${playbookRunRoute(playbookRunID)}/generate_status`;
const response = await fetch(url, Client4.getOptions({
method: 'POST',
body: JSON.stringify({
instructions,
messages,
bot: botId,
})
instructions,
messages,
bot: botId,
}),
}));

if (response.ok) {
Expand Down
119 changes: 67 additions & 52 deletions webapp/src/components/modals/ai_modal.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { WebSocketMessage } from '@mattermost/client';
import React, { useEffect, useState, useCallback, useRef } from 'react';
import ReactDOM from 'react-dom';
import {WebSocketMessage} from '@mattermost/client';
import React, {
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import styled from 'styled-components';
import { createGlobalStyle } from "styled-components";
import { FormattedMessage, useIntl } from 'react-intl';
import {FormattedMessage, useIntl} from 'react-intl';

import IconAI from 'src/components/assets/icons/ai';
import GenericModal from 'src/components/widgets/generic_modal';
import { Textbox } from 'src/webapp_globals';
import {Textbox} from 'src/webapp_globals';

import { generateStatusUpdate } from 'src/client';
import { useAIAvailableBots, useBotSelector, useBotsLoaderHook } from 'src/ai_integration';
import {generateStatusUpdate} from 'src/client';
import {useBotSelector, useBotsLoaderHook} from 'src/ai_integration';

import postEventListener, { PostUpdateWebsocketMessage } from 'src/websocket';
import postEventListener, {PostUpdateWebsocketMessage} from 'src/websocket';

type Version = {
instruction: string
Expand Down Expand Up @@ -44,22 +48,22 @@ const StyledAIModal = styled(GenericModal)`
}
`;

const AIModal = ({ playbookRunId, onAccept, onClose, isOpen }: Props) => {
const AIModal = ({playbookRunId, onAccept, onClose, isOpen}: Props) => {
const intl = useIntl();
const [copied, setCopied] = useState(false);
const [instruction, setInstruction] = useState('');
const suggestionBox = useRef<HTMLDivElement>()
const suggestionBox = useRef<HTMLDivElement>();
const BotSelector = useBotSelector() as any;
const useBotlist = useBotsLoaderHook() as any;
const { bots, activeBot, setActiveBot } = useBotlist()
const {bots, activeBot, setActiveBot} = useBotlist();
const [currentVersion, setCurrentVersion] = useState<number>(0);
const [versions, setVersions] = useState<Version[]>([]);
const [generating, setGenerating] = useState<any>(null);

useEffect(() => {
if (activeBot?.id && isOpen) {
setCurrentVersion(versions.length + 1)
setVersions([...versions, { instruction: '', value: '', prevValue: '' }])
setCurrentVersion(versions.length + 1);
setVersions([...versions, {instruction: '', value: '', prevValue: ''}]);
setGenerating(true);
generateStatusUpdate(playbookRunId, activeBot.id, [], []);
}
Expand All @@ -71,10 +75,10 @@ const AIModal = ({ playbookRunId, onAccept, onClose, isOpen }: Props) => {
const data = msg.data;
if (!data.control) {
setGenerating(true);
const newVersions = [...versions]
newVersions[versions.length - 1] = { ...newVersions[versions.length - 1], value: data.next }
setVersions(newVersions)
setTimeout(() => suggestionBox.current?.scrollTo(0, suggestionBox.current?.scrollHeight), 0)
const newVersions = [...versions];
newVersions[versions.length - 1] = {...newVersions[versions.length - 1], value: data.next};
setVersions(newVersions);
setTimeout(() => suggestionBox.current?.scrollTo(0, suggestionBox.current?.scrollHeight), 0);
} else if (data.control === 'end') {
setGenerating(false);
}
Expand All @@ -86,44 +90,44 @@ const AIModal = ({ playbookRunId, onAccept, onClose, isOpen }: Props) => {
}, [generating]);

const onBotChange = useCallback((bot: any) => {
setActiveBot(bot)
setCurrentVersion(versions.length + 1)
setVersions([...versions, { instruction: '', value: '', prevValue: '' }])
setActiveBot(bot);
setCurrentVersion(versions.length + 1);
setVersions([...versions, {instruction: '', value: '', prevValue: ''}]);
setGenerating(true);
generateStatusUpdate(playbookRunId, bot.id, [], []);
}, [versions, playbookRunId])
}, [versions, playbookRunId]);

const regenerate = useCallback(() => {
setGenerating(true);
generateStatusUpdate(playbookRunId, activeBot?.id, [versions[currentVersion - 1].instruction], [versions[currentVersion - 1].prevValue]);
setCurrentVersion(versions.length + 1)
setVersions([...versions, { ...versions[currentVersion - 1], value: '' }])
setCurrentVersion(versions.length + 1);
setVersions([...versions, {...versions[currentVersion - 1], value: ''}]);
}, [versions, playbookRunId, instruction, versions, currentVersion, activeBot?.id]);

const copyText = useCallback(() => {
navigator.clipboard.writeText(versions[currentVersion - 1].value);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
}, [versions, currentVersion])
}, [versions, currentVersion]);

const onInputEnter = useCallback((e: React.KeyboardEvent) => {
// Detect hitting enter and run the generateStatusUpdate
if (e.key === 'Enter') {
generateStatusUpdate(playbookRunId, activeBot?.id, [instruction], [versions[currentVersion - 1].value]);
setVersions([...versions, { instruction, prevValue: versions[currentVersion - 1].value, value: '' }])
setCurrentVersion(versions.length + 1)
setInstruction('')
setVersions([...versions, {instruction, prevValue: versions[currentVersion - 1].value, value: ''}]);
setCurrentVersion(versions.length + 1);
setInstruction('');
setGenerating(true);
setTimeout(() => suggestionBox.current?.scrollTo(0, suggestionBox.current?.scrollHeight), 0)
setTimeout(() => suggestionBox.current?.scrollTo(0, suggestionBox.current?.scrollHeight), 0);
}
}, [versions, instruction, playbookRunId, versions, currentVersion, activeBot?.id])
}, [versions, instruction, playbookRunId, versions, currentVersion, activeBot?.id]);

const stopGenerating = useCallback(() => {
setGenerating(false);
}, [])
}, []);

if (!activeBot?.id) {
return null
return null;
}

if (!isOpen) {
Expand All @@ -140,12 +144,21 @@ const AIModal = ({ playbookRunId, onAccept, onClose, isOpen }: Props) => {
<AIModalContainer>
<TopBar>
<Versions>
<IconButton onClick={() => setCurrentVersion(currentVersion - 1)} className={currentVersion === 1 ? 'disabled' : ''}>
<i className={'icon icon-10 icon-chevron-left'} />
<IconButton
onClick={() => setCurrentVersion(currentVersion - 1)}
className={currentVersion === 1 ? 'disabled' : ''}
>
<i className={'icon icon-10 icon-chevron-left'}/>
</IconButton>
<FormattedMessage defaultMessage='version {number} of {total}' values={{ number: currentVersion, total: versions.length }} />
<IconButton onClick={() => setCurrentVersion(currentVersion + 1)} className={currentVersion === versions.length ? 'disabled' : ''}>
<i className={'icon icon-10 icon-chevron-right'} />
<FormattedMessage
defaultMessage='version {number} of {total}'
values={{number: currentVersion, total: versions.length}}
/>
<IconButton
onClick={() => setCurrentVersion(currentVersion + 1)}
className={currentVersion === versions.length ? 'disabled' : ''}
>
<i className={'icon icon-10 icon-chevron-right'}/>
</IconButton>
</Versions>
<BotSelector
Expand All @@ -155,22 +168,25 @@ const AIModal = ({ playbookRunId, onAccept, onClose, isOpen }: Props) => {
/>
</TopBar>
<AssistantMessageBox ref={suggestionBox}>
<Textbox value={versions[currentVersion - 1]?.value || ''} preview={true} />
<Textbox
value={versions[currentVersion - 1]?.value || ''}
preview={true}
/>
</AssistantMessageBox>

{generating &&
<StopGeneratingButton onClick={stopGenerating}>
<i className='icon icon-12 icon-cancel' />
<FormattedMessage defaultMessage='Stop generating' />
<i className='icon icon-12 icon-cancel'/>
<FormattedMessage defaultMessage='Stop generating'/>
</StopGeneratingButton>
}
{!generating &&
<AIModalFooter>
<IconButton onClick={copyText}>
<i className='icon icon-content-copy' />
<i className='icon icon-content-copy'/>
</IconButton>
<IconButton onClick={regenerate}>
<i className='icon icon-refresh' />
<i className='icon icon-refresh'/>
</IconButton>
<InsertButton onClick={() => onAccept(versions[currentVersion - 1].value)}>
<i className='icon icon-10 icon-check'/>
Expand All @@ -182,9 +198,9 @@ const AIModal = ({ playbookRunId, onAccept, onClose, isOpen }: Props) => {
</AIModalFooter>
}
<ExtraInstructionsInput>
<IconAI />
<IconAI/>
<input
placeholder={intl.formatMessage({ defaultMessage: 'What would you like AI to do next?' })}
placeholder={intl.formatMessage({defaultMessage: 'What would you like AI to do next?'})}
onChange={(e) => setInstruction(e.target.value)}
value={instruction}
onKeyUp={onInputEnter}
Expand Down Expand Up @@ -228,7 +244,7 @@ const IconButton = styled.span`
pointer-events: none;
cursor: not-allowed;
}
`
`;

const StopGeneratingButton = styled.button`
display: inline-flex;
Expand Down Expand Up @@ -290,7 +306,6 @@ const AIModalFooter = styled.div`
gap: 4px;
`;


const ExtraInstructionsInput = styled.div`
display: flex;
width: 100%;
Expand Down Expand Up @@ -320,7 +335,7 @@ const ExtraInstructionsInput = styled.div`
svg {
color: var(--center-channel-color-64);
}
`
`;

const InsertButton = styled.button`
display: inline-block;
Expand All @@ -344,7 +359,7 @@ const InsertButton = styled.button`
&:focus {
outline: none;
}
`
`;

const AssistantMessageBox = styled.div`
max-height: 200px;
Expand All @@ -355,7 +370,7 @@ const AssistantMessageBox = styled.div`
box-shadow: none;
height: 100%;
}
`
`;

const Copied = styled.span<{ copied: boolean }>`
color: var(--online-indicator);
Expand All @@ -364,14 +379,14 @@ const Copied = styled.span<{ copied: boolean }>`
font-size: 11px;
transition: opacity 1s;
opacity: ${(props) => (props.copied ? 1 : 0)};
`
`;

const TopBar = styled.div`
display: flex;
justify-content: space-between;
padding-bottom: 12px;
margin-right: 24px;
`
`;

const Versions = styled.div`
display: flex;
Expand All @@ -386,6 +401,6 @@ const Versions = styled.div`
color: var(--center-channel-color-64);
pointer-events: none;
}
`
`;

export default AIModal;
63 changes: 35 additions & 28 deletions webapp/src/components/modals/update_run_status_modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ import {GlobalState} from '@mattermost/types/store';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
import {getChannel} from 'mattermost-redux/selectors/entities/channels';

import {TertiaryButton, QuaternaryButton} from 'src/components/assets/buttons';

import {ApolloProvider, useQuery} from '@apollo/client';

import {QuaternaryButton, TertiaryButton} from 'src/components/assets/buttons';

import GenericModal, {Description, Label} from 'src/components/widgets/generic_modal';
import UnsavedChangesModal from 'src/components/widgets/unsaved_changes_modal';
import IconAI from 'src/components/assets/icons/ai';
Expand Down Expand Up @@ -267,36 +267,43 @@ const UpdateRunStatusModal = ({
<FormContainer>
<Description data-testid='update_run_status_description'>{description()}</Description>
<LastChangeSince disabled={aiModalOpen}>
<Label>
{formatMessage({defaultMessage: 'Change since last update'})}
</Label>
{ aiAvailable && aiAvailableBots.length > 0 &&
<Label>
{formatMessage({defaultMessage: 'Change since last update'})}
</Label>
{ aiAvailable && aiAvailableBots.length > 0 &&
(aiModalOpen ? (
<TertiaryButton onClick={() => {
setAIModalOpen(true);
}}>
<IconAI size={14}/>
<FormattedMessage defaultMessage="Generate update with AI"/>
</TertiaryButton>
<TertiaryButton
onClick={() => {
setAIModalOpen(true);
}}
>
<IconAI size={14}/>
<FormattedMessage defaultMessage='Generate update with AI'/>
</TertiaryButton>
) : (
<QuaternaryButton onClick={() => {
setAIModalOpen(true);
}}>
<IconAI size={14}/>
<FormattedMessage defaultMessage="Generate update with AI"/>
</QuaternaryButton>
<QuaternaryButton
onClick={() => {
setAIModalOpen(true);
}}
>
<IconAI size={14}/>
<FormattedMessage defaultMessage='Generate update with AI'/>
</QuaternaryButton>
))
}
}
</LastChangeSince>
{ aiAvailable &&
<AiModalContainer>
<AiModalContainer>
<AIModal
playbookRunId={playbookRunId}
onAccept={(text) => { setMessage(text); setAIModalOpen(false); }}
onClose={() => setAIModalOpen(false)}
isOpen={aiModalOpen}
playbookRunId={playbookRunId}
onAccept={(text) => {
setMessage(text);
setAIModalOpen(false);
}}
onClose={() => setAIModalOpen(false)}
isOpen={aiModalOpen}
/>
</AiModalContainer>
</AiModalContainer>
}
<MarkdownTextbox
id='update_run_status_textbox'
Expand Down Expand Up @@ -554,10 +561,10 @@ const StyledCheckboxInput = styled(CheckboxInput)`
}
`;

const AiModalContainer = styled.div`
const AiModalContainer = styled.div`
position: relative;
box-shadow: var(--elevation-6);
`
`;

const LastChangeSince = styled.div`
display: flex;
Expand All @@ -572,7 +579,7 @@ const LastChangeSince = styled.div`
padding: 0 10px;
font-size: 12px;
}
`
`;

const ApolloWrappedModal = (props: Props) => {
const client = getPlaybooksGraphQLClient();
Expand Down
Loading

0 comments on commit d842868

Please sign in to comment.