Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 11 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ REMOTE_CONTROL_WEB_ORIGIN=https://remote.eigent.ai
VITE_REMOTE_CONTROL_LOCAL_API_URL=https://dev.eigent.ai
# =======Production environment variables end =======

# =======Test environment variables start =======
# SERVER_URL=https://test-dev.eigent.ai
# VITE_PROXY_URL=https://test-dev.eigent.ai
# VITE_SITE_URL=https://test.eigent.ai
# VITE_USE_LOCAL_PROXY=false
# VITE_REMOTE_CONTROL_WEB_ORIGIN=https://test-remote.eigent.ai
# REMOTE_CONTROL_WEB_ORIGIN=https://test-remote.eigent.ai
# VITE_REMOTE_CONTROL_LOCAL_API_URL=https://test-dev.eigent.ai
# =======Test environment variables end=======

# =======Local environment variables start =======
# SERVER_URL=http://localhost:3001
# VITE_PROXY_URL=http://localhost:3001
Expand All @@ -23,4 +33,4 @@ VITE_REMOTE_CONTROL_LOCAL_API_URL=https://dev.eigent.ai
# Replace with real values from Stack dashboard when needed.
VITE_STACK_PROJECT_ID=dummy_project_id
VITE_STACK_PUBLISHABLE_CLIENT_KEY=dummy_publishable_key
VITE_STACK_SECRET_SERVER_KEY=dummy_secret_server_key
VITE_STACK_SECRET_SERVER_KEY=dummy_secret_server_key
46 changes: 0 additions & 46 deletions src/components/ChatBox/BottomBox/BoxAction.tsx

This file was deleted.

69 changes: 69 additions & 0 deletions src/components/ChatBox/BottomBox/BoxBottom.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========

import { ProjectModeToggle } from '@/components/Workspace/ProjectModeToggle';
import type { SessionModeType, ThinkingEffortType } from '@/types/constants';
import { ChatInputModelDropdown } from './ChatInputModelDropdown';
import { ThinkingEffortSelect } from './ThinkingEffortSelect';

export interface BoxBottomProps {
/** Left side: single-agent / multi-agent mode control. */
sessionMode: SessionModeType;
onSessionModeChange?: (mode: SessionModeType) => void;
/** Reasoning effort control; omit to hide it from the row. */
thinkingEffort?: ThinkingEffortType;
onThinkingEffortChange?: (effort: ThinkingEffortType) => void;
/**
* Project-setup controls: interactive on the workspace composer only.
* Once the project has started both controls render read-only.
*/
interactive?: boolean;
disabled?: boolean;
}

/**
* BoxBottom — project-setup row under BoxMain in the BottomBox shell.
* Left: session mode control. Right: thinking effort + default model controls
* (dropdown redesign tracked separately).
*/
export function BoxBottom({
sessionMode,
onSessionModeChange,
thinkingEffort,
onThinkingEffortChange,
interactive = false,
disabled = false,
}: BoxBottomProps) {
return (
<div className="flex w-full flex-wrap items-center justify-between gap-2 px-3 py-1">
<ProjectModeToggle
value={sessionMode}
onValueChange={onSessionModeChange ?? (() => {})}
readOnly={!interactive}
className="shrink-0"
/>
<div className="flex shrink-0 items-center gap-1">
<ChatInputModelDropdown disabled={disabled} readOnly={!interactive} />
{thinkingEffort !== undefined && (
<ThinkingEffortSelect
value={thinkingEffort}
onValueChange={onThinkingEffortChange}
disabled={disabled}
readOnly={!interactive}
/>
)}
</div>
</div>
);
}
70 changes: 36 additions & 34 deletions src/components/ChatBox/BottomBox/ChatInputModelDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import {
Key,
Layers,
Server,
Sparkles,
} from 'lucide-react';
import type { Dispatch, SetStateAction } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
Expand Down Expand Up @@ -365,6 +364,11 @@ export function ChatInputModelDropdown({
]
);

// Grow the trigger to match the open dropdown's content width (never shrink
// below its own natural content width). Keep in sync with the `w-[180px]`
// on `DropdownMenuContent` below.
const [open, setOpen] = useState(false);

const activeSubTriggerRef = useRef<HTMLElement | null>(null);

