Skip to content

Commit 463b85f

Browse files
committed
feat: change prompt modal
1 parent 354178c commit 463b85f

File tree

4 files changed

+119
-56
lines changed

4 files changed

+119
-56
lines changed

src/client/components/ChatV2/PromptModal.tsx

Lines changed: 107 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
import { PUBLIC_URL } from '@config'
2-
import { ContentCopyOutlined, Visibility, VisibilityOff } from '@mui/icons-material'
2+
import { ContentCopyOutlined, InfoOutlined, EditOutlined, Close } from '@mui/icons-material'
33
import DeleteOutline from '@mui/icons-material/DeleteOutline'
4-
import { Box, Dialog, Divider, IconButton, Link, MenuItem, Stack, Tab, Tabs, Tooltip, Typography } from '@mui/material'
4+
import { Box, Dialog, Typography } from '@mui/material'
55
import { enqueueSnackbar } from 'notistack'
66
import { useState } from 'react'
77
import { useTranslation } from 'react-i18next'
8-
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom'
8+
import { useNavigate, useParams } from 'react-router-dom'
99
import useCourse from '../../hooks/useCourse'
1010
import useCurrentUser from '../../hooks/useCurrentUser'
1111
import type { Prompt as PromptType } from '../../types'
1212
import { PromptEditor } from '../Prompt/PromptEditor'
1313
import { OutlineButtonBlack, TextButton } from './general/Buttons'
1414
import { usePromptState } from './PromptState'
15+
import { PromptInfoContent } from '../Prompt/PromptInfoContent'
16+
import { Tab, Tabs, IconButton } from '@mui/material'
1517

