Skip to content

Commit

Permalink
Merge pull request #54 from youngle316/dev
Browse files Browse the repository at this point in the history
feat: add custom prompt lib
  • Loading branch information
youngle316 authored Mar 31, 2023
2 parents 53153f6 + af54ffe commit 1be5d9d
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
import { useI18n } from '@/hook/useI18n';
import { Dispatch, SetStateAction } from 'react';

type SearchInputProps = {
setData: Dispatch<SetStateAction<string>>;
placeholder: string;
};

function SearchInput({ setData }: SearchInputProps) {
const { t } = useI18n();

function BasicInput({ setData, placeholder }: SearchInputProps) {
return (
<>
<input
type="text"
className="basic-input"
placeholder={t('searchPrompt')}
placeholder={placeholder}
onChange={(e) => setData(e.target.value)}
/>
</>
);
}

export default SearchInput;
export default BasicInput;
152 changes: 152 additions & 0 deletions src/components/promptLibModal/CustomPrompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
'use client';

import { useEffect, useState } from 'react';
import { useI18n } from '@/hook/useI18n';
import BasicInput from '../Input';
import { PlusIcon, PlusCircleIcon } from '@heroicons/react/24/outline';
import {
addDoc,
collection,
serverTimestamp,
query,
orderBy
} from 'firebase/firestore';
import { useSession } from 'next-auth/react';
import { db } from '@/service/firebase/firebase';
import { useCollection } from 'react-firebase-hooks/firestore';
import PromptItem from './PromptItem';

function CustomPrompt() {
const [isAdding, setIsAdding] = useState<boolean>(false);
const [titleValue, setTitleValue] = useState<string>('');
const [desValue, setDesValue] = useState<string>('');
const [promptValue, setPromptValue] = useState<string>('');
const [searchValue, setSearchValue] = useState<string>('');
const [addBtnDis, setAddBtnDis] = useState<boolean>(false);

const { data: session } = useSession();

const { t } = useI18n();

const [customPromptData] = useCollection(
query(
collection(db, 'users', session?.user?.email!, 'customPrompt'),
orderBy('createAt', 'asc')
)
);

const addPrompt = () => {
if (titleValue === '' || promptValue === '') {
return;
}

const newPrompt = {
title: titleValue,
des: desValue,
source: Date.now(),
prompt: promptValue,
type: 'custom',
createAt: serverTimestamp()
};

setAddBtnDis(true);

addDoc(
collection(db, 'users', session?.user?.email!, 'customPrompt'),
newPrompt
)
.then((res) => {
if (res && res.id) {
setIsAdding(false);
setTitleValue('');
setDesValue('');
setPromptValue('');
}
})
.finally(() => {
setAddBtnDis(false);
});
};

return (
<>
{isAdding ? (
<div className="flex flex-col gap-2 border-b pb-3">
<div>
<label className="label">{t('title')}</label>
<BasicInput
setData={setTitleValue}
placeholder={t('promptTitle')}
/>
</div>
<div>
<label className="label">{t('des')}</label>
<BasicInput setData={setDesValue} placeholder={t('promptDes')} />
</div>
<div>
<label className="label">{t('prompt')}</label>
<textarea
rows={4}
className="basic-input"
placeholder={t('promptContent')}
onChange={(e) => setPromptValue(e.target.value)}
/>
</div>
<div className="flex justify-center gap-5">
<button onClick={addPrompt} className="blue-button gap-1">
<PlusIcon className="h-4 w-4" />
{t('addPrompt')}
</button>
<button onClick={() => setIsAdding(false)}>
{t('cancelAddPrompt')}
</button>
</div>
</div>
) : (
<div className="flex flex-col gap-3 border-b pb-3">
<div className="flex">
<div className="flex-1">
<BasicInput
setData={setSearchValue}
placeholder={t('searchPrompt')}
/>
</div>
<div className="flex">
<button
disabled={addBtnDis}
onClick={() => setIsAdding(true)}
className="common-button gap-1"
>
<PlusCircleIcon className="h-6 w-6" />
</button>
</div>
</div>
<div
className={`${
customPromptData?.docs.length === 0 &&
'rounded-lg border border-dashed border-gray-700 px-5 py-3 text-sm text-gray-400 dark:border-white'
}`}
>
{customPromptData?.docs.length === 0 ? (
<p>{t('noCustomPrompt')}</p>
) : (
customPromptData?.docs.map((item) => {
const { title, des, source, prompt } = item.data();
if (title.includes(searchValue)) {
return (
<PromptItem
key={item.id}
prompt={{ title, des, source, content: prompt }}
/>
);
}
})
)}
</div>
</div>
)}
</>
);
}

export default CustomPrompt;
28 changes: 9 additions & 19 deletions src/components/promptLibModal/PromptContent.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PromptItem from './PromptItem';
import SearchInput from '../searchInput';
import SearchInput from '../Input';
import { useState, useEffect } from 'react';
import { useI18n } from '@/hook/useI18n';

Expand All @@ -9,23 +9,17 @@ type promptContentProps = {

function PromptContent({ content }: promptContentProps) {
const [searchValue, setSearchValue] = useState<string>('');
const [showPrompts, setShowPrompts] = useState<Prompt[]>(content.prompts);

const { t } = useI18n();

useEffect(() => {
const newPrompts = content.prompts.filter((prompt: Prompt) => {
return prompt.title.includes(searchValue);
});

setShowPrompts(newPrompts);
}, [searchValue]);

return (
<>
<div className="mb-4 flex gap-3">
<div className="flex-1">
<SearchInput setData={setSearchValue} />
<SearchInput
setData={setSearchValue}
placeholder={t('searchPrompt')}
/>
</div>
<div className="flex w-10 items-center justify-center text-sm">
<a
Expand All @@ -43,14 +37,10 @@ function PromptContent({ content }: promptContentProps) {
</div>
</div>
<div className="flex flex-col gap-3 border-b">
{showPrompts.map((prompt: Prompt) => {
return (
<PromptItem
key={prompt.source}
prompt={prompt}
type={content.type}
/>
);
{content.prompts.map((prompt: Prompt) => {
if (prompt.title.includes(searchValue)) {
return <PromptItem key={prompt.source} prompt={prompt} />;
}
})}
</div>
</>
Expand Down
10 changes: 6 additions & 4 deletions src/components/promptLibModal/PromptItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { useRecoilState } from 'recoil';
import { promptLibModalState } from '@/recoil/atom/AtomMessage';
import textAreaAutoHeight from '@/utils/textAreaAutoHeight';
import { useEffect } from 'react';
import { useI18n } from '@/hook/useI18n';

type PromptItemProps = {
prompt: Prompt;
type: Prompts['type'];
};

function PromptItem({ prompt, type }: PromptItemProps) {
function PromptItem({ prompt }: PromptItemProps) {
const [chatInputPrompt, setChatInputPrompt] =
useRecoilState(chatInputPromptState);
const [, setPromptLibModalState] = useRecoilState(promptLibModalState);
Expand All @@ -20,6 +20,8 @@ function PromptItem({ prompt, type }: PromptItemProps) {
setPromptLibModalState(false);
};

const { t } = useI18n();

useEffect(() => {
textAreaAutoHeight('chatTextArea');
}, [chatInputPrompt]);
Expand All @@ -29,9 +31,9 @@ function PromptItem({ prompt, type }: PromptItemProps) {
<div className="flex flex-1 items-center font-bold text-gray-800 dark:text-white">
{prompt.title}
</div>
<div className={`${type === 'en' ? 'w-20' : 'w-30'}`}>
<div className="w-30">
<button onClick={usePromptLib} className="blue-button">
{type === 'en' ? 'Use' : '使用'}
{t('usePrompt')}
<ArrowSmallRightIcon className="ml-2 -mr-1 h-4 w-4" />
</button>
</div>
Expand Down
4 changes: 4 additions & 0 deletions src/components/promptLibModal/PromptLibModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PromptContent from './PromptContent';
import { useRecoilState } from 'recoil';
import { promptLibModalState } from '@/recoil/atom/AtomMessage';
import { useI18n } from '@/hook/useI18n';
import CustomPrompt from './CustomPrompt';

function PromptLibModal() {
const [promptLibModal, setPromptLibModal] =
Expand Down Expand Up @@ -52,6 +53,9 @@ function PromptLibModal() {
{t('promptLib')}
</Dialog.Title>
<Tabs.Group aria-label="Tabs with underline" style="underline">
<Tabs.Item title={t('yourPrompt')}>
<CustomPrompt />
</Tabs.Item>
<Tabs.Item active={true} title="中文">
<PromptContent content={cnPrompts} />
</Tabs.Item>
Expand Down
11 changes: 10 additions & 1 deletion src/i18n/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,14 @@
"lightTheme": "Light Theme",
"yourPrompt": "Custom",
"searchPrompt": "Search Prompts",
"promptsSource": "Source"
"promptsSource": "Source",
"title": "Title",
"promptTitle": "Prompt Title",
"des": "Description(optional))",
"promptDes": "Prompt Des",
"prompt": "Prompt",
"promptContent": "Enter your prompt here",
"addPrompt": "Add Prompt",
"cancelAddPrompt": "Cancel",
"noCustomPrompt": "You don't have custom prompts, add one first"
}
11 changes: 10 additions & 1 deletion src/i18n/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,14 @@
"lightTheme": "浅色主题",
"yourPrompt": "自定义",
"searchPrompt": "搜索 Prompts",
"promptsSource": "来源"
"promptsSource": "来源",
"title": "标题",
"promptTitle": "Prompt 标题",
"des": "描述(可选的)",
"promptDes": "Prompt 描述",
"prompt": "Prompt",
"promptContent": "输入你的 Prompt",
"addPrompt": "添加 Prompt",
"cancelAddPrompt": "取消",
"noCustomPrompt": "你没有自定义的 Prompts,先添加一个吧~"
}
4 changes: 4 additions & 0 deletions src/styles/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,8 @@
@apply block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500
dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500;
}

.label {
@apply mb-2 block text-sm font-medium text-gray-900 dark:text-white;
}
}
2 changes: 2 additions & 0 deletions typing.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ interface Prompt {
des: string;
content: string;
source: string;
type?: 'custom' | 'default';
createAt?: admin.firestore.Timestamp;
}

interface Prompts {
Expand Down

0 comments on commit 1be5d9d

Please sign in to comment.