Get the FREE Ultimate OpenClaw Setup Guide →

threads

npx machina-cli add skill andrmaz/spec-driven-architecture/threads --openclaw
Files (1)
SKILL.md
9.8 KB

Threads and Input

Manages conversations, suggestions, voice input, and image attachments.

Quick Start

import { useTambo, useTamboThreadInput } from "@tambo-ai/react";

const { thread, messages, isIdle } = useTambo();
const { value, setValue, submit } = useTamboThreadInput();

await submit(); // sends current input value

Thread Management

Access and manage the current thread using useTambo() and useTamboThreadInput():

import {
  useTambo,
  useTamboThreadInput,
  ComponentRenderer,
} from "@tambo-ai/react";

function Chat() {
  const {
    thread, // Current thread state
    messages, // Messages with computed properties
    isIdle, // True when not generating
    isStreaming, // True when streaming response
    isWaiting, // True when waiting for server
    currentThreadId, // Active thread ID
    switchThread, // Switch to different thread
    startNewThread, // Create new thread, returns ID
    cancelRun, // Cancel active generation
  } = useTambo();

  const {
    value, // Current input value
    setValue, // Update input
    submit, // Send message
    isPending, // Submission in progress
    images, // Staged image files
    addImage, // Add single image
    removeImage, // Remove image by ID
  } = useTamboThreadInput();

  const handleSend = async () => {
    await submit();
  };

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          {msg.content.map((block) => {
            switch (block.type) {
              case "text":
                return <p key={block.type}>{block.text}</p>;
              case "component":
                return (
                  <ComponentRenderer
                    key={block.id}
                    content={block}
                    threadId={currentThreadId}
                    messageId={msg.id}
                  />
                );
              case "tool_use":
                return (
                  <div key={block.id}>
                    {block.statusMessage ?? `Running ${block.name}...`}
                  </div>
                );
              default:
                return null;
            }
          })}
        </div>
      ))}
      <input value={value} onChange={(e) => setValue(e.target.value)} />
      <button onClick={handleSend} disabled={!isIdle || isPending}>
        Send
      </button>
    </div>
  );
}

Streaming State

PropertyTypeDescription
isIdlebooleanNot generating
isWaitingbooleanWaiting for server response
isStreamingbooleanActively streaming response

The streamingState object provides additional detail:

const { streamingState } = useTambo();
// streamingState.status: "idle" | "waiting" | "streaming"
// streamingState.runId: current run ID
// streamingState.error: { message, code } if error occurred

Content Block Types

Messages contain an array of content blocks. Handle each type:

TypeDescriptionKey Fields
textPlain texttext
componentAI-generated componentid, name, props
tool_useTool invocationid, name, input
tool_resultTool responsetool_use_id, content
resourceMCP resourceuri, name, text

Submit Options

const { submit } = useTamboThreadInput();

await submit({
  threadId: "specific-thread", // Override target thread
  toolChoice: "auto", // "auto" | "required" | "none" | { name: "toolName" }
  maxTokens: 4096, // Max response tokens
  systemPrompt: "Be helpful", // Override system prompt
});

Fetching a Thread by ID

To fetch a specific thread (e.g., for a detail view), use useTamboThread(threadId):

import { useTamboThread } from "@tambo-ai/react";

function ThreadView({ threadId }: { threadId: string }) {
  const { data: thread, isLoading, isError } = useTamboThread(threadId);

  if (isLoading) return <Skeleton />;
  if (isError) return <div>Failed to load thread</div>;

  return <div>{thread.name}</div>;
}

This is a React Query hook - use it for read-only thread fetching, not for the active conversation.

Thread List

Manage multiple conversations:

import { useTambo, useTamboThreadList } from "@tambo-ai/react";

