Skip to content

Commit 2bf97f8

Browse files
authored
Merge branch 'llm-382' into wip
2 parents 9e9bd77 + 0486fa4 commit 2bf97f8

8 files changed

Lines changed: 155 additions & 26 deletions

File tree

DSL/Ruuter.private/rag-search/POST/prompt-configuration/save.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ declaration:
1515
extract_request_data:
1616
assign:
1717
prompt: ${incoming.body.prompt ?? ""}
18+
next: initialize_results
19+
20+
initialize_results:
21+
assign:
22+
update_result: null
23+
insert_result: null
1824
next: get_existing_prompt
1925

2026
get_existing_prompt:

GUI/src/components/FormElements/FormTextarea/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const FormTextarea = forwardRef<HTMLTextAreaElement, TextareaProps>((
6767
defaultValue={defaultValue}
6868
className={textareaAutosizeClasses}
6969
aria-label={hideLabel ? label : undefined}
70+
disabled={disabled}
7071
onChange={(e) => {
7172
if (onChange) onChange(e);
7273
handleOnChange(e);

GUI/src/pages/PromptConfigurations/PromptConfigurations.scss

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,59 @@
2929
padding: 2rem;
3030
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
3131

32+
.toggle-header {
33+
display: flex;
34+
justify-content: space-between;
35+
align-items: center;
36+
margin-bottom: 1.5rem;
37+
38+
.switch {
39+
margin: 0;
40+
gap: 0;
41+
42+
&__label {
43+
display: none;
44+
}
45+
46+
&__button {
47+
width: 65px;
48+
height:auto;
49+
min-width: 44px;
50+
padding: 2px;
51+
gap: 0;
52+
}
53+
54+
&__on,
55+
&__off {
56+
display: none;
57+
}
58+
59+
&__thumb {
60+
display: block;
61+
width: 30px;
62+
height: 30px;
63+
background-color: white;
64+
border-radius: 50%;
65+
transition: transform 0.25s ease-out;
66+
transform: translateX(2px);
67+
}
68+
69+
&__button[aria-checked="true"] .switch__thumb {
70+
transform: translateX(30px);
71+
}
72+
}
73+
}
74+
75+
.separator {
76+
border-bottom: 1px solid #e0e0e0;
77+
margin-bottom: 1.5rem;
78+
}
79+
3280
.form-actions {
3381
margin-top: 1.5rem;
3482
display: flex;
3583
justify-content: flex-end;
3684
gap: 1rem;
3785
}
3886
}
39-
}
87+
}

GUI/src/pages/PromptConfigurations/index.tsx

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { FC, useState, useEffect } from 'react';
22
import { useTranslation } from 'react-i18next';
33
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
4-
import { Button, FormTextarea } from 'components';
4+
import { Button, FormTextarea, Switch } from 'components';
55
import { ButtonAppearanceTypes, ToastTypes } from 'enums/commonEnums';
66
import CircularSpinner from 'components/molecules/CircularSpinner/CircularSpinner';
7-
import { getPromptConfiguration, savePromptConfiguration } from 'services/promptConfiguration';
7+
import { getPromptConfiguration, savePromptConfiguration, disablePromptConfiguration } from 'services/promptConfiguration';
88
import { promptConfigurationQueryKeys } from 'utils/queryKeys';
99
import { useToast } from 'hooks/useToast';
1010
import './PromptConfigurations.scss';
@@ -15,6 +15,7 @@ const PromptConfigurations: FC = () => {
1515
const queryClient = useQueryClient();
1616
const [promptText, setPromptText] = useState('');
1717
const [isUpdating, setIsUpdating] = useState(false);
18+
const [isEnabled, setIsEnabled] = useState(false);
1819

1920
// Fetch prompt configuration
2021
const { data: promptConfig, isLoading } = useQuery({
@@ -25,9 +26,14 @@ const PromptConfigurations: FC = () => {
2526

2627
// Update promptText when data is loaded
2728
useEffect(() => {
28-
if (promptConfig && promptConfig.length > 0) {
29-
setPromptText(promptConfig[0].prompt || '');
29+
if (promptConfig && promptConfig.length > 0 && promptConfig[0].prompt) {
30+
setPromptText(promptConfig[0].prompt);
3031
setIsUpdating(true);
32+
setIsEnabled(true);
33+
} else {
34+
setPromptText('');
35+
setIsUpdating(promptConfig !== undefined && promptConfig.length > 0);
36+
setIsEnabled(false);
3137
}
3238
}, [promptConfig]);
3339

@@ -52,13 +58,47 @@ const PromptConfigurations: FC = () => {
5258
},
5359
});
5460

61+
// Disable prompt mutation (saves empty prompt)
62+
const disableMutation = useMutation({
63+
mutationFn: disablePromptConfiguration,
64+
onSuccess: () => {
65+
queryClient.invalidateQueries({ queryKey: promptConfigurationQueryKeys.current() });
66+
setPromptText('');
67+
setIsEnabled(false);
68+
toast.open({
69+
type: ToastTypes.SUCCESS,
70+
title: t('toast.success.title'),
71+
message: t('promptConfigurations.deleteSuccess'),
72+
});
73+
},
74+
onError: (error: any) => {
75+
console.error('Error disabling prompt:', error);
76+
toast.open({
77+
type: ToastTypes.ERROR,
78+
title: t('toast.error.title'),
79+
message: t('promptConfigurations.deleteError'),
80+
});
81+
},
82+
});
83+
5584
const handleSubmit = () => {
5685
if (!promptText.trim()) {
5786
return;
5887
}
5988
saveMutation.mutate(promptText);
6089
};
6190

91+
const handleToggleChange = (checked: boolean) => {
92+
if (!checked) {
93+
// Disable: save empty prompt to clear configuration
94+
setPromptText('');
95+
if (isUpdating) {
96+
disableMutation.mutate();
97+
}
98+
}
99+
setIsEnabled(checked);
100+
};
101+
62102
if (isLoading) {
63103
return <CircularSpinner />;
64104
}
@@ -71,6 +111,20 @@ const PromptConfigurations: FC = () => {
71111
</div>
72112

73113
<div className="prompt-form">
114+
<div className="toggle-header">
115+
<span>{t('promptConfigurations.enableToggleLabel')}</span>
116+
<Switch
117+
label=""
118+
hideLabel={true}
119+
checked={isEnabled}
120+
onCheckedChange={handleToggleChange}
121+
name="enableCustomPrompt"
122+
onLabel=""
123+
offLabel=""
124+
/>
125+
</div>
126+
<div className="separator"></div>
127+
74128
<FormTextarea
75129
label={t('promptConfigurations.promptLabel')}
76130
name="promptText"
@@ -84,7 +138,7 @@ const PromptConfigurations: FC = () => {
84138
<Button
85139
appearance={ButtonAppearanceTypes.PRIMARY}
86140
onClick={handleSubmit}
87-
disabled={saveMutation.isLoading || !promptText.trim()}
141+
disabled={!isEnabled || saveMutation.isLoading || !promptText.trim()}
88142
>
89143
{saveMutation.isLoading
90144
? (isUpdating ? t('promptConfigurations.updating') : t('promptConfigurations.saving'))
@@ -98,4 +152,4 @@ const PromptConfigurations: FC = () => {
98152
);
99153
};
100154

101-
export default PromptConfigurations;
155+
export default PromptConfigurations;

GUI/src/services/promptConfiguration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,10 @@ export const savePromptConfiguration = async (prompt: string): Promise<PromptCon
2121
});
2222
return data?.response;
2323
};
24+
25+
export const disablePromptConfiguration = async (): Promise<PromptConfiguration[]> => {
26+
const { data } = await apiDev.post(promptConfigurationEndpoints.SAVE_PROMPT_CONFIGURATION(), {
27+
prompt: "",
28+
});
29+
return data?.response;
30+
};

GUI/translations/en/common.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,13 +432,16 @@
432432
"promptConfigurations": {
433433
"title": "Prompt Configurations",
434434
"subtitle": "Configure and manage your prompt templates",
435+
"enableToggleLabel": "Enable Custom Prompt Configuration",
435436
"promptLabel": "Prompt Template",
436437
"promptPlaceholder": "Enter your prompt template here...",
437438
"submitButton": "Save",
438439
"updateButton": "Update",
439440
"saving": "Saving...",
440441
"updating": "Updating...",
441442
"submitSuccess": "Prompt configuration saved successfully",
442-
"submitError": "Failed to save prompt configuration. Please try again."
443+
"submitError": "Failed to save prompt configuration. Please try again.",
444+
"deleteSuccess": "Prompt configuration deleted successfully",
445+
"deleteError": "Failed to delete prompt configuration. Please try again."
443446
}
444447
}

GUI/translations/et/common.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,13 +433,16 @@
433433
"promptConfigurations": {
434434
"title": "Viiba Seaded",
435435
"subtitle": "Seadista ja halda oma viiba malle",
436+
"enableToggleLabel": "Luba kohandatud viiba seadistus",
436437
"promptLabel": "Viiba Mall",
437438
"promptPlaceholder": "Sisesta siia oma viiba mall...",
438439
"submitButton": "Salvesta",
439440
"updateButton": "Uuenda",
440441
"saving": "Salvestan...",
441442
"updating": "Uuendan...",
442443
"submitSuccess": "Viiba seadistus salvestati edukalt",
443-
"submitError": "Viiba seadistuse salvestamine ebaõnnestus. Palun proovi uuesti."
444+
"submitError": "Viiba seadistuse salvestamine ebaõnnestus. Palun proovi uuesti.",
445+
"deleteSuccess": "Viiba seadistus kustutati edukalt",
446+
"deleteError": "Viiba seadistuse kustutamine ebaõnnestus. Palun proovi uuesti."
444447
}
445448
}

src/utils/prompt_config_loader.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,9 @@ def get_custom_instructions(self) -> str:
148148
return prompt_text
149149

150150
elif prompt_text is None and fetch_error is None:
151-
# No configuration found - cache empty result to avoid repeated loads
151+
# Prompt field not found - cache empty result to avoid repeated loads
152152
logger.warning(
153-
"No prompt configuration found in database; caching empty result"
153+
"Prompt field not found in database; caching empty result"
154154
)
155155
self._cached_prompt = ""
156156
self._cache_timestamp = time.time()
@@ -233,6 +233,8 @@ def _load_from_ruuter_with_retry(self) -> Optional[str]:
233233
data = data["response"]
234234

235235
# Now extract prompt from the unwrapped data
236+
prompt = None # None means field doesn't exist (not found)
237+
236238
if isinstance(data, list) and len(data) > 0:
237239
# Array format: [{"id": 1, "prompt": "..."}]
238240
first_elem_keys = (
@@ -241,28 +243,33 @@ def _load_from_ruuter_with_retry(self) -> Optional[str]:
241243
logger.debug(
242244
f"Extracting from list, first element keys: {first_elem_keys}"
243245
)
244-
prompt = data[0].get("prompt", "").strip()
246+
# Check if 'prompt' KEY exists (distinguish from missing field)
247+
if isinstance(data[0], dict) and "prompt" in data[0]:
248+
prompt = data[0].get("prompt", "").strip()
245249
elif isinstance(data, dict):
246250
# Dict format: {"id": 1, "prompt": "..."}
247251
logger.debug(f"Extracting from dict, keys: {list(data.keys())}")
248-
prompt = data.get("prompt", "").strip()
252+
# Check if 'prompt' KEY exists (distinguish from missing field)
253+
if "prompt" in data:
254+
prompt = data.get("prompt", "").strip()
249255
else:
250256
logger.warning(
251257
f"Unexpected data type: {type(data).__name__}, structure not recognized"
252258
)
253259

254-
logger.debug(
255-
f"Extracted prompt length: {len(prompt) if prompt else 0}"
256-
)
257-
258-
if prompt:
260+
# Distinguish between: exists and empty ("") vs doesn't exist (None)
261+
if prompt is not None:
262+
# Field exists - valid state (even if empty)
259263
logger.debug(
260264
f"Loaded prompt on attempt {attempt} ({len(prompt)} chars)"
261265
)
262-
return prompt
266+
return prompt # Return actual value (may be empty string)
263267
else:
264-
logger.warning(f"Prompt field is empty (attempt {attempt})")
265-
return None # Database has no configuration
268+
# Field doesn't exist - configuration not found
269+
logger.warning(
270+
f"Prompt field not found or missing (attempt {attempt})"
271+
)
272+
return None
266273

267274
else:
268275
logger.warning(
@@ -348,8 +355,8 @@ def force_refresh_with_status(self) -> Dict[str, Any]:
348355

349356
# Determine status and update cache
350357
with self._cache_condition:
351-
if prompt_text:
352-
# Success - update cache
358+
if prompt_text is not None:
359+
# Success - field exists
353360
self._cached_prompt = prompt_text
354361
self._cache_timestamp = time.time()
355362
self._last_error = None
@@ -363,14 +370,14 @@ def force_refresh_with_status(self) -> Dict[str, Any]:
363370
}
364371

365372
elif prompt_text is None and fetch_error is None:
366-
# No configuration found (explicit empty response from Ruuter)
373+
# No configuration found (prompt field missing in response from Ruuter)
367374
self._cached_prompt = ""
368375
self._cache_timestamp = time.time()
369376
self._last_error = None
370-
logger.warning("No prompt configuration found in database")
377+
logger.warning("Prompt field not found in database")
371378
return {
372379
"status": RefreshStatus.NOT_FOUND,
373-
"message": "No prompt configuration found in database",
380+
"message": "Prompt field not found in database",
374381
"error": None,
375382
}
376383

0 commit comments

Comments
 (0)