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.aiHistory 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
| Parameter | Type | Required | Description |
|---|---|---|---|
suid | string | Yes | Session 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
| Field | Type | Description |
|---|---|---|
role | string | Message role: user, assistant, system, or tool |
content | array | Message content items (text, files, etc.) |
cuid | string | Conversation message unique identifier |
created_at | string | Message timestamp |
searched_files | array | File 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
| Parameter | Type | Required | Description |
|---|---|---|---|
suid | string | Yes | Session 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 successful401- Unauthorized404- 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
| Field | Type | Required | Description |
|---|---|---|---|
cuid | string | Yes | Conversation message unique identifier |
feedback_type | string | Yes | Type 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
| Parameter | Type | Required | Description |
|---|---|---|---|
cuid | string | Yes | Conversation 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
| Parameter | Type | Required | Description |
|---|---|---|---|
cuid | string | Yes | Conversation 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
| Field | Type | Required | Description |
|---|---|---|---|
cuids | array | Yes | Array 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 successful201- Feedback created successfully400- Invalid request (e.g., trying to provide feedback on non-assistant messages)401- Unauthorized404- 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 charsclass 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
- Pagination: Implement pagination for large history lists
- Caching: Cache frequently accessed sessions locally
- Search: Provide search and filtering capabilities
- Export: Allow users to export conversation history
- Privacy: Implement appropriate data retention policies
Feedback Collection
- Make it Easy: Provide simple, one-click feedback options
- Visual Feedback: Show clear visual indicators of current feedback state
- Non-intrusive: Don't interrupt the conversation flow
- Optional: Keep feedback completely optional
- Analytics: Regularly analyze feedback patterns
Performance Optimization
- Batch Operations: Use batch endpoints when working with multiple items
- Lazy Loading: Load session details only when needed
- Debouncing: Debounce feedback updates to avoid excessive API calls
- Error Recovery: Implement retry logic for transient failures
- Caching: Cache feedback state to reduce API calls
Error Handling
Common Error Codes
| Code | Description |
|---|---|
MESSAGE_NOT_FOUND | The specified message does not exist |
INVALID_MESSAGE_TYPE | Feedback can only be provided on assistant messages |
SESSION_NOT_FOUND | The specified session does not exist |
UNAUTHORIZED | Invalid or missing authentication credentials |
RATE_LIMIT_EXCEEDED | Too many requests in a short period |
Error Recovery Strategies
- Retry with Backoff: Implement exponential backoff for rate limits
- Graceful Degradation: Continue functioning when feedback fails
- User Notification: Inform users of persistent issues
- Logging: Log errors for debugging and monitoring
- Fallback Options: Provide alternative actions when primary fails