Share project JSON transport, interaction replies and report selectors. Settle tool progress consistently for direct streams, reconnects and history, preserving failure reasons and stopped state.
78 lines
3.3 KiB
TypeScript
78 lines
3.3 KiB
TypeScript
import { requestWorkbenchJson } from "./request-json";
|
|
import { z } from "zod";
|
|
import type { WorkbenchAccess } from "./request-context";
|
|
import { type PlaceSearchConfiguration } from "./project-client";
|
|
|
|
export const MAX_PLACE_SEARCH_RESULTS = 10;
|
|
|
|
const placeSchema = z.object({
|
|
id: z.string().min(1), name: z.string().trim().min(1), address: z.string(),
|
|
longitude: z.number().finite().min(-180).max(180),
|
|
latitude: z.number().finite().min(-90).max(90),
|
|
category: z.string().nullable(), district: z.string().trim().min(1)
|
|
});
|
|
const resultsSchema = z.object({
|
|
items: z.array(placeSchema).max(MAX_PLACE_SEARCH_RESULTS), total: z.number().int().min(0).max(MAX_PLACE_SEARCH_RESULTS),
|
|
truncated: z.boolean().default(false), region: z.string().trim().min(1)
|
|
});
|
|
export type PlaceSearchItem = z.infer<typeof placeSchema>;
|
|
export type PlaceSearchResults = z.infer<typeof resultsSchema>;
|
|
|
|
export async function searchPlaces(
|
|
keyword: string,
|
|
projectId: string | null,
|
|
getAccessToken?: WorkbenchAccess,
|
|
signal?: AbortSignal,
|
|
configuration?: PlaceSearchConfiguration | null
|
|
): Promise<PlaceSearchResults> {
|
|
const query = keyword.trim();
|
|
if (!query) throw new Error("请输入需要搜索的地址或地名");
|
|
if (query.length > 120) throw new Error("查询关键字不能超过120个字符");
|
|
const payload = await requestPlaceData("place-search-requests", { keyword: query }, projectId, getAccessToken, signal);
|
|
const parsed = resultsSchema.safeParse(payload);
|
|
if (!parsed.success
|
|
|| new Set(parsed.data.items.map((item) => item.id)).size !== parsed.data.items.length
|
|
|| parsed.data.total !== parsed.data.items.length
|
|
|| (configuration && (parsed.data.region !== configuration.region
|
|
|| (!configuration.admin_code.endsWith("00") && parsed.data.items.some((item) => item.district !== configuration.region))))) {
|
|
throw new Error("未获得有效的地名搜索结果,请稍后重试");
|
|
}
|
|
return parsed.data;
|
|
}
|
|
|
|
|
|
export async function reverseGeocodePoint(
|
|
longitude: number,
|
|
latitude: number,
|
|
projectId: string | null,
|
|
getAccessToken?: WorkbenchAccess,
|
|
signal?: AbortSignal,
|
|
configuration?: PlaceSearchConfiguration | null
|
|
): Promise<PlaceSearchItem> {
|
|
const coordinates = z.object({
|
|
longitude: z.number().finite().min(-180).max(180),
|
|
latitude: z.number().finite().min(-90).max(90)
|
|
}).safeParse({ longitude, latitude });
|
|
if (!coordinates.success) throw new Error("地图取点坐标无效");
|
|
const payload = await requestPlaceData("reverse-geocoding-requests", coordinates.data, projectId, getAccessToken, signal);
|
|
const parsed = placeSchema.safeParse(payload);
|
|
if (!parsed.success || parsed.data.longitude !== longitude || parsed.data.latitude !== latitude
|
|
|| (configuration && !configuration.admin_code.endsWith("00") && parsed.data.district !== configuration.region)) {
|
|
throw new Error("未获得有效的地图取点结果,请重新取点");
|
|
}
|
|
return parsed.data;
|
|
}
|
|
|
|
async function requestPlaceData(
|
|
path: string,
|
|
body: Record<string, string | number>,
|
|
projectId: string | null,
|
|
getAccessToken?: WorkbenchAccess,
|
|
signal?: AbortSignal
|
|
): Promise<unknown> {
|
|
return requestWorkbenchJson(`/api/v1/${path}`, "地名搜索", {
|
|
access: getAccessToken, projectId, timeoutMs: 20_000,
|
|
init: { method: "POST", body: JSON.stringify(body), signal }
|
|
});
|
|
}
|