Skip to content

Commit

Permalink
feat: #2 add prompt hints
Browse files Browse the repository at this point in the history
  • Loading branch information
Yidadaa committed Mar 28, 2023
1 parent 7d5e742 commit 6782e65
Show file tree
Hide file tree
Showing 10 changed files with 239 additions and 48 deletions.
58 changes: 56 additions & 2 deletions app/components/home.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,65 @@

.chat-input-panel {
position: absolute;
bottom: 20px;
bottom: 0px;
display: flex;
width: 100%;
padding: 20px;
box-sizing: border-box;
flex-direction: column;
}

@mixin single-line {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.prompt-hints {
min-height: 20px;
width: 100%;
max-height: 50vh;
overflow: auto;
display: flex;
flex-direction: column-reverse;

background-color: var(--white);
border: var(--border-in-light);
border-radius: 10px;
margin-bottom: 10px;
box-shadow: var(--shadow);

.prompt-hint {
color: var(--black);
padding: 6px 10px;
animation: slide-in ease 0.3s;
cursor: pointer;
transition: all ease 0.3s;
border: transparent 1px solid;
margin: 4px;
border-radius: 8px;

&:not(:last-child) {
margin-top: 0;
}

.hint-title {
font-size: 12px;
font-weight: bolder;

@include single-line();
}
.hint-content {
font-size: 12px;

@include single-line();
}

&-selected,
&:hover {
border-color: var(--primary);
}
}
}

.chat-input-panel-inner {
Expand Down Expand Up @@ -375,7 +429,7 @@

position: absolute;
right: 30px;
bottom: 10px;
bottom: 30px;
}

.export-content {
Expand Down
82 changes: 72 additions & 10 deletions app/components/home.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useState, useRef, useEffect, useLayoutEffect } from "react";
import { useDebouncedCallback } from "use-debounce";

import { IconButton } from "./button";
import styles from "./home.module.scss";
Expand Down Expand Up @@ -28,6 +29,7 @@ import Locale from "../locales";
import dynamic from "next/dynamic";
import { REPO_URL } from "../constant";
import { ControllerPool } from "../requests";
import { Prompt, usePromptStore } from "../store/prompt";

export function Loading(props: { noLogo?: boolean }) {
return (
Expand Down Expand Up @@ -146,24 +148,77 @@ function useSubmitHandler() {
};
}

export function PromptHints(props: {
prompts: Prompt[];
onPromptSelect: (prompt: Prompt) => void;
}) {
if (props.prompts.length === 0) return null;

return (
<div className={styles["prompt-hints"]}>
{props.prompts.map((prompt, i) => (
<div
className={styles["prompt-hint"]}
key={prompt.title + i.toString()}
onClick={() => props.onPromptSelect(prompt)}
>
<div className={styles["hint-title"]}>{prompt.title}</div>
<div className={styles["hint-content"]}>{prompt.content}</div>
</div>
))}
</div>
);
}

export function Chat(props: { showSideBar?: () => void }) {
type RenderMessage = Message & { preview?: boolean };

const chatStore = useChatStore();
const [session, sessionIndex] = useChatStore((state) => [
state.currentSession(),
state.currentSessionIndex,
]);

const inputRef = useRef<HTMLTextAreaElement>(null);
const [userInput, setUserInput] = useState("");
const [isLoading, setIsLoading] = useState(false);
const { submitKey, shouldSubmit } = useSubmitHandler();

const onUserInput = useChatStore((state) => state.onUserInput);
// prompt hints
const promptStore = usePromptStore();
const [promptHints, setPromptHints] = useState<Prompt[]>([]);
const onSearch = useDebouncedCallback(
(text: string) => {
if (chatStore.config.disablePromptHint) return;
setPromptHints(promptStore.search(text));
},
100,
{ leading: true, trailing: true }
);

const onPromptSelect = (prompt: Prompt) => {
setUserInput(prompt.content);
setPromptHints([]);
inputRef.current?.focus();
};

// only search prompts when user input is short
const SEARCH_TEXT_LIMIT = 10;
const onInput = (text: string) => {
setUserInput(text);
const n = text.trim().length;
if (n === 0 || n > SEARCH_TEXT_LIMIT) {
setPromptHints([]);
} else {
onSearch(text);
}
};

// submit user input
const onUserSubmit = () => {
if (userInput.length <= 0) return;
setIsLoading(true);
onUserInput(userInput).then(() => setIsLoading(false));
chatStore.onUserInput(userInput).then(() => setIsLoading(false));
setUserInput("");
inputRef.current?.focus();
};
Expand Down Expand Up @@ -198,15 +253,16 @@ export function Chat(props: { showSideBar?: () => void }) {
for (let i = botIndex; i >= 0; i -= 1) {
if (messages[i].role === "user") {
setIsLoading(true);
onUserInput(messages[i].content).then(() => setIsLoading(false));
chatStore
.onUserInput(messages[i].content)
.then(() => setIsLoading(false));
return;
}
}
};

// for auto-scroll
const latestMessageRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null);

// wont scroll while hovering messages
const [autoScroll, setAutoScroll] = useState(false);
Expand Down Expand Up @@ -373,17 +429,21 @@ export function Chat(props: { showSideBar?: () => void }) {
</div>

<div className={styles["chat-input-panel"]}>
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
<div className={styles["chat-input-panel-inner"]}>
<textarea
ref={inputRef}
className={styles["chat-input"]}
placeholder={Locale.Chat.Input(submitKey)}
rows={3}
onInput={(e) => setUserInput(e.currentTarget.value)}
rows={4}
onInput={(e) => onInput(e.currentTarget.value)}
value={userInput}
onKeyDown={(e) => onInputKeyDown(e as any)}
onFocus={() => setAutoScroll(true)}
onBlur={() => setAutoScroll(false)}
onBlur={() => {
setAutoScroll(false);
setTimeout(() => setPromptHints([]), 100);
}}
autoFocus
/>
<IconButton
Expand Down Expand Up @@ -411,9 +471,11 @@ function useSwitchTheme() {
document.body.classList.add("light");
}

const themeColor = getComputedStyle(document.body).getPropertyValue("--theme-color").trim();
const themeColor = getComputedStyle(document.body)
.getPropertyValue("--theme-color")
.trim();
const metaDescription = document.querySelector('meta[name="theme-color"]');
metaDescription?.setAttribute('content', themeColor);
metaDescription?.setAttribute("content", themeColor);
}, [config.theme]);
}

