mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-10-07 13:58:33 +08:00
* 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>
84 lines
2.2 KiB
TypeScript
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;
|