add custom system prompt (#39)

This commit is contained in:
Mckay Wrigley
2023-03-21 01:39:32 -06:00
committed by GitHub
parent 6e19d44020
commit 0d6ff739a2
16 changed files with 310 additions and 206 deletions
+23 -34
View File
@@ -1,10 +1,11 @@
import { Conversation, Message, OpenAIModel } from "@/types";
import { Conversation, KeyValuePair, Message, OpenAIModel } from "@/types";
import { FC, useEffect, useRef, useState } from "react";
import { ChatInput } from "./ChatInput";
import { ChatLoader } from "./ChatLoader";
import { ChatMessage } from "./ChatMessage";
import { ModelSelect } from "./ModelSelect";
import { Regenerate } from "./Regenerate";
import { SystemPrompt } from "./SystemPrompt";
interface Props {
conversation: Conversation;
@@ -15,20 +16,10 @@ interface Props {
loading: boolean;
lightMode: "light" | "dark";
onSend: (message: Message, isResend: boolean) => void;
onModelChange: (conversation: Conversation, model: OpenAIModel) => void;
onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void;
}
export const Chat: FC<Props> = ({
conversation,
models,
messageIsStreaming,
modelError,
messageError,
loading,
lightMode,
onSend,
onModelChange,
}) => {
export const Chat: FC<Props> = ({ conversation, models, messageIsStreaming, modelError, messageError, loading, lightMode, onSend, onUpdateConversation }) => {
const [currentMessage, setCurrentMessage] = useState<Message>();
const messagesEndRef = useRef<HTMLDivElement>(null);
@@ -46,38 +37,36 @@ export const Chat: FC<Props> = ({
{modelError ? (
<div className="flex flex-col justify-center mx-auto h-full w-[300px] sm:w-[500px] space-y-6">
<div className="text-center text-red-500">Error fetching models.</div>
<div className="text-center text-red-500">
Make sure your OpenAI API key is set in the bottom left of the
sidebar or in a .env.local file and refresh.
</div>
<div className="text-center text-red-500">
If you completed this step, OpenAI may be experiencing issues.
</div>
<div className="text-center text-red-500">Make sure your OpenAI API key is set in the bottom left of the sidebar or in a .env.local file and refresh.</div>
<div className="text-center text-red-500">If you completed this step, OpenAI may be experiencing issues.</div>
</div>
) : (
<>
<div>
{conversation.messages.length === 0 ? (
<>
<div className="flex justify-center pt-8">
<ModelSelect
model={conversation.model}
models={models}
onModelChange={(model) =>
onModelChange(conversation, model)
}
/>
</div>
<div className="flex flex-col mx-auto pt-12 space-y-10 w-[350px] sm:w-[600px]">
<div className="text-4xl text-center text-neutral-600 dark:text-neutral-200">{models.length === 0 ? "Loading..." : "Chatbot UI"}</div>
<div className="text-4xl text-center text-neutral-600 dark:text-neutral-200 pt-[160px] sm:pt-[280px]">
{models.length === 0 ? "Loading..." : "Chatbot UI"}
{models.length > 0 && (
<div className="flex flex-col h-full space-y-4 border p-4 rounded border-neutral-500">
<ModelSelect
model={conversation.model}
models={models}
onModelChange={(model) => onUpdateConversation(conversation, { key: "model", value: model })}
/>
<SystemPrompt
conversation={conversation}
onChangePrompt={(prompt) => onUpdateConversation(conversation, { key: "prompt", value: prompt })}
/>
</div>
)}
</div>
</>
) : (
<>
<div className="flex justify-center py-2 text-neutral-500 bg-neutral-100 dark:bg-[#444654] dark:text-neutral-200 text-sm border border-b-neutral-300 dark:border-none">
Model: {conversation.model.name}
</div>
<div className="flex justify-center py-2 text-neutral-500 bg-neutral-100 dark:bg-[#444654] dark:text-neutral-200 text-sm border border-b-neutral-300 dark:border-none">Model: {conversation.model.name}</div>
{conversation.messages.map((message, index) => (
<ChatMessage
+4 -6
View File
@@ -45,10 +45,8 @@ export const ChatInput: FC<Props> = ({ onSend, messageIsStreaming, model }) => {
};
const isMobile = () => {
const userAgent =
typeof window.navigator === "undefined" ? "" : navigator.userAgent;
const mobileRegex =
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i;
const userAgent = typeof window.navigator === "undefined" ? "" : navigator.userAgent;
const mobileRegex = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i;
return mobileRegex.test(userAgent);
};
@@ -72,12 +70,12 @@ export const ChatInput: FC<Props> = ({ onSend, messageIsStreaming, model }) => {
<div className="fixed sm:absolute bottom-4 sm:bottom-8 w-full sm:w-1/2 px-2 left-0 sm:left-[280px] lg:left-[200px] right-0 ml-auto mr-auto">
<textarea
ref={textareaRef}
className="rounded-lg pl-4 pr-8 py-3 w-full focus:outline-none max-h-[280px] dark:bg-[#40414F] dark:border-opacity-50 dark:border-neutral-800 dark:text-neutral-100 border border-neutral-300 shadow text-neutral-900"
className="rounded-lg pl-4 pr-8 py-3 w-full focus:outline-none dark:bg-[#40414F] dark:border-opacity-50 dark:border-neutral-800 dark:text-neutral-100 border border-neutral-300 shadow text-neutral-900"
style={{
resize: "none",
bottom: `${textareaRef?.current?.scrollHeight}px`,
maxHeight: "400px",
overflow: "auto",
overflow: "auto"
}}
placeholder="Type a message..."
value={content}
+14 -4
View File
@@ -20,9 +20,7 @@ export const ChatMessage: FC<Props> = ({ message, lightMode }) => {
<div className="prose dark:prose-invert mt-[-2px]">
{message.role === "user" ? (
<div className="prose dark:prose-invert whitespace-pre-wrap">
{message.content}
</div>
<div className="prose dark:prose-invert whitespace-pre-wrap">{message.content}</div>
) : (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
@@ -38,11 +36,23 @@ export const ChatMessage: FC<Props> = ({ message, lightMode }) => {
{...props}
/>
) : (
<code className={className} {...props}>
<code
className={className}
{...props}
>
{children}
</code>
);
},
table({ children }) {
return <table className="border-collapse border border-black dark:border-white py-1 px-3">{children}</table>;
},
th({ children }) {
return <th className="border border-black dark:border-white break-words py-1 px-3 bg-gray-500 text-white">{children}</th>;
},
td({ children }) {
return <td className="border border-black dark:border-white break-words py-1 px-3">{children}</td>;
}
}}
>
{message.content}
+1 -1
View File
@@ -12,7 +12,7 @@ export const ModelSelect: FC<Props> = ({ model, models, onModelChange }) => {
<div className="flex flex-col">
<label className="text-left mb-2 dark:text-neutral-400 text-neutral-700">Model</label>
<select
className="w-[300px] p-3 dark:text-white dark:bg-[#343541] border border-neutral-500 rounded-lg appearance-none focus:shadow-outline text-neutral-900 cursor-pointer"
className="w-full p-3 dark:text-white dark:bg-[#343541] border border-neutral-500 rounded-lg appearance-none focus:shadow-outline text-neutral-900 cursor-pointer"
placeholder="Select a model"
value={model.id}
onChange={(e) => {
+65
View File
@@ -0,0 +1,65 @@
import { Conversation } from "@/types";
import { DEFAULT_SYSTEM_PROMPT } from "@/utils/app/const";
import { FC, useEffect, useRef, useState } from "react";
interface Props {
conversation: Conversation;
onChangePrompt: (prompt: string) => void;
}
export const SystemPrompt: FC<Props> = ({ conversation, onChangePrompt }) => {
const [value, setValue] = useState<string>("");
const textareaRef = useRef<HTMLTextAreaElement>(null);
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
const maxLength = 4000;
if (value.length > maxLength) {
alert(`Prompt limit is ${maxLength} characters`);
return;
}
setValue(value);
if (value.length > 0) {
onChangePrompt(value);
}
};
useEffect(() => {
if (textareaRef && textareaRef.current) {
textareaRef.current.style.height = "inherit";
textareaRef.current.style.height = `${textareaRef.current?.scrollHeight}px`;
}
}, [value]);
useEffect(() => {
if (conversation.prompt) {
setValue(conversation.prompt);
} else {
setValue(DEFAULT_SYSTEM_PROMPT);
}
}, [conversation]);
return (
<div className="flex flex-col">
<label className="text-left dark:text-neutral-400 text-neutral-700 mb-2">System Prompt</label>
<textarea
ref={textareaRef}
className="w-full rounded-lg px-4 py-2 focus:outline-none dark:bg-[#40414F] dark:border-opacity-50 dark:border-neutral-800 dark:text-neutral-100 border border-neutral-500 shadow text-neutral-900"
style={{
resize: "none",
bottom: `${textareaRef?.current?.scrollHeight}px`,
maxHeight: "300px",
overflow: "auto"
}}
placeholder="Enter a prompt"
value={value}
rows={1}
onChange={handleChange}
/>
</div>
);
};
+1 -1
View File
@@ -19,7 +19,7 @@ export const Key: FC<Props> = ({ apiKey, onApiKeyChange }) => {
};
const handleUpdateKey = (newKey: string) => {
onApiKeyChange(newKey);
onApiKeyChange(newKey.trim());
setIsChanging(false);
};
+4 -4
View File
@@ -1,4 +1,4 @@
import { Conversation } from "@/types";
import { Conversation, KeyValuePair } from "@/types";
import { IconArrowBarLeft, IconPlus } from "@tabler/icons-react";
import { FC, useEffect, useState } from "react";
import { Conversations } from "./Conversations";
@@ -16,11 +16,11 @@ interface Props {
onSelectConversation: (conversation: Conversation) => void;
onDeleteConversation: (conversation: Conversation) => void;
onToggleSidebar: () => void;
onRenameConversation: (conversation: Conversation, name: string) => void;
onUpdateConversation: (conversation: Conversation, data: KeyValuePair) => void;
onApiKeyChange: (apiKey: string) => void;
}
export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onRenameConversation, onApiKeyChange }) => {
export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selectedConversation, apiKey, onNewConversation, onToggleLightMode, onSelectConversation, onDeleteConversation, onToggleSidebar, onUpdateConversation, onApiKeyChange }) => {
const [searchTerm, setSearchTerm] = useState<string>("");
const [filteredConversations, setFilteredConversations] = useState<Conversation[]>(conversations);
@@ -74,7 +74,7 @@ export const Sidebar: FC<Props> = ({ loading, conversations, lightMode, selected
setSearchTerm("");
}}
onRenameConversation={(conversation, name) => {
onRenameConversation(conversation, name);
onUpdateConversation(conversation, { key: "name", value: name });
setSearchTerm("");
}}
/>