feat: drag and drop in contextual prompts

This commit is contained in:
legao 2023-07-14 18:08:03 +08:00
parent b51f7f9a25
commit fb98050d9f

View File

@ -42,6 +42,20 @@ import { ModelConfigList } from "./model-config";
import { FileName, Path } from "../constant"; import { FileName, Path } from "../constant";
import { BUILTIN_MASK_STORE } from "../masks"; import { BUILTIN_MASK_STORE } from "../masks";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import {
DragDropContext,
Droppable,
Draggable,
OnDragEndResponder,
} from "@hello-pangea/dnd";
// drag and drop helper function
function reorder<T>(list: T[], startIndex: number, endIndex: number): T[] {
const result = [...list];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
}
export function MaskAvatar(props: { mask: Mask }) { export function MaskAvatar(props: { mask: Mask }) {
return props.mask.avatar !== DEFAULT_MASK_AVATAR ? ( return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
@ -192,6 +206,7 @@ export function MaskConfig(props: {
} }
function ContextPromptItem(props: { function ContextPromptItem(props: {
index: number;
prompt: ChatMessage; prompt: ChatMessage;
update: (prompt: ChatMessage) => void; update: (prompt: ChatMessage) => void;
remove: () => void; remove: () => void;
@ -199,53 +214,62 @@ function ContextPromptItem(props: {
const [focusingInput, setFocusingInput] = useState(false); const [focusingInput, setFocusingInput] = useState(false);
return ( return (
<div className={chatStyle["context-prompt-row"]}> <Draggable draggableId={props.prompt.id} index={props.index}>
{!focusingInput && ( {(provided) => (
<Select <div
value={props.prompt.role} className={chatStyle["context-prompt-row"]}
className={chatStyle["context-role"]} ref={provided.innerRef}
onChange={(e) => {...provided.draggableProps}
props.update({ {...provided.dragHandleProps}
...props.prompt,
role: e.target.value as any,
})
}
> >
{ROLES.map((r) => ( {!focusingInput && (
<option key={r} value={r}> <Select
{r} value={props.prompt.role}
</option> className={chatStyle["context-role"]}
))} onChange={(e) =>
</Select> props.update({
...props.prompt,
role: e.target.value as any,
})
}
>
{ROLES.map((r) => (
<option key={r} value={r}>
{r}
</option>
))}
</Select>
)}
<Input
value={props.prompt.content}
type="text"
className={chatStyle["context-content"]}
rows={focusingInput ? 5 : 1}
onFocus={() => setFocusingInput(true)}
onBlur={() => {
setFocusingInput(false);
// If the selection is not removed when the user loses focus, some
// extensions like "Translate" will always display a floating bar
window?.getSelection()?.removeAllRanges();
}}
onInput={(e) =>
props.update({
...props.prompt,
content: e.currentTarget.value as any,
})
}
/>
{!focusingInput && (
<IconButton
icon={<DeleteIcon />}
className={chatStyle["context-delete-button"]}
onClick={() => props.remove()}
bordered
/>
)}
</div>
)} )}
<Input </Draggable>
value={props.prompt.content}
type="text"
className={chatStyle["context-content"]}
rows={focusingInput ? 5 : 1}
onFocus={() => setFocusingInput(true)}
onBlur={() => {
setFocusingInput(false);
// If the selection is not removed when the user loses focus, some
// extensions like "Translate" will always display a floating bar
window?.getSelection()?.removeAllRanges();
}}
onInput={(e) =>
props.update({
...props.prompt,
content: e.currentTarget.value as any,
})
}
/>
{!focusingInput && (
<IconButton
icon={<DeleteIcon />}
className={chatStyle["context-delete-button"]}
onClick={() => props.remove()}
bordered
/>
)}
</div>
); );
} }
@ -267,17 +291,41 @@ export function ContextPrompts(props: {
props.updateContext((context) => (context[i] = prompt)); props.updateContext((context) => (context[i] = prompt));
}; };
const onDragEnd: OnDragEndResponder = (result) => {
if (!result.destination) {
return;
}
const newContext = reorder(
context,
result.source.index,
result.destination.index,
);
props.updateContext((context) => {
context.splice(0, context.length, ...newContext);
});
};
return ( return (
<> <>
<div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}> <div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
{context.map((c, i) => ( <DragDropContext onDragEnd={onDragEnd}>
<ContextPromptItem <Droppable droppableId="context-prompt-list">
key={i} {(provided) => (
prompt={c} <div ref={provided.innerRef} {...provided.droppableProps}>
update={(prompt) => updateContextPrompt(i, prompt)} {context.map((c, i) => (
remove={() => removeContextPrompt(i)} <ContextPromptItem
/> index={i}
))} key={c.id}
prompt={c}
update={(prompt) => updateContextPrompt(i, prompt)}
remove={() => removeContextPrompt(i)}
/>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<div className={chatStyle["context-prompt-row"]}> <div className={chatStyle["context-prompt-row"]}>
<IconButton <IconButton