Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ typecheck_output.txt
.turbo/
**/.turbo/

# Remotion build output
apps/api/services/remotion/out/

# ComfyUI (local-only, not deployed via CI)
apps/api/services/comfyui/
services/comfyui/
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/assets/styles/features/auth/profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -246,12 +246,17 @@
opacity: 0.5;
}

.profile-card-prompt-footer {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: var(--spacing-xxsmall);
}

.profile-card-char-count {
text-align: right;
font-size: 0.75rem;
color: var(--font-color);
opacity: 0.5;
margin-top: var(--spacing-xxsmall);
}

/* Extra fields inside profile card */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ interface ProfileViewProps {
setUsername: (value: string) => void;
customPrompt: string;
setCustomPrompt: (value: string) => void;
isPromptDirty: boolean;
isSavingPrompt: boolean;
onSaveCustomPrompt: () => void;
errorProfile: string;
isErrorProfileQuery: boolean;
errorProfileQueryMessage: string | undefined;
Expand Down Expand Up @@ -84,6 +87,9 @@ const ProfileView = ({
setUsername,
customPrompt,
setCustomPrompt,
isPromptDirty,
isSavingPrompt,
onSaveCustomPrompt,
errorProfile,
isErrorProfileQuery,
errorProfileQueryMessage,
Expand Down Expand Up @@ -227,7 +233,19 @@ const ProfileView = ({
maxLength={2000}
disabled={isLoading}
/>
<div className="profile-card-char-count">{customPrompt.length}/2000</div>
<div className="profile-card-prompt-footer">
<div className="profile-card-char-count">{customPrompt.length}/2000</div>
{isPromptDirty && (
<button
type="button"
className="btn-primary size-s"
onClick={onSaveCustomPrompt}
disabled={isSavingPrompt || isLoading}
>
{isSavingPrompt ? 'Speichert…' : 'Speichern'}
</button>
)}
</div>
</div>

{/* Hidden profile fields - only shown when not from Keycloak */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ const ProfileInfoTabContainer = ({
const [isDeletingAccount, setIsDeletingAccount] = useState(false);
const [deleteAccountError, setDeleteAccountError] = useState('');
const isInitialized = useRef(false);
const savedCustomPromptRef = useRef('');
const [isPromptDirty, setIsPromptDirty] = useState(false);
const [isSavingPrompt, setIsSavingPrompt] = useState(false);

const updateProfileMutation = useMutation({
mutationFn: async (profileData: ProfileUpdateData) => {
Expand Down Expand Up @@ -173,7 +176,6 @@ const ProfileInfoTabContainer = ({
display_name: fullDisplayName,
username: username || null,
email: email?.trim() || null,
custom_prompt: customPrompt || null,
};
try {
const timeoutPromise = new Promise((_, reject) =>
Expand All @@ -189,30 +191,44 @@ const ProfileInfoTabContainer = ({
onErrorProfileMessage('Speichern dauert zu lange. Bitte versuchen Sie es erneut.');
}
}
}, [
displayName,
username,
email,
customPrompt,
user?.username,
profile,
updateProfile,
onErrorProfileMessage,
]);
}, [displayName, username, email, user?.username, profile, updateProfile, onErrorProfileMessage]);

const handleCustomPromptChange = useCallback((value: string) => {
setCustomPrompt(value);
setIsPromptDirty(value !== savedCustomPromptRef.current);
}, []);

const handleSaveCustomPrompt = useCallback(async () => {
if (!profile || !isInitialized.current) return;
setIsSavingPrompt(true);
try {
await updateProfile({ custom_prompt: customPrompt || null });
savedCustomPromptRef.current = customPrompt;
setIsPromptDirty(false);
setErrorProfile('');
onSuccessMessage('Anweisungen gespeichert');
} catch (error) {
console.error('Custom prompt save error:', error);
setErrorProfile('Fehler beim Speichern der Anweisungen.');
onErrorProfileMessage('Fehler beim Speichern der Anweisungen.');
} finally {
setIsSavingPrompt(false);
}
}, [customPrompt, profile, updateProfile, onSuccessMessage, onErrorProfileMessage]);

const { resetTracking } = useAutosave({
saveFunction: async () => {
await stateBasedSave();
},
formRef: {
getValues: () => ({ displayName, username, email, customPrompt }),
getValues: () => ({ displayName, username, email }),
watch: (callback: (value: Record<string, unknown>, { name }: { name?: string }) => void) => ({
unsubscribe: () => {},
}),
},
enabled: profile && isInitialized.current,
debounceMs: 2000,
getFieldsToTrack: () => ['displayName', 'username', 'email', 'customPrompt'],
getFieldsToTrack: () => ['displayName', 'username', 'email'],
onError: (error: unknown) => {
console.error('Profile autosave failed:', error);
setErrorProfile('Automatisches Speichern fehlgeschlagen.');
Expand All @@ -226,7 +242,9 @@ const ProfileInfoTabContainer = ({
setDisplayName(formFields.displayName);
setUsername(formFields.username);
setEmail(formFields.email);
setCustomPrompt((profile as { custom_prompt?: string })?.custom_prompt || '');
const initialPrompt = (profile as { custom_prompt?: string })?.custom_prompt || '';
setCustomPrompt(initialPrompt);
savedCustomPromptRef.current = initialPrompt;
isInitialized.current = true;
setTimeout(() => resetTracking(), 100);
}
Expand All @@ -239,7 +257,7 @@ const ProfileInfoTabContainer = ({
}, 100);
return () => clearTimeout(timer);
}
}, [displayName, username, email, customPrompt, resetTracking, profile]);
}, [displayName, username, email, resetTracking, profile]);

useEffect(() => {
if (profileUpdateError) {
Expand Down Expand Up @@ -341,7 +359,10 @@ const ProfileInfoTabContainer = ({
username={username}
setUsername={setUsername}
customPrompt={customPrompt}
setCustomPrompt={setCustomPrompt}
setCustomPrompt={handleCustomPromptChange}
isPromptDirty={isPromptDirty}
isSavingPrompt={isSavingPrompt}
onSaveCustomPrompt={handleSaveCustomPrompt}
errorProfile={errorProfile}
isErrorProfileQuery={isErrorProfileQuery}
errorProfileQueryMessage={errorProfileQuery?.message}
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/features/auth/services/profileApiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ export const profileApiService = {
igel_modus: profile.igel_modus || false,
beta_features: profile.beta_features || {},
memory_enabled: profile.memory_enabled || false,
custom_prompt: profile.custom_prompt || '',
};

return profileData;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"diff": ">=5.2.2",
"hono": ">=4.11.4",
"markdown-it": ">=14.1.0",
"tar": ">=7.5.4",
"tar": ">=7.5.7",
"undici": ">=7.18.2",
"zod": ">=3.22.3"
}
Expand Down
14 changes: 7 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading