mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-04 14:52:12 +00:00
fix: tidy agents home + mobile logo + PWA dev/icon issues (#1419)
* fix: tidy agents home, mobile logo, and PWA dev/icon issues - Remove recent-runs cards from the new agent page - Scale the ASCII logo to fit narrow viewports instead of overflowing - Serve manifest in dev and skip SW registration in dev to clear console errors - Use a flat, opaque apple-touch-icon so iOS stops adding a black border - Ignore generated ui/dev-dist * feat: add send button and prevent iOS focus-zoom on chat input - Add a circular send button (accent, spinner while sending) to the prompt bar - Bump textarea to 16px on mobile so iOS doesn't zoom the viewport on focus * fix: polish prompt bar and center logo - Center the ASCII logo at all widths - Prevent iOS focus-zoom via viewport maximum-scale instead of bumping the input to 16px, so mobile text stays the intended size - Give the model picker a chip/chevron treatment to anchor it next to the send button * fix: simplify model picker to plain text + chevron * fix: tighten prompt bar padding and shrink send button --------- Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>
This commit is contained in:
parent
a511add856
commit
6f330f777d
8 changed files with 44 additions and 43 deletions
1
ui/.gitignore
vendored
1
ui/.gitignore
vendored
|
|
@ -10,5 +10,6 @@ count.txt
|
||||||
.wrangler
|
.wrangler
|
||||||
.output
|
.output
|
||||||
.vinxi
|
.vinxi
|
||||||
|
dev-dist
|
||||||
__unconfig*
|
__unconfig*
|
||||||
todos.json
|
todos.json
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 14 KiB |
|
|
@ -4,8 +4,11 @@ import { hydrateRoot } from "react-dom/client"
|
||||||
import { registerSW } from "virtual:pwa-register"
|
import { registerSW } from "virtual:pwa-register"
|
||||||
|
|
||||||
// prompt mode: a new SW installs in the background and takes over on the next
|
// prompt mode: a new SW installs in the background and takes over on the next
|
||||||
// load, so deploys never reload a tab mid-agent-run.
|
// load, so deploys never reload a tab mid-agent-run. Skip in dev — the SW
|
||||||
registerSW()
|
// precaches production-only build artifacts that 404 against the dev server.
|
||||||
|
if (import.meta.env.PROD) {
|
||||||
|
registerSW()
|
||||||
|
}
|
||||||
|
|
||||||
hydrateRoot(
|
hydrateRoot(
|
||||||
document,
|
document,
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,15 @@
|
||||||
import { Link } from "@tanstack/react-router"
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
|
|
||||||
import type { ModelSelection } from "@/lib/agents/useModelOptions"
|
import type { ModelSelection } from "@/lib/agents/useModelOptions"
|
||||||
import { AgentPromptBar } from "@/components/agents/AgentPromptBar"
|
import { AgentPromptBar } from "@/components/agents/AgentPromptBar"
|
||||||
import { AgentRunCard } from "@/components/agents/AgentRunCard"
|
|
||||||
import { SlackConnectDialog } from "@/components/agents/SlackConnectDialog"
|
import { SlackConnectDialog } from "@/components/agents/SlackConnectDialog"
|
||||||
import { Logo } from "@/components/agents/ported/Logo"
|
import { Logo } from "@/components/agents/ported/Logo"
|
||||||
import { useAgentThreads, useCreateAgentThread } from "@/lib/agents/queries"
|
import { useCreateAgentThread } from "@/lib/agents/queries"
|
||||||
import { useModelOptions } from "@/lib/agents/useModelOptions"
|
import { useModelOptions } from "@/lib/agents/useModelOptions"
|
||||||
import { useProfile, useRepos } from "@/lib/profile"
|
import { useProfile, useRepos } from "@/lib/profile"
|
||||||
|
|
||||||
export function AgentsHome() {
|
export function AgentsHome() {
|
||||||
const threadsQuery = useAgentThreads()
|
|
||||||
const createThread = useCreateAgentThread()
|
const createThread = useCreateAgentThread()
|
||||||
const recentRuns = (threadsQuery.data ?? []).slice(0, 5)
|
|
||||||
const { models, defaultSelection } = useModelOptions()
|
const { models, defaultSelection } = useModelOptions()
|
||||||
const [selection, setSelection] = useState<ModelSelection | null>(null)
|
const [selection, setSelection] = useState<ModelSelection | null>(null)
|
||||||
const activeSelection = selection ?? defaultSelection
|
const activeSelection = selection ?? defaultSelection
|
||||||
|
|
@ -53,35 +49,7 @@ export function AgentsHome() {
|
||||||
onRepoChange={setRepoOverride}
|
onRepoChange={setRepoOverride}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mx-auto mt-6 w-full max-w-[640px] space-y-2">
|
|
||||||
{threadsQuery.isLoading ? (
|
|
||||||
<p className="text-center text-sm text-[var(--ui-text-dim)]">
|
|
||||||
Loading agents…
|
|
||||||
</p>
|
|
||||||
) : recentRuns.length === 0 ? (
|
|
||||||
<AgentsHomeEmptyState />
|
|
||||||
) : (
|
|
||||||
recentRuns.map((thread) => (
|
|
||||||
<AgentRunCard key={thread.id} thread={thread} />
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgentsHomeEmptyState() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center gap-3 py-4 text-center">
|
|
||||||
<p className="text-sm text-[var(--ui-text-muted)]">No agents yet.</p>
|
|
||||||
<Link
|
|
||||||
to="/agents"
|
|
||||||
className="text-sm font-medium text-[var(--ui-accent)] hover:underline"
|
|
||||||
>
|
|
||||||
Start your first agent
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ArrowUp, ChevronDown, LoaderCircle } from "lucide-react"
|
||||||
import {
|
import {
|
||||||
memo,
|
memo,
|
||||||
useCallback,
|
useCallback,
|
||||||
|
|
@ -121,7 +122,7 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex min-h-[106px] flex-col rounded-2xl border border-[var(--ui-border)] bg-[var(--ui-surface)] px-4 py-3.5 shadow-sm",
|
"relative flex min-h-[106px] flex-col rounded-2xl border border-[var(--ui-border)] bg-[var(--ui-surface)] px-3 py-2.5 shadow-sm",
|
||||||
compact && "min-h-[88px]"
|
compact && "min-h-[88px]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
@ -134,7 +135,7 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({
|
||||||
placeholder={busy ? "Send a message to queue next..." : placeholder}
|
placeholder={busy ? "Send a message to queue next..." : placeholder}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full min-w-0 resize-none overflow-hidden bg-transparent leading-[1.45] text-[color:var(--ui-text)] outline-none placeholder:text-[color:var(--ui-text-dim)]",
|
"w-full min-w-0 resize-none overflow-hidden bg-transparent text-[13px] leading-[1.45] text-[color:var(--ui-text)] outline-none placeholder:text-[color:var(--ui-text-dim)]",
|
||||||
compact ? "min-h-[36px]" : "min-h-[52px]"
|
compact ? "min-h-[36px]" : "min-h-[52px]"
|
||||||
)}
|
)}
|
||||||
style={{ maxHeight: PROMPT_TEXTAREA_MAX_HEIGHT }}
|
style={{ maxHeight: PROMPT_TEXTAREA_MAX_HEIGHT }}
|
||||||
|
|
@ -146,9 +147,12 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({
|
||||||
type="button"
|
type="button"
|
||||||
disabled={pickerDisabled}
|
disabled={pickerDisabled}
|
||||||
onClick={() => setModelDropdownOpen((open) => !open)}
|
onClick={() => setModelDropdownOpen((open) => !open)}
|
||||||
className="max-w-[220px] cursor-pointer truncate text-[color:var(--ui-text-muted)] transition-opacity hover:opacity-80 disabled:cursor-default disabled:opacity-60"
|
className="flex max-w-[220px] cursor-pointer items-center gap-0.5 text-[13px] text-[color:var(--ui-text-muted)] transition-opacity hover:opacity-80 disabled:cursor-default disabled:opacity-60"
|
||||||
>
|
>
|
||||||
{selectionLabel}
|
<span className="truncate">{selectionLabel}</span>
|
||||||
|
{!pickerDisabled && (
|
||||||
|
<ChevronDown className="size-3.5 shrink-0 opacity-60" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
{modelDropdownOpen && combos.length > 0 && (
|
{modelDropdownOpen && combos.length > 0 && (
|
||||||
<div className="absolute bottom-full left-0 z-50 mb-1 max-h-72 overflow-hidden overflow-y-auto rounded border border-[var(--ui-border)] bg-[var(--ui-surface)] shadow-lg">
|
<div className="absolute bottom-full left-0 z-50 mb-1 max-h-72 overflow-hidden overflow-y-auto rounded border border-[var(--ui-border)] bg-[var(--ui-surface)] shadow-lg">
|
||||||
|
|
@ -184,6 +188,20 @@ export const CloudPromptBar = memo(function CloudPromptBarComponent({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!value.trim() || disabled}
|
||||||
|
aria-label="Send message"
|
||||||
|
className="ml-auto flex size-7 shrink-0 items-center justify-center rounded-full bg-[var(--ui-accent)] text-white transition-opacity hover:opacity-90 disabled:cursor-default disabled:opacity-40"
|
||||||
|
>
|
||||||
|
{disabled ? (
|
||||||
|
<LoaderCircle className="size-3.5 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ArrowUp className="size-3.5" strokeWidth={2.5} />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,13 @@ export function Logo() {
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre className="m-0 max-w-full overflow-x-auto font-mono text-[7px] leading-none text-[#87CEEB]">
|
<div className="w-full" style={{ containerType: "inline-size" }}>
|
||||||
{ascii}
|
<pre
|
||||||
</pre>
|
className="m-0 w-full overflow-hidden text-center font-mono leading-none text-[#87CEEB]"
|
||||||
|
style={{ fontSize: "min(7px, 1.9cqw)" }}
|
||||||
|
>
|
||||||
|
{ascii}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ export const Route = createRootRoute({
|
||||||
head: () => ({
|
head: () => ({
|
||||||
meta: [
|
meta: [
|
||||||
{ charSet: "utf-8" },
|
{ charSet: "utf-8" },
|
||||||
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
{
|
||||||
|
name: "viewport",
|
||||||
|
content: "width=device-width, initial-scale=1, maximum-scale=1",
|
||||||
|
},
|
||||||
{ name: "theme-color", content: "#000000" },
|
{ name: "theme-color", content: "#000000" },
|
||||||
{ title: "open-swe" },
|
{ title: "open-swe" },
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ const config = defineConfig({
|
||||||
injectRegister: false,
|
injectRegister: false,
|
||||||
registerType: "prompt",
|
registerType: "prompt",
|
||||||
outDir: ".output/public",
|
outDir: ".output/public",
|
||||||
|
devOptions: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
integration: {
|
integration: {
|
||||||
closeBundleOrder: "pre",
|
closeBundleOrder: "pre",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue