Appearance
Links ​
Links let you share a Gnosari agent through a short, trackable URL: joina.chat/l/{slug}. Visitors who open the link chat with the linked agent -- either in a classic chat widget, or in an immersive full-page conversation experience with its own greeting, headline, and visual theme.
Every link also gets a downloadable QR code, so the same feature covers both digital sharing (email, social bio, personal invitations) and physical media (flyers, business cards, product stickers).
Renamed from QR Codes. Links used to be called "QR Codes" and only supported the classic chat widget. The old
/qr-codesURL now redirects to/links-- any bookmarks or printed material referencingjoina.chat/q/{slug}keep working unchanged.
Concepts ​
What is a Link? ​
A Link connects three things: a slug (the short URL), an agent (what the visitor chats with), and a presentation (how it's rendered). Each Link has:
- A name for internal identification -- never shown to visitors
- A slug that becomes the URL (
joina.chat/l/{slug}) - An agent the visitor chats with when they open the link
- A presentation:
chat(classic widget) orconversation(immersive full page) - A visit counter that tracks how many times the link has been opened
Key benefit: you can remap a Link to a different agent, change its overrides, or retire it at any time -- without reprinting physical media or reissuing a URL you've already shared.
Presentation: Chat vs Conversation ​
Every Link picks one of two ways to render the agent:
| Presentation | What the visitor sees |
|---|---|
| Chat (default) | The classic Gnosari chat widget -- the same experience as the agent's own public page |
| Conversation | A full-page, editorial experience: an eyebrow label, a large headline (the link's topic), and a headerless chat underneath. The headline shrinks out of the way once the visitor sends their first message |
Conversation presentation is designed for a single, personal invitation -- a follow-up email, a VIP outreach, a curated campaign page -- where the destination should feel like "a conversation about you," not a generic embedded widget.
Per-Link Overrides ​
A Link can override parts of the agent's own configuration, scoped to that Link only:
| Override | Behavior when unset |
|---|---|
| Greeting | Falls back to the agent's own greeting |
| Topic | Only used by the conversation presentation (a headline like "A conversation about your stay"); ignored by the chat presentation |
| Subtitle (caption) | Optional line under the topic headline, shown before the visitor sends their first message; only used by the conversation presentation, ignored by chat. Unset = no subtitle |
| Theme | Falls back to the agent's theme, which itself falls back to the account's default theme |
| Expiry | Never expires |
This means the same agent can power many Links, each with a different first impression, headline, and visual identity -- one per outreach channel -- while the agent's own configuration stays the shared source of truth.
Slugs: Auto-Generated vs Vanity ​
A Link's slug can be:
- Auto-generated (default, recommended for personal invitations): a 16-character unguessable token. Nobody can find this Link by guessing -- only people who receive the exact URL can open it
- Vanity (opt-in): a short, memorable slug you choose (e.g.
vip-dinner), globally unique across all accounts. Easier to share verbally or print, but guessable -- don't use a vanity slug for a link meant to be private
The QR Artifact ​
Every Link -- regardless of presentation -- has a downloadable QR code encoding its public URL. This is what makes Links suitable for physical media: print the QR on a flyer, business card, or product sticker, and remap the underlying agent at any time without reprinting.
Flows ​
From Link to Conversation ​
Visitor opens joina.chat/l/{slug}
│
â–¼
Public resolve (server-side, no login required)
│ looks up the Link by slug
│ checks: does it exist? has it expired? is the agent still linked?
│ atomically increments the visit counter (exactly once per visit)
│ resolves the effective greeting (Link override → agent's own greeting)
│ resolves the effective theme (Link override → agent theme → account default)
│
â–¼
Presentation branch
│
├─ presentation = "chat" ────────► Classic chat widget renders
│
└─ presentation = "conversation" ─► Immersive conversation page renders:
editorial topic headline + headerless chatExpiry ​
Setting an expiry date on a Link makes it stop working automatically after that date -- visitors then see a "this link has expired" message instead of the agent. This is useful for time-boxed campaigns (an event invitation, a limited-time offer) where you want the link to self-retire rather than remembering to delete it.
Agent Deletion ​
If the agent behind a Link is deleted, the Link is not deleted -- it survives with no agent attached, and visitors see a "link no longer available" message. Remap the Link to a new agent at any time to bring it back to life.
Redirect from /qr-codes ​
Links used to be a dedicated "QR Codes" feature at /qr-codes. That URL, and every /qr-codes/{id} detail page, now redirects (301) to the equivalent /links URL. Existing bookmarks and any external references to the old admin URL keep working.
Guides ​
Creating a Chat Link ​
Use this when you want a simple, trackable share link or a physical-media QR code that opens the classic chat widget.
- Navigate to Links → Create Link
- Enter a Name for internal identification (e.g. "Conference Booth QR")
- Leave the slug on auto-generate, or choose a vanity slug if you want something memorable
- Select the Agent visitors will chat with
- Leave Presentation on its default (Chat)
- Click Create
The detail page shows the public URL and a downloadable QR code, ready to print or share.
Creating an Immersive Conversation Link ​
Use this for a personal invitation or a curated campaign that should feel like a dedicated page, not an embedded widget.
- Navigate to Links → Create Link
- Enter a Name and select the Agent
- Set Presentation to Conversation
- Fill in the conversation fields:
- Topic: the headline visitors see (e.g. "A conversation about your stay")
- Greeting: a personal first message (e.g. "Hi Jordan! Following up on your visit")
- Theme: optionally pick a theme override for this Link only
- Leave the slug on auto-generate for a private, unguessable invitation URL
- Click Create
Tip: Combine an auto-generated slug with a personal greeting for outreach that feels one-to-one, even though it's powered by the same agent as everyone else's link.
Setting an Expiry ​
From the Link detail page, open the edit view and set an Expiry date. After that date, the link stops working automatically -- no need to remember to delete it manually.
Remapping a Link to a Different Agent ​
Click Change Agent on the Link detail page to open the agent picker. This is useful when:
- You retire an agent and create a replacement
- You want to run A/B tests with different agents behind the same shared link
- You need to temporarily redirect traffic to a different agent
The change takes effect immediately -- existing printed QR codes and shared URLs start directing to the new agent right away.
Embedding a Link ​
Every Link exposes an embed URL (/embed/l/{slug}) alongside its public URL. Use the embed URL as an iframe src to drop the Link's presentation (chat or conversation) into any web page. This is what powers joina.chat/l/{slug} itself -- a thin iframe wrapper around the same embed surface.
Blending the embed into the host page (conversation presentation): append query params to the embed URL to control how the canvas meets the surrounding page.
| Param | Effect |
|---|---|
?theme=light or ?theme=dark | Force the color mode instead of following the visitor's OS preference |
?transparent=1 | See-through canvas -- the conversation merges into the host page's own background. Requires an explicit ?theme= matching the host's color scheme (see caveat below) |
?fade=1 | Soft gradient behind the composer band, instead of a hard edge |
?bg=<hex> | Paint the canvas an exact color (no #, e.g. ?bg=0a0a0a) to color-match the host page |
Precedence when combined: transparent > bg > the derived theme surface.
Host caveat: a host page that forces
color-scheme: dark(orlight) at the page level always renders the iframe canvas opaque, regardless of?transparent=1-- Chromium opacifies an iframe whose used color scheme differs from the host's. Use?bg=<hex>to color-match instead on these hosts.
Deleting a Link ​
- Navigate to the Link detail page
- Click Delete
- Confirm the deletion
Warning: deletion is permanent. Any printed QR codes or shared URLs pointing to this Link will stop working.
Reference ​
List View ​
| Column | Description |
|---|---|
| Name | Link display name |
| Slug | Public URL (joina.chat/l/{slug}) |
| Presentation | Badge: "Chat" or "Conversation" |
| Visit Count | Number of times the link has been opened |
| Created | When the link was created |
Empty state: if you have no links yet, the page shows a prompt to create your first one. Error state: if links fail to load, an error message with a retry button is displayed.
Create / Edit Fields ​
Required:
| Field | Detail |
|---|---|
| Name | Human-readable, internal only. Example: Conference Booth QR, VIP dinner invitations |
| Agent | Which agent the visitor chats with |
Slug rules (auto-generated by default):
| Rule | Detail |
|---|---|
| Length | 3-50 characters |
| Characters | Lowercase a-z, numbers 0-9, hyphens - |
| Start/End | Must start and end with a letter or number |
| Reserved | Cannot use: create, stats, settings, admin, api, new, list, edit, delete |
Optional (conversation presentation only, ignored for chat):
| Field | Detail |
|---|---|
| Topic | Headline shown on the conversation page |
| Greeting | First message the visitor sees, overriding the agent's own greeting |
| Theme | Overrides the agent's theme for this link only |
| Expiry | Date the link stops working |
Link Detail Page ​
The detail page shows:
QR Preview -- a visual QR code rendered from the public URL via the QrPreview component.
- PNG: high-resolution (1024px) for digital use
- SVG: vector format for print media (scales to any size without quality loss)
Link Information -- full public URL, embed URL, current agent assignment, presentation, visit count, and timestamps.
Agent Remapping -- the ChangeAgentModal component, opened via Change Agent.
Components ​
QrPreview ​
Renders a QR code from a URL using the qrcode library.
| Prop | Type | Default | Description |
|---|---|---|---|
url | string | required | The full URL to encode |
downloadName | string | "qr" | Filename prefix for downloads |
previewSize | number | 256 | Preview size in pixels |
ChangeAgentModal ​
Modal dialog for remapping a Link to a different agent.
| Prop | Type | Description |
|---|---|---|
qrLinkId | string | Link ID to update |
currentAgentId | string or null | Currently assigned agent ID |
open | boolean | Whether the modal is open |
| Event | Description |
|---|---|
update:open | Emitted when modal open state changes |
updated | Emitted after successful agent remapping |
LinkListRow ​
Displays one Link as a table row: name, joina.chat/l/{slug} URL, presentation badge, visit count, created date, and row actions.
State Management ​
The useAgentLinks composable manages Link mutation state and API operations. List fetching for the /links page uses the shared usePaginatedList composable instead (skeleton-first, URL-synced pagination).
Reactive state:
| Property | Type | Description |
|---|---|---|
currentLink | readonly Ref<AgentLink or null> | Currently selected link (set by get/update) |
loading | readonly Ref<boolean> | Whether a mutation is in progress |
error | readonly Ref<string or null> | Last error message |
Methods:
| Method | Parameters | Description |
|---|---|---|
get(id) | id: number | Fetch a single link |
create(data) | AgentLinkCreate | Create a new link |
update(id, data) | id: number, AgentLinkUpdate | Update a link |
remove(id) | id: number | Delete a link |
generateSlug(name) | name: string | Generate a URL-safe vanity slug from a name |
checkSlugAvailability(slug) | slug: string | Check whether a vanity slug is available |
API Integration ​
Links use these API endpoints (proxied via /api/):
| Operation | Method | Endpoint |
|---|---|---|
| List | GET | /api/agent-links |
| Get | GET | /api/agent-links/{id} |
| Create | POST | /api/agent-links |
| Update | PUT | /api/agent-links/{id} |
| Delete | DELETE | /api/agent-links/{id} |
| Check slug | GET | /api/agent-links/check-slug/{slug} |
Public Surfaces ​
| Surface | Path | Notes |
|---|---|---|
| Public link | joina.chat/l/{slug} | Thin iframe wrapper; resolves nothing itself |
| Embed | {app}/embed/l/{slug} | Owns resolution, presentation branching, and expired/not-found states; used as the iframe target above |
| Legacy printed QR | joina.chat/q/{slug} | Unchanged; resolves through the same AgentLink data via a retained alias |
Related Features ​
- Agents - Create and configure agents to link
- Chat Themes - Themes a Link can override per-link
- Widget Embedding - Alternative digital embedding for agents