mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	Merge pull request #2388 from yikayiyo/feat-drag-prompts
feat: #2297 drag and drop in contextual prompts
This commit is contained in:
		@@ -42,6 +42,20 @@ import { ModelConfigList } from "./model-config";
 | 
			
		||||
import { FileName, Path } from "../constant";
 | 
			
		||||
import { BUILTIN_MASK_STORE } from "../masks";
 | 
			
		||||
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 }) {
 | 
			
		||||
  return props.mask.avatar !== DEFAULT_MASK_AVATAR ? (
 | 
			
		||||
@@ -192,6 +206,7 @@ export function MaskConfig(props: {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ContextPromptItem(props: {
 | 
			
		||||
  index: number;
 | 
			
		||||
  prompt: ChatMessage;
 | 
			
		||||
  update: (prompt: ChatMessage) => void;
 | 
			
		||||
  remove: () => void;
 | 
			
		||||
@@ -199,53 +214,62 @@ function ContextPromptItem(props: {
 | 
			
		||||
  const [focusingInput, setFocusingInput] = useState(false);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={chatStyle["context-prompt-row"]}>
 | 
			
		||||
      {!focusingInput && (
 | 
			
		||||
        <Select
 | 
			
		||||
          value={props.prompt.role}
 | 
			
		||||
          className={chatStyle["context-role"]}
 | 
			
		||||
          onChange={(e) =>
 | 
			
		||||
            props.update({
 | 
			
		||||
              ...props.prompt,
 | 
			
		||||
              role: e.target.value as any,
 | 
			
		||||
            })
 | 
			
		||||
          }
 | 
			
		||||
    <Draggable draggableId={props.prompt.id} index={props.index}>
 | 
			
		||||
      {(provided) => (
 | 
			
		||||
        <div
 | 
			
		||||
          className={chatStyle["context-prompt-row"]}
 | 
			
		||||
          ref={provided.innerRef}
 | 
			
		||||
          {...provided.draggableProps}
 | 
			
		||||
          {...provided.dragHandleProps}
 | 
			
		||||
        >
 | 
			
		||||
          {ROLES.map((r) => (
 | 
			
		||||
            <option key={r} value={r}>
 | 
			
		||||
              {r}
 | 
			
		||||
            </option>
 | 
			
		||||
          ))}
 | 
			
		||||
        </Select>
 | 
			
		||||
          {!focusingInput && (
 | 
			
		||||
            <Select
 | 
			
		||||
              value={props.prompt.role}
 | 
			
		||||
              className={chatStyle["context-role"]}
 | 
			
		||||
              onChange={(e) =>
 | 
			
		||||
                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
 | 
			
		||||
        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>
 | 
			
		||||
    </Draggable>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -267,17 +291,41 @@ export function ContextPrompts(props: {
 | 
			
		||||
    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 (
 | 
			
		||||
    <>
 | 
			
		||||
      <div className={chatStyle["context-prompt"]} style={{ marginBottom: 20 }}>
 | 
			
		||||
        {context.map((c, i) => (
 | 
			
		||||
          <ContextPromptItem
 | 
			
		||||
            key={i}
 | 
			
		||||
            prompt={c}
 | 
			
		||||
            update={(prompt) => updateContextPrompt(i, prompt)}
 | 
			
		||||
            remove={() => removeContextPrompt(i)}
 | 
			
		||||
          />
 | 
			
		||||
        ))}
 | 
			
		||||
        <DragDropContext onDragEnd={onDragEnd}>
 | 
			
		||||
          <Droppable droppableId="context-prompt-list">
 | 
			
		||||
            {(provided) => (
 | 
			
		||||
              <div ref={provided.innerRef} {...provided.droppableProps}>
 | 
			
		||||
                {context.map((c, 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"]}>
 | 
			
		||||
          <IconButton
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user