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
11 changes: 11 additions & 0 deletions src/deepscientist/daemon/api/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2533,6 +2533,17 @@ def config_show(self, name: str) -> dict:
}

def config_save(self, name: str, body: dict) -> dict:
if name == "config" and isinstance(body.get("structured"), dict):
default_runner = self.app.config_manager._normalize_runtime_runner_name(
body["structured"].get("default_runner")
)
if default_runner in {"codex", "claude", "kimi", "opencode"}:
os.environ["DEEPSCIENTIST_DEFAULT_RUNNER"] = default_runner
os.environ["DEEPSCIENTIST_ENABLE_RUNNER"] = default_runner
os.environ.pop("DS_DEFAULT_RUNNER", None)
os.environ.pop("DS_ENABLE_RUNNER", None)
os.environ.pop("DEEPSCIENTIST_ENABLE_RUNNERS", None)
os.environ.pop("DS_ENABLE_RUNNERS", None)
if isinstance(body.get("structured"), dict):
result = self.app.config_manager.save_named_payload(name, body["structured"])
else:
Expand Down
2 changes: 1 addition & 1 deletion src/ui/src/components/landing/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ export default function Hero(props: {
className="h-12 rounded-full bg-[#C7AD96] px-7 text-[#2D2A26] shadow-[0_12px_28px_-14px_rgba(45,42,38,0.55)] transition-all duration-200 hover:-translate-y-0.5 hover:bg-[#D7C6AE]"
onClick={() => {
window.setTimeout(() => {
setActiveDialog('autonomous')
setActiveDialog('launch')
}, 120)
}}
data-onboarding-id="landing-start-research"
Expand Down
29 changes: 15 additions & 14 deletions src/ui/src/components/landing/HeroNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function HeroNav(props: {
'supports-[backdrop-filter]:bg-white/40'
)}
>
<div className="mx-auto flex min-h-16 w-full max-w-[90vw] items-center justify-between gap-4 px-6">
<div className="mx-auto flex min-h-16 w-full max-w-[min(1180px,100vw)] items-center justify-between gap-2 px-3 sm:max-w-[90vw] sm:gap-4 sm:px-6">
<Link
href="/"
className="flex items-center gap-2 rounded-full px-2 py-1 transition-colors hover:bg-black/[0.03]"
Expand All @@ -41,22 +41,23 @@ export default function HeroNav(props: {
decoding="async"
draggable={false}
/>
<span className="text-sm font-semibold tracking-tight text-[#2D2A26]">
<span className="hidden text-sm font-semibold tracking-tight text-[#2D2A26] sm:inline">
DeepScientist
</span>
</Link>

<div className="flex items-center gap-2">
<div className="flex min-w-0 items-center gap-1.5 sm:gap-2">
<SystemUpdateButton />
<LocalAuthTokenButton />
<Button
variant="outline"
size="sm"
className="h-9 rounded-full border-black/10 bg-white/60 text-[#2D2A26] hover:bg-white/90"
className="h-9 w-9 rounded-full border-black/10 bg-white/60 px-0 text-[#2D2A26] hover:bg-white/90 sm:w-auto sm:px-3"
onClick={toggleLocale}
aria-label={locale === 'zh' ? 'Switch to English' : '切换到中文'}
>
<Languages className="mr-2 h-4 w-4" />
{locale === 'zh' ? 'English' : '中文'}
<Languages className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">{locale === 'zh' ? 'English' : '中文'}</span>
</Button>
<Button
variant="outline"
Expand All @@ -74,12 +75,12 @@ export default function HeroNav(props: {
<Button
variant="outline"
size="sm"
className="h-9 rounded-full border-black/10 bg-white/60 text-[#2D2A26] hover:bg-white/90"
className="h-9 w-9 rounded-full border-black/10 bg-white/60 px-0 text-[#2D2A26] hover:bg-white/90 sm:w-auto sm:px-3"
asChild
>
<Link href="/docs">
<BookOpen className="mr-2 h-4 w-4" />
{t('navDocs')}
<Link href="/docs" aria-label={t('navDocs')}>
<BookOpen className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">{t('navDocs')}</span>
</Link>
</Button>
{props.onOpenBenchStore ? (
Expand All @@ -95,12 +96,12 @@ export default function HeroNav(props: {
) : null}
<Button
size="sm"
className="h-9 rounded-full bg-[#C7AD96] text-[#2D2A26] hover:bg-[#D7C6AE]"
className="h-9 w-9 rounded-full bg-[#C7AD96] px-0 text-[#2D2A26] hover:bg-[#D7C6AE] sm:w-auto sm:px-3"
asChild
>
<Link href="/settings">
<Settings2 className="mr-2 h-4 w-4" />
{t('navSettings')}
<Link href="/settings" aria-label={t('navSettings')}>
<Settings2 className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">{t('navSettings')}</span>
</Link>
</Button>
</div>
Expand Down
149 changes: 137 additions & 12 deletions src/ui/src/components/onboarding/OnboardingOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -651,6 +651,25 @@ function queryTarget(id?: string | null) {
return document.querySelector<HTMLElement>(`[data-onboarding-id="${id}"]`)
}

function waitForTarget(id: string, timeoutMs = 2400) {
const startedAt = Date.now()
return new Promise<HTMLElement | null>((resolve) => {
const tick = () => {
const target = queryTarget(id)
if (target) {
resolve(target)
return
}
if (Date.now() - startedAt >= timeoutMs) {
resolve(null)
return
}
window.setTimeout(tick, 80)
}
tick()
})
}

function stepNeedsCopilotPanel(step: OnboardingStep | null) {
if (!step) return false
const ids = [step.targetId, step.actionTargetId, step.waitForElementId].filter(Boolean)
Expand Down Expand Up @@ -724,25 +743,31 @@ function scrollTargetIntoViewVerticalOnly(target: HTMLElement) {
function triggerTargetAction(id?: string | null) {
const target = queryTarget(id)
if (!target) return false
const clickableTarget =
target instanceof HTMLElement &&
!target.matches('button, a, [role="button"], input, select, textarea')
? target.querySelector<HTMLElement>('button, a, [role="button"], input, select, textarea')
: null
const actionTarget = clickableTarget || target
const isFileTreeNode = Boolean(target.closest('[data-node-id]'))
if (isFileTreeNode) {
target.dispatchEvent(
actionTarget.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
detail: 1,
})
)
target.dispatchEvent(
actionTarget.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
detail: 2,
})
)
target.dispatchEvent(
actionTarget.dispatchEvent(
new MouseEvent('dblclick', {
bubbles: true,
cancelable: true,
Expand All @@ -752,11 +777,11 @@ function triggerTargetAction(id?: string | null) {
)
return true
}
if (typeof target.click === 'function') {
target.click()
if (typeof actionTarget.click === 'function') {
actionTarget.click()
return true
}
target.dispatchEvent(
actionTarget.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
Expand Down Expand Up @@ -784,6 +809,51 @@ function clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max)
}

function cardTargetOverlap(args: {
top: number
left: number
cardWidth: number
cardHeight: number
targetRect: DOMRect
}) {
const right = args.left + args.cardWidth
const bottom = args.top + args.cardHeight
const overlapWidth = Math.max(0, Math.min(right, args.targetRect.right) - Math.max(args.left, args.targetRect.left))
const overlapHeight = Math.max(0, Math.min(bottom, args.targetRect.bottom) - Math.max(args.top, args.targetRect.top))
return overlapWidth * overlapHeight
}

function bestFallbackCardPosition(args: {
targetRect: DOMRect
cardWidth: number
cardHeight: number
margin: number
viewportWidth: number
viewportHeight: number
}) {
const maxTop = Math.max(args.margin, args.viewportHeight - args.margin - args.cardHeight)
const maxLeft = Math.max(args.margin, args.viewportWidth - args.margin - args.cardWidth)
const candidates = [
{ top: args.margin, left: args.margin },
{ top: args.margin, left: maxLeft },
{ top: maxTop, left: args.margin },
{ top: maxTop, left: maxLeft },
]

return candidates
.map((candidate, index) => ({
...candidate,
index,
overlap: cardTargetOverlap({
...candidate,
cardWidth: args.cardWidth,
cardHeight: args.cardHeight,
targetRect: args.targetRect,
}),
}))
.sort((a, b) => a.overlap - b.overlap || b.index - a.index)[0]
}

function resolveCardPosition(args: {
targetRect: DOMRect | null
cardWidth: number
Expand All @@ -809,12 +879,29 @@ function resolveCardPosition(args: {
const canPlaceLeft = rect.left - gap - args.cardWidth >= margin

let placement = args.placement || 'auto'
if (
(placement === 'bottom' && !canPlaceBottom) ||
(placement === 'top' && !canPlaceTop) ||
(placement === 'right' && !canPlaceRight) ||
(placement === 'left' && !canPlaceLeft)
) {
placement = 'auto'
}
if (placement === 'auto') {
if (canPlaceBottom) placement = 'bottom'
else if (canPlaceTop) placement = 'top'
else if (canPlaceRight) placement = 'right'
else if (canPlaceLeft) placement = 'left'
else placement = 'bottom'
else {
return bestFallbackCardPosition({
targetRect: rect,
cardWidth: args.cardWidth,
cardHeight: args.cardHeight,
margin,
viewportWidth,
viewportHeight,
})
}
}

let top = rect.bottom + gap
Expand Down Expand Up @@ -921,6 +1008,7 @@ export function OnboardingOverlay() {
neverShowAgain,
close,
completeTutorial,
goToStep,
} = useOnboardingStore((state) => ({
hydrated: state.hydrated,
status: state.status,
Expand All @@ -935,6 +1023,7 @@ export function OnboardingOverlay() {
neverShowAgain: state.neverShowAgain,
close: state.close,
completeTutorial: state.completeTutorial,
goToStep: state.goToStep,
}))

const [targetRect, setTargetRect] = React.useState<DOMRect | null>(null)
Expand Down Expand Up @@ -990,6 +1079,40 @@ export function OnboardingOverlay() {
advance()
}, [advance, navigate, status, step])

const restoreBenchstoreStartStep = React.useCallback(async () => {
triggerTargetAction('experiment-launch-close')
if (!queryTarget('benchstore-dialog')) {
triggerTargetAction('landing-benchstore')
await waitForTarget('benchstore-dialog')
}
if (!queryTarget('benchstore-detail-action-strip')) {
const featuredCard = await waitForTarget('benchstore-featured-card')
if (featuredCard) {
triggerTargetAction('benchstore-featured-card')
await waitForTarget('benchstore-detail-action-strip')
}
}
goToStep(7)
}, [goToStep])

const handlePrevious = React.useCallback(() => {
if (step?.id === 'benchstore-overview') {
triggerTargetAction('benchstore-close')
goToStep(2)
return
}
if (step?.id === 'landing-open-dialog') {
void restoreBenchstoreStartStep()
return
}
if (step?.id === 'launch-mode-overview') {
triggerTargetAction('experiment-launch-close')
goToStep(8)
return
}
previousStep()
}, [goToStep, previousStep, restoreBenchstoreStartStep, step?.id])

React.useEffect(() => {
hydrate()
}, [hydrate])
Expand Down Expand Up @@ -1331,14 +1454,15 @@ export function OnboardingOverlay() {

<div
ref={cardRef}
className="pointer-events-auto fixed w-[min(466px,calc(100vw-32px))] overflow-hidden rounded-[30px] border border-[rgba(255,255,255,0.28)] bg-[linear-gradient(180deg,rgba(255,252,248,0.98),rgba(246,239,231,0.95))] p-5 shadow-[0_32px_110px_-44px_rgba(15,23,42,0.62)] backdrop-blur-xl"
className="pointer-events-auto fixed flex max-h-[calc(100vh-32px)] w-[min(466px,calc(100vw-32px))] flex-col overflow-hidden rounded-[30px] border border-[rgba(255,255,255,0.28)] bg-[linear-gradient(180deg,rgba(255,252,248,0.98),rgba(246,239,231,0.95))] p-5 shadow-[0_32px_110px_-44px_rgba(15,23,42,0.62)] backdrop-blur-xl"
style={{
top: cardPosition.top,
left: cardPosition.left,
}}
>
<div className="absolute inset-x-0 top-0 h-px bg-[linear-gradient(90deg,transparent,rgba(160,133,103,0.4),transparent)]" />
<div className="flex items-start gap-3">
<div className="min-h-0 flex-1 overflow-y-auto pr-1">
<div className="flex items-start gap-3">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[rgba(199,173,150,0.22)] text-[rgba(92,78,58,0.95)]">
{step.route === 'landing' ? <Compass className="h-5 w-5" /> : <Sparkles className="h-5 w-5" />}
</div>
Expand Down Expand Up @@ -1397,18 +1521,19 @@ export function OnboardingOverlay() {
</div>
) : null}
</div>
</div>
</div>

<div className={cn('mt-6 flex gap-2', stepIndex === 0 ? 'justify-end' : 'justify-between')}>
<div className={cn('mt-6 flex shrink-0 flex-wrap gap-2', stepIndex === 0 ? 'justify-end' : 'justify-between')}>
{stepIndex > 0 ? (
<Button variant="ghost" onClick={previousStep}>
<Button variant="ghost" onClick={handlePrevious}>
<ChevronLeft className="mr-1 h-4 w-4" />
{copy.back}
</Button>
) : (
<span />
)}
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center justify-end gap-2">
<Button variant="secondary" onClick={() => exitTutorial('close')}>
<BookOpen className="mr-2 h-4 w-4" />
{copy.skip}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export function ExperimentLaunchModeDialog({
description={t.body}
onClose={onClose}
dataOnboardingId="experiment-launch-dialog"
closeButtonDataOnboardingId="experiment-launch-close"
className={cn(LAUNCH_DIALOG_SHELL_CLASS, 'h-auto lg:max-h-[78svh]')}
>
<div className="feed-scrollbar h-full min-h-0 overflow-y-auto p-4 sm:p-6 lg:p-7">
Expand Down
6 changes: 3 additions & 3 deletions src/ui/src/components/settings/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -975,11 +975,11 @@ export function SettingsPage({
<main className="mx-auto mt-5 min-h-0 w-full flex-1 overflow-hidden">
<div
className={cn(
'mx-auto grid h-full min-h-0 w-full max-w-[90vw] grid-rows-[auto_minmax(0,1fr)] gap-0 xl:grid-rows-1',
'mx-auto grid h-full min-h-0 w-full max-w-[90vw] grid-rows-[auto_minmax(0,1fr)] gap-4 xl:grid-rows-1 xl:gap-0',
dockOpen ? 'xl:grid-cols-[260px_minmax(0,1fr)_420px]' : 'xl:grid-cols-[260px_minmax(0,1fr)]'
)}
>
<aside className="feed-scrollbar flex min-h-0 flex-col overflow-auto border-b border-black/[0.08] pb-6 xl:border-b-0 xl:border-r xl:pb-0 xl:pr-6 dark:border-white/[0.08]">
<aside className="feed-scrollbar flex max-h-[34vh] min-h-0 flex-col overflow-auto border-b border-black/[0.08] pb-4 xl:max-h-none xl:border-b-0 xl:border-r xl:pb-0 xl:pr-6 dark:border-white/[0.08]">
<div className="text-sm font-medium">{t.title}</div>

<div className="relative mt-4">
Expand Down Expand Up @@ -1142,7 +1142,7 @@ export function SettingsPage({
</div>
</aside>

<section ref={contentRef} className="feed-scrollbar min-h-0 overflow-y-auto py-6 xl:px-8">
<section ref={contentRef} className="feed-scrollbar min-h-0 overflow-y-auto py-4 xl:px-8 xl:py-6">
<div className={cn('mx-auto w-full', dockOpen ? 'max-w-[1010px]' : 'max-w-[1180px]')}>
{isPageLoading ? (
<div className="flex items-center gap-3 text-sm text-muted-foreground">
Expand Down
Loading