[Refactor]: Collapse initial user message for cloud resolver (#7871)

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Rohit Malhotra 2025-04-17 15:09:28 -04:00 committed by GitHub
parent fedd517a71
commit 0491357fef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 136 additions and 7 deletions

View File

@ -1,9 +1,18 @@
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { describe, it, expect, vi } from "vitest";
import { Messages } from "#/components/features/chat/messages";
import type { Message } from "#/message";
import { renderWithProviders } from "test-utils";
// Mock the useParams hook to provide a conversationId
vi.mock("react-router", async () => {
const actual = await vi.importActual<typeof import("react-router")>("react-router");
return {
...actual,
useParams: () => ({ conversationId: "test-conversation-id" }),
};
});
describe("File Operations Messages", () => {
it("should show success indicator for successful file read operation", () => {
const messages: Message[] = [

View File

@ -70,6 +70,8 @@ export interface AuthenticateResponse {
error?: string;
}
export type ConversationTrigger = "resolver" | "gui";
export interface Conversation {
conversation_id: string;
title: string;
@ -77,6 +79,7 @@ export interface Conversation {
last_updated_at: string;
created_at: string;
status: ProjectStatus;
trigger?: ConversationTrigger;
}
export interface ResultSet<T> {

View File

@ -53,6 +53,7 @@ export function ExpandableMessage({
});
useEffect(() => {
// If we have a translation ID, process it
if (id && i18n.exists(id)) {
let processedObservation = observation;
let processedAction = action;

View File

@ -4,6 +4,9 @@ import { ChatMessage } from "#/components/features/chat/chat-message";
import { ConfirmationButtons } from "#/components/shared/buttons/confirmation-buttons";
import { ImageCarousel } from "../images/image-carousel";
import { ExpandableMessage } from "./expandable-message";
import { useUserConversation } from "#/hooks/query/use-user-conversation";
import { useConversation } from "#/context/conversation-context";
import { I18nKey } from "#/i18n/declaration";
interface MessagesProps {
messages: Message[];
@ -11,13 +14,38 @@ interface MessagesProps {
}
export const Messages: React.FC<MessagesProps> = React.memo(
({ messages, isAwaitingUserConfirmation }) =>
messages.map((message, index) => {
({ messages, isAwaitingUserConfirmation }) => {
const { conversationId } = useConversation();
const { data: conversation } = useUserConversation(conversationId || null);
// Check if conversation metadata has trigger=resolver
const isResolverTrigger = conversation?.trigger === "resolver";
return messages.map((message, index) => {
const shouldShowConfirmationButtons =
messages.length - 1 === index &&
message.sender === "assistant" &&
isAwaitingUserConfirmation;
const isFirstUserMessageWithResolverTrigger =
index === 0 && message.sender === "user" && isResolverTrigger;
// Special case: First user message with resolver trigger
if (isFirstUserMessageWithResolverTrigger) {
return (
<div key={index}>
<ExpandableMessage
type="action"
message={message.content}
id={I18nKey.CHAT$RESOLVER_INSTRUCTIONS}
/>
{message.imageUrls && message.imageUrls.length > 0 && (
<ImageCarousel size="small" images={message.imageUrls} />
)}
</div>
);
}
if (message.type === "error" || message.type === "action") {
return (
<div key={index}>
@ -46,7 +74,8 @@ export const Messages: React.FC<MessagesProps> = React.memo(
{shouldShowConfirmationButtons && <ConfirmationButtons />}
</ChatMessage>
);
}),
});
},
);
Messages.displayName = "Messages";

View File

@ -1,5 +1,6 @@
// this file generate by script, don't modify it manually!!!
export enum I18nKey {
CHAT$RESOLVER_INSTRUCTIONS = "CHAT$RESOLVER_INSTRUCTIONS",
SETTINGS$ADVANCED = "SETTINGS$ADVANCED",
SETTINGS$BASE_URL = "SETTINGS$BASE_URL",
SETTINGS$AGENT = "SETTINGS$AGENT",

View File

@ -1,4 +1,19 @@
{
"CHAT$RESOLVER_INSTRUCTIONS": {
"en": "Resolver Instructions",
"ja": "リゾルバの指示",
"zh-CN": "解析器指令",
"zh-TW": "解析器指令",
"ko-KR": "리졸버 지침",
"no": "Resolver-instruksjoner",
"it": "Istruzioni del resolver",
"pt": "Instruções do resolvedor",
"es": "Instrucciones del resolvedor",
"ar": "تعليمات المحلل",
"fr": "Instructions du résolveur",
"tr": "Çözümleyici Talimatları",
"de": "Resolver-Anweisungen"
},
"SETTINGS$ADVANCED": {
"en": "Advanced",
"ja": "詳細設定",

View File

@ -0,0 +1,20 @@
You are requested to fix issue number #{{ issue_number }} in a repository.
A comment on the issue has been addressed to you.
# Comment
{{ issue_comment }}
# Steps to Handle the Comment
1. Address the comment. Use the GitHub API to read issue title, body, and comments if you need more context
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
2. The PR description should mention that it "fixes" or "closes" the issue number
3. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`

View File

@ -0,0 +1,12 @@
Please fix a issue number #{{ issue_number }} in your repository. Do the following
1. Read the issue body and comments using the Github API
2. For all changes to actual application code (e.g. in Python or Javascript), add an appropriate test to the testing directory to make sure that the issue has been fixed
3. Run the tests, and if they pass you are done!
4. You do NOT need to write new tests if there are only changes to documentation or configuration files.
When you're done, make sure to
1. Use the `GITHUB_TOKEN` environment variable and GitHub API to open a new PR
2. The PR description should mention that it "fixes" or "closes" the issue number
3. Make sure to leave the following sentence at the end of the PR description: `@{{ username }} can click here to [continue refining the PR]({{ conversation_url }})`

View File

@ -0,0 +1,25 @@
You are checked out to branch {{ branch_name }}, which has an open PR #{{ pr_number }}.
A comment on the PR (below) has been addressed to you. Do NOT respond to this comment via the GitHub API.
{% if file_location %} The comment is on the file `{{ file_location }}`{% endif %}.
# Comment
{{ pr_comment }}
# Steps to Handle the Comment
## Understand the PR Context
Use the GitHub API to:
1. Retrieve the diff against main to understand the changes
2. Fetch the PR body and the linked issue for context
## Process the Comment
If it's a question:
1. Answer the question asked
2. DO NOT leave any comments on the PR
If it requests a code update:
1. Modify the code accordingly in the current branch
2. Push the changes to update the PR
3. DO NOT leave any comments on the PR

View File

@ -474,3 +474,4 @@ class RemoteRuntime(ActionExecutionClient):
def _stop_if_closed(self, retry_state: tenacity.RetryCallState) -> bool:
return self._runtime_closed

View File

@ -1,6 +1,7 @@
from dataclasses import dataclass, field
from datetime import datetime, timezone
from openhands.storage.data_models.conversation_metadata import ConversationTrigger
from openhands.storage.data_models.conversation_status import ConversationStatus
@ -16,4 +17,5 @@ class ConversationInfo:
last_updated_at: datetime | None = None
status: ConversationStatus = ConversationStatus.STOPPED
selected_repository: str | None = None
trigger: ConversationTrigger | None = None
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

View File

@ -33,7 +33,7 @@ from openhands.server.shared import (
file_store,
)
from openhands.server.types import LLMAuthenticationError, MissingSettingsError
from openhands.storage.data_models.conversation_metadata import ConversationMetadata
from openhands.storage.data_models.conversation_metadata import ConversationMetadata, ConversationTrigger
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
@ -57,7 +57,8 @@ async def _create_new_conversation(
initial_user_msg: str | None,
image_urls: list[str] | None,
replay_json: str | None,
attach_convo_id: bool = False,
conversation_trigger: ConversationTrigger = ConversationTrigger.GUI,
attach_convo_id: bool = False
):
logger.info(
'Creating conversation',
@ -108,6 +109,7 @@ async def _create_new_conversation(
logger.info(f'Saving metadata for conversation {conversation_id}')
await conversation_store.save_metadata(
ConversationMetadata(
trigger=conversation_trigger,
conversation_id=conversation_id,
title=conversation_title,
user_id=user_id,
@ -388,6 +390,7 @@ async def _get_conversation_info(
if not title:
title = get_default_conversation_title(conversation.conversation_id)
return ConversationInfo(
trigger=conversation.trigger,
conversation_id=conversation.conversation_id,
title=title,
last_updated_at=conversation.last_updated_at,

View File

@ -1,9 +1,16 @@
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from openhands.integrations.service_types import ProviderType
class ConversationTrigger(Enum):
RESOLVER = 'resolver'
GUI = 'gui'
@dataclass
class ConversationMetadata:
class ConversationMetadata:
conversation_id: str
github_user_id: str | None
selected_repository: str | None
@ -11,6 +18,7 @@ class ConversationMetadata:
selected_branch: str | None = None
title: str | None = None
last_updated_at: datetime | None = None
trigger: ConversationTrigger | None = None
created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
# Cost and token metrics
accumulated_cost: float = 0.0