feat: chat presets (experimental)
This commit is contained in:
154
frontend/src/pages/PresetsManager/MessagesEditor.tsx
Normal file
154
frontend/src/pages/PresetsManager/MessagesEditor.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||
import commonStore from '../../stores/commonStore';
|
||||
import { Preset } from './PresetsButton';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Button, Card, Dropdown, Option, Textarea } from '@fluentui/react-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolTipButton } from '../../components/ToolTipButton';
|
||||
import { Delete20Regular, ReOrderDotsVertical20Regular } from '@fluentui/react-icons';
|
||||
import { ConversationMessage, Role } from '../Chat';
|
||||
|
||||
type Item = {
|
||||
id: string;
|
||||
role: Role;
|
||||
content: string;
|
||||
}
|
||||
|
||||
const getItems = (messages: ConversationMessage[]) =>
|
||||
messages.map((message, index) => ({
|
||||
id: uuid(),
|
||||
role: message.role,
|
||||
content: message.content
|
||||
})) as Item[];
|
||||
|
||||
const reorder = (list: Item[], startIndex: number, endIndex: number) => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const MessagesEditor: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const editingPreset = commonStore.editingPreset!;
|
||||
const setEditingPreset = (newParams: Partial<Preset>) => {
|
||||
commonStore.setEditingPreset({
|
||||
...editingPreset,
|
||||
...newParams
|
||||
});
|
||||
};
|
||||
|
||||
const [items, setItems] = useState(getItems(editingPreset.messages));
|
||||
|
||||
const updateItems = (items: Item[]) => {
|
||||
setEditingPreset({
|
||||
messages: items.map(item => ({
|
||||
role: item.role,
|
||||
content: item.content
|
||||
}))
|
||||
});
|
||||
setItems(items);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
if (!result.destination) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newItems = reorder(
|
||||
items,
|
||||
result.source.index,
|
||||
result.destination.index
|
||||
);
|
||||
|
||||
updateItems(newItems);
|
||||
};
|
||||
|
||||
const createNewItem = () => {
|
||||
const newItems: Item[] = [...items, {
|
||||
id: uuid(),
|
||||
role: 'assistant',
|
||||
content: ''
|
||||
}];
|
||||
updateItems(newItems);
|
||||
};
|
||||
|
||||
const deleteItem = (id: string) => {
|
||||
const newItems: Item[] = items.filter(item => item.id !== id);
|
||||
updateItems(newItems);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-2 overflow-hidden">
|
||||
<Button style={{ width: '100%' }} onClick={createNewItem}>{t('New')}</Button>
|
||||
<div className="overflow-x-hidden overflow-y-auto p-2">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId="droppable">
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<Draggable key={item.id} draggableId={item.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
style={provided.draggableProps.style}
|
||||
className="select-none mb-2"
|
||||
>
|
||||
<div className="flex">
|
||||
<Card appearance="outline"
|
||||
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}>
|
||||
<ReOrderDotsVertical20Regular />
|
||||
</Card>
|
||||
<Dropdown style={{ minWidth: 0, borderRadius: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value={t(item.role)!}
|
||||
selectedOptions={[item.role]}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
items[index] = {
|
||||
...item,
|
||||
role: data.optionValue as Role
|
||||
};
|
||||
updateItems([...items]);
|
||||
}
|
||||
}}>
|
||||
<Option value="user">{t('user')!}</Option>
|
||||
<Option value="assistant">{t('assistant')!}</Option>
|
||||
<Option value="system">{t('system')!}</Option>
|
||||
</Dropdown>
|
||||
<Textarea resize="vertical" className="grow" value={item.content}
|
||||
style={{ minWidth: 0, borderRadius: 0 }}
|
||||
onChange={(e, data) => {
|
||||
items[index] = {
|
||||
...item,
|
||||
content: data.value
|
||||
};
|
||||
updateItems([...items]);
|
||||
}}></Textarea>
|
||||
<ToolTipButton
|
||||
style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }} desc={t('Delete')}
|
||||
icon={<Delete20Regular />} onClick={() => {
|
||||
deleteItem(item.id);
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user