Skip to content

Commit

Permalink
feat: Improve the logical part of message-settings
Browse files Browse the repository at this point in the history
  • Loading branch information
yzh990918 committed May 8, 2023
1 parent 61b6907 commit b9a071b
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 49 deletions.
8 changes: 4 additions & 4 deletions src/components/Send.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { currentErrorMessage, isSendBoxFocus, scrollController } from '@/stores/
import { addConversation, conversationMap, currentConversationId } from '@/stores/conversation'
import { loadingStateMap, streamsMap } from '@/stores/streams'
import { handlePrompt } from '@/logics/conversation'
import { globalAbortController } from '@/stores/settings'

export default () => {
let inputRef: HTMLTextAreaElement
Expand All @@ -14,7 +15,7 @@ export default () => {
const $currentErrorMessage = useStore(currentErrorMessage)
const $streamsMap = useStore(streamsMap)
const $loadingStateMap = useStore(loadingStateMap)
const [controller, setController] = createSignal<AbortController>()
const $globalAbortController = useStore(globalAbortController)

const [inputPrompt, setInputPrompt] = createSignal('')
const isEditing = () => inputPrompt() || $isSendBoxFocus()
Expand Down Expand Up @@ -96,12 +97,11 @@ export default () => {

const clearPrompt = () => {
setInputPrompt('')
inputRef.value = ''
isSendBoxFocus.set(false)
}

const handleAbortFetch = () => {
controller()!.abort()
$globalAbortController()?.abort()
clearPrompt()
}

Expand All @@ -124,7 +124,7 @@ export default () => {
addConversation()

const controller = new AbortController()
setController(controller)
globalAbortController.set(controller)
handlePrompt(currentConversation(), inputRef.value, controller.signal)
clearPrompt()
scrollController().scrollToBottom()
Expand Down
127 changes: 97 additions & 30 deletions src/components/main/MessageItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { For } from 'solid-js/web'
import { For, Show } from 'solid-js/web'
import { createSignal } from 'solid-js'
import { useStore } from '@nanostores/solid'
import { useClipboardCopy } from '@/hooks'
import { deleteMessageByConversationId } from '@/stores/messages'
import { deleteMessageByConversationId, spliceMessageByConversationId, spliceUpdateMessageByConversationId } from '@/stores/messages'
import { conversationMap } from '@/stores/conversation'
import { handlePrompt } from '@/logics/conversation'
import { scrollController } from '@/stores/ui'
import { globalAbortController } from '@/stores/settings'
import StreamableText from '../StreamableText'
import { Tooltip } from '../ui/base'
import { DropDownMenu, Tooltip } from '../ui/base'
import type { MenuItem } from '../ui/base'
import type { MessageInstance } from '@/types/message'

Expand All @@ -15,28 +20,65 @@ interface Props {
}

export default (props: Props) => {
const roleClass = {
system: 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300',
user: 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300',
assistant: 'bg-gradient-to-b from-[#fccb90] to-[#d57eeb]',
}
const $conversationMap = useStore(conversationMap)

const [copied, copy] = useClipboardCopy(props.message.content)
const [showRawCode, setShowRawCode] = createSignal(false)
const [copied, setCopied] = createSignal(false)
const [isEditing, setIsEditing] = createSignal(false)
let inputRef: HTMLTextAreaElement
const [inputPrompt, setInputPrompt] = createSignal(props.message.content)

const currentConversation = () => {
return $conversationMap()[props.conversationId]
}

const handleCopyMessageItem = () => {
const [Iscopied, copy] = useClipboardCopy(props.message.content)
copy()
setCopied(Iscopied())
setTimeout(() => setCopied(false), 1000)
}
const handleDeleteMessageItem = () => {
deleteMessageByConversationId(props.conversationId, props.message)
}

const handleRetryMessageItem = () => {
const controller = new AbortController()
globalAbortController.set(controller)
spliceMessageByConversationId(props.conversationId, props.message)
handlePrompt(currentConversation(), '', controller.signal)
// TODO: scrollController seems not working
scrollController().scrollToBottom()
}

const handleEditMessageItem = () => {
setIsEditing(true)
inputRef.focus()
}

const handleSend = () => {
if (!inputRef.value)
return
const controller = new AbortController()
const currentMessage: MessageInstance = {
...props.message,
content: inputPrompt(),
}

globalAbortController.set(controller)
spliceUpdateMessageByConversationId(props.conversationId, currentMessage)
setIsEditing(false)
handlePrompt(currentConversation(), '', controller.signal)
scrollController().scrollToBottom()
}

const [menuList, setMenuList] = createSignal<MenuItem[]>([
// TODO: Retry send message
{ id: 'retry', label: 'Retry send', icon: 'i-ion:refresh-outline', role: 'all' },
{ id: 'retry', label: 'Retry send', icon: 'i-ion:refresh-outline', role: 'all', action: handleRetryMessageItem },
{ id: 'raw', label: 'Show raw code', icon: 'i-carbon-code', role: 'system', action: () => setShowRawCode(!showRawCode()) },
// TODO: Share message
// { id: 'share', label: 'Share message', icon: 'i-ion:ios-share-alt' },
// TODO: Edit message
{ id: 'edit', label: 'Edit message', icon: 'i-ion:md-create', role: 'user' },
{ id: 'copy', label: 'Copy message', icon: 'i-carbon-copy', role: 'all', action: copy },
{ id: 'edit', label: 'Edit message', icon: 'i-ion:md-create', role: 'user', action: handleEditMessageItem },
{ id: 'copy', label: 'Copy message', icon: 'i-carbon-copy', role: 'all', action: handleCopyMessageItem },
{ id: 'delete', label: 'Delete message', icon: 'i-carbon-trash-can', role: 'all', action: handleDeleteMessageItem },
])

Expand All @@ -45,6 +87,12 @@ export default (props: Props) => {
else
setMenuList(menuList().filter(item => ['all', 'system'].includes(item.role!)))

const roleClass = {
system: 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300',
user: 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300',
assistant: 'bg-gradient-to-b from-[#fccb90] to-[#d57eeb]',
}

return (
<div
class="p-6 break-words group relative"
Expand All @@ -54,13 +102,12 @@ export default (props: Props) => {
>
<div class="max-w-base flex gap-4 overflow-hidden">
<div class={`shrink-0 w-7 h-7 rounded-md op-80 ${roleClass[props.message.role]}`} />
{/* TODO: MessageItem options menu */}
<div class="sm:hidden block absolute bottom-2 right-2 z-10 op-70 cursor-pointer">
{/* <DropDownMenu menuList={menuList}>
<div class={`sm:hidden block absolute bottom-2 right-4 z-10 op-70 cursor-pointer ${isEditing() && '!hidden'}`}>
<DropDownMenu menuList={menuList()}>
<div class="text-xl i-carbon:overflow-menu-horizontal" />
</DropDownMenu> */}
</DropDownMenu>
</div>
<div class={`hidden sm:block absolute right-6 -top-4 ${!props.index && 'top-0'}`}>
<div class={`hidden sm:block absolute right-6 -top-4 ${!props.index && 'top-0'} ${isEditing() && '!hidden'}`}>
<div class="op-0 group-hover:op-80 fcc space-x-2 !bg-base px-4 py-1 rounded-xl b border-base transition-opacity duration-400">
<For each={menuList()}>
{item => (
Expand All @@ -75,17 +122,37 @@ export default (props: Props) => {
</div>
</div>
<div class="flex-1 min-w-0">
<StreamableText
text={props.message.content}
streamInfo={props.message.stream
? () => ({
conversationId: props.conversationId,
messageId: props.message.id || '',
handleStreaming: props.handleStreaming,
})
: undefined}
showRawCode={showRawCode()}
/>
<Show when={isEditing()} >
<textarea
ref={inputRef!}
value={inputPrompt()}
autocomplete="off"
onInput={() => { setInputPrompt(inputRef.value) }}
onKeyDown={(e) => {
e.key === 'Enter' && !e.isComposing && !e.shiftKey && handleSend()
}}
class="op-70 bg-darker py-4 px-[calc(max(1.5rem,(100%-48rem)/2))] w-full inset-0 scroll-pa-4 input-base rounded-md"
/>

<div class="flex justify-end space-x-2 -mt-1">
<div class="inline-flex items-center button" onClick={() => setIsEditing(false)}>Cancel</div>
<div class="inline-flex items-center button" onClick={() => handleSend()}>Submit</div>
</div>
</Show>
<Show when={!isEditing()}>
<StreamableText
text={props.message.content}
streamInfo={props.message.stream
? () => ({
conversationId: props.conversationId,
messageId: props.message.id || '',
handleStreaming: props.handleStreaming,
})
: undefined}
showRawCode={showRawCode()}
/>
</Show>

</div>

</div>
Expand Down
9 changes: 6 additions & 3 deletions src/components/ui/base/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export interface MenuItem {
id: string
label: string | JSXElement
icon?: string
// TODO: nested menu
children?: MenuItem[]
role?: string
action?: (params?: any) => void
Expand All @@ -24,7 +23,11 @@ export const DropDownMenu = (props: Props) => {
menu.machine({
id: createUniqueId(),
onSelect(details) {
console.log(details)
if (details.value) {
const currentAction = props.menuList.find(item => item.id === details.value)?.action
if (typeof currentAction === 'function')
currentAction()
}
},
}),
)
Expand All @@ -45,7 +48,7 @@ export const DropDownMenu = (props: Props) => {
})

return (
<div>
<div class="!outline-none">
<Dynamic component={resolvedChild} />
<Show when={api().isOpen}>
<Portal>
Expand Down
21 changes: 11 additions & 10 deletions src/logics/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { HandlerPayload, PromptResponse } from '@/types/provider'
import type { Conversation } from '@/types/conversation'
import type { ErrorMessage, Message } from '@/types/message'

export const handlePrompt = async(conversation: Conversation, prompt: string, signal?: AbortSignal) => {
export const handlePrompt = async(conversation: Conversation, prompt?: string, signal?: AbortSignal) => {
const generalSettings = getGeneralSettings()
const bot = getBotMetaById(conversation.bot)
const [providerId, botId] = conversation.bot.split(':')
Expand All @@ -22,13 +22,14 @@ export const handlePrompt = async(conversation: Conversation, prompt: string, si

if (bot.type !== 'chat_continuous')
clearMessagesByConversationId(conversation.id)

pushMessageByConversationId(conversation.id, {
id: `${conversation.id}:user:${Date.now()}`,
role: 'user',
content: prompt,
dateTime: new Date().getTime(),
})
if (prompt) {
pushMessageByConversationId(conversation.id, {
id: `${conversation.id}:user:${Date.now()}`,
role: 'user',
content: prompt,
dateTime: new Date().getTime(),
})
}

setLoadingStateByConversationId(conversation.id, true)
let providerResponse: PromptResponse
Expand Down Expand Up @@ -85,7 +86,7 @@ export const handlePrompt = async(conversation: Conversation, prompt: string, si

// Update conversation title
if (providerResponse && bot.type === 'chat_continuous' && !conversation.name) {
const inputText = conversation.systemInfo || prompt
const inputText = conversation.systemInfo || prompt!
const rapidPayload = generateRapidProviderPayload(promptHelper.summarizeText(inputText), provider.id)
const generatedTitle = await getProviderResponse(provider.id, rapidPayload).catch(() => {}) as string || inputText
updateConversationById(conversation.id, {
Expand Down Expand Up @@ -128,7 +129,7 @@ export const callProviderHandler = async(providerId: string, payload: HandlerPay

let response: PromptResponse
if (payload.botId === 'temp')
response = await provider.handleRapidPrompt?.(payload.prompt, payload.globalSettings)
response = await provider.handleRapidPrompt?.(payload.prompt!, payload.globalSettings)
else
response = await provider.handlePrompt?.(payload, signal)

Expand Down
28 changes: 28 additions & 0 deletions src/stores/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,31 @@ export const deleteMessageByConversationId = action(
})
},
)

export const spliceMessageByConversationId = action(
conversationMessagesMap,
'spliceMessagesByConversationId',
(map, id: string, payload: MessageInstance) => {
const oldMessages = map.get()[id] || []
const currentIndex = oldMessages.findIndex(message => message.id === payload.id)
map.setKey(id, [...oldMessages.slice(0, currentIndex + 1)])
db.setItem(id, [...oldMessages.slice(0, currentIndex + 1)])
updateConversationById(id, {
lastUseTime: Date.now(),
})
},
)

export const spliceUpdateMessageByConversationId = action(
conversationMessagesMap,
'spliceMessagesByConversationId',
(map, id: string, payload: MessageInstance) => {
const oldMessages = map.get()[id] || []
const currentIndex = oldMessages.findIndex(message => message.id === payload.id)
map.setKey(id, [...oldMessages.slice(0, currentIndex), payload])
db.setItem(id, [...oldMessages.slice(0, currentIndex), payload])
updateConversationById(id, {
lastUseTime: Date.now(),
})
},
)
3 changes: 2 additions & 1 deletion src/stores/settings.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { action, map } from 'nanostores'
import { action, atom, map } from 'nanostores'
import { db } from './storage/settings'
import { getProviderById, providerMetaList } from './provider'
import type { SettingsPayload } from '@/types/provider'
import type { GeneralSettings } from '@/types/app'

export const providerSettingsMap = map<Record<string, SettingsPayload>>({})
export const globalAbortController = atom<AbortController | null>(null)

export const rebuildSettingsStore = async() => {
const exportData = await db.exportData()
Expand Down
2 changes: 1 addition & 1 deletion src/types/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface HandlerPayload {
botId: string
globalSettings: SettingsPayload
botSettings: SettingsPayload
prompt: string
prompt?: string
messages: Message[]
}

Expand Down

0 comments on commit b9a071b

Please sign in to comment.