95 lines
3.2 KiB
TypeScript
95 lines
3.2 KiB
TypeScript
/**
|
|
* Stable marker for conversations entered from 「我的项目」.
|
|
*
|
|
* Keep this separate from ``project_id``: that key belongs to DeerFlow's
|
|
* folder-style conversation projects, while this marker describes the product
|
|
* mode that created (or explicitly continued) a conversation.
|
|
*/
|
|
export const MY_PROJECT_THREAD_SOURCE = "my-projects" as const;
|
|
|
|
export const MY_PROJECT_THREAD_SOURCE_KEY = "project_source" as const;
|
|
export const MY_PROJECT_THREAD_TYPE_KEY = "project_type" as const;
|
|
|
|
export const MY_PROJECT_QUERY_SOURCE_KEY = "projectSource" as const;
|
|
export const MY_PROJECT_QUERY_TYPE_KEY = "projectType" as const;
|
|
|
|
export type MyProjectConversationType = "qa" | "strategy" | "solution";
|
|
|
|
export const MY_PROJECT_CONVERSATION_LABELS: Record<
|
|
MyProjectConversationType,
|
|
string
|
|
> = {
|
|
qa: "普通问答",
|
|
strategy: "策略优选",
|
|
solution: "方案生成",
|
|
};
|
|
|
|
const MY_PROJECT_CONVERSATION_TYPES = new Set<MyProjectConversationType>([
|
|
"qa",
|
|
"strategy",
|
|
"solution",
|
|
]);
|
|
|
|
function typeFromRecord(
|
|
value: Record<string, unknown> | null | undefined,
|
|
): MyProjectConversationType | null {
|
|
if (value?.[MY_PROJECT_THREAD_SOURCE_KEY] !== MY_PROJECT_THREAD_SOURCE) {
|
|
return null;
|
|
}
|
|
const type = value[MY_PROJECT_THREAD_TYPE_KEY];
|
|
return typeof type === "string" &&
|
|
MY_PROJECT_CONVERSATION_TYPES.has(type as MyProjectConversationType)
|
|
? (type as MyProjectConversationType)
|
|
: null;
|
|
}
|
|
|
|
export function myProjectThreadMetadata(
|
|
type: MyProjectConversationType,
|
|
): Record<string, unknown> {
|
|
return {
|
|
[MY_PROJECT_THREAD_SOURCE_KEY]: MY_PROJECT_THREAD_SOURCE,
|
|
[MY_PROJECT_THREAD_TYPE_KEY]: type,
|
|
};
|
|
}
|
|
|
|
/** Read persisted metadata first; context is only a compatibility fallback. */
|
|
export function myProjectConversationTypeOfThread(thread: {
|
|
metadata?: Record<string, unknown> | null;
|
|
context?: Record<string, unknown> | null;
|
|
}): MyProjectConversationType | null {
|
|
return typeFromRecord(thread.metadata) ?? typeFromRecord(thread.context);
|
|
}
|
|
|
|
export function myProjectConversationTypeFromSearchParams(params: {
|
|
get(name: string): string | null;
|
|
}): MyProjectConversationType | null {
|
|
if (params.get(MY_PROJECT_QUERY_SOURCE_KEY) !== MY_PROJECT_THREAD_SOURCE) {
|
|
return null;
|
|
}
|
|
const type = params.get(MY_PROJECT_QUERY_TYPE_KEY);
|
|
return typeof type === "string" &&
|
|
MY_PROJECT_CONVERSATION_TYPES.has(type as MyProjectConversationType)
|
|
? (type as MyProjectConversationType)
|
|
: null;
|
|
}
|
|
|
|
/** Add an explicit, one-shot marker to a new-conversation route. */
|
|
export function withMyProjectConversationMarker(
|
|
path: string,
|
|
type: MyProjectConversationType,
|
|
): string {
|
|
const hashIndex = path.indexOf("#");
|
|
const hash = hashIndex >= 0 ? path.slice(hashIndex) : "";
|
|
const pathWithoutHash = hashIndex >= 0 ? path.slice(0, hashIndex) : path;
|
|
const queryIndex = pathWithoutHash.indexOf("?");
|
|
const pathname =
|
|
queryIndex >= 0 ? pathWithoutHash.slice(0, queryIndex) : pathWithoutHash;
|
|
const params = new URLSearchParams(
|
|
queryIndex >= 0 ? pathWithoutHash.slice(queryIndex + 1) : "",
|
|
);
|
|
params.set(MY_PROJECT_QUERY_SOURCE_KEY, MY_PROJECT_THREAD_SOURCE);
|
|
params.set(MY_PROJECT_QUERY_TYPE_KEY, type);
|
|
const search = params.toString();
|
|
return `${pathname}${search ? `?${search}` : ""}${hash}`;
|
|
}
|