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
18 changes: 15 additions & 3 deletions packages/app/src/components/deepagent-settings-ux.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,21 @@ describe("DeepAgent settings UX", () => {
expect(v2.indexOf('data-action="settings-deepagent-prompt-mode"')).toBeLessThan(
v2.indexOf('data-action="settings-deepagent-intelligence-model"'),
)
expect(v2.indexOf('data-action="settings-deepagent-intelligence-model"')).toBeLessThan(
v2.indexOf('data-action="settings-auto-accept-permissions"'),
)
})

test("moves the permission approval control out of settings into the composer toolbar", async () => {
const v2 = await readFile(path.join(here, "settings-v2/general.tsx"), "utf8")
const composer = await readFile(path.join(here, "prompt-input.tsx"), "utf8")
const control = await readFile(path.join(here, "deepagent/approval-control.tsx"), "utf8")

// The auto-accept toggle no longer lives in settings…
expect(v2).not.toContain('data-action="settings-auto-accept-permissions"')
// …it is a composer control next to the agent selector, backed by directory-level auto-accept.
expect(composer).toContain("ApprovalControl")
expect(control).toContain('"data-action": "prompt-approval"')
expect(control).toContain("toggleAutoAcceptDirectory")
expect(control).toContain("composer.approval.request")
expect(control).toContain("composer.approval.auto")
})

