1
- import { useState } from "react" ;
2
1
import { ThemeProvider } from "@mui/material/styles" ;
3
2
import theme from "./components/layout/Theme" ;
4
- import {
5
- Container ,
6
- TextField ,
7
- Button ,
8
- Typography ,
9
- MenuItem ,
10
- Select ,
11
- InputLabel ,
12
- FormControl ,
13
- IconButton ,
14
- Grid2 as Grid ,
15
- Accordion ,
16
- AccordionSummary ,
17
- AccordionDetails ,
18
- Box ,
19
- } from "@mui/material" ;
20
- import { Add , Delete , FileCopy , GitHub , LinkedIn } from "@mui/icons-material" ;
21
- import { useBoundStore } from "./stores" ;
3
+ import Calculator from "./components/calculator/Calculator" ;
4
+ import CalculationExplanation from "./components/calculator/CalculationExplanation" ;
5
+ import Footer from "./components/layout/Footer" ;
6
+ import { Container , Typography , Grid2 as Grid } from "@mui/material" ;
22
7
23
8
function App ( ) {
24
- const [ modelName , setModelName ] = useState ( "" ) ;
25
-
26
- const images = useBoundStore ( ( state ) => state . images ) ;
27
- const models = useBoundStore ( ( state ) => state . models ) ;
28
- const setModel = useBoundStore ( ( state ) => state . setModel ) ;
29
- const addImage = useBoundStore ( ( state ) => state . addImage ) ;
30
- const updateImage = useBoundStore ( ( state ) => state . updateImage ) ;
31
- const removeImage = useBoundStore ( ( state ) => state . removeImage ) ;
32
- const runCalculation = useBoundStore ( ( state ) => state . runCalculation ) ;
33
- const resetCalculation = useBoundStore ( ( state ) => state . resetCalculation ) ;
34
- const totalTokens = useBoundStore ( ( state ) => state . totalTokens ) ;
35
- const totalCost = useBoundStore ( ( state ) => state . totalCost ) ;
36
- const model = useBoundStore ( ( state ) => state . model ) ;
37
-
38
- const selectModel = ( model ) => {
39
- setModelName ( model ) ;
40
-
41
- const selectedModel = models . find ( ( m ) => m . name === model ) ;
42
- if ( selectedModel ) {
43
- setModel ( selectedModel ) ;
44
- runCalculation ( ) ;
45
- } else {
46
- setModel ( null ) ;
47
- resetCalculation ( ) ;
48
- }
49
- } ;
50
-
51
- const addNewImage = ( ) => {
52
- addImage ( { height : 0 , width : 0 , multiplier : 1 } ) ;
53
- } ;
54
-
55
- const cloneImage = ( index ) => {
56
- const image = images [ index ] ;
57
- addImage ( {
58
- height : image . height ,
59
- width : image . width ,
60
- multiplier : image . multiplier ,
61
- } ) ;
62
- } ;
63
-
64
- const handleSubmit = ( event ) => {
65
- event . preventDefault ( ) ;
66
- runCalculation ( ) ;
67
- } ;
68
-
69
9
return (
70
10
< ThemeProvider theme = { theme } >
71
11
< Container maxWidth = "lg" >
@@ -74,291 +14,12 @@ function App() {
74
14
</ Typography >
75
15
< Grid container spacing = { 4 } >
76
16
< Grid size = { { xs : 12 , md : 8 } } >
77
- < form onSubmit = { handleSubmit } >
78
- < FormControl fullWidth margin = "normal" required >
79
- < InputLabel id = "model-label" > Model</ InputLabel >
80
- < Select
81
- labelId = "model-label"
82
- value = { modelName }
83
- onChange = { ( e ) => selectModel ( e . target . value ) }
84
- label = "Model"
85
- >
86
- < MenuItem value = "" >
87
- < em > None</ em >
88
- </ MenuItem >
89
- { models . map ( ( model ) => (
90
- < MenuItem key = { model . name } value = { model . name } >
91
- { model . name }
92
- </ MenuItem >
93
- ) ) }
94
- </ Select >
95
- </ FormControl >
96
-
97
- { images . map ( ( image , index ) => (
98
- < Grid container spacing = { 1 } alignItems = "center" key = { index } >
99
- < Grid size = { { sm : 4 } } >
100
- < TextField
101
- label = "Image Height (px)"
102
- type = "number"
103
- value = { image . height }
104
- onChange = { ( e ) =>
105
- updateImage ( index , "height" , e . target . value )
106
- }
107
- margin = "normal"
108
- required
109
- fullWidth
110
- />
111
- </ Grid >
112
- < Grid size = { { sm : 4 } } >
113
- < TextField
114
- label = "Image Width (px)"
115
- type = "number"
116
- value = { image . width }
117
- onChange = { ( e ) =>
118
- updateImage ( index , "width" , e . target . value )
119
- }
120
- margin = "normal"
121
- required
122
- fullWidth
123
- />
124
- </ Grid >
125
- < Grid size = { { sm : 2 } } >
126
- < TextField
127
- label = "Count"
128
- type = "number"
129
- value = { image . multiplier }
130
- onChange = { ( e ) =>
131
- updateImage ( index , "multiplier" , e . target . value )
132
- }
133
- margin = "normal"
134
- required
135
- fullWidth
136
- />
137
- </ Grid >
138
- < Grid size = { { sm : 1 } } >
139
- < IconButton
140
- onClick = { ( ) => cloneImage ( index ) }
141
- color = "primary"
142
- >
143
- < FileCopy />
144
- </ IconButton >
145
- </ Grid >
146
- < Grid size = { { sm : 1 } } >
147
- < IconButton
148
- onClick = { ( ) => removeImage ( index ) }
149
- color = "secondary"
150
- >
151
- < Delete />
152
- </ IconButton >
153
- </ Grid >
154
- </ Grid >
155
- ) ) }
156
-
157
- < Button
158
- onClick = { addNewImage }
159
- variant = "contained"
160
- color = "info"
161
- style = { { marginBottom : "20px" } }
162
- fullWidth
163
- >
164
- < Add /> Add Image
165
- </ Button >
166
-
167
- < Button
168
- type = "submit"
169
- variant = "contained"
170
- color = "primary"
171
- fullWidth
172
- >
173
- Calculate
174
- </ Button >
175
- </ form >
176
-
177
- < Box mt = { 2 } >
178
- { images . map ( ( image , index ) =>
179
- image . resizedHeight ? (
180
- < Box key = { index } sx = { { display : "flex" } } mb = { 2 } >
181
- < Box sx = { { flex : "1 0 auto" } } >
182
- < Typography variant = "h6" gutterBottom >
183
- Image { index + 1 }
184
- </ Typography >
185
- < Box
186
- sx = { {
187
- display : "flex" ,
188
- alignItems : "center" ,
189
- } }
190
- >
191
- < Box sx = { { flexGrow : 1 } } >
192
- < Typography variant = "body2" color = "textSecondary" >
193
- Resized Size
194
- </ Typography >
195
- < Typography variant = "body1" gutterBottom >
196
- { image . resizedHeight } x { image . resizedWidth }
197
- </ Typography >
198
- </ Box >
199
- < Box sx = { { flexGrow : 1 } } >
200
- < Typography variant = "body2" color = "textSecondary" >
201
- Tiles (per image)
202
- </ Typography >
203
- < Typography variant = "body1" gutterBottom >
204
- { image . tilesHigh } x { image . tilesWide }
205
- </ Typography >
206
- </ Box >
207
- < Box sx = { { flexGrow : 1 } } >
208
- < Typography variant = "body2" color = "textSecondary" >
209
- Total tiles
210
- </ Typography >
211
- < Typography variant = "body1" gutterBottom >
212
- { image . totalTiles }
213
- </ Typography >
214
- </ Box >
215
- </ Box >
216
- </ Box >
217
- </ Box >
218
- ) : null
219
- ) }
220
-
221
- { totalTokens !== null && (
222
- < Box sx = { { display : "flex" } } mb = { 2 } >
223
- < Box sx = { { flex : "1 0 auto" } } >
224
- < Typography variant = "h6" gutterBottom >
225
- Result
226
- </ Typography >
227
- < Box
228
- sx = { {
229
- display : "flex" ,
230
- alignItems : "center" ,
231
- } }
232
- >
233
- { model && (
234
- < Box sx = { { flexGrow : 1 } } >
235
- < Typography variant = "body2" color = "textSecondary" >
236
- Base tokens
237
- </ Typography >
238
- < Typography variant = "body1" gutterBottom >
239
- { model . baseTokens }
240
- </ Typography >
241
- </ Box >
242
- ) }
243
-
244
- { model && (
245
- < Box sx = { { flexGrow : 1 } } >
246
- < Typography variant = "body2" color = "textSecondary" >
247
- Tile tokens
248
- </ Typography >
249
- < Typography variant = "body1" gutterBottom >
250
- { model . tokensPerTile } x{ " " }
251
- { images
252
- . map ( ( image ) => image . totalTiles ?? 0 )
253
- . reduce ( ( acc , val ) => acc + val , 0 ) } { " " }
254
- = { totalTokens - model . baseTokens }
255
- </ Typography >
256
- </ Box >
257
- ) }
258
-
259
- { totalTokens !== null && (
260
- < Box sx = { { flexGrow : 1 } } >
261
- < Typography variant = "body2" color = "textSecondary" >
262
- Total tokens
263
- </ Typography >
264
- < Typography variant = "body1" gutterBottom >
265
- { totalTokens }
266
- </ Typography >
267
- </ Box >
268
- ) }
269
-
270
- { totalCost !== null && (
271
- < Box sx = { { flexGrow : 1 } } >
272
- < Typography variant = "body2" color = "textSecondary" >
273
- Total cost
274
- </ Typography >
275
- < Typography variant = "body1" gutterBottom >
276
- ${ totalCost }
277
- </ Typography >
278
- </ Box >
279
- ) }
280
- </ Box >
281
- </ Box >
282
- </ Box >
283
- ) }
284
- </ Box >
17
+ < Calculator />
285
18
</ Grid >
286
- < Grid size = { { xs : 12 , md : 4 } } >
287
- < Accordion expanded >
288
- < AccordionSummary
289
- aria-controls = "calculation-explanation-content"
290
- id = "calculation-explanation-header"
291
- >
292
- < Typography variant = "h6" > How the Calculation Works</ Typography >
293
- </ AccordionSummary >
294
- { model ? (
295
- < AccordionDetails >
296
- < Typography >
297
- < p > The calculation involves several steps:</ p >
298
- </ Typography >
299
- < Typography >
300
- 1. < b > Resizing Images</ b > : Ensure each image is resized to
301
- fit within the maximum dimension { model . maxImageDimension }
302
- px, and has at least { model . imageMinSizeLength } px on the
303
- shortest side, while maintaining its aspect ratio.
304
- </ Typography >
305
- < Typography >
306
- 2. < b > Calculating Tiles</ b > : The resized image is divided
307
- into tiles based on the model's tile size of{ " " }
308
- { model . tileSizeLength } px by { model . tileSizeLength } px.
309
- </ Typography >
310
- < Typography >
311
- 3. < b > Token Calculation</ b > : The total number of tokens is
312
- calculated by multiplying the number of tiles by the tokens
313
- per tile ({ model . tokensPerTile } ) and adding{ " " }
314
- { model . baseTokens } base tokens.
315
- </ Typography >
316
- < Typography >
317
- 4. < b > Cost Calculation</ b > : The total cost is calculated
318
- based on the total number of tokens and the cost per
319
- thousand tokens (${ model . costPerThousandTokens } ).
320
- </ Typography >
321
- </ AccordionDetails >
322
- ) : (
323
- < AccordionDetails >
324
- < Typography >
325
- Please select a model to see the calculation explanation.
326
- </ Typography >
327
- </ AccordionDetails >
328
- ) }
329
- </ Accordion >
330
19
331
- < Box mt = { 4 } textAlign = "center" >
332
- < Typography variant = "body2" > Created by James Croft.</ Typography >
333
- </ Box >
334
- < Box
335
- sx = { {
336
- display : "flex" ,
337
- flexDirection : "column" ,
338
- mt : 2 ,
339
- gap : 1 ,
340
- } }
341
- >
342
- < Button
343
- fullWidth
344
- variant = "outlined"
345
- color = "dark"
346
- startIcon = { < GitHub /> }
347
- href = "https://github.com/jamesmcroft/openai-image-token-calculator"
348
- target = "_blank"
349
- >
350
- View Source on GitHub
351
- </ Button >
352
- < Button
353
- variant = "outlined"
354
- color = "primary"
355
- startIcon = { < LinkedIn /> }
356
- href = "https://www.linkedin.com/in/jmcroft"
357
- target = "_blank"
358
- >
359
- View Profile on LinkedIn
360
- </ Button >
361
- </ Box >
20
+ < Grid size = { { xs : 12 , md : 4 } } >
21
+ < CalculationExplanation />
22
+ < Footer />
362
23
</ Grid >
363
24
</ Grid >
364
25
</ Container >
0 commit comments