Expand Down Expand Up @@ -566,7 +628,7 @@ export function Home() {
<IconButton
icon={<AddIcon />}
text={Locale.Home.NewChat}
onClick={()=>{
onClick={() => {
createNewSession();
setShowSideBar(false);
}}
Expand Down
41 changes: 39 additions & 2 deletions app/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import styles from "./settings.module.scss";
import ResetIcon from "../icons/reload.svg";
import CloseIcon from "../icons/close.svg";
import ClearIcon from "../icons/clear.svg";
import EditIcon from "../icons/edit.svg";

import { List, ListItem, Popover } from "./ui-lib";
import { List, ListItem, Popover, showToast } from "./ui-lib";

import { IconButton } from "./button";
import {
Expand All @@ -19,12 +20,13 @@ import {
useUpdateStore,
useAccessStore,
} from "../store";
import { Avatar } from "./home";
import { Avatar, PromptHints } from "./home";

import Locale, { changeLang, getLang } from "../locales";
import { getCurrentCommitId } from "../utils";
import Link from "next/link";
import { UPDATE_URL } from "../constant";
import { SearchService, usePromptStore } from "../store/prompt";

function SettingItem(props: {
title: string;
Expand Down Expand Up @@ -78,6 +80,10 @@ export function Settings(props: { closeSettings: () => void }) {
[]
);

const promptStore = usePromptStore();
const builtinCount = SearchService.count.builtin;
const customCount = promptStore.prompts.size ?? 0;

return (
<>
<div className={styles["window-header"]}>
Expand Down Expand Up @@ -242,6 +248,37 @@ export function Settings(props: { closeSettings: () => void }) {
</SettingItem>
</div>
</List>
<List>
<SettingItem
title={Locale.Settings.Prompt.Disable.Title}
subTitle={Locale.Settings.Prompt.Disable.SubTitle}
>
<input
type="checkbox"
checked={config.disablePromptHint}
onChange={(e) =>
updateConfig(
(config) =>
(config.disablePromptHint = e.currentTarget.checked)
)
}
></input>
</SettingItem>

<SettingItem
title={Locale.Settings.Prompt.List}
subTitle={Locale.Settings.Prompt.ListCount(
builtinCount,
customCount
)}
>
<IconButton
icon={<EditIcon />}
text={Locale.Settings.Prompt.Edit}
onClick={() => showToast(Locale.WIP)}
/>
</SettingItem>
</List>
<List>
{enabledAccessControl ? (
<SettingItem
Expand Down
2 changes: 1 addition & 1 deletion app/components/ui-lib.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function ListItem(props: { children: JSX.Element[] }) {
return <div className={styles["list-item"]}>{props.children}</div>;
}

export function List(props: { children: JSX.Element[] }) {
export function List(props: { children: JSX.Element[] | JSX.Element }) {
return <div className={styles.list}>{props.children}</div>;
}

Expand Down
1 change: 1 addition & 0 deletions app/icons/edit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions app/locales/cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,16 @@ const cn = {
SendKey: "发送键",
Theme: "主题",
TightBorder: "紧凑边框",
Prompt: {
Disable: {
Title: "禁用提示词自动补全",
SubTitle: "禁用后将无法自动根据输入补全",
},
List: "自定义提示词列表",
ListCount: (builtin: number, custom: number) =>
`内置 ${builtin} 条,用户定义 ${custom} 条`,
Edit: "编辑",
},
HistoryCount: {
Title: "附带历史消息数",
SubTitle: "每次请求携带的历史消息数",
Expand Down
4 changes: 4 additions & 0 deletions app/store/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export interface ChatConfig {
theme: Theme;
tightBorder: boolean;

disablePromptHint: boolean;

modelConfig: {
model: string;
temperature: number;
Expand Down Expand Up @@ -124,6 +126,8 @@ const DEFAULT_CONFIG: ChatConfig = {
theme: Theme.Auto as Theme,
tightBorder: false,

disablePromptHint: false,

modelConfig: {
model: "gpt-3.5-turbo",
temperature: 1,
Expand Down
Loading

0 comments on commit 6782e65

Please sign in to comment.