mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-12 14:00:35 +00:00
fix(frontend): copy share links on the current origin (#8126)
* fix(frontend): copy share links on the current origin The sidebar Share action rewrote the origin to the hosted demo (https://deer-flow-v2.vercel.app) whenever the page ran on localhost or 127.0.0.1, which are the default make dev and Docker entry points. The copied link never opened the thread, because the thread only exists on the local deployment, and opening it sent the thread id (and a custom agent name) to a host the operator does not control. Always build the link from window.location.origin and pathOfThread(). * docs(changelog): reference #8126 for the share link origin fix
This commit is contained in:
parent
4a27b133f8
commit
c8d5c108f3
@ -51,6 +51,13 @@ This section accumulates work toward the **2.3.0** milestone
|
||||
|
||||
### Fixed
|
||||
|
||||
- **frontend:** **Share** in a sidebar chat's menu now copies a link to the
|
||||
deployment you are using. On `localhost` and `127.0.0.1`, the default
|
||||
`make dev` and Docker addresses, it copied a link to the hosted demo
|
||||
(`deer-flow-v2.vercel.app`) instead. That link never opened the
|
||||
conversation, and opening it sent the thread id to an outside host.
|
||||
([#8126])
|
||||
|
||||
- **runtime:** In multi-worker mode (`run_ownership.heartbeat_enabled`), a
|
||||
worker that had lost its run to a peer could put the row back. Its assembly
|
||||
evidence write was an unconditional full-row upsert, so after a peer
|
||||
@ -16291,3 +16298,4 @@ with **180 merged pull requests** since the first 2.0 milestone tag.
|
||||
[#8069]: https://github.com/bytedance/deer-flow/issues/8069
|
||||
[#8071]: https://github.com/bytedance/deer-flow/issues/8071
|
||||
[#8072]: https://github.com/bytedance/deer-flow/pull/8072
|
||||
[#8126]: https://github.com/bytedance/deer-flow/pull/8126
|
||||
|
||||
@ -26,6 +26,11 @@
|
||||
|
||||
### 修复
|
||||
|
||||
- **前端:** 侧边栏对话菜单中的 **分享** 现在复制指向当前部署的链接。此前在 `localhost` 和
|
||||
`127.0.0.1`(`make dev` 与 Docker 的默认地址)上,复制的是托管演示站
|
||||
(`deer-flow-v2.vercel.app`)的链接:该链接无法打开对话,打开时还会把线程 ID 发送到外部主机。
|
||||
([#8126])
|
||||
|
||||
- **运行时:** 多 Worker 模式(`run_ownership.heartbeat_enabled`)下,已被其他 Worker
|
||||
接管的运行不再被原 Worker 恢复。此前装配证据通过无条件的整行 upsert 写入,在对端接管后会把
|
||||
行改回 `running` 并清除接管错误;运行可能在对端已报告失败后以 `success` 结束,或被再次接管。
|
||||
@ -12862,3 +12867,4 @@ DeerFlow 2.0 是围绕"超级智能体"框架的彻底重写,核心包含子
|
||||
[#8043]: https://github.com/bytedance/deer-flow/pull/8043
|
||||
[#8046]: https://github.com/bytedance/deer-flow/pull/8046
|
||||
[#8064]: https://github.com/bytedance/deer-flow/pull/8064
|
||||
[#8126]: https://github.com/bytedance/deer-flow/pull/8126
|
||||
|
||||
@ -178,14 +178,10 @@ export function ThreadSidebarItem({
|
||||
}, [t.chats.pinChatFailed, thread, updatePinnedThread]);
|
||||
|
||||
const handleShare = useCallback(async () => {
|
||||
// Always use Vercel URL for sharing so others can access
|
||||
const VERCEL_URL = "https://deer-flow-v2.vercel.app";
|
||||
const isLocalhost =
|
||||
window.location.hostname === "localhost" ||
|
||||
window.location.hostname === "127.0.0.1";
|
||||
// On localhost: use Vercel URL; On production: use current origin
|
||||
const baseUrl = isLocalhost ? VERCEL_URL : window.location.origin;
|
||||
const shareUrl = `${baseUrl}${pathOfThread(thread)}`;
|
||||
// Link to this deployment, the only place the thread exists. Never
|
||||
// rewrite the origin to another host: the thread id would leave the
|
||||
// deployment and the link would not open the thread.
|
||||
const shareUrl = `${window.location.origin}${pathOfThread(thread)}`;
|
||||
try {
|
||||
const didCopy = await writeTextToClipboard(shareUrl);
|
||||
if (!didCopy) {
|
||||
|
||||
@ -0,0 +1,114 @@
|
||||
import { afterEach, describe, expect, it, rs } from "@rstest/core";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import {
|
||||
cleanup,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
waitFor,
|
||||
} from "@testing-library/react";
|
||||
|
||||
rs.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: rs.fn(), replace: rs.fn(), refresh: rs.fn() }),
|
||||
usePathname: () => "/workspace",
|
||||
useSearchParams: () => new URLSearchParams(),
|
||||
useParams: () => ({}),
|
||||
}));
|
||||
rs.mock("@/core/clipboard", () => ({ writeTextToClipboard: rs.fn() }));
|
||||
|
||||
import { SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { ThreadSidebarItem } from "@/components/workspace/recent-chat-list";
|
||||
import { ThreadDeleteDialogProvider } from "@/components/workspace/thread-delete-dialog";
|
||||
import { AuthProvider } from "@/core/auth/AuthProvider";
|
||||
import type { User } from "@/core/auth/types";
|
||||
import { writeTextToClipboard } from "@/core/clipboard";
|
||||
import { DEFAULT_LOCALE } from "@/core/i18n";
|
||||
import { I18nProvider } from "@/core/i18n/context";
|
||||
|
||||
type HappyDOMWindow = Window & {
|
||||
happyDOM: { setURL: (url: string) => void };
|
||||
};
|
||||
|
||||
const THREAD_ID = "83d5133d-f8aa-4095-9bba-2aca03f8f61c";
|
||||
const writeText = rs.mocked(writeTextToClipboard);
|
||||
const initialURL = window.location.href;
|
||||
|
||||
function renderItem(metadata: Record<string, unknown> = {}) {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
|
||||
});
|
||||
const user = {
|
||||
id: "user-1",
|
||||
email: "user@example.test",
|
||||
system_role: "user",
|
||||
} as User;
|
||||
const thread = {
|
||||
thread_id: THREAD_ID,
|
||||
updated_at: "2026-01-01T00:00:00Z",
|
||||
metadata,
|
||||
values: { title: "A thread" },
|
||||
} as never;
|
||||
return render(
|
||||
<I18nProvider initialLocale={DEFAULT_LOCALE}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider initialUser={user}>
|
||||
<SidebarProvider>
|
||||
<ThreadDeleteDialogProvider>
|
||||
<ThreadSidebarItem thread={thread} isActive={false} />
|
||||
</ThreadDeleteDialogProvider>
|
||||
</SidebarProvider>
|
||||
</AuthProvider>
|
||||
</QueryClientProvider>
|
||||
</I18nProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
/** Opens the row's "more" menu and selects Share. Radix opens on pointerdown. */
|
||||
async function share(): Promise<string> {
|
||||
const trigger = await screen.findByRole("button", { name: /more/i });
|
||||
fireEvent.pointerDown(trigger, { button: 0, pointerType: "mouse" });
|
||||
fireEvent.click(trigger);
|
||||
fireEvent.click(await screen.findByText("Share"));
|
||||
await waitFor(() => expect(writeText).toHaveBeenCalledTimes(1));
|
||||
return writeText.mock.calls[0]![0];
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
writeText.mockReset();
|
||||
(window as unknown as HappyDOMWindow).happyDOM.setURL(initialURL);
|
||||
});
|
||||
|
||||
describe("ThreadSidebarItem Share", () => {
|
||||
// The default `make dev` and Docker entry points are localhost:2026 and
|
||||
// 127.0.0.1:2026. The link must stay on the deployment that holds the
|
||||
// thread rather than point at a hosted demo.
|
||||
for (const origin of [
|
||||
"http://localhost:2026",
|
||||
"http://127.0.0.1:2026",
|
||||
"http://localhost:3000",
|
||||
"http://[::1]:2026",
|
||||
"http://192.168.1.20:2026",
|
||||
"https://deerflow.example.com",
|
||||
]) {
|
||||
it(`copies a link on the current origin (${origin})`, async () => {
|
||||
(window as unknown as HappyDOMWindow).happyDOM.setURL(
|
||||
`${origin}/workspace/chats`,
|
||||
);
|
||||
writeText.mockResolvedValue(true);
|
||||
renderItem();
|
||||
expect(await share()).toBe(`${origin}/workspace/chats/${THREAD_ID}`);
|
||||
});
|
||||
}
|
||||
|
||||
it("copies the custom agent route on the current origin", async () => {
|
||||
(window as unknown as HappyDOMWindow).happyDOM.setURL(
|
||||
"http://localhost:2026/workspace",
|
||||
);
|
||||
writeText.mockResolvedValue(true);
|
||||
renderItem({ agent_name: "research bot" });
|
||||
expect(await share()).toBe(
|
||||
`http://localhost:2026/workspace/agents/research%20bot/chats/${THREAD_ID}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user