Skip to main content

Overview

The Message API provides access to chat conversation history between users and the AI assistant about specific documents.
Currently, the Message API only supports retrieving messages. Message creation is handled through a separate AI chat system.

Procedures

getAllByDocId

Retrieve all chat messages for a specific document. Available to document owners and collaborators.
docId
string
required
The document ID to retrieve messages for
Returns: Array of messages with role identification
[]
Message[]
Example:
import { api } from "@/lib/api";

function ChatHistory({ docId }: { docId: string }) {
  const { data: messages, isLoading } = api.message.getAllByDocId.useQuery({ 
    docId 
  });
  
  if (isLoading) return <div>Loading chat history...</div>;
  
  return (
    <div className="chat-history">
      {messages?.map((msg) => (
        <div 
          key={msg.id} 
          className={msg.role === "user" ? "user-message" : "ai-message"}
        >
          <strong>{msg.role === "user" ? "You" : "AI"}:</strong>
          <div>{JSON.stringify(msg.parts)}</div>
        </div>
      ))}
    </div>
  );
}

Data Models

Message Schema

interface Message {
  id: string;
  createdAt: Date;
  userId: string | null;  // null for AI messages
  documentId: string;
  parts: Json | null;     // Message content in JSON format
}

Message Parts

The parts field contains the message content in a JSON structure. The exact format depends on the AI provider being used (e.g., OpenAI, Anthropic, etc.). Common structure:
interface MessageParts {
  type: "text" | "image" | "code";
  content: string;
  metadata?: {
    sources?: string[];     // Document sections referenced
    confidence?: number;    // AI confidence score
    tokens?: number;        // Token count
  };
}

Role Determination

The role field is determined by the presence of a userId:
  • user: Message has a userId (sent by a user)
  • assistant: Message has no userId (generated by AI)
const role = message.userId ? "user" : "assistant";

Authorization

Messages can be retrieved by:
  • The document owner, OR
  • Any collaborator (regardless of role)
This allows all team members to see the full conversation history.
// Authorization check in the API
const res = await ctx.prisma.document.findUnique({
  where: {
    id: input.docId,
    OR: [
      { ownerId: ctx.session.user.id },
      {
        collaborators: {
          some: {
            userId: ctx.session.user.id,
          },
        },
      },
    ],
  },
  select: {
    messages: true,
  },
});

Message Ordering

Messages are returned in the order they were created (oldest first), as determined by the database’s natural ordering. For reverse chronological order, sort on the client:
const { data: messages } = api.message.getAllByDocId.useQuery({ docId });

const sortedMessages = messages?.sort((a, b) => 
  b.createdAt.getTime() - a.createdAt.getTime()
);

Use Cases

Display Chat History

function DocumentChat({ docId }: { docId: string }) {
  const { data: messages } = api.message.getAllByDocId.useQuery({ docId });
  
  return (
    <div>
      {messages?.map((msg) => (
        <ChatBubble key={msg.id} message={msg} />
      ))}
    </div>
  );
}

Export Conversation

function ExportChat({ docId }: { docId: string }) {
  const { data: messages } = api.message.getAllByDocId.useQuery({ docId });
  
  const exportToText = () => {
    const text = messages
      ?.map(msg => `${msg.role.toUpperCase()}: ${JSON.stringify(msg.parts)}`)
      .join('\n\n');
      
    // Download or copy to clipboard
    navigator.clipboard.writeText(text);
  };
  
  return <button onClick={exportToText}>Export Chat</button>;
}

Search Messages

function SearchMessages({ docId, query }: { docId: string; query: string }) {
  const { data: messages } = api.message.getAllByDocId.useQuery({ docId });
  
  const filteredMessages = messages?.filter(msg =>
    JSON.stringify(msg.parts).toLowerCase().includes(query.toLowerCase())
  );
  
  return (
    <div>
      {filteredMessages?.map(msg => (
        <SearchResult key={msg.id} message={msg} />
      ))}
    </div>
  );
}

Error Handling

Common errors:
  • UNAUTHORIZED - User doesn’t have access to the document
const { data, error } = api.message.getAllByDocId.useQuery(
  { docId },
  {
    onError: (error) => {
      if (error.data?.code === "UNAUTHORIZED") {
        toast.error("You don't have access to this document's chat");
        router.push("/dashboard");
      }
    },
  }
);

Performance Considerations

Caching

Messages are cached by React Query. Use refetchInterval for real-time updates:
const { data } = api.message.getAllByDocId.useQuery(
  { docId },
  { refetchInterval: 5000 } // Poll every 5 seconds
);

Pagination

For documents with many messages, consider implementing pagination or infinite scroll on the client side.

Future Enhancements

Planned features for the Message API:
  • Create new messages via API
  • Delete or edit messages
  • Message reactions or annotations
  • Real-time message streaming
  • Message search and filtering on the server
  • Pagination support

Build docs developers (and LLMs) love