fix(itinerary): fix route optimization reactivity and replace api key provider dropdown with AI LLM providers

- fix optimizeDayOrder() dual-update: directly set days[dayIndex].items + days before
  saveReorderedItems() so it reads the correct post-optimization order synchronously
  (Svelte 4 batches reactive statements; days wasn't updated before save read it)
- also patch collection.itinerary order values so reactive rebuild uses new order
- replace single google_maps <option> with 8 AI LLM provider options
  (anthropic, openai, gemini, ollama, groq, mistral, github_models, openrouter)
- add getApiKeyProviderLabel() helper for saved key display with google_maps fallback
- add i18n keys for all new provider labels in en.json and de.json
This commit is contained in:
2026-03-08 17:45:44 +00:00
parent c5be09bcb9
commit d4e0ef14b8
4 changed files with 62 additions and 6 deletions

View File

@@ -1191,8 +1191,22 @@
return !getCoordinatesFromItineraryItem(item);
});
days[dayIndex].items = [...optimizedPath, ...nonCoordinateItems, ...shadowItems];
const newDayItems = [...optimizedPath, ...nonCoordinateItems, ...shadowItems];
// Patch collection.itinerary order values so the reactive $: days rebuild
// uses the new order when it eventually fires.
newDayItems.forEach((item, index) => {
const itineraryItem = collection.itinerary?.find((it) => it.id === item.id);
if (itineraryItem) {
itineraryItem.order = index;
}
});
// Also directly set days so saveReorderedItems() reads the correct order synchronously
// (Svelte 4 batches reactive statements; days isn't updated yet when saveReorderedItems runs)
days[dayIndex].items = newDayItems;
days = [...days];
// Trigger collection reactivity so the eventual reactive rebuild uses new orders
collection = { ...collection, itinerary: [...(collection.itinerary || [])] };
isSavingOrder = true;
savingDay = day.date;

View File

@@ -748,6 +748,14 @@
"no_api_keys_saved": "Noch keine API-Schlüssel gespeichert.",
"add_api_key": "API-Schlüssel hinzufügen",
"provider": "Anbieter",
"api_key_provider_anthropic": "Anthropic",
"api_key_provider_openai": "OpenAI",
"api_key_provider_gemini": "Google Gemini",
"api_key_provider_ollama": "Ollama (Lokal)",
"api_key_provider_groq": "Groq",
"api_key_provider_mistral": "Mistral AI",
"api_key_provider_github_models": "GitHub Models",
"api_key_provider_openrouter": "OpenRouter",
"api_key_provider_google_places": "Google Places API",
"api_key_value": "API-Schlüssel",
"api_key_value_placeholder": "Geben Sie Ihren API-Schlüssel ein",

View File

@@ -745,6 +745,14 @@
"no_api_keys_saved": "No API keys saved yet.",
"add_api_key": "Add API Key",
"provider": "Provider",
"api_key_provider_anthropic": "Anthropic",
"api_key_provider_openai": "OpenAI",
"api_key_provider_gemini": "Google Gemini",
"api_key_provider_ollama": "Ollama (Local)",
"api_key_provider_groq": "Groq",
"api_key_provider_mistral": "Mistral AI",
"api_key_provider_github_models": "GitHub Models",
"api_key_provider_openrouter": "OpenRouter",
"api_key_provider_google_places": "Google Places API",
"api_key_value": "API Key",
"api_key_value_placeholder": "Enter your API key",

View File

@@ -45,7 +45,7 @@
};
let userApiKeys: UserAPIKey[] = data.props.apiKeys ?? [];
let apiKeysConfigError: string | null = data.props.apiKeysConfigError ?? null;
let newApiKeyProvider = 'google_maps';
let newApiKeyProvider = 'anthropic';
let newApiKeyValue = '';
let isSavingApiKey = false;
let deletingApiKeyId: string | null = null;
@@ -53,6 +53,30 @@
let isLoadingMcpToken = false;
let activeSection: string = 'profile';
const API_KEY_PROVIDER_OPTIONS = [
{ value: 'anthropic', labelKey: 'settings.api_key_provider_anthropic' },
{ value: 'openai', labelKey: 'settings.api_key_provider_openai' },
{ value: 'gemini', labelKey: 'settings.api_key_provider_gemini' },
{ value: 'ollama', labelKey: 'settings.api_key_provider_ollama' },
{ value: 'groq', labelKey: 'settings.api_key_provider_groq' },
{ value: 'mistral', labelKey: 'settings.api_key_provider_mistral' },
{ value: 'github_models', labelKey: 'settings.api_key_provider_github_models' },
{ value: 'openrouter', labelKey: 'settings.api_key_provider_openrouter' }
];
function getApiKeyProviderLabel(provider: string): string {
const option = API_KEY_PROVIDER_OPTIONS.find((entry) => entry.value === provider);
if (option) {
return $t(option.labelKey);
}
if (provider === 'google_maps') {
return $t('settings.api_key_provider_google_places');
}
return provider;
}
// typed alias for social providers to satisfy TypeScript
let socialProviders: Provider[] = data.props.socialProviders ?? [];
@@ -1586,7 +1610,7 @@
{#each userApiKeys as apiKey}
<div class="flex items-center justify-between gap-4 p-4 bg-base-100 rounded-lg">
<div>
<div class="font-medium">{apiKey.provider}</div>
<div class="font-medium">{getApiKeyProviderLabel(apiKey.provider)}</div>
<div class="text-sm text-base-content/70 font-mono">
{apiKey.masked_api_key}
</div>
@@ -1619,9 +1643,11 @@
class="select select-bordered select-primary w-full"
bind:value={newApiKeyProvider}
>
<option value="google_maps">{$t('settings.api_key_provider_google_places')}</option>
</select>
</div>
{#each API_KEY_PROVIDER_OPTIONS as option}
<option value={option.value}>{$t(option.labelKey)}</option>
{/each}
</select>
</div>
<div class="form-control">
<label class="label" for="api-key-value">
<span class="label-text font-medium">{$t('settings.api_key_value')}</span>