// Bottom-align the sub content with the trigger row purely imperatively:
Expand Down Expand Up @@ -394,9 +398,8 @@ export function ChatInputModelDropdown({
}
)}
>
<span className="min-w-0 gap-1.5 inline-flex min-h-[1.25rem] items-center overflow-hidden">
<Sparkles className="size-3.5 shrink-0" strokeWidth={2} aria-hidden />
<span className="min-w-0 !text-label-xs font-semibold truncate">
<span className="inline-flex min-h-[1.25rem] min-w-0 items-center gap-1.5 overflow-hidden">
<span className="min-w-0 truncate !text-label-xs font-semibold">
{triggerModelName}
</span>
</span>
Expand All @@ -406,8 +409,9 @@ export function ChatInputModelDropdown({

return (
<DropdownMenu
onOpenChange={(open) => {
if (open) void fetchCloudModels();
onOpenChange={(next) => {
setOpen(next);
if (next) void fetchCloudModels();
}}
>
<DropdownMenuTrigger asChild>
Expand All @@ -420,19 +424,17 @@ export function ChatInputModelDropdown({
className={cn(
modelTriggerShellClass,
'min-w-0 cursor-pointer border-0 text-left',
'font-semibold justify-between transition-colors',
'hover:bg-ds-bg-neutral-subtle-hover active:bg-ds-bg-neutral-subtle-default',
'focus-visible:ring-ds-border-neutral-strong-default focus-visible:ring-offset-ds-bg-neutral-default-default focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none',
'disabled:pointer-events-none disabled:opacity-50'
'justify-between font-semibold transition-all',
'hover:bg-ds-bg-neutral-subtle-default active:bg-ds-bg-neutral-subtle-default data-[state=open]:bg-ds-bg-neutral-subtle-default',
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ds-border-neutral-strong-default focus-visible:ring-offset-2 focus-visible:ring-offset-ds-bg-neutral-default-default',
'disabled:pointer-events-none disabled:opacity-50',
// While open, only the trigger's content grows to the menu width;
// the chevron stays pinned at the right edge.
open && 'min-w-[180px]'
)}
>
<span className="min-w-0 gap-1.5 flex flex-1 items-center overflow-hidden">
<Sparkles
className="size-3.5 shrink-0"
strokeWidth={2}
aria-hidden
/>
<span className="min-w-0 !text-label-xs text-ds-text-neutral-default-default flex-1 truncate text-left">
<span className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
<span className="min-w-0 flex-1 truncate text-center !text-label-xs text-ds-text-neutral-default-default">
{triggerModelName}
</span>
</span>
Expand All @@ -444,7 +446,7 @@ export function ChatInputModelDropdown({
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
align="end"
side="top"
sideOffset={4}
collisionPadding={12}
Expand All @@ -454,7 +456,7 @@ export function ChatInputModelDropdown({
{import.meta.env.VITE_USE_LOCAL_PROXY !== 'true' && (
<DropdownMenuSub>
<DropdownMenuSubTrigger
className="min-w-0 gap-2 [&>svg:first-child]:!h-4 [&>svg:first-child]:!min-h-4 [&>svg:first-child]:!w-4 [&>svg:first-child]:!min-w-4 flex w-full items-center justify-start"
className="flex w-full min-w-0 items-center justify-start gap-2 [&>svg:first-child]:!h-4 [&>svg:first-child]:!min-h-4 [&>svg:first-child]:!w-4 [&>svg:first-child]:!min-w-4"
onPointerEnter={(e) => {
activeSubTriggerRef.current = e.currentTarget;
}}
Expand All @@ -465,7 +467,7 @@ export function ChatInputModelDropdown({
className="mt-0.5 h-4 w-4 shrink-0"
aria-hidden
/>
<span className="min-w-0 text-body-sm flex-1 text-left">
<span className="min-w-0 flex-1 text-left text-body-sm">
{t('setting.eigent-cloud')}
</span>
</DropdownMenuSubTrigger>
Expand Down Expand Up @@ -493,16 +495,16 @@ export function ChatInputModelDropdown({

<DropdownMenuSub>
<DropdownMenuSubTrigger
className="min-w-0 gap-2 [&>svg:first-child]:!h-5 [&>svg:first-child]:!min-h-4 [&>svg:first-child]:!w-4 [&>svg:first-child]:!min-w-4 flex w-full items-center justify-start"
className="flex w-full min-w-0 items-center justify-start gap-2 [&>svg:first-child]:!h-5 [&>svg:first-child]:!min-h-4 [&>svg:first-child]:!w-4 [&>svg:first-child]:!min-w-4"
onPointerEnter={(e) => {
activeSubTriggerRef.current = e.currentTarget;
}}
>
<Layers
className="text-ds-icon-neutral-default-default shrink-0"
className="shrink-0 text-ds-icon-neutral-default-default"
aria-hidden
/>
<span className="min-w-0 text-body-sm flex-1 text-left">
<span className="min-w-0 flex-1 text-left text-body-sm">
{t('setting.custom-model')}
</span>
</DropdownMenuSubTrigger>
Expand Down Expand Up @@ -545,7 +547,7 @@ export function ChatInputModelDropdown({
}}
className="flex items-center justify-between"
>
<div className="gap-2 flex items-center">
<div className="flex items-center gap-2">
{modelImage ? (
<img
src={modelImage}
Expand All @@ -566,15 +568,15 @@ export function ChatInputModelDropdown({
{item.name}
</span>
</div>
<div className="gap-1 flex items-center">
<div className="flex items-center gap-1">
{!isConfigured && (
<div className="h-2 w-2 bg-ds-text-neutral-subtle-default rounded-full opacity-10" />
<div className="h-2 w-2 rounded-full bg-ds-text-neutral-subtle-default opacity-10" />
)}
{isPreferred && (
<Check className="h-4 w-4 text-ds-text-success-default-default" />
)}
{isConfigured && !isPreferred && (
<div className="h-2 w-2 bg-ds-text-success-default-default rounded-full" />
<div className="h-2 w-2 rounded-full bg-ds-text-success-default-default" />
)}
</div>
</DropdownMenuItem>
Expand All @@ -585,16 +587,16 @@ export function ChatInputModelDropdown({

<DropdownMenuSub>
<DropdownMenuSubTrigger
className="min-w-0 gap-2 [&>svg:first-child]:!h-4 [&>svg:first-child]:!min-h-4 [&>svg:first-child]:!w-4 [&>svg:first-child]:!min-w-4 flex w-full items-center justify-start"
className="flex w-full min-w-0 items-center justify-start gap-2 [&>svg:first-child]:!h-4 [&>svg:first-child]:!min-h-4 [&>svg:first-child]:!w-4 [&>svg:first-child]:!min-w-4"
onPointerEnter={(e) => {
activeSubTriggerRef.current = e.currentTarget;
}}
>
<HardDrive
className="text-ds-icon-neutral-default-default shrink-0"
className="shrink-0 text-ds-icon-neutral-default-default"
aria-hidden
/>
<span className="min-w-0 text-body-sm flex-1 text-left">
<span className="min-w-0 flex-1 text-left text-body-sm">
{t('setting.local-model')}
</span>
</DropdownMenuSubTrigger>
Expand All @@ -615,7 +617,7 @@ export function ChatInputModelDropdown({
}}
className="flex items-center justify-between"
>
<div className="gap-2 flex items-center">
<div className="flex items-center gap-2">
{modelImage ? (
<img
src={modelImage}
Expand All @@ -636,15 +638,15 @@ export function ChatInputModelDropdown({
{model.name}
</span>
</div>
<div className="gap-1 flex items-center">
<div className="flex items-center gap-1">
{!isConfigured && (
<div className="h-2 w-2 bg-ds-text-neutral-subtle-default rounded-full opacity-10" />
<div className="h-2 w-2 rounded-full bg-ds-text-neutral-subtle-default opacity-10" />
)}
{isPreferred && (
<Check className="h-4 w-4 text-ds-text-success-default-default" />
)}
{isConfigured && !isPreferred && (
<div className="h-2 w-2 bg-ds-text-success-default-default rounded-full" />
<div className="h-2 w-2 rounded-full bg-ds-text-success-default-default" />
)}
</div>
</DropdownMenuItem>
Expand Down
Loading
Loading