Merge branch 'dhk-1' of http://192.168.0.200/knowledge-base-bj/deerflow-server into dhk-1
This commit is contained in:
commit
a642ba91fa
21 files changed
+1587
-47
No files matched your search
@@ -0,0 +1,131 @@
|
||||
import { apiFetch } from "@/core/api";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
// Share codes share the same alphabet/shape as conversation shares, so reuse the
|
||||
// extractor instead of duplicating the regex.
|
||||
export { extractShareCode } from "@/core/thread-shares/api";
|
||||
|
||||
/**
|
||||
* Roundtable-draft share modes:
|
||||
* - `import` — recipient (a logged-in user) copies the 会商 draft into their own
|
||||
* account and can keep working on the copy.
|
||||
* - `view` — a public, read-only page anyone can open without logging in; it
|
||||
* renders the draft's Step 3 result report (方案总览 HTML).
|
||||
*/
|
||||
export type DraftShareMode = "import" | "view";
|
||||
|
||||
export interface DraftShare {
|
||||
share_code: string;
|
||||
owner_user_id: string;
|
||||
draft_id: string;
|
||||
revoked: boolean;
|
||||
mode: DraftShareMode;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
/** Read-only roundtable-result snapshot returned by the public view-share endpoint. */
|
||||
export interface PublicDraftShareSnapshot {
|
||||
share_code: string;
|
||||
title: string;
|
||||
mode: string;
|
||||
/** Self-contained Step 3 result report HTML (empty when no report exists). */
|
||||
html: string;
|
||||
summary: string | null;
|
||||
generated_at: string | null;
|
||||
has_report: boolean;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface DraftSharePreview {
|
||||
share_code: string;
|
||||
shared_by: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface DraftImportResult {
|
||||
imported: number;
|
||||
skipped: number;
|
||||
draft_id: string | null;
|
||||
}
|
||||
|
||||
const baseUrl = () => `${getBackendBaseURL()}/api/roundtable-draft-shares`;
|
||||
|
||||
async function parseError(response: Response, fallback: string): Promise<Error> {
|
||||
const data = (await response.json().catch(() => ({}))) as { detail?: unknown };
|
||||
const detail = data.detail;
|
||||
const message =
|
||||
typeof detail === "string" && detail
|
||||
? detail
|
||||
: `${fallback} (HTTP ${response.status})`;
|
||||
return new Error(message);
|
||||
}
|
||||
|
||||
/** Create (or reuse) a share code for one roundtable draft in the given mode. */
|
||||
export async function createDraftShare(
|
||||
draftId: string,
|
||||
mode: DraftShareMode = "import",
|
||||
): Promise<DraftShare> {
|
||||
const response = await apiFetch(baseUrl(), {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ draft_id: draftId, mode }),
|
||||
});
|
||||
if (!response.ok) throw await parseError(response, "创建分享失败");
|
||||
return response.json() as Promise<DraftShare>;
|
||||
}
|
||||
|
||||
/** List the current user's own roundtable-draft share links. */
|
||||
export async function listDraftShares(): Promise<DraftShare[]> {
|
||||
const response = await apiFetch(baseUrl());
|
||||
if (!response.ok) throw await parseError(response, "加载分享列表失败");
|
||||
const json = (await response.json()) as { shares: DraftShare[] };
|
||||
return json.shares;
|
||||
}
|
||||
|
||||
/** Revoke a share link so it can no longer be previewed, imported, or viewed. */
|
||||
export async function revokeDraftShare(shareCode: string): Promise<void> {
|
||||
const response = await apiFetch(`${baseUrl()}/${encodeURIComponent(shareCode)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
if (!response.ok) throw await parseError(response, "撤销分享失败");
|
||||
}
|
||||
|
||||
/** Preview a share link before importing it. */
|
||||
export async function previewDraftShare(shareCode: string): Promise<DraftSharePreview> {
|
||||
const response = await apiFetch(
|
||||
`${baseUrl()}/${encodeURIComponent(shareCode)}/preview`,
|
||||
);
|
||||
if (!response.ok) throw await parseError(response, "无法读取该分享");
|
||||
return response.json() as Promise<DraftSharePreview>;
|
||||
}
|
||||
|
||||
/** Import the roundtable draft behind a share link into the current account. */
|
||||
export async function importDraftShare(shareCode: string): Promise<DraftImportResult> {
|
||||
const response = await apiFetch(
|
||||
`${baseUrl()}/${encodeURIComponent(shareCode)}/import`,
|
||||
{ method: "POST" },
|
||||
);
|
||||
if (!response.ok) throw await parseError(response, "导入失败");
|
||||
return response.json() as Promise<DraftImportResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the public read-only snapshot of a `view`-mode roundtable share.
|
||||
*
|
||||
* Uses a plain `fetch` (no auth header) — the `/api/public/` endpoint is
|
||||
* intentionally world-readable so anyone with the link can open it.
|
||||
*/
|
||||
export async function getPublicDraftShareSnapshot(
|
||||
shareCode: string,
|
||||
): Promise<PublicDraftShareSnapshot> {
|
||||
const response = await fetch(
|
||||
`${getBackendBaseURL()}/api/public/roundtable-shares/${encodeURIComponent(shareCode)}`,
|
||||
);
|
||||
if (!response.ok) throw await parseError(response, "无法打开该分享");
|
||||
return response.json() as Promise<PublicDraftShareSnapshot>;
|
||||
}
|
||||
|
||||
/** Build the shareable public URL for a `view`-mode roundtable share code. */
|
||||
export function buildPublicDraftShareUrl(shareCode: string): string {
|
||||
// The app uses HashRouter, so the public viewer lives at `#/roundtable-share/<code>`.
|
||||
return `${window.location.origin}${window.location.pathname}#/roundtable-share/${shareCode}`;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
createDraftShare,
|
||||
importDraftShare,
|
||||
listDraftShares,
|
||||
previewDraftShare,
|
||||
revokeDraftShare,
|
||||
type DraftShareMode,
|
||||
} from "./api";
|
||||
|
||||
export function useDraftShares(enabled = true) {
|
||||
return useQuery({
|
||||
queryKey: ["roundtable-draft-shares"],
|
||||
queryFn: listDraftShares,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateDraftShare() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
draftId,
|
||||
mode = "import",
|
||||
}: {
|
||||
draftId: string;
|
||||
mode?: DraftShareMode;
|
||||
}) => createDraftShare(draftId, mode),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["roundtable-draft-shares"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useRevokeDraftShare() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (shareCode: string) => revokeDraftShare(shareCode),
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: ["roundtable-draft-shares"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function usePreviewDraftShare() {
|
||||
return useMutation({
|
||||
mutationFn: (shareCode: string) => previewDraftShare(shareCode),
|
||||
});
|
||||
}
|
||||
|
||||
export function useImportDraftShare() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (shareCode: string) => importDraftShare(shareCode),
|
||||
onSuccess: () => {
|
||||
// Imported drafts show up in the user's roundtable history sidebar.
|
||||
void queryClient.invalidateQueries({ queryKey: ["roundtable-drafts"] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user