From 1841147519324b01e183088de34f0da3e2ef3341 Mon Sep 17 00:00:00 2001 From: Calvin Secrest Date: Fri, 5 Sep 2025 18:27:33 -0400 Subject: [PATCH] docs: document message and conversation types --- .../chat-ui/src/components/MessageActions.jsx | 6 +++ .../chat-ui/src/components/MessageBubble.jsx | 6 +++ frontend/chat-ui/src/components/Sidebar.jsx | 31 ++++++++++--- frontend/chat-ui/src/store.jsx | 46 +++++++++++++++++++ frontend/chat-ui/src/types.js | 24 ++++++++++ 5 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 frontend/chat-ui/src/types.js diff --git a/frontend/chat-ui/src/components/MessageActions.jsx b/frontend/chat-ui/src/components/MessageActions.jsx index 56c3cdd..7cb2682 100644 --- a/frontend/chat-ui/src/components/MessageActions.jsx +++ b/frontend/chat-ui/src/components/MessageActions.jsx @@ -1,8 +1,14 @@ import React from 'react'; import { useStore } from '../store'; +/** @typedef {import('../types').Message} Message */ + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; +/** + * Action buttons associated with a message. + * @param {{message: Message}} props + */ export default function MessageActions({ message }) { const { state } = useStore(); diff --git a/frontend/chat-ui/src/components/MessageBubble.jsx b/frontend/chat-ui/src/components/MessageBubble.jsx index b8c6732..95027e7 100644 --- a/frontend/chat-ui/src/components/MessageBubble.jsx +++ b/frontend/chat-ui/src/components/MessageBubble.jsx @@ -1,6 +1,12 @@ import React from 'react'; import MessageActions from './MessageActions.jsx'; +/** @typedef {import('../types').Message} Message */ + +/** + * Displays a single chat message. + * @param {{message: Message}} props + */ export default function MessageBubble({ message }) { const isUser = message.role === 'user' || message.sender === 'user' || message.from === 'user'; const alignment = isUser ? 'items-end text-right' : 'items-start text-left'; diff --git a/frontend/chat-ui/src/components/Sidebar.jsx b/frontend/chat-ui/src/components/Sidebar.jsx index 59900b0..d433044 100644 --- a/frontend/chat-ui/src/components/Sidebar.jsx +++ b/frontend/chat-ui/src/components/Sidebar.jsx @@ -1,8 +1,17 @@ import React, { useEffect } from 'react'; import { useStore } from '../store'; +/** + * @typedef {import('../types').Conversation} Conversation + * @typedef {import('../types').Message} Message + */ + const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || ''; +/** + * Sidebar containing conversation list and controls. + * @returns {JSX.Element} + */ export default function Sidebar() { const { state, dispatch } = useStore(); @@ -39,19 +48,27 @@ export default function Sidebar() { } }; + /** + * Load a conversation and its messages. + * @param {Conversation} c + */ const openConversation = async (c) => { try { - const res = await fetch(`${API_BASE_URL}/messages/${c.id}`, { - headers: { 'x-api-key': import.meta.env.VITE_API_KEY }, - }); - const msgs = await res.json(); - dispatch({ type: 'SET_CURRENT_CONVERSATION', conversation: c }); - dispatch({ type: 'SET_MESSAGES', messages: msgs }); + const res = await fetch(`${API_BASE_URL}/messages/${c.id}`, { + headers: { 'x-api-key': import.meta.env.VITE_API_KEY }, + }); + const msgs = /** @type {Message[]} */ (await res.json()); + dispatch({ type: 'SET_CURRENT_CONVERSATION', conversation: c }); + dispatch({ type: 'SET_MESSAGES', messages: msgs }); } catch (e) { console.error(e); } }; + /** + * Toggle the pinned state of a conversation. + * @param {Conversation} c + */ const togglePin = async (c) => { try { await fetch(`${API_BASE_URL}/conversations/${c.id}/pin`, { @@ -72,7 +89,9 @@ export default function Sidebar() { } }; + /** @type {Conversation[]} */ const pinned = state.conversations.filter((c) => c.pinned); + /** @type {Conversation[]} */ const others = state.conversations.filter((c) => !c.pinned); return ( diff --git a/frontend/chat-ui/src/store.jsx b/frontend/chat-ui/src/store.jsx index f2a88a7..71a1948 100644 --- a/frontend/chat-ui/src/store.jsx +++ b/frontend/chat-ui/src/store.jsx @@ -1,5 +1,35 @@ import React, { createContext, useContext, useReducer } from 'react'; +/** + * @typedef {import('./types').Message} Message + * @typedef {import('./types').Conversation} Conversation + */ + +/** + * @typedef {Object} State + * @property {Message[]} messages + * @property {Conversation[]} conversations + * @property {Conversation|null} currentConversation + * @property {boolean} sidebarOpen + * @property {string} theme + * @property {boolean} memory + */ + +/** + * @typedef {( + * | { type: 'ADD_MESSAGE', message: Message } + * | { type: 'UPSERT_MESSAGE', message: Message } + * | { type: 'APPEND_MESSAGE_CONTENT', id: string, content: string } + * | { type: 'SET_MESSAGES', messages: Message[] } + * | { type: 'SET_CONVERSATIONS', conversations: Conversation[] } + * | { type: 'SET_CURRENT_CONVERSATION', conversation: Conversation | null } + * | { type: 'TOGGLE_SIDEBAR' } + * | { type: 'SET_THEME', theme: string } + * | { type: 'TOGGLE_MEMORY' } + * )} Action + */ + +/** @type {State} */ const initialState = { messages: [], conversations: [], @@ -9,6 +39,12 @@ const initialState = { memory: true, }; +/** + * Reducer handling chat UI actions. + * @param {State} state + * @param {Action} action + * @returns {State} + */ function reducer(state, action) { switch (action.type) { case 'ADD_MESSAGE': @@ -46,8 +82,14 @@ function reducer(state, action) { } } +/** @type {React.Context<{state: State, dispatch: React.Dispatch}>} */ const StoreContext = createContext(); +/** + * Provides the global store context. + * @param {{children: React.ReactNode}} props + * @returns {JSX.Element} + */ export function StoreProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); return ( @@ -57,6 +99,10 @@ export function StoreProvider({ children }) { ); } +/** + * Hook to access store state and dispatch. + * @returns {{state: State, dispatch: React.Dispatch}} + */ export function useStore() { return useContext(StoreContext); } diff --git a/frontend/chat-ui/src/types.js b/frontend/chat-ui/src/types.js new file mode 100644 index 0000000..2b025d7 --- /dev/null +++ b/frontend/chat-ui/src/types.js @@ -0,0 +1,24 @@ +/** + * Represents a single chat message. + * @typedef {Object} Message + * @property {string} id - Unique identifier for the message. + * @property {string} [role] - Role associated with the message sender. + * @property {string} [sender] - Alternative field for the sender. + * @property {string} [from] - Original sender of the message. + * @property {string} [to] - Intended recipient of the message. + * @property {string} [type] - Type of message, e.g. 'text' or 'file'. + * @property {string} [content] - Textual content of the message. + * @property {string} [message] - Alternate field for message content. + * @property {{path:string,originalname:string}} [file] - File metadata when type is 'file'. + * @property {number} [timestamp] - Unix epoch timestamp for when the message was created. + */ + +/** + * Represents a chat conversation. + * @typedef {Object} Conversation + * @property {string} id - Unique identifier for the conversation. + * @property {string} title - Display title of the conversation. + * @property {boolean} [pinned] - Whether the conversation is pinned in the sidebar. + */ + +export {};