feat: show available skills for v1 conversations (#12039)

This commit is contained in:
Hiep Le
2025-12-17 23:25:10 +07:00
committed by GitHub
parent f98e7fbc49
commit 9ef11bf930
28 changed files with 1325 additions and 489 deletions

View File

@@ -42,7 +42,7 @@ vi.mock("react-i18next", async () => {
BUTTON$EXPORT_CONVERSATION: "Export Conversation",
BUTTON$DOWNLOAD_VIA_VSCODE: "Download via VS Code",
BUTTON$SHOW_AGENT_TOOLS_AND_METADATA: "Show Agent Tools",
CONVERSATION$SHOW_MICROAGENTS: "Show Microagents",
CONVERSATION$SHOW_SKILLS: "Show Skills",
BUTTON$DISPLAY_COST: "Display Cost",
COMMON$CLOSE_CONVERSATION_STOP_RUNTIME:
"Close Conversation (Stop Runtime)",
@@ -290,7 +290,7 @@ describe("ConversationNameContextMenu", () => {
onStop: vi.fn(),
onDisplayCost: vi.fn(),
onShowAgentTools: vi.fn(),
onShowMicroagents: vi.fn(),
onShowSkills: vi.fn(),
onExportConversation: vi.fn(),
onDownloadViaVSCode: vi.fn(),
};
@@ -304,7 +304,7 @@ describe("ConversationNameContextMenu", () => {
expect(screen.getByTestId("stop-button")).toBeInTheDocument();
expect(screen.getByTestId("display-cost-button")).toBeInTheDocument();
expect(screen.getByTestId("show-agent-tools-button")).toBeInTheDocument();
expect(screen.getByTestId("show-microagents-button")).toBeInTheDocument();
expect(screen.getByTestId("show-skills-button")).toBeInTheDocument();
expect(
screen.getByTestId("export-conversation-button"),
).toBeInTheDocument();
@@ -321,9 +321,7 @@ describe("ConversationNameContextMenu", () => {
expect(
screen.queryByTestId("show-agent-tools-button"),
).not.toBeInTheDocument();
expect(
screen.queryByTestId("show-microagents-button"),
).not.toBeInTheDocument();
expect(screen.queryByTestId("show-skills-button")).not.toBeInTheDocument();
expect(
screen.queryByTestId("export-conversation-button"),
).not.toBeInTheDocument();
@@ -410,19 +408,19 @@ describe("ConversationNameContextMenu", () => {
it("should call show microagents handler when show microagents button is clicked", async () => {
const user = userEvent.setup();
const onShowMicroagents = vi.fn();
const onShowSkills = vi.fn();
renderWithProviders(
<ConversationNameContextMenu
{...defaultProps}
onShowMicroagents={onShowMicroagents}
onShowSkills={onShowSkills}
/>,
);
const showMicroagentsButton = screen.getByTestId("show-microagents-button");
const showMicroagentsButton = screen.getByTestId("show-skills-button");
await user.click(showMicroagentsButton);
expect(onShowMicroagents).toHaveBeenCalledTimes(1);
expect(onShowSkills).toHaveBeenCalledTimes(1);
});
it("should call export conversation handler when export conversation button is clicked", async () => {
@@ -519,7 +517,7 @@ describe("ConversationNameContextMenu", () => {
onStop: vi.fn(),
onDisplayCost: vi.fn(),
onShowAgentTools: vi.fn(),
onShowMicroagents: vi.fn(),
onShowSkills: vi.fn(),
onExportConversation: vi.fn(),
onDownloadViaVSCode: vi.fn(),
};
@@ -541,8 +539,8 @@ describe("ConversationNameContextMenu", () => {
expect(screen.getByTestId("show-agent-tools-button")).toHaveTextContent(
"Show Agent Tools",
);
expect(screen.getByTestId("show-microagents-button")).toHaveTextContent(
"Show Microagents",
expect(screen.getByTestId("show-skills-button")).toHaveTextContent(
"Show Skills",
);
expect(screen.getByTestId("export-conversation-button")).toHaveTextContent(
"Export Conversation",

View File

@@ -1,91 +0,0 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderWithProviders } from "test-utils";
import { MicroagentsModal } from "#/components/features/conversation-panel/microagents-modal";
import ConversationService from "#/api/conversation-service/conversation-service.api";
import { AgentState } from "#/types/agent-state";
import { useAgentState } from "#/hooks/use-agent-state";
// Mock the agent state hook
vi.mock("#/hooks/use-agent-state", () => ({
useAgentState: vi.fn(),
}));
// Mock the conversation ID hook
vi.mock("#/hooks/use-conversation-id", () => ({
useConversationId: () => ({ conversationId: "test-conversation-id" }),
}));
describe("MicroagentsModal - Refresh Button", () => {
const mockOnClose = vi.fn();
const conversationId = "test-conversation-id";
const defaultProps = {
onClose: mockOnClose,
conversationId,
};
const mockMicroagents = [
{
name: "Test Agent 1",
type: "repo" as const,
triggers: ["test", "example"],
content: "This is test content for agent 1",
},
{
name: "Test Agent 2",
type: "knowledge" as const,
triggers: ["help", "support"],
content: "This is test content for agent 2",
},
];
beforeEach(() => {
// Reset all mocks before each test
vi.clearAllMocks();
// Setup default mock for getMicroagents
vi.spyOn(ConversationService, "getMicroagents").mockResolvedValue({
microagents: mockMicroagents,
});
// Mock the agent state to return a ready state
vi.mocked(useAgentState).mockReturnValue({
curAgentState: AgentState.AWAITING_USER_INPUT,
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("Refresh Button Rendering", () => {
it("should render the refresh button with correct text and test ID", async () => {
renderWithProviders(<MicroagentsModal {...defaultProps} />);
// Wait for the component to load and render the refresh button
const refreshButton = await screen.findByTestId("refresh-microagents");
expect(refreshButton).toBeInTheDocument();
expect(refreshButton).toHaveTextContent("BUTTON$REFRESH");
});
});
describe("Refresh Button Functionality", () => {
it("should call refetch when refresh button is clicked", async () => {
const user = userEvent.setup();
const refreshSpy = vi.spyOn(ConversationService, "getMicroagents");
renderWithProviders(<MicroagentsModal {...defaultProps} />);
// Wait for the component to load and render the refresh button
const refreshButton = await screen.findByTestId("refresh-microagents");
refreshSpy.mockClear();
await user.click(refreshButton);
expect(refreshSpy).toHaveBeenCalledTimes(1);
});
});
});

View File

@@ -0,0 +1,394 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderWithProviders } from "test-utils";
import { SkillsModal } from "#/components/features/conversation-panel/skills-modal";
import ConversationService from "#/api/conversation-service/conversation-service.api";
import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api";
import { AgentState } from "#/types/agent-state";
import { useAgentState } from "#/hooks/use-agent-state";
import SettingsService from "#/api/settings-service/settings-service.api";
// Mock the agent state hook
vi.mock("#/hooks/use-agent-state", () => ({
useAgentState: vi.fn(),
}));
// Mock the conversation ID hook
vi.mock("#/hooks/use-conversation-id", () => ({
useConversationId: () => ({ conversationId: "test-conversation-id" }),
}));
describe("SkillsModal - Refresh Button", () => {
const mockOnClose = vi.fn();
const conversationId = "test-conversation-id";
const defaultProps = {
onClose: mockOnClose,
conversationId,
};
const mockSkills = [
{
name: "Test Agent 1",
type: "repo" as const,
triggers: ["test", "example"],
content: "This is test content for agent 1",
},
{
name: "Test Agent 2",
type: "knowledge" as const,
triggers: ["help", "support"],
content: "This is test content for agent 2",
},
];
beforeEach(() => {
// Reset all mocks before each test
vi.clearAllMocks();
// Setup default mock for getMicroagents (V0)
vi.spyOn(ConversationService, "getMicroagents").mockResolvedValue({
microagents: mockSkills,
});
// Mock the agent state to return a ready state
vi.mocked(useAgentState).mockReturnValue({
curAgentState: AgentState.AWAITING_USER_INPUT,
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("Refresh Button Rendering", () => {
it("should render the refresh button with correct text and test ID", async () => {
renderWithProviders(<SkillsModal {...defaultProps} />);
// Wait for the component to load and render the refresh button
const refreshButton = await screen.findByTestId("refresh-skills");
expect(refreshButton).toBeInTheDocument();
expect(refreshButton).toHaveTextContent("BUTTON$REFRESH");
});
});
describe("Refresh Button Functionality", () => {
it("should call refetch when refresh button is clicked", async () => {
const user = userEvent.setup();
const refreshSpy = vi.spyOn(ConversationService, "getMicroagents");
renderWithProviders(<SkillsModal {...defaultProps} />);
// Wait for the component to load and render the refresh button
const refreshButton = await screen.findByTestId("refresh-skills");
// Clear previous calls to only track the click
refreshSpy.mockClear();
await user.click(refreshButton);
// Verify the refresh triggered a new API call
expect(refreshSpy).toHaveBeenCalled();
});
});
});
describe("useConversationSkills - V1 API Integration", () => {
const conversationId = "test-conversation-id";
const mockMicroagents = [
{
name: "V0 Test Agent",
type: "repo" as const,
triggers: ["v0"],
content: "V0 skill content",
},
];
const mockSkills = [
{
name: "V1 Test Skill",
type: "knowledge" as const,
triggers: ["v1", "skill"],
content: "V1 skill content",
},
];
beforeEach(() => {
vi.clearAllMocks();
// Mock agent state
vi.mocked(useAgentState).mockReturnValue({
curAgentState: AgentState.AWAITING_USER_INPUT,
});
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("V0 API Usage (v1_enabled: false)", () => {
it("should call v0 ConversationService.getMicroagents when v1_enabled is false", async () => {
// Arrange
const getMicroagentsSpy = vi
.spyOn(ConversationService, "getMicroagents")
.mockResolvedValue({ microagents: mockMicroagents });
vi.spyOn(SettingsService, "getSettings").mockResolvedValue({
v1_enabled: false,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
// Act
renderWithProviders(<SkillsModal onClose={vi.fn()} />);
// Assert
await screen.findByText("V0 Test Agent");
expect(getMicroagentsSpy).toHaveBeenCalledWith(conversationId);
expect(getMicroagentsSpy).toHaveBeenCalledTimes(1);
});
it("should display v0 skills correctly", async () => {
// Arrange
vi.spyOn(ConversationService, "getMicroagents").mockResolvedValue({
microagents: mockMicroagents,
});
vi.spyOn(SettingsService, "getSettings").mockResolvedValue({
v1_enabled: false,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
// Act
renderWithProviders(<SkillsModal onClose={vi.fn()} />);
// Assert
const agentName = await screen.findByText("V0 Test Agent");
expect(agentName).toBeInTheDocument();
});
});
describe("V1 API Usage (v1_enabled: true)", () => {
it("should call v1 V1ConversationService.getSkills when v1_enabled is true", async () => {
// Arrange
const getSkillsSpy = vi
.spyOn(V1ConversationService, "getSkills")
.mockResolvedValue({ skills: mockSkills });
vi.spyOn(SettingsService, "getSettings").mockResolvedValue({
v1_enabled: true,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
// Act
renderWithProviders(<SkillsModal onClose={vi.fn()} />);
// Assert
await screen.findByText("V1 Test Skill");
expect(getSkillsSpy).toHaveBeenCalledWith(conversationId);
expect(getSkillsSpy).toHaveBeenCalledTimes(1);
});
it("should display v1 skills correctly", async () => {
// Arrange
vi.spyOn(V1ConversationService, "getSkills").mockResolvedValue({
skills: mockSkills,
});
vi.spyOn(SettingsService, "getSettings").mockResolvedValue({
v1_enabled: true,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
// Act
renderWithProviders(<SkillsModal onClose={vi.fn()} />);
// Assert
const skillName = await screen.findByText("V1 Test Skill");
expect(skillName).toBeInTheDocument();
});
it("should use v1 API when v1_enabled is true", async () => {
// Arrange
vi.spyOn(SettingsService, "getSettings").mockResolvedValue({
v1_enabled: true,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
const getSkillsSpy = vi
.spyOn(V1ConversationService, "getSkills")
.mockResolvedValue({
skills: mockSkills,
});
// Act
renderWithProviders(<SkillsModal onClose={vi.fn()} />);
// Assert
await screen.findByText("V1 Test Skill");
// Verify v1 API was called
expect(getSkillsSpy).toHaveBeenCalledWith(conversationId);
});
});
describe("API Switching on Settings Change", () => {
it("should refetch using different API when v1_enabled setting changes", async () => {
// Arrange
const getMicroagentsSpy = vi
.spyOn(ConversationService, "getMicroagents")
.mockResolvedValue({ microagents: mockMicroagents });
const getSkillsSpy = vi
.spyOn(V1ConversationService, "getSkills")
.mockResolvedValue({ skills: mockSkills });
const settingsSpy = vi
.spyOn(SettingsService, "getSettings")
.mockResolvedValue({
v1_enabled: false,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
// Act - Initial render with v1_enabled: false
const { rerender } = renderWithProviders(
<SkillsModal onClose={vi.fn()} />,
);
// Assert - v0 API called initially
await screen.findByText("V0 Test Agent");
expect(getMicroagentsSpy).toHaveBeenCalledWith(conversationId);
// Arrange - Change settings to v1_enabled: true
settingsSpy.mockResolvedValue({
v1_enabled: true,
llm_model: "test-model",
llm_base_url: "",
agent: "test-agent",
language: "en",
llm_api_key: null,
llm_api_key_set: false,
search_api_key_set: false,
confirmation_mode: false,
security_analyzer: null,
remote_runtime_resource_factor: null,
provider_tokens_set: {},
enable_default_condenser: false,
condenser_max_size: null,
enable_sound_notifications: false,
enable_proactive_conversation_starters: false,
enable_solvability_analysis: false,
user_consents_to_analytics: null,
max_budget_per_task: null,
});
// Act - Force re-render
rerender(<SkillsModal onClose={vi.fn()} />);
// Assert - v1 API should be called after settings change
await screen.findByText("V1 Test Skill");
expect(getSkillsSpy).toHaveBeenCalledWith(conversationId);
});
});
});

View File

@@ -11,6 +11,7 @@ import type {
V1AppConversationStartTask,
V1AppConversationStartTaskPage,
V1AppConversation,
GetSkillsResponse,
} from "./v1-conversation-service.types";
class V1ConversationService {
@@ -315,6 +316,18 @@ class V1ConversationService {
);
return data;
}
/**
* Get all skills associated with a V1 conversation
* @param conversationId The conversation ID
* @returns The available skills associated with the conversation
*/
static async getSkills(conversationId: string): Promise<GetSkillsResponse> {
const { data } = await openHands.get<GetSkillsResponse>(
`/api/v1/app-conversations/${conversationId}/skills`,
);
return data;
}
}
export default V1ConversationService;

View File

@@ -99,3 +99,14 @@ export interface V1AppConversation {
conversation_url: string | null;
session_api_key: string | null;
}
export interface Skill {
name: string;
type: "repo" | "knowledge";
content: string;
triggers: string[];
}
export interface GetSkillsResponse {
skills: Skill[];
}

View File

@@ -26,14 +26,14 @@ const contextMenuListItemClassName = cn(
interface ToolsContextMenuProps {
onClose: () => void;
onShowMicroagents: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowSkills: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools: (event: React.MouseEvent<HTMLButtonElement>) => void;
shouldShowAgentTools?: boolean;
}
export function ToolsContextMenu({
onClose,
onShowMicroagents,
onShowSkills,
onShowAgentTools,
shouldShowAgentTools = true,
}: ToolsContextMenuProps) {
@@ -41,7 +41,6 @@ export function ToolsContextMenu({
const { data: conversation } = useActiveConversation();
const { providers } = useUserProviders();
// TODO: Hide microagent menu items for V1 conversations
// This is a temporary measure and may be re-enabled in the future
const isV1Conversation = conversation?.conversation_version === "V1";
@@ -130,20 +129,17 @@ export function ToolsContextMenu({
{(!isV1Conversation || shouldShowAgentTools) && <Divider />}
{/* Show Available Microagents - Hidden for V1 conversations */}
{!isV1Conversation && (
<ContextMenuListItem
testId="show-microagents-button"
onClick={onShowMicroagents}
className={contextMenuListItemClassName}
>
<ToolsContextMenuIconText
icon={<RobotIcon width={16} height={16} />}
text={t(I18nKey.CONVERSATION$SHOW_MICROAGENTS)}
className={CONTEXT_MENU_ICON_TEXT_CLASSNAME}
/>
</ContextMenuListItem>
)}
<ContextMenuListItem
testId="show-skills-button"
onClick={onShowSkills}
className={contextMenuListItemClassName}
>
<ToolsContextMenuIconText
icon={<RobotIcon width={16} height={16} />}
text={t(I18nKey.CONVERSATION$SHOW_SKILLS)}
className={CONTEXT_MENU_ICON_TEXT_CLASSNAME}
/>
</ContextMenuListItem>
{/* Show Agent Tools and Metadata - Only show if system message is available */}
{shouldShowAgentTools && (

View File

@@ -7,7 +7,7 @@ import { ToolsContextMenu } from "./tools-context-menu";
import { useConversationNameContextMenu } from "#/hooks/use-conversation-name-context-menu";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { SystemMessageModal } from "../conversation-panel/system-message-modal";
import { MicroagentsModal } from "../conversation-panel/microagents-modal";
import { SkillsModal } from "../conversation-panel/skills-modal";
export function Tools() {
const { t } = useTranslation();
@@ -17,11 +17,11 @@ export function Tools() {
const {
handleShowAgentTools,
handleShowMicroagents,
handleShowSkills,
systemModalVisible,
setSystemModalVisible,
microagentsModalVisible,
setMicroagentsModalVisible,
skillsModalVisible,
setSkillsModalVisible,
systemMessage,
shouldShowAgentTools,
} = useConversationNameContextMenu({
@@ -51,7 +51,7 @@ export function Tools() {
{contextMenuOpen && (
<ToolsContextMenu
onClose={() => setContextMenuOpen(false)}
onShowMicroagents={handleShowMicroagents}
onShowSkills={handleShowSkills}
onShowAgentTools={handleShowAgentTools}
shouldShowAgentTools={shouldShowAgentTools}
/>
@@ -64,9 +64,9 @@ export function Tools() {
systemMessage={systemMessage ? systemMessage.args : null}
/>
{/* Microagents Modal */}
{microagentsModalVisible && (
<MicroagentsModal onClose={() => setMicroagentsModalVisible(false)} />
{/* Skills Modal */}
{skillsModalVisible && (
<SkillsModal onClose={() => setSkillsModalVisible(false)} />
)}
</div>
);

View File

@@ -1,147 +0,0 @@
import {
Trash,
Power,
Pencil,
Download,
Wallet,
Wrench,
Bot,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { useClickOutsideElement } from "#/hooks/use-click-outside-element";
import { cn } from "#/utils/utils";
import { ContextMenu } from "#/ui/context-menu";
import { ContextMenuListItem } from "../context-menu/context-menu-list-item";
import { Divider } from "#/ui/divider";
import { I18nKey } from "#/i18n/declaration";
import { ContextMenuIconText } from "../context-menu/context-menu-icon-text";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
interface ConversationCardContextMenuProps {
onClose: () => void;
onDelete?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onStop?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onEdit?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDisplayCost?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowMicroagents?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDownloadViaVSCode?: (event: React.MouseEvent<HTMLButtonElement>) => void;
position?: "top" | "bottom";
}
export function ConversationCardContextMenu({
onClose,
onDelete,
onStop,
onEdit,
onDisplayCost,
onShowAgentTools,
onShowMicroagents,
onDownloadViaVSCode,
position = "bottom",
}: ConversationCardContextMenuProps) {
const { t } = useTranslation();
const ref = useClickOutsideElement<HTMLUListElement>(onClose);
const { data: conversation } = useActiveConversation();
// TODO: Hide microagent menu items for V1 conversations
// This is a temporary measure and may be re-enabled in the future
const isV1Conversation = conversation?.conversation_version === "V1";
const hasEdit = Boolean(onEdit);
const hasDownload = Boolean(onDownloadViaVSCode);
const hasTools = Boolean(onShowAgentTools || onShowMicroagents);
const hasInfo = Boolean(onDisplayCost);
const hasControl = Boolean(onStop || onDelete);
return (
<ContextMenu
ref={ref}
testId="context-menu"
className={cn(
"right-0 absolute mt-3",
position === "top" && "bottom-full",
position === "bottom" && "top-full",
)}
>
{onEdit && (
<ContextMenuListItem testId="edit-button" onClick={onEdit}>
<ContextMenuIconText
icon={Pencil}
text={t(I18nKey.BUTTON$EDIT_TITLE)}
/>
</ContextMenuListItem>
)}
{hasEdit && (hasDownload || hasTools || hasInfo || hasControl) && (
<Divider />
)}
{onDownloadViaVSCode && (
<ContextMenuListItem
testId="download-vscode-button"
onClick={onDownloadViaVSCode}
>
<ContextMenuIconText
icon={Download}
text={t(I18nKey.BUTTON$DOWNLOAD_VIA_VSCODE)}
/>
</ContextMenuListItem>
)}
{hasDownload && (hasTools || hasInfo || hasControl) && <Divider />}
{onShowAgentTools && (
<ContextMenuListItem
testId="show-agent-tools-button"
onClick={onShowAgentTools}
>
<ContextMenuIconText
icon={Wrench}
text={t(I18nKey.BUTTON$SHOW_AGENT_TOOLS_AND_METADATA)}
/>
</ContextMenuListItem>
)}
{onShowMicroagents && !isV1Conversation && (
<ContextMenuListItem
testId="show-microagents-button"
onClick={onShowMicroagents}
>
<ContextMenuIconText
icon={Bot}
text={t(I18nKey.CONVERSATION$SHOW_MICROAGENTS)}
/>
</ContextMenuListItem>
)}
{hasTools && (hasInfo || hasControl) && <Divider />}
{onDisplayCost && (
<ContextMenuListItem
testId="display-cost-button"
onClick={onDisplayCost}
>
<ContextMenuIconText
icon={Wallet}
text={t(I18nKey.BUTTON$DISPLAY_COST)}
/>
</ContextMenuListItem>
)}
{hasInfo && hasControl && <Divider />}
{onStop && (
<ContextMenuListItem testId="stop-button" onClick={onStop}>
<ContextMenuIconText icon={Power} text={t(I18nKey.BUTTON$PAUSE)} />
</ContextMenuListItem>
)}
{onDelete && (
<ContextMenuListItem testId="delete-button" onClick={onDelete}>
<ContextMenuIconText icon={Trash} text={t(I18nKey.BUTTON$DELETE)} />
</ContextMenuListItem>
)}
</ContextMenu>
);
}

View File

@@ -22,7 +22,7 @@ interface ConversationCardContextMenuProps {
onEdit?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDisplayCost?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowMicroagents?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowSkills?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDownloadViaVSCode?: (event: React.MouseEvent<HTMLButtonElement>) => void;
position?: "top" | "bottom";
}
@@ -37,7 +37,7 @@ export function ConversationCardContextMenu({
onEdit,
onDisplayCost,
onShowAgentTools,
onShowMicroagents,
onShowSkills,
onDownloadViaVSCode,
position = "bottom",
}: ConversationCardContextMenuProps) {
@@ -96,15 +96,15 @@ export function ConversationCardContextMenu({
/>
</ContextMenuListItem>
),
onShowMicroagents && (
onShowSkills && (
<ContextMenuListItem
testId="show-microagents-button"
onClick={onShowMicroagents}
testId="show-skills-button"
onClick={onShowSkills}
className={contextMenuListItemClassName}
>
<ConversationNameContextMenuIconText
icon={<RobotIcon width={16} height={16} />}
text={t(I18nKey.CONVERSATION$SHOW_MICROAGENTS)}
text={t(I18nKey.CONVERSATION$SHOW_SKILLS)}
/>
</ContextMenuListItem>
),

View File

@@ -3,17 +3,17 @@ import { I18nKey } from "#/i18n/declaration";
import { Typography } from "#/ui/typography";
import { Pre } from "#/ui/pre";
interface MicroagentContentProps {
interface SkillContentProps {
content: string;
}
export function MicroagentContent({ content }: MicroagentContentProps) {
export function SkillContent({ content }: SkillContentProps) {
const { t } = useTranslation();
return (
<div className="mt-2">
<Typography.Text className="text-sm font-semibold text-gray-300 mb-2">
{t(I18nKey.MICROAGENTS_MODAL$CONTENT)}
{t(I18nKey.COMMON$CONTENT)}
</Typography.Text>
<Pre
size="default"
@@ -28,7 +28,7 @@ export function MicroagentContent({ content }: MicroagentContentProps) {
overflow="auto"
className="mt-2"
>
{content || t(I18nKey.MICROAGENTS_MODAL$NO_CONTENT)}
{content || t(I18nKey.SKILLS_MODAL$NO_CONTENT)}
</Pre>
</div>
);

View File

@@ -1,35 +1,31 @@
import { ChevronDown, ChevronRight } from "lucide-react";
import { Microagent } from "#/api/open-hands.types";
import { Typography } from "#/ui/typography";
import { MicroagentTriggers } from "./microagent-triggers";
import { MicroagentContent } from "./microagent-content";
import { SkillTriggers } from "./skill-triggers";
import { SkillContent } from "./skill-content";
import { Skill } from "#/api/conversation-service/v1-conversation-service.types";
interface MicroagentItemProps {
agent: Microagent;
interface SkillItemProps {
skill: Skill;
isExpanded: boolean;
onToggle: (agentName: string) => void;
}
export function MicroagentItem({
agent,
isExpanded,
onToggle,
}: MicroagentItemProps) {
export function SkillItem({ skill, isExpanded, onToggle }: SkillItemProps) {
return (
<div className="rounded-md overflow-hidden">
<button
type="button"
onClick={() => onToggle(agent.name)}
onClick={() => onToggle(skill.name)}
className="w-full py-3 px-2 text-left flex items-center justify-between hover:bg-gray-700 transition-colors"
>
<div className="flex items-center">
<Typography.Text className="font-bold text-gray-100">
{agent.name}
{skill.name}
</Typography.Text>
</div>
<div className="flex items-center">
<Typography.Text className="px-2 py-1 text-xs rounded-full bg-gray-800 mr-2">
{agent.type === "repo" ? "Repository" : "Knowledge"}
{skill.type === "repo" ? "Repository" : "Knowledge"}
</Typography.Text>
<Typography.Text className="text-gray-300">
{isExpanded ? (
@@ -43,8 +39,8 @@ export function MicroagentItem({
{isExpanded && (
<div className="px-2 pb-3 pt-1">
<MicroagentTriggers triggers={agent.triggers} />
<MicroagentContent content={agent.content} />
<SkillTriggers triggers={skill.triggers} />
<SkillContent content={skill.content} />
</div>
)}
</div>

View File

@@ -2,11 +2,11 @@ import { useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
import { Typography } from "#/ui/typography";
interface MicroagentTriggersProps {
interface SkillTriggersProps {
triggers: string[];
}
export function MicroagentTriggers({ triggers }: MicroagentTriggersProps) {
export function SkillTriggers({ triggers }: SkillTriggersProps) {
const { t } = useTranslation();
if (!triggers || triggers.length === 0) {
@@ -16,7 +16,7 @@ export function MicroagentTriggers({ triggers }: MicroagentTriggersProps) {
return (
<div className="mt-2 mb-3">
<Typography.Text className="text-sm font-semibold text-gray-300 mb-2">
{t(I18nKey.MICROAGENTS_MODAL$TRIGGERS)}
{t(I18nKey.COMMON$TRIGGERS)}
</Typography.Text>
<div className="flex flex-wrap gap-1">
{triggers.map((trigger) => (

View File

@@ -2,19 +2,19 @@ import { useTranslation } from "react-i18next";
import { I18nKey } from "#/i18n/declaration";
import { Typography } from "#/ui/typography";
interface MicroagentsEmptyStateProps {
interface SkillsEmptyStateProps {
isError: boolean;
}
export function MicroagentsEmptyState({ isError }: MicroagentsEmptyStateProps) {
export function SkillsEmptyState({ isError }: SkillsEmptyStateProps) {
const { t } = useTranslation();
return (
<div className="flex items-center justify-center h-full p-4">
<Typography.Text className="text-gray-400">
{isError
? t(I18nKey.MICROAGENTS_MODAL$FETCH_ERROR)
: t(I18nKey.CONVERSATION$NO_MICROAGENTS)}
? t(I18nKey.COMMON$FETCH_ERROR)
: t(I18nKey.CONVERSATION$NO_SKILLS)}
</Typography.Text>
</div>
);

View File

@@ -1,4 +1,4 @@
export function MicroagentsLoadingState() {
export function SkillsLoadingState() {
return (
<div className="flex justify-center items-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary" />

View File

@@ -4,28 +4,28 @@ import { BaseModalTitle } from "#/components/shared/modals/confirmation-modals/b
import { I18nKey } from "#/i18n/declaration";
import { BrandButton } from "../settings/brand-button";
interface MicroagentsModalHeaderProps {
interface SkillsModalHeaderProps {
isAgentReady: boolean;
isLoading: boolean;
isRefetching: boolean;
onRefresh: () => void;
}
export function MicroagentsModalHeader({
export function SkillsModalHeader({
isAgentReady,
isLoading,
isRefetching,
onRefresh,
}: MicroagentsModalHeaderProps) {
}: SkillsModalHeaderProps) {
const { t } = useTranslation();
return (
<div className="flex flex-col gap-6 w-full">
<div className="flex items-center justify-between w-full">
<BaseModalTitle title={t(I18nKey.MICROAGENTS_MODAL$TITLE)} />
<BaseModalTitle title={t(I18nKey.SKILLS_MODAL$TITLE)} />
{isAgentReady && (
<BrandButton
testId="refresh-microagents"
testId="refresh-skills"
type="button"
variant="primary"
className="flex items-center gap-2"

View File

@@ -3,43 +3,32 @@ import { useTranslation } from "react-i18next";
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
import { ModalBody } from "#/components/shared/modals/modal-body";
import { I18nKey } from "#/i18n/declaration";
import { useConversationMicroagents } from "#/hooks/query/use-conversation-microagents";
import { useConversationSkills } from "#/hooks/query/use-conversation-skills";
import { AgentState } from "#/types/agent-state";
import { Typography } from "#/ui/typography";
import { MicroagentsModalHeader } from "./microagents-modal-header";
import { MicroagentsLoadingState } from "./microagents-loading-state";
import { MicroagentsEmptyState } from "./microagents-empty-state";
import { MicroagentItem } from "./microagent-item";
import { SkillsModalHeader } from "./skills-modal-header";
import { SkillsLoadingState } from "./skills-loading-state";
import { SkillsEmptyState } from "./skills-empty-state";
import { SkillItem } from "./skill-item";
import { useAgentState } from "#/hooks/use-agent-state";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
interface MicroagentsModalProps {
interface SkillsModalProps {
onClose: () => void;
}
export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
export function SkillsModal({ onClose }: SkillsModalProps) {
const { t } = useTranslation();
const { curAgentState } = useAgentState();
const { data: conversation } = useActiveConversation();
const [expandedAgents, setExpandedAgents] = useState<Record<string, boolean>>(
{},
);
const {
data: microagents,
data: skills,
isLoading,
isError,
refetch,
isRefetching,
} = useConversationMicroagents();
// TODO: Hide MicroagentsModal for V1 conversations
// This is a temporary measure and may be re-enabled in the future
const isV1Conversation = conversation?.conversation_version === "V1";
// Don't render anything for V1 conversations
if (isV1Conversation) {
return null;
}
} = useConversationSkills();
const toggleAgent = (agentName: string) => {
setExpandedAgents((prev) => ({
@@ -57,9 +46,9 @@ export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
<ModalBody
width="medium"
className="max-h-[80vh] flex flex-col items-start"
testID="microagents-modal"
testID="skills-modal"
>
<MicroagentsModalHeader
<SkillsModalHeader
isAgentReady={isAgentReady}
isLoading={isLoading}
isRefetching={isRefetching}
@@ -68,7 +57,7 @@ export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
{isAgentReady && (
<Typography.Text className="text-sm text-gray-400">
{t(I18nKey.MICROAGENTS_MODAL$WARNING)}
{t(I18nKey.SKILLS_MODAL$WARNING)}
</Typography.Text>
)}
@@ -81,33 +70,30 @@ export function MicroagentsModal({ onClose }: MicroagentsModalProps) {
</div>
)}
{isLoading && <MicroagentsLoadingState />}
{isLoading && <SkillsLoadingState />}
{!isLoading &&
isAgentReady &&
(isError || !microagents || microagents.length === 0) && (
<MicroagentsEmptyState isError={isError} />
(isError || !skills || skills.length === 0) && (
<SkillsEmptyState isError={isError} />
)}
{!isLoading &&
isAgentReady &&
microagents &&
microagents.length > 0 && (
<div className="p-2 space-y-3">
{microagents.map((agent) => {
const isExpanded = expandedAgents[agent.name] || false;
{!isLoading && isAgentReady && skills && skills.length > 0 && (
<div className="p-2 space-y-3">
{skills.map((skill) => {
const isExpanded = expandedAgents[skill.name] || false;
return (
<MicroagentItem
key={agent.name}
agent={agent}
isExpanded={isExpanded}
onToggle={toggleAgent}
/>
);
})}
</div>
)}
return (
<SkillItem
key={skill.name}
skill={skill}
isExpanded={isExpanded}
onToggle={toggleAgent}
/>
);
})}
</div>
)}
</div>
</ModalBody>
</ModalBackdrop>

View File

@@ -31,7 +31,7 @@ interface ConversationNameContextMenuProps {
onStop?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDisplayCost?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowAgentTools?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowMicroagents?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onShowSkills?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onExportConversation?: (event: React.MouseEvent<HTMLButtonElement>) => void;
onDownloadViaVSCode?: (event: React.MouseEvent<HTMLButtonElement>) => void;
position?: "top" | "bottom";
@@ -44,7 +44,7 @@ export function ConversationNameContextMenu({
onStop,
onDisplayCost,
onShowAgentTools,
onShowMicroagents,
onShowSkills,
onExportConversation,
onDownloadViaVSCode,
position = "bottom",
@@ -55,13 +55,12 @@ export function ConversationNameContextMenu({
const ref = useClickOutsideElement<HTMLUListElement>(onClose);
const { data: conversation } = useActiveConversation();
// TODO: Hide microagent menu items for V1 conversations
// This is a temporary measure and may be re-enabled in the future
const isV1Conversation = conversation?.conversation_version === "V1";
const hasDownload = Boolean(onDownloadViaVSCode);
const hasExport = Boolean(onExportConversation);
const hasTools = Boolean(onShowAgentTools || onShowMicroagents);
const hasTools = Boolean(onShowAgentTools || onShowSkills);
const hasInfo = Boolean(onDisplayCost);
const hasControl = Boolean(onStop || onDelete);
@@ -91,15 +90,15 @@ export function ConversationNameContextMenu({
{hasTools && <Divider testId="separator-tools" />}
{onShowMicroagents && !isV1Conversation && (
{onShowSkills && (
<ContextMenuListItem
testId="show-microagents-button"
onClick={onShowMicroagents}
testId="show-skills-button"
onClick={onShowSkills}
className={contextMenuListItemClassName}
>
<ConversationNameContextMenuIconText
icon={<RobotIcon width={16} height={16} />}
text={t(I18nKey.CONVERSATION$SHOW_MICROAGENTS)}
text={t(I18nKey.CONVERSATION$SHOW_SKILLS)}
className={CONTEXT_MENU_ICON_TEXT_CLASSNAME}
/>
</ContextMenuListItem>

View File

@@ -9,7 +9,7 @@ import { I18nKey } from "#/i18n/declaration";
import { EllipsisButton } from "../conversation-panel/ellipsis-button";
import { ConversationNameContextMenu } from "./conversation-name-context-menu";
import { SystemMessageModal } from "../conversation-panel/system-message-modal";
import { MicroagentsModal } from "../conversation-panel/microagents-modal";
import { SkillsModal } from "../conversation-panel/skills-modal";
import { ConfirmDeleteModal } from "../conversation-panel/confirm-delete-modal";
import { ConfirmStopModal } from "../conversation-panel/confirm-stop-modal";
import { MetricsModal } from "./metrics-modal/metrics-modal";
@@ -32,7 +32,7 @@ export function ConversationName() {
handleDownloadViaVSCode,
handleDisplayCost,
handleShowAgentTools,
handleShowMicroagents,
handleShowSkills,
handleExportConversation,
handleConfirmDelete,
handleConfirmStop,
@@ -40,8 +40,8 @@ export function ConversationName() {
setMetricsModalVisible,
systemModalVisible,
setSystemModalVisible,
microagentsModalVisible,
setMicroagentsModalVisible,
skillsModalVisible,
setSkillsModalVisible,
confirmDeleteModalVisible,
setConfirmDeleteModalVisible,
confirmStopModalVisible,
@@ -52,7 +52,7 @@ export function ConversationName() {
shouldShowExport,
shouldShowDisplayCost,
shouldShowAgentTools,
shouldShowMicroagents,
shouldShowSkills,
} = useConversationNameContextMenu({
conversationId,
conversationStatus: conversation?.status,
@@ -170,9 +170,7 @@ export function ConversationName() {
onShowAgentTools={
shouldShowAgentTools ? handleShowAgentTools : undefined
}
onShowMicroagents={
shouldShowMicroagents ? handleShowMicroagents : undefined
}
onShowSkills={shouldShowSkills ? handleShowSkills : undefined}
onExportConversation={
shouldShowExport ? handleExportConversation : undefined
}
@@ -199,9 +197,9 @@ export function ConversationName() {
systemMessage={systemMessage ? systemMessage.args : null}
/>
{/* Microagents Modal */}
{microagentsModalVisible && (
<MicroagentsModal onClose={() => setMicroagentsModalVisible(false)} />
{/* Skills Modal */}
{skillsModalVisible && (
<SkillsModal onClose={() => setSkillsModalVisible(false)} />
)}
{/* Confirm Delete Modal */}

View File

@@ -1,19 +1,29 @@
import { useQuery } from "@tanstack/react-query";
import ConversationService from "#/api/conversation-service/conversation-service.api";
import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api";
import { useConversationId } from "../use-conversation-id";
import { AgentState } from "#/types/agent-state";
import { useAgentState } from "#/hooks/use-agent-state";
import { useSettings } from "./use-settings";
export const useConversationMicroagents = () => {
export const useConversationSkills = () => {
const { conversationId } = useConversationId();
const { curAgentState } = useAgentState();
const { data: settings } = useSettings();
return useQuery({
queryKey: ["conversation", conversationId, "microagents"],
queryKey: ["conversation", conversationId, "skills", settings?.v1_enabled],
queryFn: async () => {
if (!conversationId) {
throw new Error("No conversation ID provided");
}
// Check if V1 is enabled and use the appropriate API
if (settings?.v1_enabled) {
const data = await V1ConversationService.getSkills(conversationId);
return data.skills;
}
const data = await ConversationService.getMicroagents(conversationId);
return data.microagents;
},

View File

@@ -41,8 +41,7 @@ export function useConversationNameContextMenu({
const [metricsModalVisible, setMetricsModalVisible] = React.useState(false);
const [systemModalVisible, setSystemModalVisible] = React.useState(false);
const [microagentsModalVisible, setMicroagentsModalVisible] =
React.useState(false);
const [skillsModalVisible, setSkillsModalVisible] = React.useState(false);
const [confirmDeleteModalVisible, setConfirmDeleteModalVisible] =
React.useState(false);
const [confirmStopModalVisible, setConfirmStopModalVisible] =
@@ -161,11 +160,9 @@ export function useConversationNameContextMenu({
onContextMenuToggle?.(false);
};
const handleShowMicroagents = (
event: React.MouseEvent<HTMLButtonElement>,
) => {
const handleShowSkills = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation();
setMicroagentsModalVisible(true);
setSkillsModalVisible(true);
onContextMenuToggle?.(false);
};
@@ -178,7 +175,7 @@ export function useConversationNameContextMenu({
handleDownloadViaVSCode,
handleDisplayCost,
handleShowAgentTools,
handleShowMicroagents,
handleShowSkills,
handleConfirmDelete,
handleConfirmStop,
@@ -187,8 +184,8 @@ export function useConversationNameContextMenu({
setMetricsModalVisible,
systemModalVisible,
setSystemModalVisible,
microagentsModalVisible,
setMicroagentsModalVisible,
skillsModalVisible,
setSkillsModalVisible,
confirmDeleteModalVisible,
setConfirmDeleteModalVisible,
confirmStopModalVisible,
@@ -204,6 +201,6 @@ export function useConversationNameContextMenu({
shouldShowExport: Boolean(conversationId && showOptions),
shouldShowDisplayCost: showOptions,
shouldShowAgentTools: Boolean(showOptions && systemMessage),
shouldShowMicroagents: Boolean(showOptions && conversationId),
shouldShowSkills: Boolean(showOptions && conversationId),
};
}

View File

@@ -640,17 +640,16 @@ export enum I18nKey {
TOS$CONTINUE = "TOS$CONTINUE",
TOS$ERROR_ACCEPTING = "TOS$ERROR_ACCEPTING",
TIPS$CUSTOMIZE_MICROAGENT = "TIPS$CUSTOMIZE_MICROAGENT",
CONVERSATION$SHOW_MICROAGENTS = "CONVERSATION$SHOW_MICROAGENTS",
CONVERSATION$NO_MICROAGENTS = "CONVERSATION$NO_MICROAGENTS",
CONVERSATION$NO_SKILLS = "CONVERSATION$NO_SKILLS",
CONVERSATION$FAILED_TO_FETCH_MICROAGENTS = "CONVERSATION$FAILED_TO_FETCH_MICROAGENTS",
MICROAGENTS_MODAL$TITLE = "MICROAGENTS_MODAL$TITLE",
MICROAGENTS_MODAL$WARNING = "MICROAGENTS_MODAL$WARNING",
MICROAGENTS_MODAL$TRIGGERS = "MICROAGENTS_MODAL$TRIGGERS",
SKILLS_MODAL$WARNING = "SKILLS_MODAL$WARNING",
COMMON$TRIGGERS = "COMMON$TRIGGERS",
MICROAGENTS_MODAL$INPUTS = "MICROAGENTS_MODAL$INPUTS",
MICROAGENTS_MODAL$TOOLS = "MICROAGENTS_MODAL$TOOLS",
MICROAGENTS_MODAL$CONTENT = "MICROAGENTS_MODAL$CONTENT",
MICROAGENTS_MODAL$NO_CONTENT = "MICROAGENTS_MODAL$NO_CONTENT",
MICROAGENTS_MODAL$FETCH_ERROR = "MICROAGENTS_MODAL$FETCH_ERROR",
COMMON$CONTENT = "COMMON$CONTENT",
SKILLS_MODAL$NO_CONTENT = "SKILLS_MODAL$NO_CONTENT",
COMMON$FETCH_ERROR = "COMMON$FETCH_ERROR",
TIPS$SETUP_SCRIPT = "TIPS$SETUP_SCRIPT",
TIPS$VSCODE_INSTANCE = "TIPS$VSCODE_INSTANCE",
TIPS$SAVE_WORK = "TIPS$SAVE_WORK",
@@ -957,4 +956,6 @@ export enum I18nKey {
COMMON$PLAN_AGENT_DESCRIPTION = "COMMON$PLAN_AGENT_DESCRIPTION",
PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED = "PLANNING_AGENTT$PLANNING_AGENT_INITIALIZED",
OBSERVATION_MESSAGE$SKILL_READY = "OBSERVATION_MESSAGE$SKILL_READY",
CONVERSATION$SHOW_SKILLS = "CONVERSATION$SHOW_SKILLS",
SKILLS_MODAL$TITLE = "SKILLS_MODAL$TITLE",
}

View File

@@ -10239,37 +10239,21 @@
"tr": "Kullanılabilir bir mikro ajan kullanarak OpenHands'i deponuz için özelleştirebilirsiniz. OpenHands'ten deponun açıklamasını, kodun nasıl çalıştırılacağı dahil, .openhands/microagents/repo.md dosyasına koymasını isteyin.",
"uk": "Ви можете налаштувати OpenHands для свого репозиторію за допомогою доступного мікроагента. Попросіть OpenHands розмістити опис репозиторію, включаючи інформацію про те, як запустити код, у файлі .openhands/microagents/repo.md."
},
"CONVERSATION$SHOW_MICROAGENTS": {
"en": "Show Available Microagents",
"ja": "利用可能なマイクロエージェントを表示",
"zh-CN": "显示可用微代理",
"zh-TW": "顯示可用微代理",
"ko-KR": "사용 가능한 마이크로에이전트 표시",
"no": "Vis tilgjengelige mikroagenter",
"ar": "عرض الوكلاء المصغرين المتاحة",
"de": "Verfügbare Mikroagenten anzeigen",
"fr": "Afficher les micro-agents disponibles",
"it": "Mostra microagenti disponibili",
"pt": "Mostrar microagentes disponíveis",
"es": "Mostrar microagentes disponibles",
"tr": "Kullanılabilir mikro ajanları göster",
"uk": "Показати доступних мікроагентів"
},
"CONVERSATION$NO_MICROAGENTS": {
"en": "No available microagents found for this conversation.",
"ja": "この会話用の利用可能なマイクロエージェントが見つかりませんでした。",
"zh-CN": "未找到此对话的可用微代理。",
"zh-TW": "未找到此對話的可用微代理。",
"ko-KR": "이 대화에 대한 사용 가능한 마이크로에이전트를 찾을 수 없습니다.",
"no": "Ingen tilgjengelige mikroagenter funnet for denne samtalen.",
"ar": "لم يتم العثور على وكلاء مصغرين متاحة لهذه المحادثة.",
"de": "Keine verfügbaren Mikroagenten für dieses Gespräch gefunden.",
"fr": "Aucun micro-agent disponible trouvé pour cette conversation.",
"it": "Nessun microagente disponibile trovato per questa conversazione.",
"pt": "Nenhum microagente disponível encontrado para esta conversa.",
"es": "No se encontraron microagentes disponibles para esta conversación.",
"tr": "Bu konuşma için kullanılabilir mikro ajan bulunamadı.",
"uk": "Для цієї розмови не знайдено доступних мікроагентів."
"CONVERSATION$NO_SKILLS": {
"en": "No available skills found for this conversation.",
"ja": "この会話には利用可能なスキルが見つかりません。",
"zh-CN": "本会话未找到可用技能。",
"zh-TW": "此對話中未找到可用技能。",
"ko-KR": "이 대화에서 사용 가능한 스킬을 찾을 수 없습니다.",
"no": "Ingen tilgjengelige ferdigheter ble funnet for denne samtalen.",
"ar": "لم يتم العثور على مهارات متاحة لهذه المحادثة.",
"de": "Für diese Unterhaltung wurden keine verfügbaren Skills gefunden.",
"fr": "Aucune compétence disponible trouvée pour cette conversation.",
"it": "Nessuna abilità disponibile trovata per questa conversazione.",
"pt": "Nenhuma habilidade disponível encontrada para esta conversa.",
"es": "No se encontraron habilidades disponibles para esta conversación.",
"tr": "Bu sohbet için kullanılabilir yetenek bulunamadı.",
"uk": "У цій розмові не знайдено доступних навичок."
},
"CONVERSATION$FAILED_TO_FETCH_MICROAGENTS": {
"en": "Failed to fetch available microagents",
@@ -10303,23 +10287,23 @@
"tr": "Kullanılabilir mikro ajanlar",
"uk": "Доступні мікроагенти"
},
"MICROAGENTS_MODAL$WARNING": {
"en": "If you update the microagents, you will need to stop the conversation and then click on the refresh button to see the changes.",
"ja": "マイクロエージェントを更新する場合、会話を停止してから更新ボタンをクリックして変更を確認する必要があります。",
"zh-CN": "如果您更新微代理,您需要停止对话,然后点击刷新按钮以查看更改。",
"zh-TW": "如果您更新微代理,您需要停止對話,然後點擊重新整理按鈕以查看更改。",
"ko-KR": "마이크로에이전트를 업데이트하는 경우 대화를 중한 후 새로고침 버튼을 클릭하여 변경사항을 확인해야 합니다.",
"no": "Hvis du oppdaterer mikroagentene, må du stoppe samtalen og deretter klikke på oppdater-knappen for å se endringene.",
"ar": "إذا قمت بتحديث الوكلاء المصغرين، فستحتاج إلى إيقاف المحادثة ثم النقر على زر التحديث لرؤية التغييرات.",
"de": "Wenn Sie die Mikroagenten aktualisieren, müssen Sie das Gespräch beenden und dann auf die Aktualisieren-Schaltfläche klicken, um die Änderungen zu sehen.",
"fr": "Si vous mettez à jour les micro-agents, vous devrez arrêter la conversation puis cliquer sur le bouton actualiser pour voir les changements.",
"it": "Se aggiorni i microagenti, dovrai fermare la conversazione e poi cliccare sul pulsante aggiorna per vedere le modifiche.",
"pt": "Se você atualizar os microagentes, precisará parar a conversa e depois clicar no botão atualizar para ver as alterações.",
"es": "Si actualiza los microagentes, necesitará detener la conversación y luego hacer clic en el botón actualizar para ver los cambios.",
"tr": "Mikro ajanları güncellerseniz, konuşmayı durdurmanız ve ardından değişiklikleri görmek için yenile düğmesine tıklamanız gerekecektir.",
"uk": "Якщо ви оновите мікроагенти, вам потрібно буде зупинити розмову, а потім натиснути кнопку оновлення, щоб побачити зміни."
"SKILLS_MODAL$WARNING": {
"en": "If you update the skills, you will need to stop the conversation and then click on the refresh button to see the changes.",
"ja": "スキルを更新する場合、会話を停止し、その後、更新ボタンをクリックして変更を反映させる必要があります。",
"zh-CN": "如果您更新技能,需要停止对话,然后点击刷新按钮以查看更改。",
"zh-TW": "如果您更新技能,需要停止對話,然後點擊刷新按鈕以查看更改。",
"ko-KR": "스킬을 업데이트하 대화를 중한 후 새로 고침 버튼을 클릭해야 변경 사항을 볼 수 있습니다.",
"no": "Hvis du oppdaterer ferdighetene, må du stoppe samtalen og deretter klikke på oppdateringsknappen for å se endringene.",
"ar": "إذا قمت بتحديث المهارات، ستحتاج إلى إيقاف المحادثة ثم النقر على زر التحديث لرؤية التغييرات.",
"de": "Wenn Sie die Fähigkeiten aktualisieren, müssen Sie das Gespräch beenden und dann auf die Schaltfläche 'Aktualisieren' klicken, um die Änderungen zu sehen.",
"fr": "Si vous mettez à jour les compétences, vous devrez arrêter la conversation, puis cliquer sur le bouton dactualisation pour voir les modifications.",
"it": "Se aggiorni le competenze, dovrai interrompere la conversazione e poi cliccare sul pulsante di aggiornamento per vedere le modifiche.",
"pt": "Se você atualizar as habilidades, precisará interromper a conversa e clicar no botão de atualizar para ver as mudanças.",
"es": "Si actualizas las habilidades, debes detener la conversación y luego hacer clic en el botón de actualizar para ver los cambios.",
"tr": "Yetenekleri güncellerseniz, değişiklikleri görmek için sohbeti durdurmalı ve ardından yenile düğmesine tıklamalısınız.",
"uk": "Якщо ви оновите навички, вам потрібно буде зупинити розмову, а потім натиснути кнопку оновлення, щоб побачити зміни."
},
"MICROAGENTS_MODAL$TRIGGERS": {
"COMMON$TRIGGERS": {
"en": "Triggers",
"ja": "トリガー",
"zh-CN": "触发器",
@@ -10367,7 +10351,7 @@
"tr": "Araçlar",
"uk": "Інструменти"
},
"MICROAGENTS_MODAL$CONTENT": {
"COMMON$CONTENT": {
"en": "Content",
"ja": "コンテンツ",
"zh-CN": "内容",
@@ -10383,37 +10367,37 @@
"tr": "İçerik",
"uk": "Вміст"
},
"MICROAGENTS_MODAL$NO_CONTENT": {
"en": "Microagent has no content",
"ja": "マイクロエージェントにコンテンツがありません",
"zh-CN": "微代理没有内容",
"zh-TW": "微代理沒有內容",
"ko-KR": "마이크로에이전트텐츠가 없습니다",
"no": "Mikroagenten har ikke innhold",
"ar": "الوكيل المصغر ليس لديه محتوى",
"de": "Mikroagent hat keinen Inhalt",
"fr": "Le micro-agent n'a pas de contenu",
"it": "Il microagente non ha contenuto",
"pt": "Microagente não tem conteúdo",
"es": "El microagente no tiene contenido",
"tr": "Mikroajanın içeriği yok",
"uk": "Мікроагент не має вмісту"
"SKILLS_MODAL$NO_CONTENT": {
"en": "Skill has no content",
"ja": "スキルにはコンテンツがありません",
"zh-CN": "技能没有内容",
"zh-TW": "技能沒有內容",
"ko-KR": "스킬텐츠가 없습니다",
"no": "Ferdighet har ikke noe innhold",
"ar": "المهارة ليس لديها محتوى",
"de": "Die Fähigkeit hat keinen Inhalt",
"fr": "La compétence n'a pas de contenu",
"it": "La competenza non ha contenuti",
"pt": "A habilidade não possui conteúdo",
"es": "La habilidad no tiene contenido",
"tr": "Beceride içerik yok",
"uk": "У навички немає вмісту"
},
"MICROAGENTS_MODAL$FETCH_ERROR": {
"en": "Failed to fetch microagents. Please try again later.",
"ja": "マイクロエージェントの取得に失敗しました。後でもう一度お試しください。",
"zh-CN": "获取微代理失败。请稍后再试。",
"zh-TW": "獲取微代理失敗。請稍後再試。",
"ko-KR": "마이크로에이전트를 가져오지 못했습니다. 나중에 다시 시도해 주세요.",
"no": "Kunne ikke hente mikroagenter. Prøv igjen senere.",
"ar": "فشل في جلب الوكلاء المصغرين. يرجى المحاولة مرة أخرى لاحقًا.",
"de": "Mikroagenten konnten nicht abgerufen werden. Bitte versuchen Sie es später erneut.",
"fr": "Échec de la récupération des micro-agents. Veuillez réessayer plus tard.",
"it": "Impossibile recuperare i microagenti. Riprova più tardi.",
"pt": "Falha ao buscar microagentes. Por favor, tente novamente mais tarde.",
"es": "Error al obtener microagentes. Por favor, inténtelo de nuevo más tarde.",
"tr": "Mikroajanlar getirilemedi. Lütfen daha sonra tekrar deneyin.",
"uk": "Не вдалося отримати мікроагентів. Будь ласка, спробуйте пізніше."
"COMMON$FETCH_ERROR": {
"en": "Failed to fetch skills. Please try again later.",
"ja": "スキルの取得に失敗しました。後でもう一度お試しください。",
"zh-CN": "获取技能失败。请稍后再试。",
"zh-TW": "取得技能失敗。請稍後再試。",
"ko-KR": "스킬을 가져오지 못했습니다. 나중에 다시 시도해주세요.",
"no": "Kunne ikke hente ferdigheter. Prøv igjen senere.",
"ar": "فشل في جلب المهارات. يرجى المحاولة لاحقًا.",
"de": "Die Fähigkeiten konnten nicht abgerufen werden. Bitte versuchen Sie es später erneut.",
"fr": "Échec de la récupération des compétences. Veuillez réessayer plus tard.",
"it": "Impossibile recuperare le competenze. Riprova più tardi.",
"pt": "Falha ao buscar as habilidades. Por favor, tente novamente mais tarde.",
"es": "No se pudieron obtener las habilidades. Por favor, inténtalo de nuevo más tarde.",
"tr": "Beceriler alınamadı. Lütfen daha sonra tekrar deneyin.",
"uk": "Не вдалося отримати навички. Будь ласка, спробуйте пізніше."
},
"TIPS$SETUP_SCRIPT": {
"en": "You can add .openhands/setup.sh to your repository to automatically run a setup script every time you start an OpenHands conversation.",
@@ -15310,5 +15294,37 @@
"tr": "Yetenek hazır",
"de": "Fähigkeit bereit",
"uk": "Навичка готова"
},
"CONVERSATION$SHOW_SKILLS": {
"en": "Show Available Skills",
"ja": "利用可能なスキルを表示",
"zh-CN": "显示可用技能",
"zh-TW": "顯示可用技能",
"ko-KR": "사용 가능한 스킬 표시",
"no": "Vis tilgjengelige ferdigheter",
"ar": "عرض المهارات المتاحة",
"de": "Verfügbare Fähigkeiten anzeigen",
"fr": "Afficher les compétences disponibles",
"it": "Mostra abilità disponibili",
"pt": "Mostrar habilidades disponíveis",
"es": "Mostrar habilidades disponibles",
"tr": "Kullanılabilir yetenekleri göster",
"uk": "Показати доступні навички"
},
"SKILLS_MODAL$TITLE": {
"en": "Available Skills",
"ja": "利用可能なスキル",
"zh-CN": "可用技能",
"zh-TW": "可用技能",
"ko-KR": "사용 가능한 스킬",
"no": "Tilgjengelige ferdigheter",
"ar": "المهارات المتاحة",
"de": "Verfügbare Fähigkeiten",
"fr": "Compétences disponibles",
"it": "Abilità disponibili",
"pt": "Habilidades disponíveis",
"es": "Habilidades disponibles",
"tr": "Kullanılabilir yetenekler",
"uk": "Доступні навички"
}
}