1618
const PromptModal = () => {
1719
const { activePrompt, handleChangePrompt, coursePrompts, myPrompts, deletePromptMutation } = usePromptState()
@@ -20,6 +22,8 @@ const PromptModal = () => {
2022
const { t } = useTranslation()
2123
const [createNewOpen, setCreateNewOpen] = useState(false)
2224
const [tab, setTab] = useState(0)
25+
const [infoModalOpen, setInfoModalOpen] = useState(false)
26+
const [infoModalPrompt, setInfoModalPrompt] = useState<PromptType | undefined>()
2327

2428
const { user } = useCurrentUser()
2529
const { data: chatInstance } = useCourse(courseId)
@@ -64,64 +68,81 @@ const PromptModal = () => {
6468
handleChangePrompt(undefined)
6569
}
6670

71+
const handleShowInfo = (event: React.MouseEvent<HTMLButtonElement>, prompt: PromptType) => {
72+
event.stopPropagation()
73+
setInfoModalPrompt(prompt)
74+
setInfoModalOpen(true)
75+
}
76+
6777
const canCreatePrompt = courseId === 'general' || tab === 1 || amongResponsibles
6878

6979
const renderPromptItem = (prompt: PromptType, isPersonal: boolean) => (
70-
<MenuItem
80+
<Box
7181
key={prompt.id}
72-
data-testid="pick-prompt-button"
73-
selected={prompt.id === activePrompt?.id}
7482
onClick={() => handleSelect(prompt)}
7583
sx={{
76-
borderRadius: '1.25rem',
7784
display: 'flex',
7885
alignItems: 'center',
79-
gap: 1,
86+
justifyContent: 'space-between',
87+
p: 2,
88+
cursor: 'pointer',
89+
backgroundColor: prompt.id === activePrompt?.id ? 'action.selected' : 'transparent',
90+
'&:hover': {
91+
backgroundColor: 'action.hover',
92+
},
93+
borderBottom: '1px solid',
94+
borderColor: 'divider',
8095
}}
96+
data-testid={`prompt-row-${prompt.name}`}
8197
>
82-
<Box sx={{ flexGrow: 1 }}>{prompt.name}</Box>
83-
{!isPersonal && (
84-
<>
85-
{prompt.hidden ? (
86-
<Tooltip title={t('hiddenPromptInfo')}>
87-
<VisibilityOff />
88-
</Tooltip>
89-
) : (
90-
<Tooltip title={t('visiblePromptInfo')}>
91-
<Visibility />
92-
</Tooltip>
93-
)}
94-
<Link component={RouterLink} to={`/${courseId}?promptId=${prompt.id}`} variant="caption" onClick={(e) => e.stopPropagation()}>
95-
{t('course:directPromptLink', { name: prompt.name })}
96-
</Link>
97-
<TextButton onClick={(e) => handleCopyLink(e, prompt.id)} sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
98-
<ContentCopyOutlined fontSize="small" />
99-
{t('copyStudentLink')}
100-
</TextButton>
101-
</>
102-
)}
103-
104-
{(isPersonal || amongResponsibles) && (
105-
<>
106-
<TextButton onClick={(e) => handleEdit(e, prompt)} color="primary" data-testid={`edit-prompt-${prompt.name}`} aria-label={t('common:edit')}>
98+
<Typography>{prompt.name}</Typography>
99+
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
100+
<TextButton
101+
startIcon={<InfoOutlined fontSize="small" />}
102+
onClick={(e) => handleShowInfo(e, prompt)}
103+
sx={{ minWidth: 'auto' }}
104+
>
105+
{t('common:info')}
106+
</TextButton>
107+
{(isPersonal || amongResponsibles) && (
108+
<TextButton
109+
startIcon={<EditOutlined fontSize="small" />}
110+
onClick={(e) => handleEdit(e, prompt)}
111+
color="primary"
112+
data-testid={`edit-prompt-${prompt.name}`}
113+
sx={{ minWidth: 'auto' }}
114+
>
107115
{t('common:edit')}
108116
</TextButton>
109-
110-
<IconButton
117+
)}
118+
{!isPersonal && (
119+
<TextButton
120+
startIcon={<ContentCopyOutlined fontSize="small" />}
121+
onClick={(e) => handleCopyLink(e, prompt.id)}
122+
sx={{ minWidth: 'auto' }}
123+
>
124+
{t('common:link')}
125+
</TextButton>
126+
)}
127+
{(isPersonal || amongResponsibles) && (
128+
<TextButton
129+
startIcon={<DeleteOutline fontSize="small" />}
111130
onClick={(event) => handleDelete(event, prompt)}
112-
size="small"
113-
aria-label={t('common:delete')}
131+
color="error"
114132
data-testid={`delete-prompt-${prompt.name}`}
133+
sx={{ minWidth: 'auto' }}
115134
>
116-
<DeleteOutline fontSize="small" />
117-
</IconButton>
118-
</>
119-
)}
120-
</MenuItem>
135+
{t('common:delete')}
136+
</TextButton>
137+
)}
138+
</Box>
139+
</Box>
121140
)
122141

123142
const renderPromptList = (prompts: PromptType[], isPersonal: boolean) => (
124-
<Box>{prompts.length > 0 && <Stack divider={<Divider flexItem />}>{prompts.map((prompt) => renderPromptItem(prompt, isPersonal))}</Stack>}</Box>
143+
<Box>
144+
{prompts.map((prompt) => renderPromptItem(prompt, isPersonal))}
145+
</Box>
125146
)
126147

127148
return (
@@ -142,32 +163,62 @@ const PromptModal = () => {
142163
<Tab label={t('settings:myPrompts')} sx={{ '&.Mui-selected': { fontWeight: 'bold' } }} />
143164
</Tabs>
144165
<Box sx={{ display: 'flex', flex: 1, gap: 2, overflow: 'hidden', mt: 2 }}>
145-
<Box sx={{ flex: '1', overflowY: 'auto', paddingRight: 1 }}>
166+
<Box sx={{ flex: '1', overflowY: 'auto' }}>
146167
{canCreatePrompt && (
147-
<>
148-
<OutlineButtonBlack data-testid="create-prompt-button" sx={{ mb: 2 }} onClick={handleCreateNew}>
149-
{t('settings:saveNewPrompt')}
150-
</OutlineButtonBlack>
151-
<MenuItem selected={activePrompt === undefined} sx={{ borderRadius: '1.25rem', py: 1.5 }} onClick={() => handleSelect(undefined)}>
152-
{t('settings:noPrompt')}
153-
</MenuItem>
154-
<Divider sx={{ my: 1 }} />
155-
</>
168+
<TextButton
169+
data-testid="create-prompt-button"
170+
sx={{ mb: 2 }}
171+
onClick={handleCreateNew}
172+
startIcon={<Typography sx={{ fontSize: '1.5rem', lineHeight: 1 }}>+</Typography>}
173+
>
174+
{t('settings:saveNewPrompt')}
175+
</TextButton>
156176
)}
157177

158-
{courseId !== 'general' && tab === 0 && <>{renderPromptList(coursePrompts, false)}</>}
178+
{courseId !== 'general' && tab === 0 && coursePrompts.length > 0 && renderPromptList(coursePrompts, false)}
159179

160-
{(courseId === 'general' || tab === 1) && <>{renderPromptList(myPrompts, true)}</>}
180+
{(courseId === 'general' || tab === 1) && myPrompts.length > 0 && renderPromptList(myPrompts, true)}
161181

162-
{myPrompts.length === 0 && coursePrompts.length === 0 && <MenuItem disabled>{t('settings:noPrompts')}</MenuItem>}
182+
{myPrompts.length === 0 && coursePrompts.length === 0 && (
183+
<Box sx={{ p: 3, textAlign: 'center', color: 'text.secondary' }}>
184+
<Typography>{t('settings:noPrompts')}</Typography>
185+
</Box>
186+
)}
163187
</Box>
188+
164189
<Dialog fullWidth open={createNewOpen} onClose={(_event, reason) => {
165190
if (reason === 'backdropClick') return
166191
setCreateNewOpen(false)
167192
}}>
168193
{courseId !== 'general' && tab === 0 && <PromptEditor back={`/${courseId}/prompts`} setEditorOpen={setCreateNewOpen} />}
169194
{(courseId === 'general' || tab === 1) && <PromptEditor back={`/${courseId}/prompts`} setEditorOpen={setCreateNewOpen} personal />}
170195
</Dialog>
196+
197+
<Dialog
198+
fullWidth
199+
maxWidth="md"
200+
open={infoModalOpen}
201+
onClose={() => setInfoModalOpen(false)}
202+
>
203+
<Box sx={{ position: 'relative', p: 3 }}>
204+
<IconButton
205+
onClick={() => setInfoModalOpen(false)}
206+
sx={{ position: 'absolute', top: 16, right: 16, zIndex: 1 }}
207+
data-testid="close-info-modal"
208+
>
209+
<Close />
210+
</IconButton>
211+
{infoModalPrompt && (
212+
<PromptInfoContent
213+
name={infoModalPrompt.name}
214+
userInstructions={infoModalPrompt.userInstructions ?? ''}
215+
systemMessage={infoModalPrompt.systemMessage}
216+
hidden={infoModalPrompt.hidden}
217+
type={infoModalPrompt.type}
218+
/>
219+
)}
220+
</Box>
221+
</Dialog>
171222
</Box>
172223
</Box>
173224
)

src/client/locales/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"copy": "Copy",
4040
"tryNew": "Try the new chat",
4141
"close": "Close",
42+
"info": "Info",
43+
"showInfo": "Show info",
44+
"link": "Link",
4245
"useOld": "Old chat view",
4346
"iframeCopy": "Copy embeddable iframe element:\n\n{{- iframeHtml}}",
4447
"model": "Language model",
@@ -456,6 +459,7 @@
456459
"defaultChatInstructionsVisible": "You are interacting with a chat interface that has a pre-defined system prompt. This means the AI has been given a specific role, personality, or set of rules to follow.\n\nHow to start:\n- Simply type \"Hello\" or ask a question to see how it responds.\n- The AI will adhere to its instructions while chatting with you. You can review these instructions in the 'AI Instructions' tab.",
457460
"studentViewInfoAlert": "This view is visible to students in the \"Prompt Information\" section.",
458461
"promptName": "Prompt name",
462+
"name": "Name",
459463
"promptInstructions": "User instructions",
460464
"systemMessageLabel": "System message",
461465
"noSystemMessage": "No system message",

src/client/locales/fi.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
"curreTitle": "Curre",
4141
"tryNew": "Kokeile uutta chattiä",
4242
"close": "Sulje",
43+
"info": "Info",
44+
"showInfo": "Näytä tiedot",
45+
"link": "Linkki",
4346
"useOld": "Vanha chattinäkymä",
4447
"iframeCopy": "Kopioi upotettava iframe-elementti:\n\n{{- iframeHtml}}",
4548
"model": "Kielimalli",
@@ -458,6 +461,7 @@
458461
"defaultChatInstructionsVisible": "Olet vuorovaikutuksessa chat-käyttöliittymän kanssa, jossa on ennalta määritetty järjestelmäkehote. Tämä tarkoittaa, että tekoälylle on annettu tietty rooli, persoonallisuus tai säännöt, joita seurata.\n\nKuinka aloittaa:\n- Kirjoita yksinkertaisesti \"Moi\" tai kysy kysymys nähdäksesi, miten se vastaa.\n- Tekoäly noudattaa ohjeitaan jutellessaan kanssasi. Voit tutustua näihin ohjeisiin 'Tekoälyn ohjeistus' -välilehdellä.",
459462
"studentViewInfoAlert": "Tämä näkymä näkyy opiskelijoille kohdassa \"Alustuksen tiedot\".",
460463
"promptName": "Alustuksen nimi",
464+
"name": "Nimi",
461465
"promptInstructions": "Alustuksen käyttöohjeet",
462466
"systemMessageLabel": "Kielimallille annettava ohjeistus",
463467
"noSystemMessage": "Ei kielimallin ohjeita",

src/client/locales/sv.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
"curreTitle": "Curre",
4141
"tryNew": "Prova den nya chattappen",
4242
"close": "Stänga",
43+
"info": "Info",
44+
"showInfo": "Visa info",
45+
"link": "Länk",
4346
"loading": "Belastning",
4447
"added": "Tillagd",
4548
"fileType": "Filtyp",
@@ -429,6 +432,7 @@
429432
"defaultChatInstructionsVisible": "Du interagerar med ett chattgränssnitt som har en fördefinierad systemprompt. Detta innebär att AI:n har tilldelats en specifik roll, personlighet eller uppsättning regler att följa.\n\nHur du börjar:\n- Skriv helt enkelt \"Hej\" eller ställ en fråga för att se hur den svarar.\n- AI:n kommer att följa sina instruktioner medan den chattar med dig. Du kan granska dessa instruktioner på fliken 'AI-instruktioner'.",
430433
"studentViewInfoAlert": "Denna vy visas för studenter under avsnittet \"Promptinformation\".",
431434
"promptName": "Promptnamn",
435+
"name": "Namn",
432436
"promptInstructions": "Användarinstruktioner",
433437
"systemMessageLabel": "Systemmeddelande",
434438
"noSystemMessage": "Inget systemmeddelande",

0 commit comments

Comments
 (0)