function ThreadSidebar() {
  const { data, isLoading } = useTamboThreadList();
  const { currentThreadId, switchThread, startNewThread } = useTambo();

  if (isLoading) return <Skeleton />;

  return (
    <div>
      <button onClick={() => startNewThread()}>New Thread</button>
      <ul>
        {data?.threads.map((t) => (
          <li key={t.id}>
            <button
              onClick={() => switchThread(t.id)}
              className={currentThreadId === t.id ? "active" : ""}
            >
              {t.name || "Untitled"}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

Thread List Options

const { data } = useTamboThreadList({
  userKey: "user_123", // Filter by user (defaults to provider's userKey)
  limit: 20, // Max results
  cursor: nextCursor, // Pagination cursor
});

// data.threads: TamboThread[]
// data.hasMore: boolean
// data.nextCursor: string

Suggestions

AI-generated follow-up suggestions after each assistant message:

import { useTamboSuggestions } from "@tambo-ai/react";

function Suggestions() {
  const { suggestions, isLoading, accept, isAccepting } = useTamboSuggestions({
    maxSuggestions: 3, // 1-10, default 3
    autoGenerate: true, // Auto-generate after assistant message
  });

  if (isLoading) return <Skeleton />;

  return (
    <div className="suggestions">
      {suggestions.map((s) => (
        <button
          key={s.id}
          onClick={() => accept({ suggestion: s })}
          disabled={isAccepting}
        >
          {s.title}
        </button>
      ))}
    </div>
  );
}

Auto-Submit Suggestion

// Accept and immediately submit as a message
accept({ suggestion: s, shouldSubmit: true });

Manual Generation

const { generate, isGenerating } = useTamboSuggestions({
  autoGenerate: false, // Disable auto-generation
});

<button onClick={() => generate()} disabled={isGenerating}>
  Get suggestions
</button>;

Voice Input

Speech-to-text transcription:

import { useTamboVoice } from "@tambo-ai/react";

function VoiceButton() {
  const {
    startRecording,
    stopRecording,
    isRecording,
    isTranscribing,
    transcript,
    transcriptionError,
    mediaAccessError,
  } = useTamboVoice();

  return (
    <div>
      <button onClick={isRecording ? stopRecording : startRecording}>
        {isRecording ? "Stop" : "Record"}
      </button>
      {isTranscribing && <span>Transcribing...</span>}
      {transcript && <p>{transcript}</p>}
      {transcriptionError && <p className="error">{transcriptionError}</p>}
    </div>
  );
}

Voice Hook Returns

PropertyTypeDescription
startRecording() => voidStart recording, reset transcript
stopRecording() => voidStop and start transcription
isRecordingbooleanCurrently recording
isTranscribingbooleanProcessing audio
transcriptstring | nullTranscribed text
transcriptionErrorstring | nullTranscription error
mediaAccessErrorstring | nullMic access error

Image Attachments

Images are managed via useTamboThreadInput():

import { useTamboThreadInput } from "@tambo-ai/react";

function ImageInput() {
  const { images, addImage, addImages, removeImage, clearImages } =
    useTamboThreadInput();

  const handleFiles = async (files: FileList) => {
    await addImages(Array.from(files));
  };

  return (
    <div>
      <input
        type="file"
        accept="image/*"
        multiple
        onChange={(e) => handleFiles(e.target.files!)}
      />
      {images.map((img) => (
        <div key={img.id}>
          <img src={img.dataUrl} alt={img.name} />
          <button onClick={() => removeImage(img.id)}>Remove</button>
        </div>
      ))}
    </div>
  );
}

StagedImage Properties

PropertyTypeDescription
idstringUnique image ID
namestringFile name
dataUrlstringBase64 data URL
fileFileOriginal File object
sizenumberFile size in bytes
typestringMIME type

User Authentication

Enable per-user thread isolation:

import { TamboProvider } from "@tambo-ai/react";

function App() {
  return (
    <TamboProvider
      apiKey={apiKey}
      userKey="user_123" // Simple user identifier
    >
      <Chat />
    </TamboProvider>
  );
}

For OAuth-based auth, use userToken instead:

function App() {
  const userToken = useUserToken(); // From your auth provider

  return (
    <TamboProvider apiKey={apiKey} userToken={userToken}>
      <Chat />
    </TamboProvider>
  );
}

Use userKey for simple user identification or userToken for OAuth JWT tokens. Don't use both.

Source

git clone https://github.com/andrmaz/spec-driven-architecture/blob/develop/.agents/skills/threads/SKILL.mdView on GitHub

Overview

Threads manages conversations, messages, AI suggestions, voice input, and image attachments across multi-thread UIs. It exposes hooks like useTambo and useTamboThreadInput to access thread state, send messages, and control attachments and voice features.

How This Skill Works

Use useTambo to access the current thread and messages, and use useTamboThreadInput to manage user input, images, and submission. Messages are composed of content blocks (text, component, tool_use, etc.) that you render based on their type, while streaming and idle flags indicate generation status.

When to Use It

  • Building a multi-thread chat UI where users switch between conversations
  • Sending messages with AI-generated suggestions or tool invocations
  • Adding voice input and staging image attachments within messages
  • Managing thread lifecycle: creating new threads and switching active ones
  • Monitoring streaming, waiting, and idle states during generation

Quick Start

  1. Step 1: Import hooks from @tambo-ai/react
  2. Step 2: Use const { thread, messages, isIdle } = useTambo(); const { value, setValue, submit } = useTamboThreadInput();
  3. Step 3: Call await submit(); to send the current input value

Best Practices

  • Keep threadId and currentThreadId synchronized when switching threads
  • Use isIdle, isStreaming, and isWaiting to drive loading states in the UI
  • Validate and handle images via images, addImage, and removeImage before submitting
  • Render content blocks by type (text, component, tool_use, etc.) to ensure correct UI
  • Handle streamingState and errors gracefully to provide clear feedback

Example Use Cases

  • A customer-support dashboard that maintains multiple chat threads with AI-assisted suggestions
  • A content drafting app that inserts AI components and tool results within messages
  • A voice-enabled chat where users can attach images and receive streaming responses
  • A collaborative chat where users switch between product discussion threads in real time
  • An admin panel that creates and manages threads for different customer inquiries

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers