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
35 changes: 31 additions & 4 deletions packages/claw-client/src/components/home/HomeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ import { SectionHeader } from "./SectionHeader";
import { NotifPanel } from "./notif/NotifPanel";
import type { HomeNotif, NotifType } from "./notif/types";

// Seeds a prompt into the composer (the home-page composer or, once in a
// thread, the chat composer) — `SessionComposer` listens for this event.
// Mirrors the command-palette priming in `AppOverlays`. Pass `submit: true`
// to also send it immediately (the "Create an example" CTAs do); otherwise
// the text just lands in the input for the user to tweak.
export function primeComposer(text: string, submit = false) {
window.dispatchEvent(new CustomEvent("openclaw-os:prime-composer", { detail: { text, submit } }));
}

// Prompts seeded by the "Create an example" CTAs in the empty Apps /
// Artifacts home sections (desktop + mobile). Rather than hard-coding one
// app/doc, we let the agent propose something it can actually pull off with
// whatever integrations and credentials are already wired up — lower-effort
// and personalized.
export const APP_EXAMPLE_PROMPT =
"Suggest a few apps you could build for me — dashboards, work trackers, calendars, etc. — using the tools and integrations I already have connected, then build the one I pick.";
export const ARTIFACT_EXAMPLE_PROMPT =
"Suggest a couple of documents or reports you could put together for me based on what you know about my work, then draft the one I pick as an artifact.";

// ────────────────────────────────────────────────────────────────────────────
// Adapters: real app data → homepage view-models
// ────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -254,8 +273,12 @@ export function HomeView({
<br />
work trackers, marketing calendars, etc.
</p>
<Button variant="secondary" size="md">
See an example
<Button
variant="secondary"
size="md"
onClick={() => primeComposer(APP_EXAMPLE_PROMPT, true)}
>
Create an example
</Button>
</div>
) : (
Expand Down Expand Up @@ -294,8 +317,12 @@ export function HomeView({
<br />
slides and reports.
</p>
<Button variant="secondary" size="md">
See an example
<Button
variant="secondary"
size="md"
onClick={() => primeComposer(ARTIFACT_EXAMPLE_PROMPT, true)}
>
Create an example
</Button>
</div>
) : (
Expand Down
23 changes: 18 additions & 5 deletions packages/claw-client/src/components/mobile/MobileHomeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import { useMemo } from "react";

import { AgentCard, type AgentCardData } from "@/components/cards/AgentCard";
import { Greeting } from "@/components/home/Greeting";
import type { HomeViewProps } from "@/components/home/HomeView";
import {
APP_EXAMPLE_PROMPT,
ARTIFACT_EXAMPLE_PROMPT,
primeComposer,
type HomeViewProps,
} from "@/components/home/HomeView";
import { SectionHeader } from "@/components/home/SectionHeader";
import { MobileButton } from "@/components/mobile/MobileButton";
import { MobileCronRow } from "@/components/mobile/MobileCronRow";
Expand Down Expand Up @@ -131,8 +136,12 @@ export function MobileHomeView({
<br />
work trackers, marketing calendars, etc.
</p>
<Button variant="secondary" size="md">
See an example
<Button
variant="secondary"
size="md"
onClick={() => primeComposer(APP_EXAMPLE_PROMPT, true)}
>
Create an example
</Button>
</div>
) : (
Expand Down Expand Up @@ -163,8 +172,12 @@ export function MobileHomeView({
<br />
slides and reports.
</p>
<Button variant="secondary" size="md">
See an example
<Button
variant="secondary"
size="md"
onClick={() => primeComposer(ARTIFACT_EXAMPLE_PROMPT, true)}
>
Create an example
</Button>
</div>
) : (
Expand Down
25 changes: 23 additions & 2 deletions packages/claw-client/src/components/session/SessionComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,9 @@ export function SessionComposer({
const [isDragOver, setIsDragOver] = useState(false);
const dragDepthRef = useRef(0);
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
// Set when a `prime-composer` event asks us to auto-send the seeded text.
// The flush effect below picks it up once the text has landed in state.
const submitAfterPrimeRef = useRef(false);

// Rotating-placeholder UX (home composer only — `rotatingPlaceholders`
// is undefined for chat). The hook owns the cycle interval, the
Expand Down Expand Up @@ -554,10 +557,14 @@ export function SessionComposer({

useEffect(() => {
const listener = (event: Event) => {
const detail = (event as CustomEvent<{ text?: string }>).detail;
const detail = (event as CustomEvent<{ text?: string; submit?: boolean }>).detail;
if (!detail?.text) return;
setTextContent(detail.text);
requestAnimationFrame(() => textareaRef.current?.focus());
if (detail.submit) {
submitAfterPrimeRef.current = true;
} else {
requestAnimationFrame(() => textareaRef.current?.focus());
}
};
window.addEventListener("openclaw-os:prime-composer", listener as EventListener);
return () =>
Expand Down Expand Up @@ -653,6 +660,20 @@ export function SessionComposer({
await sendPromise;
};

// Flush a queued auto-submit (from a `prime-composer` event with
// `submit: true`) once the seeded text has actually landed in state — we
// can't call `handleSubmit` straight from the event listener because it
// reads `textContent` from the render closure, which is still empty there.
useEffect(() => {
if (!submitAfterPrimeRef.current || !textContent.trim()) return;
submitAfterPrimeRef.current = false;
void handleSubmit();
// `handleSubmit` is intentionally not a dep — the effect re-runs on the
// render where `textContent` equals the primed value, so this closure's
// `handleSubmit` already sees that text.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [textContent]);

// Drag-drop & clipboard-paste plumbing. Both route through `onAddFiles`,
// which mirrors the file-picker path. We use a depth counter for dragenter/
// dragleave so nested children inside the composer don't toggle the overlay
Expand Down
Loading