1
- import { useEffect , useRef } from "react" ;
2
-
3
1
import { Node } from "reactflow" ;
4
2
5
- import { Spinner , Text } from "@chakra-ui/react" ;
3
+ import { useState , useEffect , useRef } from "react" ;
4
+
5
+ import { Spinner , Text , Button } from "@chakra-ui/react" ;
6
+
7
+ import { EditIcon , ViewIcon } from "@chakra-ui/icons" ;
6
8
7
9
import TextareaAutosize from "react-textarea-autosize" ;
8
10
9
11
import { getFluxNodeTypeColor , getFluxNodeTypeDarkColor } from "../utils/color" ;
12
+ import { TextAndCodeBlock } from "./utils/TextAndCodeBlock" ;
10
13
import { FluxNodeData , FluxNodeType , Settings } from "../utils/types" ;
11
14
import { displayNameFromFluxNodeType } from "../utils/fluxNode" ;
12
15
import { LabeledSlider } from "./utils/LabeledInputs" ;
13
- import { Row , Center } from "../utils/chakra" ;
16
+ import { Row , Center , Column } from "../utils/chakra" ;
14
17
import { BigButton } from "./utils/BigButton" ;
15
18
16
19
export function Prompt ( {
@@ -44,6 +47,15 @@ export function Prompt({
44
47
}
45
48
} ;
46
49
50
+ /*//////////////////////////////////////////////////////////////
51
+ STATE
52
+ //////////////////////////////////////////////////////////////*/
53
+
54
+ const [ isEditing , setIsEditing ] = useState (
55
+ promptNodeType === FluxNodeType . User || promptNodeType === FluxNodeType . System
56
+ ) ;
57
+ const [ hoveredNodeId , setHoveredNodeId ] = useState < string | null > ( null ) ;
58
+
47
59
/*//////////////////////////////////////////////////////////////
48
60
EFFECTS
49
61
//////////////////////////////////////////////////////////////*/
@@ -57,17 +69,30 @@ export function Prompt({
57
69
. getElementById ( "promptButtons" )
58
70
?. scrollIntoView ( /* { behavior: "smooth" } */ ) ;
59
71
60
- const promptBox = window . document . getElementById (
61
- "promptBox"
62
- ) as HTMLTextAreaElement | null ;
72
+ // If the user clicked on the node, we assume they want to edit it.
73
+ // Otherwise, we only put them in edit mode if its a user or system node.
74
+ setIsEditing (
75
+ textOffsetRef . current !== - 1 ||
76
+ promptNodeType === FluxNodeType . User ||
77
+ promptNodeType === FluxNodeType . System
78
+ ) ;
79
+ } , [ promptNode . id ] ) ;
63
80
64
- // Focus the text box and move the cursor to chosen offset (defaults to end).
65
- promptBox ?. setSelectionRange ( textOffsetRef . current , textOffsetRef . current ) ;
66
- promptBox ?. focus ( ) ;
81
+ // Focus the textbox when the user changes into edit mode.
82
+ useEffect ( ( ) => {
83
+ if ( isEditing ) {
84
+ const promptBox = window . document . getElementById (
85
+ "promptBox"
86
+ ) as HTMLTextAreaElement | null ;
67
87
68
- // Default to moving to the start of the text.
69
- textOffsetRef . current = - 1 ;
70
- } , [ promptNode . id ] ) ;
88
+ // Focus the text box and move the cursor to chosen offset (defaults to end).
89
+ promptBox ?. setSelectionRange ( textOffsetRef . current , textOffsetRef . current ) ;
90
+ promptBox ?. focus ( ) ;
91
+
92
+ // Default to moving to the end of the text.
93
+ textOffsetRef . current = - 1 ;
94
+ }
95
+ } , [ promptNode . id , isEditing ] ) ;
71
96
72
97
/*//////////////////////////////////////////////////////////////
73
98
APP
@@ -87,62 +112,98 @@ export function Prompt({
87
112
< Row
88
113
mb = { 2 }
89
114
p = { 3 }
90
- whiteSpace = "pre-wrap" // Preserve newlines.
91
115
mainAxisAlignment = "flex-start"
92
116
crossAxisAlignment = "flex-start"
93
117
borderRadius = "6px"
94
118
borderLeftWidth = { isLast ? "4px" : "0px" }
119
+ _hover = { {
120
+ boxShadow : isLast ? "none" : "0 0 0 0.5px #1a192b" ,
121
+ } }
95
122
borderColor = { getFluxNodeTypeDarkColor ( data . fluxNodeType ) }
123
+ position = "relative"
124
+ onMouseEnter = { ( ) => setHoveredNodeId ( node . id ) }
125
+ onMouseLeave = { ( ) => setHoveredNodeId ( null ) }
96
126
bg = { getFluxNodeTypeColor ( data . fluxNodeType ) }
97
127
key = { node . id }
98
- { ...( ! isLast
99
- ? {
100
- onClick : ( ) => {
128
+ onClick = {
129
+ isLast
130
+ ? undefined
131
+ : ( ) => {
101
132
const selection = window . getSelection ( ) ;
102
133
103
134
// We don't want to trigger the selection
104
135
// if they're just selecting/copying text.
105
136
if ( selection ?. isCollapsed ) {
137
+ // TODO: Note this is basically broken because of codeblocks.
106
138
textOffsetRef . current = selection . anchorOffset ?? 0 ;
107
139
108
140
selectNode ( node . id ) ;
141
+ setIsEditing ( true ) ;
109
142
}
110
- } ,
111
- cursor : "pointer" ,
112
- }
113
- : { } ) }
143
+ }
144
+ }
145
+ cursor = { isLast && isEditing ? "text" : "pointer" }
114
146
>
115
147
{ data . generating && data . text === "" ? (
116
148
< Center expand >
117
149
< Spinner />
118
150
</ Center >
119
151
) : (
120
152
< >
153
+ < Button
154
+ display = {
155
+ hoveredNodeId === promptNode . id && promptNode . id === node . id
156
+ ? "block"
157
+ : "none"
158
+ }
159
+ onClick = { ( ) => setIsEditing ( ! isEditing ) }
160
+ position = "absolute"
161
+ top = { 1 }
162
+ right = { 1 }
163
+ zIndex = { 10 }
164
+ variant = "outline"
165
+ border = "0px"
166
+ _hover = { { background : "none" } }
167
+ p = { 1 }
168
+ >
169
+ { isEditing ? < ViewIcon boxSize = { 4 } /> : < EditIcon boxSize = { 4 } /> }
170
+ </ Button >
121
171
< Text fontWeight = "bold" width = "auto" whiteSpace = "nowrap" >
122
172
{ displayNameFromFluxNodeType ( data . fluxNodeType ) }
123
173
:
124
174
</ Text >
125
- { isLast ? (
126
- < TextareaAutosize
127
- id = "promptBox"
128
- style = { {
129
- width : "100%" ,
130
- backgroundColor : "transparent" ,
131
- outline : "none" ,
132
- } }
133
- value = { data . text ?? "" }
134
- onChange = { ( e ) => onType ( e . target . value ) }
135
- placeholder = {
136
- data . fluxNodeType === FluxNodeType . User
137
- ? "Write a poem about..."
138
- : data . fluxNodeType === FluxNodeType . System
139
- ? "You are ChatGPT..."
140
- : undefined
141
- }
142
- />
143
- ) : (
144
- data . text
145
- ) }
175
+ < Column
176
+ width = "100%"
177
+ marginRight = "30px"
178
+ whiteSpace = "pre-wrap" // Preserve newlines.
179
+ mainAxisAlignment = "flex-start"
180
+ crossAxisAlignment = "flex-start"
181
+ borderRadius = "6px"
182
+ wordBreak = "break-word"
183
+ onClick = { isEditing ? undefined : ( ) => setIsEditing ( true ) }
184
+ >
185
+ { isLast && isEditing ? (
186
+ < TextareaAutosize
187
+ id = "promptBox"
188
+ style = { {
189
+ width : "100%" ,
190
+ backgroundColor : "transparent" ,
191
+ outline : "none" ,
192
+ } }
193
+ value = { data . text ?? "" }
194
+ onChange = { ( e ) => onType ( e . target . value ) }
195
+ placeholder = {
196
+ data . fluxNodeType === FluxNodeType . User
197
+ ? "Write a poem about..."
198
+ : data . fluxNodeType === FluxNodeType . System
199
+ ? "You are ChatGPT..."
200
+ : undefined
201
+ }
202
+ />
203
+ ) : (
204
+ < TextAndCodeBlock text = { data . text } />
205
+ ) }
206
+ </ Column >
146
207
</ >
147
208
) }
148
209
</ Row >
0 commit comments