Compare commits

...

2 Commits

Author SHA1 Message Date
openhands
01f5d89dcb fix: Fix frontend linting issues 2025-03-31 15:58:25 +00:00
openhands
677b8e8551 fix: Move conversation title generation to a dedicated endpoint and avoid modifications in GET requests 2025-03-31 15:50:04 +00:00
6 changed files with 83 additions and 38 deletions

View File

@@ -254,6 +254,16 @@ class OpenHands {
return data;
}
static async generateConversationTitle(
conversationId: string,
): Promise<Conversation | null> {
const { data } = await openHands.post<Conversation | null>(
`/api/conversations/${conversationId}/generate-title`,
);
return data;
}
/**
* Get the settings from the server or use the default settings if not found
*/

View File

@@ -77,6 +77,7 @@ export interface Conversation {
last_updated_at: string;
created_at: string;
status: ProjectStatus;
needs_title_update?: boolean;
}
export interface ResultSet<T> {

View File

@@ -58,9 +58,15 @@ export const useSettings = () => {
// that would prepopulate the data to the cache and mess with expectations. Read more:
// https://tanstack.com/query/latest/docs/framework/react/guides/initial-query-data#using-initialdata-to-prepopulate-a-query
if (query.error?.status === 404) {
// Return only the necessary properties to avoid excessive re-renders
return {
...query,
data: DEFAULT_SETTINGS,
status: query.status,
error: query.error,
isLoading: query.isLoading,
isError: query.isError,
isSuccess: query.isSuccess,
refetch: query.refetch,
};
}

View File

@@ -44,33 +44,36 @@ export function useAutoTitle() {
return;
}
if (conversation.title && !defaultTitlePattern.test(conversation.title)) {
// Check if the conversation needs a title update or has a default title
const needsUpdate =
conversation.needs_title_update ||
(conversation.title && defaultTitlePattern.test(conversation.title));
if (!needsUpdate) {
return;
}
updateConversation(
{
id: conversationId,
conversation: { title: "" },
},
{
onSuccess: async () => {
try {
const updatedConversation =
await OpenHands.getConversation(conversationId);
// Use the dedicated endpoint for generating titles
const generateTitle = async () => {
try {
const updatedConversation =
await OpenHands.generateConversationTitle(conversationId);
queryClient.setQueryData(
["user", "conversation", conversationId],
updatedConversation,
);
} catch (error) {
queryClient.invalidateQueries({
queryKey: ["user", "conversation", conversationId],
});
}
},
},
);
if (updatedConversation) {
queryClient.setQueryData(
["user", "conversation", conversationId],
updatedConversation,
);
}
} catch (error) {
// Silently handle error and invalidate the query to refresh data
queryClient.invalidateQueries({
queryKey: ["user", "conversation", conversationId],
});
}
};
generateTitle();
}, [
messages,
conversationId,

View File

@@ -17,3 +17,4 @@ class ConversationInfo:
status: ConversationStatus = ConversationStatus.STOPPED
selected_repository: str | None = None
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
needs_title_update: bool = False

View File

@@ -5,6 +5,7 @@ from fastapi import APIRouter, Body, Request, status
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from openhands.core.config.llm_config import LLMConfig
from openhands.core.logger import openhands_logger as logger
from openhands.events.action.message import MessageAction
from openhands.events.event import EventSource
@@ -34,6 +35,7 @@ from openhands.server.types import LLMAuthenticationError, MissingSettingsError
from openhands.storage.data_models.conversation_metadata import ConversationMetadata
from openhands.storage.data_models.conversation_status import ConversationStatus
from openhands.utils.async_utils import wait_all
from openhands.utils.conversation_summary import generate_conversation_title
app = APIRouter(prefix='/api')
@@ -244,22 +246,47 @@ async def get_conversation(
metadata = await conversation_store.get_metadata(conversation_id)
is_running = await conversation_manager.is_agent_loop_running(conversation_id)
# Check if we need to update the title
# Check if the title needs to be generated but don't update it in the GET request
needs_title_update = False
if is_running and metadata:
# Check if the title is a default title (contains the conversation ID)
if metadata.title and conversation_id[:5] in metadata.title:
# Generate a new title
new_title = await auto_generate_title(
conversation_id, get_user_id(request)
)
needs_title_update = True
if new_title:
# Update the metadata
metadata.title = new_title
await conversation_store.save_metadata(metadata)
conversation_info = await _get_conversation_info(metadata, is_running)
# Refresh metadata after update
metadata = await conversation_store.get_metadata(conversation_id)
# Add a flag to indicate that the title needs to be updated
if needs_title_update and conversation_info:
conversation_info.needs_title_update = True
return conversation_info
except FileNotFoundError:
return None
@app.post('/conversations/{conversation_id}/generate-title')
async def generate_conversation_title_endpoint(
conversation_id: str, request: Request
) -> ConversationInfo | None:
"""Generate a title for a conversation based on the first user message."""
conversation_store = await ConversationStoreImpl.get_instance(
config, get_user_id(request), get_github_user_id(request)
)
try:
metadata = await conversation_store.get_metadata(conversation_id)
is_running = await conversation_manager.is_agent_loop_running(conversation_id)
if metadata:
# Generate a new title
new_title = await auto_generate_title(conversation_id, get_user_id(request))
if new_title:
# Update the metadata
metadata.title = new_title
await conversation_store.save_metadata(metadata)
# Refresh metadata after update
metadata = await conversation_store.get_metadata(conversation_id)
conversation_info = await _get_conversation_info(metadata, is_running)
return conversation_info
@@ -312,9 +339,6 @@ async def auto_generate_title(conversation_id: str, user_id: str | None) -> str:
if first_user_message:
# Try LLM-based title generation first
from openhands.core.config.llm_config import LLMConfig
from openhands.utils.conversation_summary import generate_conversation_title
# Get LLM config from user settings
try:
settings_store = await SettingsStoreImpl.get_instance(config, user_id)