test("routes the legacy settings dialog import to the unified settings page", async () => {
Expand Down
56 changes: 56 additions & 0 deletions packages/app/src/components/deepagent/approval-control.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { createMemo, type JSX } from "solid-js"
import { Select } from "@deepagent-code/ui/select"
import { useLanguage } from "@/context/language"
import { usePermission } from "@/context/permission"

/**
* Approval-mode control for the composer toolbar, next to the agent (build/plan) selector.
*
* Mirrors Codex's approval selector UX, simplified to two options: the button shows the CURRENT mode
* ("Request approval" by default, "Auto-approve" when armed); clicking opens a small picker to switch.
* The mode is DIRECTORY-scoped (persists across sessions in the same workspace), backed by the existing
* permission context (isAutoAcceptingDirectory / toggleAutoAcceptDirectory) — the same state the old
* settings toggle drove, now surfaced where the user acts.
*/

type ApprovalMode = "request" | "auto"

export function ApprovalControl(props: { directory: string; triggerStyle?: JSX.CSSProperties; onAfter?: () => void }) {
const language = useLanguage()
const permission = usePermission()

const auto = createMemo(() => (props.directory ? permission.isAutoAcceptingDirectory(props.directory) : false))
const current = createMemo<ApprovalMode>(() => (auto() ? "auto" : "request"))

const options: ApprovalMode[] = ["request", "auto"]
const label = (mode: ApprovalMode) =>
mode === "auto"
? language.t("composer.approval.auto")
: language.t("composer.approval.request")

const onSelect = (mode: ApprovalMode | undefined) => {
if (!mode || !props.directory) return
const isAuto = mode === "auto"
if (isAuto === auto()) return
// toggleAutoAcceptDirectory flips the directory-level state; only call it when the target differs.
permission.toggleAutoAcceptDirectory(props.directory)
props.onAfter?.()
}

return (
<Select
size="normal"
data-component="prompt-approval-control"
options={options}
current={current()}
value={(o) => o}
label={label}
onSelect={onSelect}
class="capitalize max-w-[160px] text-text-base"
valueClass="truncate text-13-regular text-text-base"
triggerStyle={props.triggerStyle}
triggerProps={{ "data-action": "prompt-approval" }}
variant="ghost"
/>
)
}
6 changes: 6 additions & 0 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
import { PanelButton } from "@/components/deepagent/panel-button"
import { fetchCapabilities } from "@/components/deepagent/panel-goal.api"
import { ApprovalControl } from "@/components/deepagent/approval-control"
import { createTextFragment, getCursorPosition, setCursorPosition, setRangeEdge } from "./prompt-input/editor-dom"
import { createPromptAttachments } from "./prompt-input/attachments"
import { ACCEPTED_FILE_TYPES, pickAttachmentFiles } from "./prompt-input/files"
Expand Down Expand Up @@ -1896,6 +1897,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
</TooltipKeybind>
</div>
</Show>
<Show when={store.mode !== "shell" && sdk.directory}>
<div data-component="prompt-approval-wrap">
<ApprovalControl directory={sdk.directory} triggerStyle={control()} onAfter={restoreFocus} />
</div>
</Show>
<Show when={panelAvailable() && store.mode !== "shell" && params.id}>
<PanelButton sessionID={params.id!} />
</Show>
Expand Down
38 changes: 0 additions & 38 deletions packages/app/src/components/settings-v2/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { Switch } from "@deepagent-code/ui/v2/switch-v2"
import { TextInputV2 } from "@deepagent-code/ui/v2/text-input-v2"
import { Tooltip } from "@deepagent-code/ui/tooltip"
import { useTheme, type ColorScheme } from "@deepagent-code/ui/theme/context"
import { useParams } from "@solidjs/router"
import { useLanguage } from "@/context/language"
import { usePermission } from "@/context/permission"
import { usePlatform, type DisplayBackend } from "@/context/platform"
import { ZOOM_LEVELS } from "@/zoom-levels"
import { useServerSync } from "@/context/server-sync"
Expand All @@ -26,7 +24,6 @@ import {
terminalInput,
useSettings,
} from "@/context/settings"
import { decode64 } from "@/utils/base64"
import { playSoundById, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "../link"
import { SettingsListV2 } from "./parts/list"
Expand Down Expand Up @@ -100,40 +97,13 @@ const playDemoSound = (id: string | undefined) => {
export const SettingsGeneralV2: Component = () => {
const theme = useTheme()
const language = useLanguage()
const permission = usePermission()
const platform = usePlatform()
const params = useParams()
const settings = useSettings()
const models = useModels()

const updater = useUpdaterAction()

const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
const dir = createMemo(() => decode64(params.dir))
const accepting = createMemo(() => {
const value = dir()
if (!value) return false
if (!params.id) return permission.isAutoAcceptingDirectory(value)
return permission.isAutoAccepting(params.id, value)
})

const toggleAccept = (checked: boolean) => {
const value = dir()
if (!value) return

if (!params.id) {
if (permission.isAutoAcceptingDirectory(value) === checked) return
permission.toggleAutoAcceptDirectory(value)
return
}

if (checked) {
permission.enableAutoAccept(params.id, value)
return
}

permission.disableAutoAccept(params.id, value)
}
const desktop = createMemo(() => platform.platform === "desktop")

const themeOptions = createMemo<ThemeOption[]>(() => theme.ids().map((id) => ({ id, name: theme.name(id) })))
Expand Down Expand Up @@ -515,14 +485,6 @@ export const SettingsGeneralV2: Component = () => {
/>
</SettingsRowV2>

<SettingsRowV2
title={language.t("command.permissions.autoaccept.enable")}
description={language.t("toast.permissions.autoaccept.on.description")}
>
<div data-action="settings-auto-accept-permissions">
<Switch checked={accepting()} disabled={!dir()} onChange={toggleAccept} />
</div>
</SettingsRowV2>
</SettingsListV2>
</div>
)
Expand Down
26 changes: 15 additions & 11 deletions packages/app/src/components/settings-v2/import-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -305,18 +305,22 @@ export const ImportSection: Component = () => {
</div>
</SettingsRowV2>

<SettingsRowV2 title={t("settings.import.run.btn", "Import")} description={t("settings.import.run.hint", "Runs the import with the options above.")}>
<div class="flex gap-2" data-action="settings-import-run">
<ButtonV2 size="normal" variant="contrast" disabled={running() || scopes().length === 0} onClick={run}>
{running() ? t("settings.import.running", "Importing…") : t("settings.import.run.btn", "Import")}
<div class="settings-v2-import-actions" data-action="settings-import-run">
<ButtonV2
size="large"
variant="contrast"
class="settings-v2-import-run"
disabled={running() || scopes().length === 0}
onClick={run}
>
{running() ? t("settings.import.running", "Importing…") : t("settings.import.run.btn", "Import")}
</ButtonV2>
<Show when={running()}>
<ButtonV2 size="large" variant="ghost" onClick={cancel}>
{t("settings.import.cancel", "Cancel")}
</ButtonV2>
<Show when={running()}>
<ButtonV2 size="normal" variant="ghost" onClick={cancel}>
{t("settings.import.cancel", "Cancel")}
</ButtonV2>
</Show>
</div>
</SettingsRowV2>
</Show>
</div>

<Show when={summary()}>
<SettingsRowV2 title={t("settings.import.result", "Result")} description="">
Expand Down
24 changes: 18 additions & 6 deletions packages/app/src/components/settings-v2/servers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ButtonV2 } from "@deepagent-code/ui/v2/button-v2"
import { Tag } from "@deepagent-code/ui/v2/badge-v2"
import { Icon as IconV2 } from "@deepagent-code/ui/v2/icon"
import { IconButtonV2 } from "@deepagent-code/ui/v2/icon-button-v2"
import { MenuV2 } from "@deepagent-code/ui/v2/menu-v2"
import { TextInputV2 } from "@deepagent-code/ui/v2/text-input-v2"
import { useDialog } from "@deepagent-code/ui/context/dialog"
import fuzzysort from "fuzzysort"
Expand Down Expand Up @@ -60,12 +61,23 @@ export const SettingsServersV2: Component = () => {
>
<div class="settings-v2-tab-header-row">
<h2 class="settings-v2-tab-title">{language.t("status.popover.tab.servers")}</h2>
<ButtonV2 variant="ghost-muted" icon="plus" onClick={openAdd}>
{language.t("dialog.server.add.button")}
</ButtonV2>
<ButtonV2 variant="ghost-muted" icon="server" onClick={openConnectServer}>
{language.t("dialog.server.connect.button")}
</ButtonV2>
{/* Single "Add server" entry: a menu picks either a direct HTTP server or a Server Edition
gateway connection — two distinct flows behind one button (no more duplicate buttons). */}
<MenuV2 gutter={4} modal={false} placement="bottom-start">
<MenuV2.Trigger as={ButtonV2} variant="ghost-muted" icon="plus">
{language.t("dialog.server.add.button")}
</MenuV2.Trigger>
<MenuV2.Portal>
<MenuV2.Content>
<MenuV2.Group>
<MenuV2.Item onSelect={openAdd}>{language.t("dialog.server.add.menu.http")}</MenuV2.Item>
<MenuV2.Item onSelect={openConnectServer}>
{language.t("dialog.server.connect.button")}
</MenuV2.Item>
</MenuV2.Group>
</MenuV2.Content>
</MenuV2.Portal>
</MenuV2>
<WslAddServerButton />
</div>
<Show when={showSearch()}>
Expand Down
13 changes: 13 additions & 0 deletions packages/app/src/components/settings-v2/settings-v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,19 @@
color: var(--v2-text-text-faint);
}

/* Import: the run action is a full-width bar button under the options list, not a small
right-aligned row control. Cancel (only while running) sits beside it, sized to content. */
.settings-v2-import-actions {
display: flex;
gap: 8px;
margin-top: 4px;
}

.settings-v2-import-run {
flex: 1;
justify-content: center;
}

.settings-v2-import-log {
margin: 0;
padding: 10px 12px;
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,12 @@ export const dict = {
"settings.general.section.updates": "التحديثات",
"settings.general.section.sounds": "المؤثرات الصوتية",
"settings.general.section.feed": "الخلاصة",
"settings.general.section.sharing": "المشاركة",
"settings.general.row.shareUrl.title": "عنوان URL لخادم المشاركة",
"settings.general.row.shareUrl.description": "عنوان URL الأساسي للخادم المستخدم عند مشاركة الجلسات. اتركه فارغًا لاستخدام الافتراضي.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "تفعيل لجنة الخبراء افتراضيًا",
"settings.general.row.expertPanelDefault.description": "بدء المحادثات الجديدة مع تفعيل لجنة الخبراء، بحيث يبدأ زرها مراجعة السياق الحالي عند الطلب.",
"settings.general.section.display": "شاشة العرض",
"settings.general.row.language.title": "اللغة",
"settings.general.row.language.description": "تغيير لغة العرض لـ DeepAgent Code",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,12 @@ export const dict = {
"settings.general.section.updates": "Atualizações",
"settings.general.section.sounds": "Efeitos sonoros",
"settings.general.section.feed": "Feed",
"settings.general.section.sharing": "Compartilhamento",
"settings.general.row.shareUrl.title": "URL do servidor de compartilhamento",
"settings.general.row.shareUrl.description": "URL base do servidor usado ao compartilhar sessões. Deixe em branco para usar o padrão.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "Ativar painel de especialistas por padrão",
"settings.general.row.expertPanelDefault.description": "Iniciar novas conversas com o painel de especialistas ativado, para que seu botão inicie uma revisão do contexto atual sob demanda.",
"settings.general.section.display": "Tela",
"settings.general.row.language.title": "Idioma",
"settings.general.row.language.description": "Alterar o idioma de exibição do DeepAgent Code",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/bs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,12 @@ export const dict = {
"settings.general.section.updates": "Ažuriranja",
"settings.general.section.sounds": "Zvučni efekti",
"settings.general.section.feed": "Feed",
"settings.general.section.sharing": "Dijeljenje",
"settings.general.row.shareUrl.title": "URL servera za dijeljenje",
"settings.general.row.shareUrl.description": "Osnovni URL servera koji se koristi pri dijeljenju sesija. Ostavite prazno za zadano.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "Zadano uključi ekspertni panel",
"settings.general.row.expertPanelDefault.description": "Pokreni nove razgovore s uključenim ekspertnim panelom, tako da njegovo dugme po potrebi pokreće pregled trenutnog konteksta.",
"settings.general.section.display": "Prikaz",

"settings.general.row.language.title": "Jezik",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,12 @@ export const dict = {
"settings.general.section.updates": "Opdateringer",
"settings.general.section.sounds": "Lydeffekter",
"settings.general.section.feed": "Feed",
"settings.general.section.sharing": "Deling",
"settings.general.row.shareUrl.title": "Delings-server-URL",
"settings.general.row.shareUrl.description": "Basis-URL for serveren, der bruges ved deling af sessioner. Lad stå tomt for standard.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "Aktivér ekspertpanel som standard",
"settings.general.row.expertPanelDefault.description": "Start nye samtaler med ekspertpanelet aktiveret, så knappen kan gennemgå den aktuelle kontekst efter behov.",
"settings.general.section.display": "Skærm",

"settings.general.row.language.title": "Sprog",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,12 @@ export const dict = {
"settings.general.section.updates": "Updates",
"settings.general.section.sounds": "Soundeffekte",
"settings.general.section.feed": "Feed",
"settings.general.section.sharing": "Teilen",
"settings.general.row.shareUrl.title": "Server-URL für Freigabe",
"settings.general.row.shareUrl.description": "Basis-URL des Servers, der beim Teilen von Sitzungen verwendet wird. Leer lassen für Standard.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "Expertengremium standardmäßig aktivieren",
"settings.general.row.expertPanelDefault.description": "Neue Unterhaltungen mit aktiviertem Expertengremium starten, damit dessen Schaltfläche den aktuellen Kontext bei Bedarf prüft.",
"settings.general.section.display": "Anzeige",
"settings.general.row.language.title": "Sprache",
"settings.general.row.language.description": "Die Anzeigesprache für DeepAgent Code ändern",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ export const dict = {
"prompt.generating": "Generating prompt...",
"prompt.action.send": "Send",
"prompt.action.stop": "Stop",
"composer.approval.request": "Request approval",
"composer.approval.auto": "Auto-approve",
"prompt.scenario.label": "Scenario mode",
"prompt.scenario.direct": "Direct",
"prompt.scenario.intelligence": "Intelligence",
Expand Down Expand Up @@ -405,6 +407,7 @@ export const dict = {
"dialog.server.add.error": "Could not connect to server",
"dialog.server.add.checking": "Checking...",
"dialog.server.add.button": "Add server",
"dialog.server.add.menu.http": "Add HTTP server",
"dialog.server.connect.button": "Connect to server",
"dialog.server.add.name": "Server name (optional)",
"dialog.server.add.namePlaceholder": "Localhost",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,12 @@ export const dict = {
"settings.general.section.updates": "Actualizaciones",
"settings.general.section.sounds": "Efectos de sonido",
"settings.general.section.feed": "Feed",
"settings.general.section.sharing": "Compartir",
"settings.general.row.shareUrl.title": "URL del servidor para compartir",
"settings.general.row.shareUrl.description": "URL base del servidor usado al compartir sesiones. Déjalo en blanco para usar el predeterminado.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "Activar el panel de expertos por defecto",
"settings.general.row.expertPanelDefault.description": "Iniciar nuevas conversaciones con el panel de expertos activado, para que su botón revise el contexto actual cuando se solicite.",
"settings.general.section.display": "Pantalla",

"settings.general.row.language.title": "Idioma",
Expand Down
6 changes: 6 additions & 0 deletions packages/app/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,12 @@ export const dict = {
"settings.general.section.updates": "Mises à jour",
"settings.general.section.sounds": "Effets sonores",
"settings.general.section.feed": "Flux",
"settings.general.section.sharing": "Partage",
"settings.general.row.shareUrl.title": "URL du serveur de partage",
"settings.general.row.shareUrl.description": "URL de base du serveur utilisé pour partager les sessions. Laissez vide pour la valeur par défaut.",
"settings.general.row.shareUrl.placeholder": "https://opncd.ai",
"settings.general.row.expertPanelDefault.title": "Activer le panel d'experts par défaut",
"settings.general.row.expertPanelDefault.description": "Démarrer les nouvelles conversations avec le panel d'experts activé, afin que son bouton examine le contexte actuel à la demande.",
"settings.general.section.display": "Affichage",
"settings.general.row.language.title": "Langue",
"settings.general.row.language.description": "Changer la langue d'affichage pour DeepAgent Code",
Expand Down
Loading
Loading