mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
[Refactor]: Collapse initial user message for cloud resolver (#7871)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
fedd517a71
commit
0491357fef
@ -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[] = [
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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": "詳細設定",
|
||||
|
||||
@ -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 }})`
|
||||
@ -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 }})`
|
||||
25
openhands/integrations/github/templates/pr_update_prompt.j2
Normal file
25
openhands/integrations/github/templates/pr_update_prompt.j2
Normal 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
|
||||
@ -474,3 +474,4 @@ class RemoteRuntime(ActionExecutionClient):
|
||||
|
||||
def _stop_if_closed(self, retry_state: tenacity.RetryCallState) -> bool:
|
||||
return self._runtime_closed
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user