Files
OpenHands/react-router.config.ts
T
c371afec55 feat: LLM profiles route integration (PR C) (#393)
* feat: integrate LLM profiles into settings route (PR C)

- Add LlmSettingsLocalView component for integrated profile management
- Extend SdkSectionSaveControl to expose form values for custom save flows
- Update LLM settings route to render profile list with create/edit views
- Add i18n keys for profile create/edit UI (CREATE_PROFILE, EDIT_PROFILE,
  PROFILE_CREATED, PROFILE_UPDATED, MODEL_REQUIRED, STATUS, BUTTON)
- Add test coverage for LlmSettingsLocalView

The integrated view shows:
- Profile list with active badge and action menu
- Add Profile button that opens create form
- Edit button that loads profile config and opens edit form
- Back/Cancel buttons to return to list view

Co-authored-by: openhands <openhands@all-hands.dev>

* chore: address PR review feedback (#393)

- Improve mock typing with properly typed helper functions that provide all
  required React Query fields, eliminating incomplete 'as unknown as' casts
- Add integration test that verifies the save flow (fills in profile name,
  clicks save, verifies UI state transitions)
- Add component documentation noting future refactoring opportunity (extract
  useProfileForm, useProfileSave hooks for better testability)
- Document API key preservation behavior: currently preserves existing encrypted
  key in edit mode with no new key; note about potential 'Clear API Key' UX
  enhancement for future
- Document auto-derive name race condition: client-side uniqueness check uses
  render-time state, so concurrent profile creation by another client would
  result in server conflict error (handled gracefully)
- Document default export change in route file: LlmSettingsLocalView is now
  the default export; named export LlmSettingsScreen remains for embedded use

Co-authored-by: openhands <openhands@all-hands.dev>

* fix: update llm-settings test to use named export

The default export of llm-settings.tsx changed to render LlmSettingsLocalView
(the profiles manager). The test needs to import the named export LlmSettingsScreen
to test the form component directly.

Co-authored-by: openhands <openhands@all-hands.dev>

* Fix LLM profile button/badge sizing and update typescript-client to v0.6.0

- BrandButton: Change padding from p-2 to px-3 py-2 for better text display
- ProfileRow: Increase active badge vertical padding from py-0.5 to py-1
- Update @openhands/typescript-client from commit SHA to v0.6.0 tag

Co-authored-by: openhands <openhands@all-hands.dev>

* Fix LLM settings to show regular form in cloud mode and empty form in create mode

- LlmSettingsRoute: Render LlmSettingsScreen (standard form) for cloud backends
  and LlmSettingsLocalView (profile manager) for local backends only
- LlmSettingsLocalView: Pass empty initial values in create mode to ensure
  fresh form fields, add key prop to force form remount between profiles
- Add unit tests for cloud vs local backend rendering
- Add unit tests for create mode empty form initialization

Co-authored-by: openhands <openhands@all-hands.dev>

* Fix edit mode form initialization to display profile values

- Fix initialValueOverrides logic to properly check for edit mode AND
  existing initialValues before using them
- Add prefix to edit mode key for clearer remount semantics
- Add unit tests verifying edit mode populates profile name correctly
- Add unit tests verifying getProfile is called with encrypted mode

Co-authored-by: openhands <openhands@all-hands.dev>

* Add debug logging to trace edit profile data flow

Co-authored-by: openhands <openhands@all-hands.dev>

* Fix edit profile config parsing - read from config directly not config.llm

The API returns profile config with llm settings at the top level
(config.model, config.api_key, config.base_url), not nested under
config.llm. Fixed the parsing to read directly from detail.config.

Co-authored-by: openhands <openhands@all-hands.dev>

* Handle profile rename during edit and update active profile

When editing a profile and changing its name:
1. Rename the profile first using ProfilesService.renameProfile
2. Then save the profile config to the new name
3. If the renamed profile was the active profile, re-activate it
   after the rename (since rename doesn't update active_profile)

This prevents creating duplicate profiles when just changing the name.

Co-authored-by: openhands <openhands@all-hands.dev>

* Fix package-lock.json to use https protocol for typescript-client

The lock file was using git+ssh:// protocol which causes Vercel build
failures since Vercel doesn't have SSH keys configured. Changed to
git+https:// and removed the integrity hash (git deps don't have one).

Co-authored-by: openhands <openhands@all-hands.dev>

* chore(profiles): UI polish, shared validation, onboarding integration (#417)

* fix(profiles): Available Profiles heading translation

* fix(profiles): use brand badge for active profile indicator

* feat(profiles): replace form heading with "Back to LLM profiles list"

* fix(profiles): unify profile-name validation and reject any whitespace

* fix(onboarding): persist onboarding LLM choice as an active profile

* refactor(profiles): drop redundant trim/wrapper after validator change

* Fix LLM profile route mocks and warnings

* chore: update baseline snapshots [skip ci]

---------

Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: Vasco Schiavo <115561717+VascoSch92@users.noreply.github.com>
Co-authored-by: Graham Neubig <neubig@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-15 02:46:35 +00:00

84 lines
2.2 KiB
TypeScript

import type { Config } from "@react-router/dev/config";
import { vercelPreset } from "@vercel/react-router/vite";
/**
* This script is used to unpack the client directory from the frontend build directory.
* Remix SPA mode builds the client directory into the build directory. This function
* moves the contents of the client directory to the build directory and then removes the
* client directory.
*
* This script is used in the buildEnd function of the Vite config.
*/
let unpackClientDirectoryPromise: Promise<void> | null = null;
const moveBuildEntry = async (
fs: typeof import("fs"),
source: string,
destination: string,
) => {
await fs.promises.rm(destination, { recursive: true, force: true });
try {
await fs.promises.rename(source, destination);
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== "EPERM" && code !== "EXDEV") {
throw error;
}
await fs.promises.cp(source, destination, {
recursive: true,
force: true,
});
await fs.promises.rm(source, { recursive: true, force: true });
}
};
const unpackClientDirectoryOnce = async () => {
if (process.env.VERCEL) {
// Vercel's React Router builder reads static assets from build/client.
return;
}
const fs = await import("fs");
const path = await import("path");
const buildDir = path.resolve(__dirname, "build");
const clientDir = path.resolve(buildDir, "client");
let files: string[];
try {
files = await fs.promises.readdir(clientDir);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
return;
}
throw error;
}
for (const file of files) {
await moveBuildEntry(
fs,
path.resolve(clientDir, file),
path.resolve(buildDir, file),
);
}
await fs.promises.rm(clientDir, { recursive: true, force: true });
};
const unpackClientDirectory = async () => {
unpackClientDirectoryPromise ??= unpackClientDirectoryOnce().finally(() => {
unpackClientDirectoryPromise = null;
});
await unpackClientDirectoryPromise;
};
export default {
appDirectory: "src",
buildEnd: unpackClientDirectory,
presets: [vercelPreset()],
ssr: false,
} satisfies Config;