Skip to content

Commit

Permalink
feat(WIP): 增加历史记录
Browse files Browse the repository at this point in the history
  • Loading branch information
weaigc committed Aug 25, 2023
1 parent 1eb068c commit 139d878
Show file tree
Hide file tree
Showing 22 changed files with 349 additions and 137 deletions.
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"cheerio": "^1.0.0-rc.12",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"dayjs": "^1.11.9",
"debug": "^4.3.4",
"dotenv": "^16.3.1",
"eslint": "8.44.0",
Expand All @@ -41,6 +42,7 @@
"js-base64": "^3.7.5",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"namestorage": "^1.3.0",
"nanoid": "^4.0.2",
"next": "13.4.9",
"next-auth": "^4.22.3",
Expand Down
51 changes: 43 additions & 8 deletions src/app/globals.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
@tailwind utilities;

:root {
--cib-comp-thread-name-border-radius: 3px;
--cib-color-foreground-accent-primary: #75306C;
--cib-color-foreground-accent-secondary: #692B61;
--cib-color-foreground-accent-tertiary: #5E2656;
Expand Down Expand Up @@ -1682,9 +1683,29 @@ button {
display: flex;
flex-direction: column;
padding: 4px;
overflow: hidden;
max-block-size: calc(100% - 44px);
overflow-y: auto;
max-height: calc(100vh - 250px);
box-sizing: border-box;
&::-webkit-scrollbar {
width: 10px;
height: 10px;
display: none;
}
&::-webkit-scrollbar-button:start:decrement,
&::-webkit-scrollbar-button:end:increment {
height: 30px;
background-color: transparent;
}
&::-webkit-scrollbar-track-piece {
background-color: #3b3b3b;
-webkit-border-radius: 16px;
}
&::-webkit-scrollbar-thumb:vertical {
height: 50px;
background-color: #666;
border: 1px solid #eee;
-webkit-border-radius: 6px;
}
}

.thread {
Expand All @@ -1702,7 +1723,7 @@ button {
flex-direction: column;
gap: 5px;

&:hover {
&:hover,&.active {
background: var(--cib-color-background-surface-card-primary);
box-shadow: var(--cib-shadow-elevation-1);

Expand All @@ -1721,14 +1742,13 @@ button {
.time {
display: none;
}
}

&:not(:hover) {
.controls {
display: none;
display: flex;
}
}



&>* {
width: 100%;
}
Expand Down Expand Up @@ -1770,14 +1790,20 @@ button {
}

.controls {
display: flex;
display: none;
align-items: center;
}

.description {
-webkit-mask-image: linear-gradient(to right, var(--cib-color-background-surface-app-primary) 90%, transparent);
}

.active {
.description {
-webkit-mask-image: none;
}
}

.name {
display: flex;
align-items: center;
Expand All @@ -1793,6 +1819,15 @@ button {
font-weight: var(--cib-type-body1-font-weight);
}

.input-name {
border-radius: var(--cib-comp-thread-name-border-radius);
outline: none;
border: 1px solid var(--cib-color-stroke-accent-primary);
pointer-events: auto;
width: 226px;
padding-block: calc( ( 20px - var(--cib-type-body1-line-height) - 2px ) / 2 );
}

.time {
display: flex;
align-items: center;
Expand Down
1 change: 0 additions & 1 deletion src/app/loading.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
}

@keyframes sk-bouncedelay {

0%,
80%,
100% {
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat-attachments.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import ClearIcon from '@/assets/images/clear.svg'
import RefreshIcon from '@/assets/images/refresh.svg'
import { cn } from '@/lib/utils'
import { useBing } from '@/lib/hooks/use-bing'
import { BingReturnType } from '@/lib/hooks/use-bing'
import { SVG } from './ui/svg'

type ChatAttachmentsProps = Pick<ReturnType<typeof useBing>, 'attachmentList' | 'setAttachmentList' | 'uploadImage'>
type ChatAttachmentsProps = Pick<BingReturnType, 'attachmentList' | 'setAttachmentList' | 'uploadImage'>

export function ChatAttachments({ attachmentList = [], setAttachmentList, uploadImage }: ChatAttachmentsProps) {
return attachmentList.length ? (
Expand Down
123 changes: 87 additions & 36 deletions src/components/chat-history.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,99 @@
import { IconEdit, IconTrash, IconMore, IconDownload } from "./ui/icons"
import { useCallback, useEffect, useState } from 'react'
import { useChatHistory, ChatConversation } from '@/lib/hooks/chat-history'
import { IconEdit, IconTrash, IconMore, IconDownload, IconCheck, IconClose } from './ui/icons'
import { cn, formatDate } from '@/lib/utils'
import { BingReturnType } from '@/lib/hooks/use-bing'

export function ChatHistory() {
return (
<div className="chat-history fixed top-18 right-4">
<div className="chat-history-header text-sm font-semibold text-left w-[280px] px-4 py-6">
历史记录
</div>
<div className="chat-history-main">
<div className="scroller">
<div className="surface">
<div className="threads">
<div className="thread">
<div className="primary-row">
<button type="button" aria-label="加载聊天">

</button>
<div className="description">
<h3 className="name">无标题的聊天</h3>
</div>
<h4 className="time">上午1:42</h4>
<div className="controls">
interface ConversationTheadProps {
conversation: ChatConversation
onRename: (conversation: ChatConversation, chatName: string) => void
onDelete: (conversation: ChatConversation) => void
onUpdate: (conversation: ChatConversation) => void
}

<button className="edit icon-button" type="button" aria-label="重命名">
<IconEdit />
</button>
export function ConversationThead({ conversation, onRename, onDelete, onUpdate }: ConversationTheadProps) {
const [isEdit, setEdit] = useState(false)
const [name, setName] = useState(conversation.chatName)
const handleSave = useCallback(() => {
if (!name) {
setName(conversation.chatName)
return
}
setEdit(false)
onRename(conversation, name)
}, [conversation, name])
const handleDelete = useCallback(() => {
onDelete(conversation)
}, [conversation])
useEffect(() => {
setName(conversation.chatName)
}, [conversation])
return (
<div className={cn('thread select-none', { active: isEdit })} onClick={() => onUpdate(conversation)}>
<div className="primary-row flex">
<div className="description flex-1">
{!isEdit ? (
<h3 className="name w-[200px]">{name}</h3>
) : (<input className="input-name" defaultValue={name} onChange={(event) => setName(event.target.value)} />)}
</div>
{!isEdit && (<h4 className="time">{formatDate(conversation.updateTimeUtc)}</h4>)}
<div className="controls">
{!isEdit ? (<>
<button className="edit icon-button" type="button" aria-label="重命名" onClick={() => setEdit(true)}>
<IconEdit />
</button>

<button className="delete icon-button" type="button" aria-label="删除">
<IconTrash />
</button>
<button className="delete icon-button" type="button" aria-label="删除" onClick={handleDelete}>
<IconTrash />
</button>

<button className="more icon-button" type="button" aria-haspopup="true" aria-expanded="false" aria-label="更多">
<IconMore />
</button>
<button className="export icon-button" type="button" aria-label="导出">
<IconDownload />
</button>
</>) : (
<>
<button className="edit icon-button" type="button" aria-label="保存" onClick={handleSave}>
<IconCheck />
</button>
<button className="edit icon-button" type="button" aria-label="取消" onClick={() => setEdit(false)}>
<IconClose />
</button>
</>
)}
</div>
</div>
</div>
)
}

<button className="export icon-button" type="button" aria-label="导出">
<IconDownload />
</button>
</div>
</div>
export function ChatHistory({ className }: { className?: string }) {
const { chatHistory, refreshChats, deleteChat, renameChat, updateMessage } = useChatHistory()
useEffect(() => {
refreshChats()
}, [])
return (
<div className={cn('chat-history right-4 z-10 fixed w-[342px]', className)}>
<div className="chat-history-header text-sm font-semibold text-left px-4 pb-6">
历史记录
</div>
{chatHistory?.chats?.length ? (
<div className="chat-history-main">
<div className="scroller">
<div className="surface">
<div className="threads w-[310px]">
{chatHistory.chats.map((chat) => (
<ConversationThead key={chat.conversationId}
conversation={chat}
onDelete={deleteChat}
onRename={renameChat}
onUpdate={updateMessage}
/>
))}
</div>
</div>
</div>
</div>
</div>
) : []}
</div>
)
}
4 changes: 2 additions & 2 deletions src/components/chat-image.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import { SVG } from "./ui/svg"
import PasteIcon from '@/assets/images/paste.svg'
import UploadIcon from '@/assets/images/upload.svg'
import CameraIcon from '@/assets/images/camera.svg'
import { useBing } from '@/lib/hooks/use-bing'
import { BingReturnType } from '@/lib/hooks/use-bing'
import { cn } from '@/lib/utils'
import { ImageUtils } from "@/lib/image"

interface ChatImageProps extends Pick<ReturnType<typeof useBing>, 'uploadImage'> {}
interface ChatImageProps extends Pick<BingReturnType, 'uploadImage'> {}

const preventDefault: MouseEventHandler<HTMLDivElement> = (event) => {
event.nativeEvent.stopImmediatePropagation()
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat-notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { useEffect } from 'react'
import IconWarning from '@/assets/images/warning.svg'
import { ChatError, ErrorCode, ChatMessageModel } from '@/lib/bots/bing/types'
import { ExternalLink } from './external-link'
import { useBing } from '@/lib/hooks/use-bing'
import { BingReturnType } from '@/lib/hooks/use-bing'
import { SVG } from './ui/svg'

export interface ChatNotificationProps extends Pick<ReturnType<typeof useBing>, 'bot'> {
export interface ChatNotificationProps extends Pick<BingReturnType, 'bot'> {
message?: ChatMessageModel
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import SendIcon from '@/assets/images/send.svg'
import PinIcon from '@/assets/images/pin.svg'
import PinFillIcon from '@/assets/images/pin-fill.svg'

import { useBing } from '@/lib/hooks/use-bing'
import { BingReturnType } from '@/lib/hooks/use-bing'
import { voiceListenAtom } from '@/state'
import Voice from './voice'
import { ChatImage } from './chat-image'
Expand All @@ -22,7 +22,7 @@ import { SVG } from './ui/svg'

export interface ChatPanelProps
extends Pick<
ReturnType<typeof useBing>,
BingReturnType,
| 'generating'
| 'input'
| 'setInput'
Expand Down
4 changes: 2 additions & 2 deletions src/components/chat-suggestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { atom, useAtom } from 'jotai'
import HelpIcon from '@/assets/images/help.svg'
import DismissFillIcon from '@/assets/images/dismiss-fill.svg'
import { SuggestedResponse } from '@/lib/bots/bing/types'
import { useBing } from '@/lib/hooks/use-bing'
import { BingReturnType } from '@/lib/hooks/use-bing'
import { SVG } from './ui/svg'

type Suggestions = SuggestedResponse[]
const helpSuggestions = ['为什么不回应某些主题', '告诉我更多关于必应的资迅', '必应如何使用 AI?'].map((text) => ({ text }))
const suggestionsAtom = atom<Suggestions>([])

type ChatSuggestionsProps = React.ComponentProps<'div'> & Pick<ReturnType<typeof useBing>, 'setInput'> & { suggestions?: Suggestions }
type ChatSuggestionsProps = React.ComponentProps<'div'> & Pick<BingReturnType, 'setInput'> & { suggestions?: Suggestions }

export function ChatSuggestions({ setInput, suggestions = [] }: ChatSuggestionsProps) {
const [currentSuggestions, setSuggestions] = useAtom(suggestionsAtom)
Expand Down
1 change: 1 addition & 0 deletions src/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export default function Chat({ className }: ChatProps) {

return (
<div className={cn('flex flex-1 flex-col', bingStyle.toLowerCase())}>
<ChatHistory bot={bot} />
<div className="global-background" />
<Settings />
<div className={cn('flex-1 pb-16', className)}>
Expand Down
Loading

0 comments on commit 139d878

Please sign in to comment.