Tutorial

History & Feedback Management

The Synvo API provides comprehensive conversation management capabilities, including history retrieval, session management, and feedback collection. Each conversation is identified by a unique session ID (SUID), and users can provide feedback on AI responses to help improve performance and track satisfaction.

Authentication

All endpoints require authentication via either:

  • Bearer Token: Authorization: Bearer <token>
  • API Key: X-API-Key: <api_key>

Base URL

https://api.synvo.ai

History Management

List Sessions

Returns a list of all conversation sessions for the authenticated user, including session summaries and creation dates.

Endpoint: GET /history

Example Request

curl -X GET "https://api.synvo.ai/history" \
  -H "X-API-Key: ${API-KEY}"
import requests

token = "<BEARER_TOKEN>"
url = "https://api.synvo.ai/history"
headers = {"X-API-Key": f"{token}"}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";

const response = await fetch("https://api.synvo.ai/history", {
  method: "GET",
  headers: {
    "X-API-Key": `${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "sessions": [
    {
      "suid": "sess_abc123xyz",
      "created_at": "2024-01-15T10:30:00Z",
      "summary": "Discussion about quarterly financial reports and revenue analysis"
    },
    {
      "suid": "sess_def456uvw",
      "created_at": "2024-01-14T15:45:00Z",
      "summary": "Technical documentation review and API integration questions"
    },
    {
      "suid": "sess_ghi789rst",
      "created_at": "2024-01-13T09:20:00Z",
      "summary": "Project planning and resource allocation discussion"
    }
  ]
}

Get Session Details

Returns the complete message history for a specific conversation session, including all user and assistant messages.

Endpoint: GET /history/{suid}

Path Parameters

ParameterTypeRequiredDescription
suidstringYesSession unique identifier

Example Request

curl -X GET "https://api.synvo.ai/history/sess_abc123xyz" \
  -H "X-API-Key: ${API-KEY}"
import requests

token = "<BEARER_TOKEN>"
suid = "sess_abc123xyz"
url = f"https://api.synvo.ai/history/{suid}"
headers = {"X-API-Key": f"{token}"}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";
const suid = "sess_abc123xyz";

const response = await fetch(`https://api.synvo.ai/history/${suid}`, {
  method: "GET",
  headers: {
    "X-API-Key": `${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "session": {
    "suid": "sess_abc123xyz",
    "created_at": "2024-01-15T10:30:00Z",
    "summary": "Discussion about quarterly financial reports and revenue analysis",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "Can you analyze the Q4 financial report I uploaded?"
          }
        ],
        "cuid": "msg_user123",
        "created_at": "2024-01-15T10:30:15Z"
      },
      {
        "role": "assistant",
        "content": [
          {
            "type": "text",
            "text": "I'll analyze your Q4 financial report. Let me examine the key metrics and trends..."
          }
        ],
        "cuid": "msg_asst456",
        "created_at": "2024-01-15T10:30:45Z",
        "searched_files": ["file_abc123xyz"]
      }
    ]
  }
}

Message Structure

FieldTypeDescription
rolestringMessage role: user, assistant, system, or tool
contentarrayMessage content items (text, files, etc.)
cuidstringConversation message unique identifier
created_atstringMessage timestamp
searched_filesarrayFile IDs that were searched (assistant messages only)

Delete Session

Permanently deletes a conversation session and all its messages. This action cannot be undone.

Endpoint: DELETE /history/delete/{suid}

Path Parameters

ParameterTypeRequiredDescription
suidstringYesSession unique identifier

Example Request

curl -X DELETE "https://api.synvo.ai/history/delete/sess_abc123xyz" \
  -H "X-API-Key: ${API-KEY}"
import requests

token = "<BEARER_TOKEN>"
suid = "sess_abc123xyz"
url = f"https://api.synvo.ai/history/delete/{suid}"
headers = {"X-API-Key": f"{token}"}

response = requests.delete(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";
const suid = "sess_abc123xyz";

const response = await fetch(`https://api.synvo.ai/history/delete/${suid}`, {
  method: "DELETE",
  headers: {
    "X-API-Key": `${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "message": "Session deleted successfully"
}

Response Codes

  • 200 - Request successful
  • 401 - Unauthorized
  • 404 - Session not found

Feedback Management

The feedback system allows users to rate AI responses with like/dislike reactions, helping improve response quality and track satisfaction.

Provide Feedback

Submit feedback for a specific assistant message in a conversation.

Endpoint: POST /feedback

Request Body

FieldTypeRequiredDescription
cuidstringYesConversation message unique identifier
feedback_typestringYesType of feedback: like or dislike

Example Request

curl -X POST "https://api.synvo.ai/feedback" \
  -H "X-API-Key: ${API-KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "cuid": "msg_asst456",
    "feedback_type": "like"
  }'
import requests

token = "<BEARER_TOKEN>"
url = "https://api.synvo.ai/feedback"
headers = {
    "X-API-Key": f"{token}",
    "Content-Type": "application/json"
}
data = {
    "cuid": "msg_asst456",
    "feedback_type": "like"
}

response = requests.post(url, headers=headers, json=data, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";

const response = await fetch("https://api.synvo.ai/feedback", {
  method: "POST",
  headers: {
    "X-API-Key": `${token}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    cuid: "msg_asst456",
    feedback_type: "like"
  })
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "feedback": {
    "cuid": "msg_asst456",
    "feedback_type": "like",
    "created_at": "2024-01-15T10:35:00Z",
    "updated_at": "2024-01-15T10:35:00Z"
  }
}

Get Feedback

Retrieve feedback for a specific message.

Endpoint: GET /feedback/{cuid}

Path Parameters

ParameterTypeRequiredDescription
cuidstringYesConversation message unique identifier

Example Request

curl -X GET "https://api.synvo.ai/feedback/msg_asst456" \
  -H "X-API-Key: ${API-KEY}"
import requests

token = "<BEARER_TOKEN>"
cuid = "msg_asst456"
url = f"https://api.synvo.ai/feedback/{cuid}"
headers = {"X-API-Key": f"{token}"}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";
const cuid = "msg_asst456";

const response = await fetch(`https://api.synvo.ai/feedback/${cuid}`, {
  method: "GET",
  headers: {
    "X-API-Key": `${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "feedback": {
    "cuid": "msg_asst456",
    "feedback_type": "like",
    "created_at": "2024-01-15T10:35:00Z",
    "updated_at": "2024-01-15T10:35:00Z"
  }
}

Cancel Feedback

Remove previously provided feedback for a message.

Endpoint: DELETE /feedback/{cuid}

Path Parameters

ParameterTypeRequiredDescription
cuidstringYesConversation message unique identifier

Example Request

curl -X DELETE "https://api.synvo.ai/feedback/msg_asst456" \
  -H "X-API-Key: ${API-KEY}"
import requests

token = "<BEARER_TOKEN>"
cuid = "msg_asst456"
url = f"https://api.synvo.ai/feedback/{cuid}"
headers = {"X-API-Key": f"{token}"}

response = requests.delete(url, headers=headers, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";
const cuid = "msg_asst456";

const response = await fetch(`https://api.synvo.ai/feedback/${cuid}`, {
  method: "DELETE",
  headers: {
    "X-API-Key": `${token}`,
  },
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "message": "Feedback removed successfully"
}

Batch Retrieve Feedback

Get feedback for multiple messages in a single request.

Endpoint: POST /feedback/batch

Request Body

FieldTypeRequiredDescription
cuidsarrayYesArray of conversation message unique identifiers

Example Request

curl -X POST "https://api.synvo.ai/feedback/batch" \
  -H "X-API-Key: ${API-KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "cuids": ["msg_asst456", "msg_asst789", "msg_asst012"]
  }'
import requests

token = "<BEARER_TOKEN>"
url = "https://api.synvo.ai/feedback/batch"
headers = {
    "X-API-Key": f"{token}",
    "Content-Type": "application/json"
}
data = {
    "cuids": ["msg_asst456", "msg_asst789", "msg_asst012"]
}

response = requests.post(url, headers=headers, json=data, timeout=30)
response.raise_for_status()
print(response.json())
const token = "<BEARER_TOKEN>";

const response = await fetch("https://api.synvo.ai/feedback/batch", {
  method: "POST",
  headers: {
    "X-API-Key": `${token}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    cuids: ["msg_asst456", "msg_asst789", "msg_asst012"]
  })
});

if (!response.ok) {
  throw new Error(`Request failed: ${response.status}`);
}

console.log(await response.json());

Example Response

{
  "success": true,
  "feedback": {
    "msg_asst456": {
      "feedback_type": "like",
      "created_at": "2024-01-15T10:35:00Z",
      "updated_at": "2024-01-15T10:35:00Z"
    },
    "msg_asst789": {
      "feedback_type": "dislike",
      "created_at": "2024-01-15T10:40:00Z",
      "updated_at": "2024-01-15T10:40:00Z"
    },
    "msg_asst012": null
  }
}

Response Codes

  • 200 - Request successful
  • 201 - Feedback created successfully
  • 400 - Invalid request (e.g., trying to provide feedback on non-assistant messages)
  • 401 - Unauthorized
  • 404 - Message not found

Complete Implementation Examples

History Manager Class

import requests
import time
from typing import Optional, List, Dict, Any

class HistoryManager:
    def __init__(self, api_key: str, base_url: str = "https://api.synvo.ai"):
        """Initialize History Manager with API credentials"""
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {"X-API-Key": api_key}
    
    def list_sessions(self) -> List[Dict[str, Any]]:
        """Get all conversation sessions"""
        response = requests.get(f"{self.base_url}/history", headers=self.headers, timeout=30)
        response.raise_for_status()
        return response.json().get("sessions", [])
    
    def get_session(self, suid: str) -> Dict[str, Any]:
        """Get complete session details including messages"""
        response = requests.get(f"{self.base_url}/history/{suid}", headers=self.headers, timeout=30)
        response.raise_for_status()
        return response.json().get("session", {})
    
    def delete_session(self, suid: str) -> Dict[str, Any]:
        """Delete a conversation session"""
        response = requests.delete(f"{self.base_url}/history/delete/{suid}", headers=self.headers, timeout=30)
        response.raise_for_status()
        return response.json()
    
    def search_sessions(self, query: str) -> List[Dict[str, Any]]:
        """Search through session summaries"""
        sessions = self.list_sessions()
        query_lower = query.lower()
        return [s for s in sessions if query_lower in s.get("summary", "").lower()]
    
    def get_recent_sessions(self, limit: int = 10) -> List[Dict[str, Any]]:
        """Get most recent sessions"""
        sessions = self.list_sessions()
        # Sort by created_at descending
        sessions.sort(key=lambda x: x.get("created_at", ""), reverse=True)
        return sessions[:limit]
    
    def export_session(self, suid: str, format: str = "json") -> Any:
        """Export session in different formats"""
        session = self.get_session(suid)
        
        if format == "json":
            return session
        elif format == "text":
            # Convert to readable text format
            text_output = f"Session: {session['suid']}\n"
            text_output += f"Created: {session['created_at']}\n"
            text_output += f"Summary: {session.get('summary', 'N/A')}\n"
            text_output += "\nMessages:\n" + "="*50 + "\n"
            
            for msg in session.get("messages", []):
                text_output += f"\n[{msg['role'].upper()}] - {msg['created_at']}\n"
                for content in msg["content"]:
                    if content["type"] == "text":
                        text_output += f"{content['text']}\n"
                text_output += "-"*30 + "\n"
            
            return text_output
        else:
            raise ValueError(f"Unsupported format: {format}")

# Usage Example
if __name__ == "__main__":
    # Initialize manager
    api_key = "your_api_key_here"
    history_manager = HistoryManager(api_key)
    
    # Get all sessions
    sessions = history_manager.list_sessions()
    print(f"Found {len(sessions)} sessions")
    
    # Get recent sessions
    recent = history_manager.get_recent_sessions(5)
    for session in recent:
        print(f"Session {session['suid']}: {session['summary'][:50]}...")
    
    # Search sessions
    search_results = history_manager.search_sessions("financial")
    print(f"Found {len(search_results)} sessions matching 'financial'")
    
    # Export a session
    if sessions:
        first_session_id = sessions[0]["suid"]
        
        # Export as JSON
        json_export = history_manager.export_session(first_session_id, "json")
        
        # Export as text
        text_export = history_manager.export_session(first_session_id, "text")
        print(text_export[:500])  # Print first 500 chars
class HistoryManager {
  constructor(apiKey, baseUrl = "https://api.synvo.ai") {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.headers = { "X-API-Key": apiKey };
  }
  
  async listSessions() {
    const response = await fetch(`${this.baseUrl}/history`, {
      method: "GET",
      headers: this.headers
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    const data = await response.json();
    return data.sessions || [];
  }
  
  async getSession(suid) {
    const response = await fetch(`${this.baseUrl}/history/${suid}`, {
      method: "GET",
      headers: this.headers
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    const data = await response.json();
    return data.session || {};
  }
  
  async deleteSession(suid) {
    const response = await fetch(`${this.baseUrl}/history/delete/${suid}`, {
      method: "DELETE",
      headers: this.headers
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    return await response.json();
  }
  
  async searchSessions(query) {
    const sessions = await this.listSessions();
    const queryLower = query.toLowerCase();
    return sessions.filter(s => 
      (s.summary || "").toLowerCase().includes(queryLower)
    );
  }
  
  async getRecentSessions(limit = 10) {
    const sessions = await this.listSessions();
    // Sort by created_at descending
    sessions.sort((a, b) => 
      new Date(b.created_at) - new Date(a.created_at)
    );
    return sessions.slice(0, limit);
  }
  
  async exportSession(suid, format = "json") {
    const session = await this.getSession(suid);
    
    if (format === "json") {
      return session;
    } else if (format === "text") {
      // Convert to readable text format
      let textOutput = `Session: ${session.suid}\n`;
      textOutput += `Created: ${session.created_at}\n`;
      textOutput += `Summary: ${session.summary || "N/A"}\n`;
      textOutput += "\nMessages:\n" + "=".repeat(50) + "\n";
      
      for (const msg of session.messages || []) {
        textOutput += `\n[${msg.role.toUpperCase()}] - ${msg.created_at}\n`;
        for (const content of msg.content) {
          if (content.type === "text") {
            textOutput += `${content.text}\n`;
          }
        }
        textOutput += "-".repeat(30) + "\n";
      }
      
      return textOutput;
    } else {
      throw new Error(`Unsupported format: ${format}`);
    }
  }
}

// Usage Example
async function main() {
  // Initialize manager
  const apiKey = "your_api_key_here";
  const historyManager = new HistoryManager(apiKey);
  
  try {
    // Get all sessions
    const sessions = await historyManager.listSessions();
    console.log(`Found ${sessions.length} sessions`);
    
    // Get recent sessions
    const recent = await historyManager.getRecentSessions(5);
    for (const session of recent) {
      console.log(`Session ${session.suid}: ${session.summary.substring(0, 50)}...`);
    }
    
    // Search sessions
    const searchResults = await historyManager.searchSessions("financial");
    console.log(`Found ${searchResults.length} sessions matching 'financial'`);
    
    // Export a session
    if (sessions.length > 0) {
      const firstSessionId = sessions[0].suid;
      
      // Export as JSON
      const jsonExport = await historyManager.exportSession(firstSessionId, "json");
      
      // Export as text
      const textExport = await historyManager.exportSession(firstSessionId, "text");
      console.log(textExport.substring(0, 500));  // Print first 500 chars
    }
  } catch (error) {
    console.error("Error:", error);
  }
}

// Run if this is the main module
if (require.main === module) {
  main();
}

Feedback Manager Class

import requests
from typing import Optional, Dict, Any, List
from enum import Enum

class FeedbackType(Enum):
    LIKE = "like"
    DISLIKE = "dislike"
    NEUTRAL = "neutral"

class FeedbackManager:
    def __init__(self, api_key: str, base_url: str = "https://api.synvo.ai"):
        """Initialize Feedback Manager with API credentials"""
        self.api_key = api_key
        self.base_url = base_url
        self.headers = {
            "X-API-Key": api_key,
            "Content-Type": "application/json"
        }
    
    def provide_feedback(self, cuid: str, feedback_type: FeedbackType) -> Dict[str, Any]:
        """Provide feedback for a message"""
        data = {
            "cuid": cuid,
            "feedback_type": feedback_type.value
        }
        response = requests.post(
            f"{self.base_url}/feedback",
            headers=self.headers,
            json=data,
            timeout=30
        )
        response.raise_for_status()
        return response.json()
    
    def get_feedback(self, cuid: str) -> Optional[Dict[str, Any]]:
        """Get feedback for a specific message"""
        response = requests.get(
            f"{self.base_url}/feedback/{cuid}",
            headers={"X-API-Key": self.api_key},
            timeout=30
        )
        
        if response.status_code == 404:
            return None
        
        response.raise_for_status()
        return response.json().get("feedback")
    
    def cancel_feedback(self, cuid: str) -> Dict[str, Any]:
        """Cancel/remove feedback for a message"""
        response = requests.delete(
            f"{self.base_url}/feedback/{cuid}",
            headers={"X-API-Key": self.api_key},
            timeout=30
        )
        response.raise_for_status()
        return response.json()
    
    def batch_get_feedback(self, cuids: List[str]) -> Dict[str, Optional[Dict[str, Any]]]:
        """Get feedback for multiple messages"""
        data = {"cuids": cuids}
        response = requests.post(
            f"{self.base_url}/feedback/batch",
            headers=self.headers,
            json=data,
            timeout=30
        )
        response.raise_for_status()
        return response.json().get("feedback", {})
    
    def like_message(self, cuid: str) -> Dict[str, Any]:
        """Convenience method to like a message"""
        return self.provide_feedback(cuid, FeedbackType.LIKE)
    
    def dislike_message(self, cuid: str) -> Dict[str, Any]:
        """Convenience method to dislike a message"""
        return self.provide_feedback(cuid, FeedbackType.DISLIKE)
    
    def toggle_like(self, cuid: str) -> FeedbackType:
        """Toggle like status: none -> like -> none"""
        current = self.get_feedback(cuid)
        
        if current and current.get("feedback_type") == "like":
            # Currently liked, remove feedback
            self.cancel_feedback(cuid)
            return FeedbackType.NEUTRAL
        else:
            # Not liked (neutral or disliked), set to like
            self.like_message(cuid)
            return FeedbackType.LIKE
    
    def toggle_dislike(self, cuid: str) -> FeedbackType:
        """Toggle dislike status: none -> dislike -> none"""
        current = self.get_feedback(cuid)
        
        if current and current.get("feedback_type") == "dislike":
            # Currently disliked, remove feedback
            self.cancel_feedback(cuid)
            return FeedbackType.NEUTRAL
        else:
            # Not disliked (neutral or liked), set to dislike
            self.dislike_message(cuid)
            return FeedbackType.DISLIKE
    
    def get_session_feedback_summary(self, session: Dict[str, Any]) -> Dict[str, Any]:
        """Get feedback summary for all messages in a session"""
        assistant_messages = [
            msg for msg in session.get("messages", [])
            if msg["role"] == "assistant" and msg.get("cuid")
        ]
        
        if not assistant_messages:
            return {
                "total_messages": 0,
                "liked": 0,
                "disliked": 0,
                "no_feedback": 0,
                "satisfaction_rate": 0.0
            }
        
        cuids = [msg["cuid"] for msg in assistant_messages]
        feedback_data = self.batch_get_feedback(cuids)
        
        liked = sum(1 for f in feedback_data.values() 
                   if f and f.get("feedback_type") == "like")
        disliked = sum(1 for f in feedback_data.values() 
                      if f and f.get("feedback_type") == "dislike")
        no_feedback = sum(1 for f in feedback_data.values() if f is None)
        
        total = len(assistant_messages)
        satisfaction_rate = (liked / total * 100) if total > 0 else 0
        
        return {
            "total_messages": total,
            "liked": liked,
            "disliked": disliked,
            "no_feedback": no_feedback,
            "satisfaction_rate": round(satisfaction_rate, 2),
            "feedback_details": feedback_data
        }

# Usage Example
if __name__ == "__main__":
    # Initialize managers
    api_key = "your_api_key_here"
    feedback_manager = FeedbackManager(api_key)
    history_manager = HistoryManager(api_key)
    
    # Get a session
    sessions = history_manager.list_sessions()
    if sessions:
        session_id = sessions[0]["suid"]
        session = history_manager.get_session(session_id)
        
        # Get feedback summary for the session
        summary = feedback_manager.get_session_feedback_summary(session)
        print(f"Session Feedback Summary:")
        print(f"  Total Messages: {summary['total_messages']}")
        print(f"  Liked: {summary['liked']}")
        print(f"  Disliked: {summary['disliked']}")
        print(f"  No Feedback: {summary['no_feedback']}")
        print(f"  Satisfaction Rate: {summary['satisfaction_rate']}%")
        
        # Provide feedback for the first assistant message
        assistant_msgs = [m for m in session["messages"] 
                         if m["role"] == "assistant" and m.get("cuid")]
        
        if assistant_msgs:
            first_msg_id = assistant_msgs[0]["cuid"]
            
            # Like the message
            result = feedback_manager.like_message(first_msg_id)
            print(f"\nLiked message {first_msg_id}")
            
            # Toggle like (will remove the like)
            new_state = feedback_manager.toggle_like(first_msg_id)
            print(f"Toggled like, new state: {new_state.value}")
const FeedbackType = {
  LIKE: "like",
  DISLIKE: "dislike",
  NEUTRAL: "neutral"
};

class FeedbackManager {
  constructor(apiKey, baseUrl = "https://api.synvo.ai") {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
    this.headers = {
      "X-API-Key": apiKey,
      "Content-Type": "application/json"
    };
  }
  
  async provideFeedback(cuid, feedbackType) {
    const response = await fetch(`${this.baseUrl}/feedback`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify({
        cuid: cuid,
        feedback_type: feedbackType
      })
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    return await response.json();
  }
  
  async getFeedback(cuid) {
    const response = await fetch(`${this.baseUrl}/feedback/${cuid}`, {
      method: "GET",
      headers: { "X-API-Key": this.apiKey }
    });
    
    if (response.status === 404) {
      return null;
    }
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    const data = await response.json();
    return data.feedback;
  }
  
  async cancelFeedback(cuid) {
    const response = await fetch(`${this.baseUrl}/feedback/${cuid}`, {
      method: "DELETE",
      headers: { "X-API-Key": this.apiKey }
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    return await response.json();
  }
  
  async batchGetFeedback(cuids) {
    const response = await fetch(`${this.baseUrl}/feedback/batch`, {
      method: "POST",
      headers: this.headers,
      body: JSON.stringify({ cuids: cuids })
    });
    
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }
    
    const data = await response.json();
    return data.feedback || {};
  }
  
  async likeMessage(cuid) {
    return await this.provideFeedback(cuid, FeedbackType.LIKE);
  }
  
  async dislikeMessage(cuid) {
    return await this.provideFeedback(cuid, FeedbackType.DISLIKE);
  }
  
  async toggleLike(cuid) {
    const current = await this.getFeedback(cuid);
    
    if (current && current.feedback_type === "like") {
      // Currently liked, remove feedback
      await this.cancelFeedback(cuid);
      return FeedbackType.NEUTRAL;
    } else {
      // Not liked (neutral or disliked), set to like
      await this.likeMessage(cuid);
      return FeedbackType.LIKE;
    }
  }
  
  async toggleDislike(cuid) {
    const current = await this.getFeedback(cuid);
    
    if (current && current.feedback_type === "dislike") {
      // Currently disliked, remove feedback
      await this.cancelFeedback(cuid);
      return FeedbackType.NEUTRAL;
    } else {
      // Not disliked (neutral or liked), set to dislike
      await this.dislikeMessage(cuid);
      return FeedbackType.DISLIKE;
    }
  }
  
  async getSessionFeedbackSummary(session) {
    const assistantMessages = (session.messages || []).filter(
      msg => msg.role === "assistant" && msg.cuid
    );
    
    if (assistantMessages.length === 0) {
      return {
        total_messages: 0,
        liked: 0,
        disliked: 0,
        no_feedback: 0,
        satisfaction_rate: 0.0
      };
    }
    
    const cuids = assistantMessages.map(msg => msg.cuid);
    const feedbackData = await this.batchGetFeedback(cuids);
    
    let liked = 0;
    let disliked = 0;
    let noFeedback = 0;
    
    for (const feedback of Object.values(feedbackData)) {
      if (!feedback) {
        noFeedback++;
      } else if (feedback.feedback_type === "like") {
        liked++;
      } else if (feedback.feedback_type === "dislike") {
        disliked++;
      }
    }
    
    const total = assistantMessages.length;
    const satisfactionRate = total > 0 ? (liked / total * 100) : 0;
    
    return {
      total_messages: total,
      liked: liked,
      disliked: disliked,
      no_feedback: noFeedback,
      satisfaction_rate: Math.round(satisfactionRate * 100) / 100,
      feedback_details: feedbackData
    };
  }
}

// Usage Example
async function main() {
  // Initialize managers
  const apiKey = "your_api_key_here";
  const feedbackManager = new FeedbackManager(apiKey);
  const historyManager = new HistoryManager(apiKey);
  
  try {
    // Get a session
    const sessions = await historyManager.listSessions();
    if (sessions.length > 0) {
      const sessionId = sessions[0].suid;
      const session = await historyManager.getSession(sessionId);
      
      // Get feedback summary for the session
      const summary = await feedbackManager.getSessionFeedbackSummary(session);
      console.log("Session Feedback Summary:");
      console.log(`  Total Messages: ${summary.total_messages}`);
      console.log(`  Liked: ${summary.liked}`);
      console.log(`  Disliked: ${summary.disliked}`);
      console.log(`  No Feedback: ${summary.no_feedback}`);
      console.log(`  Satisfaction Rate: ${summary.satisfaction_rate}%`);
      
      // Provide feedback for the first assistant message
      const assistantMsgs = session.messages.filter(
        m => m.role === "assistant" && m.cuid
      );
      
      if (assistantMsgs.length > 0) {
        const firstMsgId = assistantMsgs[0].cuid;
        
        // Like the message
        const result = await feedbackManager.likeMessage(firstMsgId);
        console.log(`\nLiked message ${firstMsgId}`);
        
        // Toggle like (will remove the like)
        const newState = await feedbackManager.toggleLike(firstMsgId);
        console.log(`Toggled like, new state: ${newState}`);
      }
    }
  } catch (error) {
    console.error("Error:", error);
  }
}

// Run if this is the main module
if (require.main === module) {
  main();
}

UI Components

Vue.js Component

<template>
  <div class="conversation-history">
    <!-- Session List -->
    <div class="sessions-panel">
      <h2>Conversation History</h2>
      <div class="session-list">
        <div 
          v-for="session in sessions" 
          :key="session.suid"
          :class="['session-item', { active: selectedSession?.suid === session.suid }]"
          @click="selectSession(session)"
        >
          <div class="session-date">{{ formatDate(session.created_at) }}</div>
          <div class="session-summary">{{ session.summary }}</div>
        </div>
      </div>
    </div>
    
    <!-- Message Display -->
    <div class="messages-panel" v-if="selectedSession">
      <div class="messages-header">
        <h3>{{ selectedSession.summary }}</h3>
        <button @click="deleteSession" class="delete-button">Delete Session</button>
      </div>
      
      <div class="messages-container">
        <div 
          v-for="message in selectedSession.messages" 
          :key="message.cuid"
          :class="['message', message.role]"
        >
          <div class="message-header">
            <span class="role">{{ message.role }}</span>
            <span class="timestamp">{{ formatTime(message.created_at) }}</span>
          </div>
          
          <div class="message-content">
            <template v-for="(content, index) in message.content" :key="index">
              <p v-if="content.type === 'text'">{{ content.text }}</p>
              <div v-else-if="content.type === 'file'" class="file-ref">
                📎 {{ content.path }}
              </div>
            </template>
          </div>
          
          <!-- Feedback buttons for assistant messages -->
          <div v-if="message.role === 'assistant'" class="feedback-buttons">
            <button 
              :class="['like-button', { active: feedback[message.cuid] === 'like' }]"
              @click="handleLike(message.cuid)"
              :disabled="loading"
            >
              👍 Like
            </button>
            <button 
              :class="['dislike-button', { active: feedback[message.cuid] === 'dislike' }]"
              @click="handleDislike(message.cuid)"
              :disabled="loading"
            >
              👎 Dislike
            </button>
          </div>
        </div>
      </div>
      
      <!-- Feedback Summary -->
      <div class="feedback-summary" v-if="feedbackSummary">
        <h4>Session Feedback</h4>
        <div class="summary-stats">
          <div class="stat">
            <span class="label">Total Responses:</span>
            <span class="value">{{ feedbackSummary.total_messages }}</span>
          </div>
          <div class="stat">
            <span class="label">Liked:</span>
            <span class="value success">{{ feedbackSummary.liked }}</span>
          </div>
          <div class="stat">
            <span class="label">Disliked:</span>
            <span class="value danger">{{ feedbackSummary.disliked }}</span>
          </div>
          <div class="stat">
            <span class="label">Satisfaction:</span>
            <span class="value">{{ feedbackSummary.satisfaction_rate }}%</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ConversationHistory',
  
  data() {
    return {
      sessions: [],
      selectedSession: null,
      feedback: {},
      feedbackSummary: null,
      loading: false,
      historyManager: null,
      feedbackManager: null
    };
  },
  
  async mounted() {
    // Initialize managers
    this.historyManager = new HistoryManager(this.$apiKey);
    this.feedbackManager = new FeedbackManager(this.$apiKey);
    
    // Load sessions
    await this.loadSessions();
  },
  
  methods: {
    async loadSessions() {
      try {
        this.sessions = await this.historyManager.listSessions();
      } catch (error) {
        console.error('Error loading sessions:', error);
        this.$toast.error('Failed to load conversation history');
      }
    },
    
    async selectSession(session) {
      this.loading = true;
      try {
        // Get full session details
        this.selectedSession = await this.historyManager.getSession(session.suid);
        
        // Load feedback for all messages
        await this.loadFeedback();
        
        // Get feedback summary
        this.feedbackSummary = await this.feedbackManager.getSessionFeedbackSummary(
          this.selectedSession
        );
      } catch (error) {
        console.error('Error loading session:', error);
        this.$toast.error('Failed to load session details');
      } finally {
        this.loading = false;
      }
    },
    
    async loadFeedback() {
      const assistantMessages = this.selectedSession.messages.filter(
        msg => msg.role === 'assistant' && msg.cuid
      );
      
      if (assistantMessages.length > 0) {
        const cuids = assistantMessages.map(msg => msg.cuid);
        const feedbackData = await this.feedbackManager.batchGetFeedback(cuids);
        
        // Convert to local state format
        this.feedback = {};
        for (const [cuid, data] of Object.entries(feedbackData)) {
          if (data) {
            this.feedback[cuid] = data.feedback_type;
          }
        }
      }
    },
    
    async handleLike(cuid) {
      this.loading = true;
      try {
        const newState = await this.feedbackManager.toggleLike(cuid);
        
        if (newState === 'neutral') {
          this.$delete(this.feedback, cuid);
        } else {
          this.$set(this.feedback, cuid, 'like');
        }
        
        // Update summary
        this.feedbackSummary = await this.feedbackManager.getSessionFeedbackSummary(
          this.selectedSession
        );
        
        this.$toast.success('Feedback updated');
      } catch (error) {
        console.error('Error updating like:', error);
        this.$toast.error('Failed to update feedback');
      } finally {
        this.loading = false;
      }
    },
    
    async handleDislike(cuid) {
      this.loading = true;
      try {
        const newState = await this.feedbackManager.toggleDislike(cuid);
        
        if (newState === 'neutral') {
          this.$delete(this.feedback, cuid);
        } else {
          this.$set(this.feedback, cuid, 'dislike');
        }
        
        // Update summary
        this.feedbackSummary = await this.feedbackManager.getSessionFeedbackSummary(
          this.selectedSession
        );
        
        this.$toast.success('Feedback updated');
      } catch (error) {
        console.error('Error updating dislike:', error);
        this.$toast.error('Failed to update feedback');
      } finally {
        this.loading = false;
      }
    },
    
    async deleteSession() {
      if (!confirm('Are you sure you want to delete this session? This cannot be undone.')) {
        return;
      }
      
      this.loading = true;
      try {
        await this.historyManager.deleteSession(this.selectedSession.suid);
        
        // Remove from list
        const index = this.sessions.findIndex(s => s.suid === this.selectedSession.suid);
        if (index > -1) {
          this.sessions.splice(index, 1);
        }
        
        // Clear selection
        this.selectedSession = null;
        this.feedback = {};
        this.feedbackSummary = null;
        
        this.$toast.success('Session deleted successfully');
      } catch (error) {
        console.error('Error deleting session:', error);
        this.$toast.error('Failed to delete session');
      } finally {
        this.loading = false;
      }
    },
    
    formatDate(dateString) {
      return new Date(dateString).toLocaleDateString();
    },
    
    formatTime(dateString) {
      return new Date(dateString).toLocaleTimeString();
    }
  }
};
</script>

<style scoped>
.conversation-history {
  display: flex;
  height: 100vh;
  background-color: #f5f5f5;
}

.sessions-panel {
  width: 300px;
  background: white;
  border-right: 1px solid #e0e0e0;
  overflow-y: auto;
}

.sessions-panel h2 {
  padding: 1rem;
  margin: 0;
  border-bottom: 1px solid #e0e0e0;
  font-size: 1.2rem;
}

.session-list {
  padding: 0.5rem;
}

.session-item {
  padding: 0.75rem;
  margin-bottom: 0.5rem;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.session-item:hover {
  background-color: #f0f0f0;
}

.session-item.active {
  background-color: #e3f2fd;
  border-left: 3px solid #2196f3;
}

.session-date {
  font-size: 0.85rem;
  color: #666;
  margin-bottom: 0.25rem;
}

.session-summary {
  font-size: 0.9rem;
  color: #333;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.messages-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  background: white;
}

.messages-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid #e0e0e0;
}

.messages-header h3 {
  margin: 0;
  font-size: 1.1rem;
}

.delete-button {
  padding: 0.5rem 1rem;
  background-color: #f44336;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.delete-button:hover {
  background-color: #d32f2f;
}

.messages-container {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
}

.message {
  margin-bottom: 1.5rem;
  padding: 1rem;
  border-radius: 8px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.message.user {
  background-color: #e3f2fd;
  margin-left: 2rem;
}

.message.assistant {
  background-color: #f5f5f5;
  margin-right: 2rem;
}

.message-header {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.5rem;
}

.role {
  font-weight: 600;
  text-transform: capitalize;
  color: #555;
}

.timestamp {
  font-size: 0.85rem;
  color: #999;
}

.message-content {
  color: #333;
  line-height: 1.6;
}

.message-content p {
  margin: 0.5rem 0;
}

.file-ref {
  padding: 0.5rem;
  background-color: #f0f0f0;
  border-radius: 4px;
  margin: 0.5rem 0;
  font-size: 0.9rem;
}

.feedback-buttons {
  margin-top: 0.75rem;
  display: flex;
  gap: 0.5rem;
}

.like-button, .dislike-button {
  padding: 0.4rem 0.8rem;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: white;
  cursor: pointer;
  transition: all 0.2s;
  font-size: 0.9rem;
}

.like-button:hover {
  background-color: #e8f5e8;
  border-color: #4caf50;
}

.dislike-button:hover {
  background-color: #ffeaea;
  border-color: #f44336;
}

.like-button.active {
  background-color: #4caf50;
  color: white;
  border-color: #4caf50;
}

.dislike-button.active {
  background-color: #f44336;
  color: white;
  border-color: #f44336;
}

.like-button:disabled, .dislike-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.feedback-summary {
  padding: 1rem;
  background-color: #f9f9f9;
  border-top: 1px solid #e0e0e0;
}

.feedback-summary h4 {
  margin: 0 0 0.75rem 0;
  font-size: 1rem;
  color: #555;
}

.summary-stats {
  display: flex;
  gap: 1.5rem;
  flex-wrap: wrap;
}

.stat {
  display: flex;
  flex-direction: column;
}

.stat .label {
  font-size: 0.85rem;
  color: #666;
  margin-bottom: 0.25rem;
}

.stat .value {
  font-size: 1.2rem;
  font-weight: 600;
  color: #333;
}

.stat .value.success {
  color: #4caf50;
}

.stat .value.danger {
  color: #f44336;
}
</style>

Best Practices

History Management

  1. Pagination: Implement pagination for large history lists
  2. Caching: Cache frequently accessed sessions locally
  3. Search: Provide search and filtering capabilities
  4. Export: Allow users to export conversation history
  5. Privacy: Implement appropriate data retention policies

Feedback Collection

  1. Make it Easy: Provide simple, one-click feedback options
  2. Visual Feedback: Show clear visual indicators of current feedback state
  3. Non-intrusive: Don't interrupt the conversation flow
  4. Optional: Keep feedback completely optional
  5. Analytics: Regularly analyze feedback patterns

Performance Optimization

  1. Batch Operations: Use batch endpoints when working with multiple items
  2. Lazy Loading: Load session details only when needed
  3. Debouncing: Debounce feedback updates to avoid excessive API calls
  4. Error Recovery: Implement retry logic for transient failures
  5. Caching: Cache feedback state to reduce API calls

Error Handling

Common Error Codes

CodeDescription
MESSAGE_NOT_FOUNDThe specified message does not exist
INVALID_MESSAGE_TYPEFeedback can only be provided on assistant messages
SESSION_NOT_FOUNDThe specified session does not exist
UNAUTHORIZEDInvalid or missing authentication credentials
RATE_LIMIT_EXCEEDEDToo many requests in a short period

Error Recovery Strategies

  1. Retry with Backoff: Implement exponential backoff for rate limits
  2. Graceful Degradation: Continue functioning when feedback fails
  3. User Notification: Inform users of persistent issues
  4. Logging: Log errors for debugging and monitoring
  5. Fallback Options: Provide alternative actions when primary fails