Files
wehub-resource-sync e30e75b5d4
Code Quality / Oxlint + Oxfmt (push) Waiting to run
Code Quality / Template Sync (push) Waiting to run
Code Quality / Build Changed Packages (push) Waiting to run
Code Quality / Test Changed Packages (push) Waiting to run
Deploy Expo Example / Deploy Production (push) Waiting to run
Deploy Ink Example / Deploy Production (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-stream, 3.12) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Waiting to run
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Waiting to run
Deploy Shadcn Registry / Deploy Production (push) Waiting to run
Template Metrics / LOC + Bundle Size (push) Waiting to run
Changesets / Create Version PR (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

174 lines
6.5 KiB
Plaintext

---
title: Gemini Clone
description: Open-source Gemini clone in React with a centered greeting over an ambient glow, a single-row pill composer, avatar-free assistant replies, and disabled, ready, and stop send states.
---
import { Gemini } from "@/components/examples/gemini";
<div className="not-prose h-[600px]">
<Gemini />
</div>
## Overview
The Gemini Clone shows how to restyle assistant-ui to match Google Gemini's current interface. The empty state centers a single-line greeting over a soft blue glow, and a single-row pill composer is shared by both the empty and chat states. Assistant replies render as full-width markdown with no avatar, and user turns sit in a rounded grey bubble.
## Features
- **Centered greeting**: one "How can I help you today?" headline, centered over a blurred radial glow.
- **Single-row composer**: a 32px pill that holds the `+` menu, the input, the model picker, the mic, and send on one line.
- **Combined `+` menu**: "Add photos & files" sits next to the Deep Research, Canvas, Create image, and Guided Learning tools.
- **Model picker**: Fast and Thinking, each with a short description and a check mark on the active model.
- **Send states**: a disabled grey arrow when empty, a blue arrow when ready, and a stop square while a response streams.
- **Avatar-free replies**: assistant messages are full-width markdown, with the action bar revealed on hover.
- **Grey user bubble**: a `rounded-3xl` warm-grey bubble, right-aligned, with copy and edit actions on hover.
## Quick Start
```bash
npx assistant-ui add thread
```
## Code
The empty and chat states render the same `Composer`. It is a flex column: an optional attachment row on top, then a single action row with the input as the `flex-1` middle and the controls bottom-aligned around it.
```tsx
import {
AuiIf,
ComposerPrimitive,
ThreadPrimitive,
} from "@assistant-ui/react";
export const Gemini = () => (
<ThreadPrimitive.Root className="bg-[#fdfcfc] dark:bg-[#131314]">
<AuiIf condition={(s) => s.thread.isEmpty}>
<EmptyState />
</AuiIf>
<AuiIf condition={(s) => !s.thread.isEmpty}>
<ThreadPrimitive.Viewport>
<ThreadPrimitive.Messages components={{ Message: ChatMessage }} />
<ThreadPrimitive.ViewportFooter className="sticky bottom-0">
<Composer />
</ThreadPrimitive.ViewportFooter>
</ThreadPrimitive.Viewport>
</AuiIf>
</ThreadPrimitive.Root>
);
const Composer = () => (
<ComposerPrimitive.Root className="flex flex-col rounded-4xl bg-white p-3 shadow-[0_2px_10px_-2px_rgba(0,0,0,0.18)] dark:bg-[#1e1f20]">
<AuiIf condition={(s) => s.composer.attachments.length > 0}>
<ComposerPrimitive.Attachments
components={{ Attachment: GeminiAttachment }}
/>
</AuiIf>
<div className="flex items-end gap-1">
<PlusMenu />
<ComposerPrimitive.Input
placeholder="Ask Gemini"
className="flex-1 resize-none bg-transparent text-[17px]"
/>
<ModelPicker />
<VoiceButton />
<SendButton />
</div>
</ComposerPrimitive.Root>
);
```
### Send states
The trailing slot swaps between Send and Cancel with `AuiIf`. `ComposerPrimitive.Send` disables itself while the composer is empty, so the disabled, ready, and running looks are all driven by state instead of manual class toggling:
```tsx
{/* Send: disabled grey when empty, blue when there is text */}
<AuiIf condition={(s) => !s.thread.isRunning}>
<ComposerPrimitive.Send className="bg-[#d3e3fd] text-[#062e6f] disabled:bg-[#e8eaed] disabled:text-[#1f1f1f]/40">
<ArrowUpIcon />
</ComposerPrimitive.Send>
</AuiIf>
{/* Cancel: stop square while a response streams */}
<AuiIf condition={(s) => s.thread.isRunning}>
<ComposerPrimitive.Cancel className="bg-[#d3e3fd] text-[#062e6f]">
<span className="size-3 rounded-[3px] bg-current" />
</ComposerPrimitive.Cancel>
</AuiIf>
```
### Combined `+` menu
The `+` button opens one menu for both attachments and tools. `ComposerPrimitive.AddAttachment` is rendered as the first item through the `render` prop, so picking it opens the file dialog and closes the menu:
```tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
<DropdownMenu>
<DropdownMenuTrigger>
<PlusIcon />
</DropdownMenuTrigger>
<DropdownMenuContent align="start" side="bottom">
<DropdownMenuItem render={<ComposerPrimitive.AddAttachment />}>
<Paperclip /> Add photos & files
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Telescope /> Deep Research
</DropdownMenuItem>
<DropdownMenuItem>
<PencilRuler /> Canvas
</DropdownMenuItem>
{/* Create image, Guided Learning ... */}
</DropdownMenuContent>
</DropdownMenu>
```
### Ambient glow
The empty state places a single blurred ellipse behind the greeting and composer. It is decorative, so it is `pointer-events-none` and `aria-hidden`:
```tsx
<div className="relative flex grow items-center justify-center">
<div
aria-hidden="true"
className="pointer-events-none absolute top-1/2 left-1/2 h-[330px] w-[720px] -translate-x-1/2 -translate-y-1/2 bg-[radial-gradient(closest-side,#a9d1fb,transparent)] opacity-70 blur-[55px] dark:bg-[radial-gradient(closest-side,#1d4068,transparent)] dark:opacity-65"
/>
<div className="relative z-10">
<h1 className="text-center text-4xl">How can I help you today?</h1>
<Composer />
</div>
</div>
```
### Color palette
| Element | Light | Dark |
| ------------------ | --------------------- | --------- |
| Background | `#fdfcfc` | `#131314` |
| Composer surface | `#ffffff` | `#1e1f20` |
| Primary text | `#1f1f1f` | `#e3e3e3` |
| Muted text & icons | `#444746` | `#c4c7c5` |
| User bubble | `#f2f0f0` | `#333537` |
| Send (ready) | `#d3e3fd` | `#1f3760` |
| Ambient glow | `#a9d1fb` | `#1d4068` |
### Messages
Assistant replies have no avatar; the markdown spans the full content width and the action bar (feedback, copy, regenerate, more) fades in on hover. User turns sit in a rounded grey bubble, right-aligned, with copy and edit actions to its left:
```tsx
<div className="max-w-[75%] rounded-3xl bg-[#f2f0f0] px-5 py-3 dark:bg-[#333537]">
<MessagePrimitive.Parts components={{ Text: MarkdownText }} />
</div>
```
## Source
<SourceLink href="https://github.com/assistant-ui/assistant-ui/blob/main/apps/docs/components/examples/gemini.tsx" />