diff --git a/desktop/renderer/src/components/agent/AgentCard.vue b/desktop/renderer/src/components/agent/AgentCard.vue index 63ca435..817689e 100644 --- a/desktop/renderer/src/components/agent/AgentCard.vue +++ b/desktop/renderer/src/components/agent/AgentCard.vue @@ -21,9 +21,16 @@ {{ t("agentMarket.remove") }} - @@ -38,6 +45,7 @@ import IconClose from "@/components/icons/IconClose.vue"; defineProps<{ agent: Agent; + adding?: boolean; }>(); defineEmits<{ @@ -169,6 +177,31 @@ defineEmits<{ color: var(--bg-primary); } +.agent-card-action:disabled { + cursor: wait; + opacity: 0.65; +} + +.agent-card-action:disabled:hover { + background: transparent; + color: var(--text-primary); +} + +.agent-card-spinner { + width: 12px; + height: 12px; + border: 2px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: agent-card-spin 0.7s linear infinite; +} + +@keyframes agent-card-spin { + to { + transform: rotate(360deg); + } +} + .agent-card-action-danger { color: var(--danger, #c0392b); } diff --git a/desktop/renderer/src/i18n/en-US.ts b/desktop/renderer/src/i18n/en-US.ts index 8b34cd3..df394eb 100644 --- a/desktop/renderer/src/i18n/en-US.ts +++ b/desktop/renderer/src/i18n/en-US.ts @@ -766,7 +766,9 @@ export default { "agentMarket.title": "New Agent", "agentMarket.pageTitle": "Popular Agents", "agentMarket.add": "Add", + "agentMarket.adding": "Adding...", "agentMarket.added": "Added", + "agentMarket.addSuccess": "{name} was added.", "agentMarket.addFailed": "Couldn't add the agent to OpenClaw. Please try again.", "agentMarket.remove": "Remove", "agentMarket.cancel": "Cancel", diff --git a/desktop/renderer/src/i18n/zh-CN.ts b/desktop/renderer/src/i18n/zh-CN.ts index 9447aaa..73934b8 100644 --- a/desktop/renderer/src/i18n/zh-CN.ts +++ b/desktop/renderer/src/i18n/zh-CN.ts @@ -736,7 +736,9 @@ export default { "agentMarket.title": "新建 Agent", "agentMarket.pageTitle": "热门 Agent", "agentMarket.add": "添加", + "agentMarket.adding": "添加中...", "agentMarket.added": "已添加", + "agentMarket.addSuccess": "已添加 {name}。", "agentMarket.addFailed": "无法将 Agent 添加到 OpenClaw,请重试。", "agentMarket.remove": "移除", "agentMarket.cancel": "取消", diff --git a/desktop/renderer/src/views/AgentMarketView.test.ts b/desktop/renderer/src/views/AgentMarketView.test.ts new file mode 100644 index 0000000..e77cd56 --- /dev/null +++ b/desktop/renderer/src/views/AgentMarketView.test.ts @@ -0,0 +1,75 @@ +import { createPinia } from "pinia"; +import { flushPromises, mount } from "@vue/test-utils"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { setLocale } from "@/i18n"; +import AgentMarketView from "./AgentMarketView.vue"; + +const { successMessage, errorMessage } = vi.hoisted(() => ({ + successMessage: vi.fn(), + errorMessage: vi.fn(), +})); + +vi.mock("element-plus", () => ({ + ElMessage: { + success: successMessage, + error: errorMessage, + }, + ElMessageBox: { + confirm: vi.fn(), + }, +})); + +function deferred() { + let resolve!: (value: T) => void; + const promise = new Promise((resolvePromise) => { + resolve = resolvePromise; + }); + return { promise, resolve }; +} + +describe("AgentMarketView", () => { + const agentsAdd = vi.fn(); + + beforeEach(() => { + setLocale("en-US"); + successMessage.mockReset(); + errorMessage.mockReset(); + agentsAdd.mockReset(); + window.openclaw = { + agents: { + list: vi.fn(), + add: agentsAdd, + remove: vi.fn(), + }, + } as unknown as typeof window.openclaw; + }); + + it("shows progress and confirms a successful agent addition", async () => { + const addition = deferred<{ + agents: { id: string; name: string }[]; + }>(); + agentsAdd.mockReturnValueOnce(addition.promise); + const wrapper = mount(AgentMarketView, { + global: { plugins: [createPinia()] }, + }); + const addButton = wrapper.find(".agent-card-action"); + + await addButton.trigger("click"); + + expect(addButton.attributes("disabled")).toBeDefined(); + expect(addButton.attributes("aria-busy")).toBe("true"); + expect(addButton.text()).toBe("Adding..."); + + addition.resolve({ + agents: [ + { id: "main", name: "Assistant" }, + { id: "master-archive", name: "Master Archive" }, + ], + }); + await flushPromises(); + + expect(agentsAdd).toHaveBeenCalledWith("master-archive"); + expect(successMessage).toHaveBeenCalledWith("Master Archive was added."); + expect(wrapper.find(".agent-card-action").text()).toBe("Remove"); + }); +}); diff --git a/desktop/renderer/src/views/AgentMarketView.vue b/desktop/renderer/src/views/AgentMarketView.vue index 50e5dc1..c592931 100644 --- a/desktop/renderer/src/views/AgentMarketView.vue +++ b/desktop/renderer/src/views/AgentMarketView.vue @@ -9,6 +9,7 @@ v-for="agent in filteredAgents" :key="agent.id" :agent="agent" + :adding="addingAgentIds.has(agent.id)" @add="handleAdd" @remove="handleRemove" /> @@ -17,7 +18,7 @@