@@ -2,16 +2,16 @@ import Statuses from "@/src/components/shared/statuses/Statuses";
2
2
import { selectAllLookupLists , setAllLookupLists } from "@/src/reduxStore/states/pages/lookup-lists" ;
3
3
import { selectAttributes , selectVisibleAttributeAC , setAllAttributes , setLabelingTasksAll , updateAttributeById } from "@/src/reduxStore/states/pages/settings" ;
4
4
import { selectProjectId } from "@/src/reduxStore/states/project"
5
- import { Attribute , AttributeState } from "@/src/types/components/projects/projectId/settings/data-schema" ;
5
+ import { Attribute , AttributeState , LLMConfig } from "@/src/types/components/projects/projectId/settings/data-schema" ;
6
6
import { DataTypeEnum } from "@/src/types/shared/general" ;
7
- import { postProcessCurrentAttribute } from "@/src/util/components/projects/projectId/settings/attribute-calculation-helper" ;
7
+ import { LLM_PROVIDER_OPTIONS , postProcessCurrentAttribute } from "@/src/util/components/projects/projectId/settings/attribute-calculation-helper" ;
8
8
import { ATTRIBUTES_VISIBILITY_STATES , DATA_TYPES , getTooltipVisibilityState } from "@/src/util/components/projects/projectId/settings/data-schema-helper" ;
9
9
import { copyToClipboard } from "@/submodules/javascript-functions/general" ;
10
10
import { Editor } from "@monaco-editor/react" ;
11
11
import { Tooltip } from "@nextui-org/react" ;
12
12
import { IconAlertTriangleFilled , IconArrowLeft , IconCircleCheckFilled } from "@tabler/icons-react" ;
13
13
import { useRouter } from "next/router" ;
14
- import { useCallback , useEffect , useState } from "react" ;
14
+ import { useCallback , useEffect , useMemo , useState } from "react" ;
15
15
import { useDispatch , useSelector } from "react-redux"
16
16
import ExecutionContainer from "./ExecutionContainer" ;
17
17
import { getPythonFunctionRegExMatch , toPythonFunctionName } from "@/submodules/javascript-functions/python-functions-parser" ;
@@ -24,7 +24,6 @@ import { TOOLTIPS_DICT } from "@/src/util/tooltip-constants";
24
24
import { selectAllUsers , selectOrganizationId , setComments } from "@/src/reduxStore/states/general" ;
25
25
import { CommentDataManager } from "@/src/util/classes/comments" ;
26
26
import { CommentType } from "@/src/types/shared/comments" ;
27
- import { AttributeCodeLookup } from "@/src/util/classes/attribute-calculation" ;
28
27
import KernDropdown from "@/submodules/react-components/components/KernDropdown" ;
29
28
import { useWebsocket } from "@/submodules/react-components/hooks/web-socket/useWebsocket" ;
30
29
import { postProcessLabelingTasksSchema } from "@/src/util/components/projects/projectId/settings/labeling-tasks-helper" ;
@@ -35,9 +34,16 @@ import { getLabelingTasksByProjectId, getProjectTokenization } from "@/src/servi
35
34
import { getAttributeByAttributeId , updateAttribute } from "@/src/services/base/project-setting" ;
36
35
import { Application , CurrentPage } from "@/submodules/react-components/hooks/web-socket/constants" ;
37
36
import { VisitBricksButton } from "@/src/components/shared/bricks/VisitBricksButton" ;
37
+ import LLMResponseConfig from "./LLMResponseConfig" ;
38
+ import useDebounce from "@/submodules/react-components/hooks/useHooks/useDebounce" ;
39
+ import useRefFor from "@/submodules/react-components/hooks/useRefFor" ;
40
+ import { simpleDictCompare } from "@/submodules/javascript-functions/validations" ;
41
+ import { LLM_CODE_TEMPLATE_EXAMPLES , LLM_CODE_TEMPLATE_OPTIONS } from "./LLM/llmTemplates" ;
38
42
39
43
const EDITOR_OPTIONS = { theme : 'vs-light' , language : 'python' , readOnly : false } ;
40
44
45
+
46
+
41
47
export default function AttributeCalculation ( ) {
42
48
const router = useRouter ( ) ;
43
49
const dispatch = useDispatch ( ) ;
@@ -59,26 +65,44 @@ export default function AttributeCalculation() {
59
65
const [ attributeName , setAttributeName ] = useState ( '' ) ;
60
66
const [ checkUnsavedChanges , setCheckUnsavedChanges ] = useState ( false ) ;
61
67
const [ enableRunButton , setEnableButton ] = useState ( false ) ;
68
+ const [ additionalConfigTmp , setAdditionalConfigTmp ] = useState < LLMConfig > ( null ) ;
69
+
70
+ const currentAttributeRef = useRefFor ( currentAttribute ) ;
71
+ const debouncedConfig = useDebounce ( additionalConfigTmp , 1000 ) ;
72
+
73
+ const updateSourceCode = useCallback ( ( value : string , attributeNameParam ?: string ) => {
74
+ var regMatch : any = getPythonFunctionRegExMatch ( value ) ;
75
+ if ( ! regMatch ) {
76
+ console . log ( "Can't find python function name -- seems wrong -- better dont save" ) ;
77
+ return ;
78
+ }
79
+ const finalSourceCode = value . replace ( regMatch [ 0 ] , 'def ac(record)' ) ;
80
+ updateAttribute ( projectId , currentAttribute . id , ( res ) => {
62
81
82
+ } , null , null , attributeNameParam , finalSourceCode ) ;
83
+ } , [ projectId , currentAttribute ] ) ;
84
+
85
+ useEffect ( ( ) => setAdditionalConfigTmp ( currentAttribute ?. additionalConfig ) , [ currentAttribute ?. additionalConfig ] )
63
86
64
87
useEffect ( ( ) => {
65
88
if ( ! projectId ) return ;
66
- if ( ! currentAttribute || attributes . length == 0 ) {
67
- getAttributes ( projectId , [ 'ALL' ] , ( res ) => {
68
- dispatch ( setAllAttributes ( res ) ) ;
69
- const currentAttribute = postProcessCurrentAttribute ( attributes . find ( ( attribute ) => attribute . id === router . query . attributeId ) ) ;
70
- setCurrentAttribute ( currentAttribute ) ;
71
- setEditorValue ( currentAttribute ?. sourceCodeToDisplay ) ;
72
- } ) ;
73
- }
74
89
if ( lookupLists . length == 0 ) {
75
90
getLookupListsByProjectId ( projectId , ( res ) => {
76
91
dispatch ( setAllLookupLists ( res ) ) ;
77
92
} ) ;
78
93
}
79
94
refetchLabelingTasksAndProcess ( ) ;
80
95
checkProjectTokenization ( ) ;
81
- } , [ projectId , attributes , currentAttribute ] ) ;
96
+ } , [ projectId , attributes ] ) ;
97
+
98
+ useEffect ( ( ) => {
99
+ if ( currentAttribute || ! projectId ) return ;
100
+ getAttributeByAttributeId ( projectId , router . query . attributeId as string , ( attribute ) => {
101
+ const currentAttribute = postProcessCurrentAttribute ( attribute ) ;
102
+ setCurrentAttribute ( currentAttribute ) ;
103
+ setEditorValue ( currentAttribute ?. sourceCodeToDisplay ) ;
104
+ } ) ;
105
+ } , [ projectId , currentAttribute , router . query . attributeId ] )
82
106
83
107
useEffect ( ( ) => {
84
108
if ( ! attributes ) return ;
@@ -97,7 +121,7 @@ export default function AttributeCalculation() {
97
121
setEditorOptions ( { ...EDITOR_OPTIONS , readOnly : false } ) ;
98
122
}
99
123
setAttributeName ( currentAttribute . name ) ;
100
- } , [ currentAttribute ] ) ;
124
+ } , [ currentAttribute , updateSourceCode ] ) ;
101
125
102
126
useEffect ( ( ) => {
103
127
if ( ! projectId || allUsers . length == 0 ) return ;
@@ -125,7 +149,21 @@ export default function AttributeCalculation() {
125
149
spinner . unsubscribe ( ) ;
126
150
subscription . unsubscribe ( ) ;
127
151
}
128
- } , [ editorValue , currentAttribute ] ) ;
152
+ } , [ editorValue , currentAttribute , updateSourceCode ] ) ;
153
+
154
+
155
+ useEffect ( ( ) => {
156
+ if ( ! currentAttributeRef . current || ! currentAttributeRef . current . additionalConfig || simpleDictCompare ( currentAttributeRef . current ?. additionalConfig , debouncedConfig ) ) return ;
157
+ const attributeNew = { ...currentAttribute } ;
158
+ attributeNew . additionalConfig = { ...debouncedConfig } ;
159
+ updateAttribute ( projectId , currentAttribute . id , ( res ) => {
160
+ setCurrentAttribute ( postProcessCurrentAttribute ( attributeNew ) ) ;
161
+ dispatch ( updateAttributeById ( attributeNew ) ) ;
162
+ setEnableButton ( true ) ;
163
+ } , null , null , null , null , null , debouncedConfig ) ;
164
+
165
+ } , [ debouncedConfig ] )
166
+
129
167
130
168
function setUpCommentsRequests ( ) {
131
169
const requests = [ ] ;
@@ -207,17 +245,6 @@ export default function AttributeCalculation() {
207
245
}
208
246
}
209
247
210
- function updateSourceCode ( value : string , attributeNameParam ?: string ) {
211
- var regMatch : any = getPythonFunctionRegExMatch ( value ) ;
212
- if ( ! regMatch ) {
213
- console . log ( "Can't find python function name -- seems wrong -- better dont save" ) ;
214
- return ;
215
- }
216
- const finalSourceCode = value . replace ( regMatch [ 0 ] , 'def ac(record)' ) ;
217
- updateAttribute ( projectId , currentAttribute . id , ( res ) => {
218
-
219
- } , null , null , attributeNameParam , finalSourceCode ) ;
220
- }
221
248
222
249
function checkProjectTokenization ( ) {
223
250
getProjectTokenization ( projectId , ( res ) => {
@@ -269,9 +296,20 @@ export default function AttributeCalculation() {
269
296
}
270
297
} , [ projectId , currentAttribute ] ) ;
271
298
299
+ const selectCodeTemplate = useCallback ( ( option ) => {
300
+ if ( ! currentAttributeRef . current ) return ;
301
+ updateSourceCode ( LLM_CODE_TEMPLATE_EXAMPLES [ option . value ] ) ;
302
+ setEditorValue ( LLM_CODE_TEMPLATE_EXAMPLES [ option . value ] . replace ( 'def ac(record)' , 'def ' + currentAttributeRef . current . name + '(record)' ) ) ;
303
+ } , [ updateSourceCode ] )
304
+
272
305
const orgId = useSelector ( selectOrganizationId ) ;
273
306
useWebsocket ( orgId , Application . REFINERY , CurrentPage . ATTRIBUTE_CALCULATION , handleWebsocketNotification , projectId ) ;
274
307
308
+ const disabledOptions = useMemo ( ( ) => {
309
+ if ( ! currentAttribute || currentAttribute . dataType == DataTypeEnum . LLM_RESPONSE ) return undefined ;
310
+ return DATA_TYPES . map ( ( e ) => e . value == DataTypeEnum . LLM_RESPONSE ) ;
311
+ } , [ currentAttribute ?. dataType ] )
312
+
275
313
return ( projectId && < div className = { `bg-white p-4 overflow-y-auto min-h-full h-[calc(100vh-4rem)]` } onScroll = { ( e : any ) => onScrollEvent ( e ) } >
276
314
{ currentAttribute && < div >
277
315
< div className = { `sticky z-50 h-12 ${ isHeaderNormal ? 'top-1' : '-top-5' } ` } >
@@ -315,11 +353,25 @@ export default function AttributeCalculation() {
315
353
316
354
< div className = "text-sm leading-5 font-medium text-gray-700" > Data type</ div >
317
355
< div className = "flex flex-row items-center" >
318
- < Tooltip color = "invert" placement = "right" content = { currentAttribute . state == AttributeState . USABLE || currentAttribute . state == AttributeState . RUNNING ? TOOLTIPS_DICT . ATTRIBUTE_CALCULATION . CANNOT_EDIT_DATATYPE : TOOLTIPS_DICT . ATTRIBUTE_CALCULATION . EDIT_DATATYPE } >
356
+ < Tooltip color = "invert" placement = "right" className = "cursor-not-allowed" content = { currentAttribute . state == AttributeState . USABLE || currentAttribute . state == AttributeState . RUNNING ? TOOLTIPS_DICT . ATTRIBUTE_CALCULATION . CANNOT_EDIT_DATATYPE : TOOLTIPS_DICT . ATTRIBUTE_CALCULATION . EDIT_DATATYPE } >
319
357
< KernDropdown buttonName = { currentAttribute . dataTypeName } options = { DATA_TYPES } dropdownWidth = "w-52"
320
- selectedOption = { ( option : any ) => updateDataType ( option ) } disabled = { currentAttribute . state == AttributeState . USABLE } dropdownClasses = "z-30" />
358
+ selectedOption = { ( option : any ) => updateDataType ( option ) } disabledOptions = { disabledOptions } disabled = { currentAttribute . state == AttributeState . USABLE || currentAttribute . dataType == DataTypeEnum . LLM_RESPONSE } dropdownClasses = "z-30" />
321
359
</ Tooltip >
322
360
{ currentAttribute . dataType == DataTypeEnum . EMBEDDING_LIST && < div className = "text-gray-700 text-sm ml-3" > Only useable for similarity search</ div > }
361
+ { currentAttribute . dataType == DataTypeEnum . LLM_RESPONSE && < div className = "ml-3 flex flex-row flex-nowrap w-full items-center gap-x-2" >
362
+ < label className = "block text-sm font-medium text-gray-900 whitespace-nowrap" > Provider</ label >
363
+ < KernDropdown
364
+ buttonName = { additionalConfigTmp ?. llmIdentifier ?? 'Select LLM provider' }
365
+ options = { LLM_PROVIDER_OPTIONS }
366
+ dropdownWidth = "w-52"
367
+ selectedOption = { ( option ) => setAdditionalConfigTmp ( p => ( { ...p , llmIdentifier : option } ) ) }
368
+ disabled = { currentAttribute . state == AttributeState . USABLE }
369
+ />
370
+ < label className = "block text-sm font-medium text-gray-900 whitespace-nowrap" > Api Key</ label >
371
+
372
+ < input type = "text" disabled = { currentAttribute . state == AttributeState . USABLE } value = { additionalConfigTmp ?. llmConfig . apiKey || "" } onInput = { ( e : any ) => setAdditionalConfigTmp ( p => ( { ...p , llmConfig : { ...additionalConfigTmp . llmConfig , apiKey : e . target . value } } ) ) }
373
+ className = "h-8 text-sm border-gray-300 rounded-md placeholder-italic w-full border text-gray-700 pl-4 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-300 focus:ring-offset-2 focus:ring-offset-gray-100 disabled:opacity-50" />
374
+ </ div > }
323
375
</ div >
324
376
< div className = "text-sm leading-5 font-medium text-gray-700 inline-block" > Attributes</ div >
325
377
< div className = "flex flex-row items-center" >
@@ -349,8 +401,24 @@ export default function AttributeCalculation() {
349
401
) ) }
350
402
</ div >
351
403
</ div >
404
+ {
405
+ currentAttribute . dataType == DataTypeEnum . LLM_RESPONSE &&
406
+ < LLMResponseConfig disabled = { currentAttribute . state == AttributeState . USABLE } attributeId = { currentAttribute ?. id } fullLlmConfig = { additionalConfigTmp } setFullLlmConfig = { setAdditionalConfigTmp } apiKey = { additionalConfigTmp ?. llmConfig . apiKey } noPlayground = { currentAttribute . state == AttributeState . USABLE } />
407
+ }
352
408
< div className = "flex flex-row items-center justify-between my-3" >
353
- < div className = "text-sm leading-5 font-medium text-gray-700 inline-block mr-2" > Editor</ div >
409
+ < div className = "flex flex-row flex-nowrap items-center" >
410
+ < span className = "text-sm leading-5 font-medium text-gray-700 inline-block mr-2" > { currentAttribute . dataType == DataTypeEnum . LLM_RESPONSE ? 'Postprocessing' : 'Editor' } </ span >
411
+ { currentAttribute . dataType == DataTypeEnum . LLM_RESPONSE &&
412
+ < Tooltip content = { TOOLTIPS_DICT . ATTRIBUTE_CALCULATION . LLM_POSTPROCESSING_CODE } color = "invert" placement = "right" >
413
+ < KernDropdown
414
+ buttonName = "Use code example"
415
+ options = { LLM_CODE_TEMPLATE_OPTIONS }
416
+ dropdownWidth = "w-52"
417
+ disabled = { currentAttribute . state == AttributeState . USABLE }
418
+ selectedOption = { selectCodeTemplate }
419
+ />
420
+ </ Tooltip > }
421
+ </ div >
354
422
< div className = "flex flex-row flex-nowrap" >
355
423
< VisitBricksButton urlExtension = "generators" tooltipPlacement = "left" size = "small" />
356
424
< Tooltip content = { TOOLTIPS_DICT . ATTRIBUTE_CALCULATION . AVAILABLE_LIBRARIES } placement = "bottom" color = "invert" >
0 commit comments