1
- import { For } from 'solid-js/web'
1
+ import { For , Show } from 'solid-js/web'
2
2
import { createSignal } from 'solid-js'
3
+ import { useStore } from '@nanostores/solid'
3
4
import { useClipboardCopy } from '@/hooks'
4
- import { deleteMessageByConversationId } from '@/stores/messages'
5
+ import { deleteMessageByConversationId , spliceMessageByConversationId , spliceUpdateMessageByConversationId } from '@/stores/messages'
6
+ import { conversationMap } from '@/stores/conversation'
7
+ import { handlePrompt } from '@/logics/conversation'
8
+ import { scrollController } from '@/stores/ui'
9
+ import { globalAbortController } from '@/stores/settings'
5
10
import StreamableText from '../StreamableText'
6
- import { Tooltip } from '../ui/base'
11
+ import { DropDownMenu , Tooltip } from '../ui/base'
7
12
import type { MenuItem } from '../ui/base'
8
13
import type { MessageInstance } from '@/types/message'
9
14
@@ -15,28 +20,65 @@ interface Props {
15
20
}
16
21
17
22
export default ( props : Props ) => {
18
- const roleClass = {
19
- system : 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300' ,
20
- user : 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300' ,
21
- assistant : 'bg-gradient-to-b from-[#fccb90] to-[#d57eeb]' ,
22
- }
23
+ const $conversationMap = useStore ( conversationMap )
23
24
24
- const [ copied , copy ] = useClipboardCopy ( props . message . content )
25
25
const [ showRawCode , setShowRawCode ] = createSignal ( false )
26
+ const [ copied , setCopied ] = createSignal ( false )
27
+ const [ isEditing , setIsEditing ] = createSignal ( false )
28
+ let inputRef : HTMLTextAreaElement
29
+ const [ inputPrompt , setInputPrompt ] = createSignal ( props . message . content )
30
+
31
+ const currentConversation = ( ) => {
32
+ return $conversationMap ( ) [ props . conversationId ]
33
+ }
26
34
35
+ const handleCopyMessageItem = ( ) => {
36
+ const [ Iscopied , copy ] = useClipboardCopy ( props . message . content )
37
+ copy ( )
38
+ setCopied ( Iscopied ( ) )
39
+ setTimeout ( ( ) => setCopied ( false ) , 1000 )
40
+ }
27
41
const handleDeleteMessageItem = ( ) => {
28
42
deleteMessageByConversationId ( props . conversationId , props . message )
29
43
}
30
44
45
+ const handleRetryMessageItem = ( ) => {
46
+ const controller = new AbortController ( )
47
+ globalAbortController . set ( controller )
48
+ spliceMessageByConversationId ( props . conversationId , props . message )
49
+ handlePrompt ( currentConversation ( ) , '' , controller . signal )
50
+ // TODO: scrollController seems not working
51
+ scrollController ( ) . scrollToBottom ( )
52
+ }
53
+
54
+ const handleEditMessageItem = ( ) => {
55
+ setIsEditing ( true )
56
+ inputRef . focus ( )
57
+ }
58
+
59
+ const handleSend = ( ) => {
60
+ if ( ! inputRef . value )
61
+ return
62
+ const controller = new AbortController ( )
63
+ const currentMessage : MessageInstance = {
64
+ ...props . message ,
65
+ content : inputPrompt ( ) ,
66
+ }
67
+
68
+ globalAbortController . set ( controller )
69
+ spliceUpdateMessageByConversationId ( props . conversationId , currentMessage )
70
+ setIsEditing ( false )
71
+ handlePrompt ( currentConversation ( ) , '' , controller . signal )
72
+ scrollController ( ) . scrollToBottom ( )
73
+ }
74
+
31
75
const [ menuList , setMenuList ] = createSignal < MenuItem [ ] > ( [
32
- // TODO: Retry send message
33
- { id : 'retry' , label : 'Retry send' , icon : 'i-ion:refresh-outline' , role : 'all' } ,
76
+ { id : 'retry' , label : 'Retry send' , icon : 'i-ion:refresh-outline' , role : 'all' , action : handleRetryMessageItem } ,
34
77
{ id : 'raw' , label : 'Show raw code' , icon : 'i-carbon-code' , role : 'system' , action : ( ) => setShowRawCode ( ! showRawCode ( ) ) } ,
35
78
// TODO: Share message
36
79
// { id: 'share', label: 'Share message', icon: 'i-ion:ios-share-alt' },
37
- // TODO: Edit message
38
- { id : 'edit' , label : 'Edit message' , icon : 'i-ion:md-create' , role : 'user' } ,
39
- { id : 'copy' , label : 'Copy message' , icon : 'i-carbon-copy' , role : 'all' , action : copy } ,
80
+ { id : 'edit' , label : 'Edit message' , icon : 'i-ion:md-create' , role : 'user' , action : handleEditMessageItem } ,
81
+ { id : 'copy' , label : 'Copy message' , icon : 'i-carbon-copy' , role : 'all' , action : handleCopyMessageItem } ,
40
82
{ id : 'delete' , label : 'Delete message' , icon : 'i-carbon-trash-can' , role : 'all' , action : handleDeleteMessageItem } ,
41
83
] )
42
84
@@ -45,6 +87,12 @@ export default (props: Props) => {
45
87
else
46
88
setMenuList ( menuList ( ) . filter ( item => [ 'all' , 'system' ] . includes ( item . role ! ) ) )
47
89
90
+ const roleClass = {
91
+ system : 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300' ,
92
+ user : 'bg-gradient-to-b from-gray-300 via-gray-200 to-gray-300' ,
93
+ assistant : 'bg-gradient-to-b from-[#fccb90] to-[#d57eeb]' ,
94
+ }
95
+
48
96
return (
49
97
< div
50
98
class = "p-6 break-words group relative"
@@ -54,13 +102,12 @@ export default (props: Props) => {
54
102
>
55
103
< div class = "max-w-base flex gap-4 overflow-hidden" >
56
104
< div class = { `shrink-0 w-7 h-7 rounded-md op-80 ${ roleClass [ props . message . role ] } ` } />
57
- { /* TODO: MessageItem options menu */ }
58
- < div class = "sm:hidden block absolute bottom-2 right-2 z-10 op-70 cursor-pointer" >
59
- { /* <DropDownMenu menuList={menuList}>
105
+ < div class = { `sm:hidden block absolute bottom-2 right-4 z-10 op-70 cursor-pointer ${ isEditing ( ) && '!hidden' } ` } >
106
+ < DropDownMenu menuList = { menuList ( ) } >
60
107
< div class = "text-xl i-carbon:overflow-menu-horizontal" />
61
- </DropDownMenu> */ }
108
+ </ DropDownMenu >
62
109
</ div >
63
- < div class = { `hidden sm:block absolute right-6 -top-4 ${ ! props . index && 'top-0' } ` } >
110
+ < div class = { `hidden sm:block absolute right-6 -top-4 ${ ! props . index && 'top-0' } ${ isEditing ( ) && '!hidden' } ` } >
64
111
< 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" >
65
112
< For each = { menuList ( ) } >
66
113
{ item => (
@@ -75,17 +122,37 @@ export default (props: Props) => {
75
122
</ div >
76
123
</ div >
77
124
< div class = "flex-1 min-w-0" >
78
- < StreamableText
79
- text = { props . message . content }
80
- streamInfo = { props . message . stream
81
- ? ( ) => ( {
82
- conversationId : props . conversationId ,
83
- messageId : props . message . id || '' ,
84
- handleStreaming : props . handleStreaming ,
85
- } )
86
- : undefined }
87
- showRawCode = { showRawCode ( ) }
88
- />
125
+ < Show when = { isEditing ( ) } >
126
+ < textarea
127
+ ref = { inputRef ! }
128
+ value = { inputPrompt ( ) }
129
+ autocomplete = "off"
130
+ onInput = { ( ) => { setInputPrompt ( inputRef . value ) } }
131
+ onKeyDown = { ( e ) => {
132
+ e . key === 'Enter' && ! e . isComposing && ! e . shiftKey && handleSend ( )
133
+ } }
134
+ 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"
135
+ />
136
+
137
+ < div class = "flex justify-end space-x-2 -mt-1" >
138
+ < div class = "inline-flex items-center button" onClick = { ( ) => setIsEditing ( false ) } > Cancel</ div >
139
+ < div class = "inline-flex items-center button" onClick = { ( ) => handleSend ( ) } > Submit</ div >
140
+ </ div >
141
+ </ Show >
142
+ < Show when = { ! isEditing ( ) } >
143
+ < StreamableText
144
+ text = { props . message . content }
145
+ streamInfo = { props . message . stream
146
+ ? ( ) => ( {
147
+ conversationId : props . conversationId ,
148
+ messageId : props . message . id || '' ,
149
+ handleStreaming : props . handleStreaming ,
150
+ } )
151
+ : undefined }
152
+ showRawCode = { showRawCode ( ) }
153
+ />
154
+ </ Show >
155
+
89
156
</ div >
90
157
91
158
</ div >
0 commit comments