2026-07-10 05:10:31 +00:00
|
|
|
|
import type {
|
|
|
|
|
|
AiSettings,
|
|
|
|
|
|
AppNotification,
|
|
|
|
|
|
Brand,
|
|
|
|
|
|
BrandProduct,
|
|
|
|
|
|
DataSource,
|
|
|
|
|
|
ExternalThreadTarget,
|
|
|
|
|
|
InspirationIdea,
|
|
|
|
|
|
InspireChatMessage,
|
|
|
|
|
|
InspireElement,
|
|
|
|
|
|
InspireSession,
|
2026-07-13 01:15:30 +00:00
|
|
|
|
InspireSessionSummary,
|
2026-07-10 12:54:45 +00:00
|
|
|
|
InviteMemberBrief,
|
|
|
|
|
|
InviteTreeNode,
|
2026-07-10 05:10:31 +00:00
|
|
|
|
Job,
|
|
|
|
|
|
Member,
|
|
|
|
|
|
MentionItem,
|
2026-07-10 12:54:45 +00:00
|
|
|
|
MyInviteNetwork,
|
2026-07-10 05:10:31 +00:00
|
|
|
|
OwnPost,
|
|
|
|
|
|
OutboxBundle,
|
|
|
|
|
|
Persona,
|
|
|
|
|
|
PlacementSettings,
|
|
|
|
|
|
ResearchHit,
|
|
|
|
|
|
Role,
|
|
|
|
|
|
ScoutHomeworkRecord,
|
|
|
|
|
|
ScoutPost,
|
|
|
|
|
|
ScoutRunBrief,
|
|
|
|
|
|
ScoutScanContext,
|
|
|
|
|
|
ThreadPlay,
|
|
|
|
|
|
ThreadsAccount,
|
|
|
|
|
|
TokenPair,
|
|
|
|
|
|
TrendItem,
|
|
|
|
|
|
TrendKind,
|
|
|
|
|
|
ViralAnalysis,
|
|
|
|
|
|
} from "../domain/types";
|
|
|
|
|
|
import type { AdminUserListPage, MemberAdminView } from "../lib/tenantUsers";
|
|
|
|
|
|
import type { PlanPurchase } from "../lib/planPurchase";
|
|
|
|
|
|
import type {
|
|
|
|
|
|
PlanId,
|
|
|
|
|
|
TenantAnalyticsQuery,
|
|
|
|
|
|
TenantUsageAnalytics,
|
|
|
|
|
|
TenantUsageSummary,
|
|
|
|
|
|
UsageEvent,
|
|
|
|
|
|
UsageMemberPrefs,
|
|
|
|
|
|
UsageMonthSummary,
|
|
|
|
|
|
} from "../lib/usageMeter";
|
|
|
|
|
|
|
|
|
|
|
|
export type MemberProfilePatch = {
|
|
|
|
|
|
display_name?: string;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
nickname?: string;
|
|
|
|
|
|
full_name?: string;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
email?: string;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
phone?: string;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
bio?: string;
|
|
|
|
|
|
timezone?: string;
|
|
|
|
|
|
notify_email?: boolean;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 頭像 URL(data URL 或 https)。
|
|
|
|
|
|
* 傳 `null` 清除;`undefined` 不改。
|
|
|
|
|
|
*/
|
|
|
|
|
|
avatar_url?: string | null;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
gender_code?: number;
|
|
|
|
|
|
birthdate?: number;
|
|
|
|
|
|
national?: string;
|
|
|
|
|
|
address?: string;
|
|
|
|
|
|
post_code?: string;
|
|
|
|
|
|
preferred_language?: string;
|
|
|
|
|
|
currency?: string;
|
2026-08-18 16:26:10 +00:00
|
|
|
|
/** skipped | completed;只寫一次 */
|
|
|
|
|
|
onboarding_status?: "skipped" | "completed";
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/** 改密時必填 */
|
|
|
|
|
|
current_password?: string;
|
|
|
|
|
|
new_password?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type AuthRepo = {
|
|
|
|
|
|
login(email: string, password: string): Promise<{ tokens: TokenPair; member: Member }>;
|
|
|
|
|
|
logout(): Promise<void>;
|
2026-07-28 06:33:51 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* null 代表「確定未登入」(沒 token 或後端回 401)。
|
|
|
|
|
|
* 暫時性失敗(斷網、後端重啟、5xx)必須 throw,不可回 null,
|
|
|
|
|
|
* 否則呼叫端會把使用者當成登出而踢回登入頁。
|
|
|
|
|
|
*/
|
2026-07-10 05:10:31 +00:00
|
|
|
|
me(): Promise<Member | null>;
|
|
|
|
|
|
/** 更新自己的會員資料(mock 寫 session) */
|
|
|
|
|
|
updateProfile(patch: MemberProfilePatch): Promise<Member>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 申請重設密碼。mock 一律回成功(不洩漏帳號是否存在);
|
|
|
|
|
|
* 若為已知帳號會給 mock_reset_path 方便本機測試。
|
|
|
|
|
|
*/
|
|
|
|
|
|
requestPasswordReset(email: string): Promise<{
|
|
|
|
|
|
ok: true;
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
}>;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 用驗證碼/token 設定新密碼。
|
|
|
|
|
|
* email 必填(live);mock 可只靠 token。
|
|
|
|
|
|
*/
|
|
|
|
|
|
resetPassword(
|
|
|
|
|
|
token: string,
|
|
|
|
|
|
newPassword: string,
|
|
|
|
|
|
email?: string,
|
|
|
|
|
|
): Promise<{ ok: true; message: string }>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/** 寄送信箱驗證碼(需已登入) */
|
|
|
|
|
|
sendEmailVerificationCode(): Promise<{
|
|
|
|
|
|
ok: true;
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
/** 輸入驗證碼完成信箱驗證 */
|
|
|
|
|
|
verifyEmail(code: string): Promise<Member>;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 上傳圖片到物件儲存(MinIO/S3),回傳公開 URL。
|
|
|
|
|
|
* content 為 data URL;kind 預設 avatar。
|
|
|
|
|
|
*/
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** kind: avatar 永久;temp 發文暫存(發成功/取消 outbox 後刪);other 相容 */
|
|
|
|
|
|
uploadImage(content: string, kind?: "avatar" | "other" | "temp"): Promise<{ url: string; key?: string }>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 管理者新增島民 */
|
|
|
|
|
|
export type AdminCreateMemberInput = {
|
|
|
|
|
|
display_name: string;
|
|
|
|
|
|
email: string;
|
|
|
|
|
|
password?: string;
|
|
|
|
|
|
roles?: Role[];
|
|
|
|
|
|
email_verified?: boolean;
|
|
|
|
|
|
bio?: string;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
/** 邀請人 uid;未傳 = 掛建立者;null = 根 */
|
|
|
|
|
|
parent_uid?: string | null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 邀請關係(mock 支線)。
|
|
|
|
|
|
* 一般會員:邀請碼 + 邀請人 + 直邀;可補填邀請碼。
|
|
|
|
|
|
* 管理員:焦點鑽取 + 調整歸屬。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export type InviteRepo = {
|
|
|
|
|
|
/** 目前登入者的邀請網絡 */
|
|
|
|
|
|
getMyNetwork(): Promise<MyInviteNetwork>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 會員補填邀請碼(僅尚無邀請人時)。
|
|
|
|
|
|
* 成功回傳更新後的網絡。
|
|
|
|
|
|
*/
|
|
|
|
|
|
applyInviteCode(code: string): Promise<MyInviteNetwork>;
|
|
|
|
|
|
/** 管理員:邀請森林 */
|
|
|
|
|
|
getTree(): Promise<InviteTreeNode[]>;
|
|
|
|
|
|
/** 管理員:改邀請人(null = 獨立根) */
|
|
|
|
|
|
moveMember(uid: string, newParentUid: string | null): Promise<InviteMemberBrief>;
|
|
|
|
|
|
/** 管理員:可當邀請人的候選(排除 subtree) */
|
|
|
|
|
|
listParentCandidates(excludeSubtreeRootUid?: string): Promise<InviteMemberBrief[]>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/** 管理員:島民列表與可寫操作 */
|
|
|
|
|
|
export type AdminUsersRepo = {
|
|
|
|
|
|
/** @deprecated 請用 listUsersPage */
|
|
|
|
|
|
listUsers(): Promise<MemberAdminView[]>;
|
|
|
|
|
|
/** 分頁列表 page 從 1 起;query 可搜名稱 / Email / uid */
|
|
|
|
|
|
listUsersPage(opts?: {
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
|
query?: string;
|
|
|
|
|
|
}): Promise<AdminUserListPage>;
|
|
|
|
|
|
getUser(uid: string): Promise<MemberAdminView | null>;
|
|
|
|
|
|
/** 新增島民(回傳臨時密碼) */
|
|
|
|
|
|
createMember(
|
|
|
|
|
|
input: AdminCreateMemberInput,
|
|
|
|
|
|
): Promise<{ user: MemberAdminView; temporary_password: string }>;
|
|
|
|
|
|
/** 停權 / 復權 */
|
|
|
|
|
|
setSuspended(uid: string, suspended: boolean): Promise<MemberAdminView>;
|
|
|
|
|
|
/** 改驗證狀態 */
|
|
|
|
|
|
setEmailVerified(uid: string, verified: boolean): Promise<MemberAdminView>;
|
|
|
|
|
|
/** 指派角色(member / admin) */
|
|
|
|
|
|
setRoles(uid: string, roles: Role[]): Promise<MemberAdminView>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 幫使用者重設密碼。未傳 newPassword 則產生臨時密碼。
|
|
|
|
|
|
* 回傳 temporary_password 僅此次顯示。
|
|
|
|
|
|
*/
|
|
|
|
|
|
resetPassword(
|
|
|
|
|
|
uid: string,
|
|
|
|
|
|
newPassword?: string,
|
|
|
|
|
|
): Promise<{ user: MemberAdminView; temporary_password: string }>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type AccountsRepo = {
|
|
|
|
|
|
list(): Promise<ThreadsAccount[]>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** mock 專用:假連帳 */
|
2026-07-10 05:10:31 +00:00
|
|
|
|
createMock(): Promise<ThreadsAccount>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** live:OAuth start(回授權 URL;fake provider 會指回 callback) */
|
|
|
|
|
|
oauthStart(): Promise<{ authorize_url: string; state: string }>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
remove(id: string): Promise<void>;
|
|
|
|
|
|
refreshSession(id: string): Promise<ThreadsAccount>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type PlaysRepo = {
|
|
|
|
|
|
list(): Promise<ThreadPlay[]>;
|
|
|
|
|
|
listByPost(ownPostId: string): Promise<ThreadPlay[]>;
|
|
|
|
|
|
/** 依外部 Threads 連結列出方案(URL 正規化後比對) */
|
|
|
|
|
|
listByExternalUrl(url: string): Promise<ThreadPlay[]>;
|
|
|
|
|
|
/** 貼連結 → mock 解析目標貼文 */
|
|
|
|
|
|
resolveExternalLink(url: string): Promise<ExternalThreadTarget>;
|
|
|
|
|
|
get(id: string): Promise<ThreadPlay | null>;
|
|
|
|
|
|
save(play: ThreadPlay): Promise<ThreadPlay>;
|
|
|
|
|
|
remove(id: string): Promise<void>;
|
|
|
|
|
|
submit(id: string): Promise<OutboxBundle>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 真 AI 產劇本一步(reply / root) */
|
|
|
|
|
|
generateStep(opts: {
|
|
|
|
|
|
personaId?: string;
|
|
|
|
|
|
context: string;
|
|
|
|
|
|
topic?: string;
|
|
|
|
|
|
speakerLabel?: string;
|
|
|
|
|
|
isLead?: boolean;
|
|
|
|
|
|
mode?: "reply" | "root";
|
|
|
|
|
|
}): Promise<string>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 一次產完整劇本(背景 Job)。
|
|
|
|
|
|
* 完成後 play 已寫入 DB,前端應 reload play。
|
|
|
|
|
|
*/
|
|
|
|
|
|
generateScript(playId: string): Promise<{ jobId: string; async: boolean }>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type OutboxRepo = {
|
|
|
|
|
|
list(): Promise<OutboxBundle[]>;
|
|
|
|
|
|
get(id: string): Promise<OutboxBundle | null>;
|
|
|
|
|
|
simulateSuccess(id: string): Promise<OutboxBundle>;
|
|
|
|
|
|
simulateRootFail(id: string): Promise<OutboxBundle>;
|
|
|
|
|
|
retryStep(bundleId: string, stepId: string): Promise<OutboxBundle>;
|
|
|
|
|
|
/** 刪除整筆發送佇列項目 */
|
|
|
|
|
|
remove(id: string): Promise<void>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-13 03:18:08 +00:00
|
|
|
|
/** 任務列表分桶:定期排程 / 執行中 / 歷史 */
|
|
|
|
|
|
export type JobListTab = "recurring" | "active" | "history";
|
|
|
|
|
|
|
|
|
|
|
|
export type JobListQuery = {
|
|
|
|
|
|
tab?: JobListTab;
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type JobListResult = {
|
|
|
|
|
|
list: Job[];
|
|
|
|
|
|
pagination: {
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
pageSize: number;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
totalPages: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-13 02:22:24 +00:00
|
|
|
|
export type ListPage<T> = {
|
|
|
|
|
|
list: T[];
|
|
|
|
|
|
pagination: {
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
pageSize: number;
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
totalPages: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type ScoutRunListQuery = {
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
|
brandId?: string | null;
|
|
|
|
|
|
mode?: import("../domain/types").ScoutMode;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type ScoutResultListQuery = {
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-10 05:10:31 +00:00
|
|
|
|
export type JobsRepo = {
|
2026-07-13 03:18:08 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 分頁列表。傳 query 時走 tab+page;無 query 時預設 active 前 50 筆(相容舊呼叫)。
|
|
|
|
|
|
* 回傳完整 JobListResult;若只要陣列可用 .list。
|
|
|
|
|
|
*/
|
|
|
|
|
|
list(query?: JobListQuery): Promise<JobListResult>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
get(id: string): Promise<Job | null>;
|
|
|
|
|
|
startDemo(): Promise<Job>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 手動刪除(執行中不可刪;後端終態約 2 天後也會自動清) */
|
|
|
|
|
|
remove(id: string): Promise<void>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type NotificationsRepo = {
|
|
|
|
|
|
list(): Promise<AppNotification[]>;
|
|
|
|
|
|
unreadCount(): Promise<number>;
|
|
|
|
|
|
markRead(id: string): Promise<void>;
|
|
|
|
|
|
markAllRead(): Promise<void>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** Threads OAuth 平台狀態(App Secret 不回傳) */
|
|
|
|
|
|
export type ThreadsPlatformStatus = {
|
|
|
|
|
|
provider: "fake" | "meta" | string;
|
|
|
|
|
|
configured: boolean;
|
|
|
|
|
|
oauth_ready: boolean;
|
|
|
|
|
|
connect_path: string;
|
|
|
|
|
|
/** Meta Valid OAuth Redirect URIs 用 */
|
|
|
|
|
|
callback_url: string;
|
|
|
|
|
|
public_web_base: string;
|
|
|
|
|
|
app_id_masked?: string;
|
|
|
|
|
|
hint: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-10 05:10:31 +00:00
|
|
|
|
export type SettingsRepo = {
|
|
|
|
|
|
getDataSource(): DataSource;
|
|
|
|
|
|
setDataSource(ds: DataSource): void;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 一次拿:已存設定(含 selected model)+ 模型清單。
|
|
|
|
|
|
* provider 可選:預覽另一個 provider 的 models(不改存檔)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
getAi(provider?: string): Promise<AiSettings>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
saveAi(patch: Partial<AiSettings> & { api_key?: string; research_api_key?: string }): Promise<AiSettings>;
|
|
|
|
|
|
getPlacement(): Promise<PlacementSettings>;
|
|
|
|
|
|
savePlacement(
|
|
|
|
|
|
patch: Partial<PlacementSettings> & { brave_api_key?: string; exa_api_key?: string },
|
|
|
|
|
|
): Promise<PlacementSettings>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** @deprecated 優先用 getAi(provider) 一併取 models + selected_model */
|
2026-07-10 05:10:31 +00:00
|
|
|
|
listModels(provider: string): Promise<string[]>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 平台 Threads App 是否已接 Meta(設定頁狀態卡) */
|
|
|
|
|
|
getThreadsStatus(): Promise<ThreadsPlatformStatus>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type PersonasRepo = {
|
|
|
|
|
|
list(): Promise<Persona[]>;
|
|
|
|
|
|
get(id: string): Promise<Persona | null>;
|
|
|
|
|
|
getActiveId(): Promise<string>;
|
|
|
|
|
|
setActiveId(id: string): Promise<void>;
|
|
|
|
|
|
save(persona: Persona): Promise<Persona>;
|
|
|
|
|
|
remove(id: string): Promise<void>;
|
|
|
|
|
|
analyzeFromText(id: string, rawText: string, sourceLabel?: string): Promise<Persona>;
|
|
|
|
|
|
analyzeFromAccount(id: string, username: string): Promise<Persona>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type OwnPostsRepo = {
|
|
|
|
|
|
list(accountId?: string): Promise<OwnPost[]>;
|
|
|
|
|
|
lastSyncedAt(): Promise<number | null>;
|
|
|
|
|
|
sync(accountId: string): Promise<OwnPost[]>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 點開貼文後才載留言(避免同步時一次打爆 API) */
|
|
|
|
|
|
loadReplies(postId: string): Promise<OwnPost>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
generateReply(opts: {
|
|
|
|
|
|
postId: string;
|
|
|
|
|
|
replyId?: string;
|
|
|
|
|
|
personaId?: string;
|
|
|
|
|
|
}): Promise<string>;
|
|
|
|
|
|
sendReply(opts: {
|
|
|
|
|
|
postId: string;
|
|
|
|
|
|
replyId?: string;
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
/** 用哪個帳號送出(mock 寫入 username) */
|
|
|
|
|
|
accountId?: string;
|
|
|
|
|
|
/** 附圖 URL(mock 記張數/可選縮圖) */
|
|
|
|
|
|
imageUrls?: string[];
|
|
|
|
|
|
}): Promise<OwnPost>;
|
2026-08-18 16:26:10 +00:00
|
|
|
|
manageReply(opts: { postId: string; replyId: string; hide: boolean }): Promise<OwnPost>;
|
|
|
|
|
|
setReplyControl(opts: { postId: string; replyControl: string }): Promise<OwnPost>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
analyzePost(postId: string): Promise<OwnPost>;
|
|
|
|
|
|
generateFromFormula(postId: string, personaId?: string): Promise<{ title: string; topic: string; root: string }>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type MentionsRepo = {
|
|
|
|
|
|
list(accountId?: string): Promise<MentionItem[]>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 從 Threads Graph 同步「@我」提及(需 threads_manage_mentions) */
|
|
|
|
|
|
sync(accountId: string): Promise<MentionItem[]>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
generateReply(id: string, personaId?: string): Promise<MentionItem>;
|
|
|
|
|
|
markReplied(id: string, text?: string, imageUrls?: string[]): Promise<MentionItem>;
|
|
|
|
|
|
skip(id: string): Promise<MentionItem>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type InspirationRepo = {
|
|
|
|
|
|
list(): Promise<InspirationIdea[]>;
|
|
|
|
|
|
/** 預設靈感榜:Threads 熱標(提示用) */
|
|
|
|
|
|
listTrends(kind?: TrendKind | "all"): Promise<TrendItem[]>;
|
|
|
|
|
|
refreshTrends(kind?: TrendKind | "all"): Promise<TrendItem[]>;
|
|
|
|
|
|
/** 元素庫 */
|
|
|
|
|
|
listElements(): Promise<InspireElement[]>;
|
|
|
|
|
|
saveElement(el: InspireElement): Promise<InspireElement>;
|
|
|
|
|
|
removeElement(id: string): Promise<void>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 目前作用中的 session */
|
2026-07-10 05:10:31 +00:00
|
|
|
|
getSession(): Promise<InspireSession>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 相容舊 API:開新對話並切過去(舊 session 保留)。
|
|
|
|
|
|
* 建議 UI 用 createSession。
|
|
|
|
|
|
*/
|
2026-07-10 05:10:31 +00:00
|
|
|
|
clearSession(): Promise<InspireSession>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** 多對話列表 */
|
|
|
|
|
|
listSessions(): Promise<InspireSessionSummary[]>;
|
|
|
|
|
|
createSession(title?: string): Promise<InspireSession>;
|
|
|
|
|
|
activateSession(id: string): Promise<InspireSession>;
|
|
|
|
|
|
deleteSession(id: string): Promise<InspireSession>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 聊天/產文。pinnedIds 為本輪套用元素;會寫回 session。
|
|
|
|
|
|
* mode=generate 時 assistant 帶 draft.body(乾淨正文)。
|
|
|
|
|
|
*/
|
|
|
|
|
|
chat(opts: {
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
pinnedIds: string[];
|
|
|
|
|
|
mode: "chat" | "generate";
|
2026-07-13 01:15:30 +00:00
|
|
|
|
personaId?: string;
|
|
|
|
|
|
sessionId?: string;
|
2026-07-20 06:33:14 +00:00
|
|
|
|
/** chat:本輪先用 Exa 查資料 */
|
|
|
|
|
|
useWeb?: boolean;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** generate:待改寫素材 */
|
|
|
|
|
|
material?: string;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
}): Promise<{ session: InspireSession; messages: InspireChatMessage[] }>;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 串流聊天/產文。onDelta 每收到一段正文就回呼;結束回完整 session。
|
2026-07-28 06:33:51 +00:00
|
|
|
|
* 中止時 reject `AbortError`(`isAbortError()` 可判斷),呼叫端不需當成失敗顯示。
|
2026-07-13 01:15:30 +00:00
|
|
|
|
*/
|
|
|
|
|
|
chatStream(
|
|
|
|
|
|
opts: {
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
pinnedIds: string[];
|
|
|
|
|
|
mode: "chat" | "generate";
|
|
|
|
|
|
personaId?: string;
|
|
|
|
|
|
sessionId?: string;
|
2026-07-20 06:33:14 +00:00
|
|
|
|
useWeb?: boolean;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
material?: string;
|
2026-07-28 06:33:51 +00:00
|
|
|
|
/** 使用者按停止或元件卸載時中止;不帶則跑到結束。 */
|
|
|
|
|
|
signal?: AbortSignal;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
},
|
|
|
|
|
|
onDelta: (chunk: string) => void,
|
|
|
|
|
|
): Promise<{
|
|
|
|
|
|
session: InspireSession;
|
|
|
|
|
|
/** 實際送 AI 的完整 prompt(與預覽同組裝) */
|
|
|
|
|
|
prompt?: string;
|
|
|
|
|
|
fingerprint?: string;
|
|
|
|
|
|
charCount?: number;
|
|
|
|
|
|
runeCount?: number;
|
|
|
|
|
|
sections?: string[];
|
|
|
|
|
|
}>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 觀測:後端用與送出相同的 buildInspirePrompt 組裝真實 prompt(不呼叫 AI、不扣額)。
|
|
|
|
|
|
* fingerprint 與送出後 done 事件的 prompt_fingerprint 可比對是否一致。
|
|
|
|
|
|
*/
|
|
|
|
|
|
previewPrompt(opts: {
|
|
|
|
|
|
message: string;
|
|
|
|
|
|
pinnedIds: string[];
|
|
|
|
|
|
mode: "chat" | "generate";
|
|
|
|
|
|
personaId?: string;
|
|
|
|
|
|
sessionId?: string;
|
|
|
|
|
|
material?: string;
|
|
|
|
|
|
}): Promise<{
|
|
|
|
|
|
prompt: string;
|
|
|
|
|
|
mode: string;
|
|
|
|
|
|
sections: string[];
|
|
|
|
|
|
blocks: Array<{ title: string; body: string }>;
|
|
|
|
|
|
fingerprint: string;
|
|
|
|
|
|
charCount: number;
|
|
|
|
|
|
runeCount: number;
|
|
|
|
|
|
pinned: Array<{ id: string; kind: string; title: string; body: string }>;
|
|
|
|
|
|
note?: string;
|
|
|
|
|
|
}>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type ResearchRepo = {
|
|
|
|
|
|
search(query: string): Promise<ResearchHit[]>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type MediaRepo = {
|
|
|
|
|
|
generateImage(prompt: string): Promise<{ id: string; url: string; prompt: string }>;
|
|
|
|
|
|
/** 本機選檔 → 可預覽附圖(Phase B mock,不真上傳) */
|
|
|
|
|
|
attachLocal(files: FileList | File[]): Promise<{ id: string; url: string; name?: string }[]>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-13 01:15:30 +00:00
|
|
|
|
export type PersonaPreviewResult = {
|
|
|
|
|
|
topic: string;
|
|
|
|
|
|
topic_source: "news" | "manual" | "fallback" | string;
|
|
|
|
|
|
post_text: string;
|
|
|
|
|
|
reply_text: string;
|
|
|
|
|
|
notes?: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-10 05:10:31 +00:00
|
|
|
|
export type ComposeRepo = {
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 仿寫:走背景 Job。
|
|
|
|
|
|
* - async=true → 回 jobId,前端輪詢 job 取 payload.result_text
|
|
|
|
|
|
* - async=false → 同步 text(測試降級)
|
|
|
|
|
|
*/
|
|
|
|
|
|
mimic(
|
|
|
|
|
|
sourceText: string,
|
|
|
|
|
|
personaId?: string,
|
2026-07-20 06:33:14 +00:00
|
|
|
|
direction?: string,
|
2026-07-13 01:15:30 +00:00
|
|
|
|
structureNotes?: string,
|
|
|
|
|
|
): Promise<{ text?: string; jobId?: string; async: boolean }>;
|
|
|
|
|
|
/** 依指紋真 LLM 試產主貼 + 回文(可抓新聞話題) */
|
|
|
|
|
|
personaPreview(opts: {
|
|
|
|
|
|
personaId: string;
|
|
|
|
|
|
topic?: string;
|
|
|
|
|
|
useNews?: boolean;
|
|
|
|
|
|
}): Promise<PersonaPreviewResult>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
analyzeViral(text: string): Promise<ViralAnalysis>;
|
|
|
|
|
|
/** 單篇送出 Outbox */
|
|
|
|
|
|
publishSingle(opts: {
|
|
|
|
|
|
accountId: string;
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
imageUrls?: string[];
|
2026-07-10 12:54:45 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 預計發送時間 unix nanoseconds UTC。
|
|
|
|
|
|
* 未傳或 ≤ now → 立即/盡快排程。
|
|
|
|
|
|
*/
|
|
|
|
|
|
schedule_start_at?: number;
|
2026-07-13 01:15:30 +00:00
|
|
|
|
/** Threads 話題標籤(topic_tag,可不加 #) */
|
|
|
|
|
|
topicTag?: string;
|
2026-08-18 16:26:10 +00:00
|
|
|
|
/** Threads reply_control,發文時設定誰可以回覆 */
|
|
|
|
|
|
replyControl?: string;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
}): Promise<OutboxBundle>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type ScoutRepo = {
|
|
|
|
|
|
listBrands(): Promise<Brand[]>;
|
|
|
|
|
|
get(id: string): Promise<Brand | null>;
|
|
|
|
|
|
getActiveBrandId(): Promise<string>;
|
|
|
|
|
|
setActiveBrandId(id: string): Promise<void>;
|
|
|
|
|
|
/** 對齊舊 BrandsPage:建立牌子 */
|
|
|
|
|
|
createBrand(input?: { display_name?: string; brief?: string }): Promise<Brand>;
|
|
|
|
|
|
saveBrand(brand: Brand): Promise<Brand>;
|
|
|
|
|
|
removeBrand(id: string): Promise<void>;
|
|
|
|
|
|
listProducts(brandId: string): Promise<BrandProduct[]>;
|
|
|
|
|
|
/** 全部產品(探查下拉用) */
|
|
|
|
|
|
listAllProducts(): Promise<BrandProduct[]>;
|
|
|
|
|
|
getProduct(id: string): Promise<BrandProduct | null>;
|
|
|
|
|
|
saveProduct(product: BrandProduct): Promise<BrandProduct>;
|
|
|
|
|
|
removeProduct(id: string): Promise<void>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 反著做:貼商品連結 → 抓取/推估後回填表單草稿(尚未存檔)。
|
|
|
|
|
|
* Phase B mock;live 應走後端爬頁。
|
|
|
|
|
|
*/
|
|
|
|
|
|
importProductFromUrl(url: string): Promise<{
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
product_context: string;
|
|
|
|
|
|
pain_points: string[];
|
|
|
|
|
|
match_tags: string[];
|
|
|
|
|
|
placement_url: string;
|
|
|
|
|
|
source_note: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 意圖 + 可選產品 → 可審知識 brief(痛點/周邊/掃描詞)
|
|
|
|
|
|
* 無產品 = theme 模式
|
|
|
|
|
|
*/
|
|
|
|
|
|
prepareBrief(opts: {
|
|
|
|
|
|
intent: string;
|
|
|
|
|
|
brandId?: string | null;
|
|
|
|
|
|
productId?: string | null;
|
2026-08-06 14:16:49 +00:00
|
|
|
|
/** demand=找需求痛點;provider=找解法提供者;activity=非推廣主題互動 */
|
|
|
|
|
|
purpose?: "demand" | "provider" | "activity";
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 是否做完整上網功課(摘要+分層來源)。
|
|
|
|
|
|
* 預設 false:只組痛點/掃描詞,不擋海巡;背景再 deep 補齊。
|
|
|
|
|
|
*/
|
|
|
|
|
|
deep?: boolean;
|
|
|
|
|
|
}): Promise<ScoutRunBrief>;
|
2026-07-13 08:59:13 +00:00
|
|
|
|
/** 依 brief(含使用者勾選後的 scan_terms)建立背景海巡任務。 */
|
2026-08-13 02:22:24 +00:00
|
|
|
|
runScanFromBrief(brief: ScoutRunBrief): Promise<{ job: Job; run: import("../domain/types").ScoutRun }>;
|
|
|
|
|
|
listRuns(query?: ScoutRunListQuery): Promise<ListPage<import("../domain/types").ScoutRun>>;
|
|
|
|
|
|
listRunPosts(
|
|
|
|
|
|
runId: string,
|
|
|
|
|
|
query?: ScoutResultListQuery,
|
|
|
|
|
|
): Promise<
|
|
|
|
|
|
ListPage<ScoutPost> & {
|
|
|
|
|
|
run: import("../domain/types").ScoutRun;
|
|
|
|
|
|
}
|
|
|
|
|
|
>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/** @deprecated 用 prepareBrief */
|
|
|
|
|
|
getScanContext(brandId: string, productId?: string | null): Promise<ScoutScanContext>;
|
|
|
|
|
|
/** 列出命中;brandId 空 = 全部(含主題模式) */
|
|
|
|
|
|
listPosts(brandId?: string | null): Promise<ScoutPost[]>;
|
|
|
|
|
|
/** @deprecated 用 runScanFromBrief */
|
|
|
|
|
|
runScan(
|
|
|
|
|
|
brandId: string,
|
|
|
|
|
|
productId?: string | null,
|
|
|
|
|
|
extraTerms?: string[],
|
2026-07-13 08:59:13 +00:00
|
|
|
|
): Promise<{ job: Job }>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
draftOutreach(postId: string, personaId?: string): Promise<ScoutPost>;
|
|
|
|
|
|
skipOutreach(postId: string): Promise<ScoutPost>;
|
|
|
|
|
|
markPublished(postId: string): Promise<ScoutPost>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
/** 升級為商機:複製成 Opportunity,不改 outreach_status */
|
|
|
|
|
|
promoteToOpportunity(postId: string): Promise<{
|
|
|
|
|
|
opportunity_id: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
intent_band?: string;
|
|
|
|
|
|
intent_score?: number;
|
|
|
|
|
|
}>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 模擬發送外展回覆:寫入草稿、標記 published、可帶帳號
|
|
|
|
|
|
*/
|
|
|
|
|
|
sendOutreach(opts: {
|
|
|
|
|
|
postId: string;
|
|
|
|
|
|
text: string;
|
|
|
|
|
|
accountId?: string;
|
|
|
|
|
|
personaId?: string;
|
|
|
|
|
|
}): Promise<ScoutPost>;
|
|
|
|
|
|
/** 刪除單則命中 */
|
|
|
|
|
|
removePost(postId: string): Promise<void>;
|
|
|
|
|
|
/** 刪除同一主題分組下全部命中 */
|
|
|
|
|
|
removeTheme(themeKey: string): Promise<void>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
/** 刪除獨立海巡批次及其結果 */
|
|
|
|
|
|
removeRun(runId: string): Promise<void>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/** 已完成的功課列表(持久化) */
|
|
|
|
|
|
listHomework(): Promise<ScoutHomeworkRecord[]>;
|
|
|
|
|
|
/** 存一輪功課(同 theme_key 覆蓋) */
|
|
|
|
|
|
saveHomework(record: ScoutHomeworkRecord): Promise<ScoutHomeworkRecord>;
|
|
|
|
|
|
getHomework(themeKey: string): Promise<ScoutHomeworkRecord | null>;
|
|
|
|
|
|
removeHomework(themeKey: string): Promise<void>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type UsageRepo = {
|
2026-07-28 06:33:51 +00:00
|
|
|
|
/** 目前登入者本月摘要 + 明細 */
|
|
|
|
|
|
getSummary(monthKey?: string): Promise<UsageMonthSummary>;
|
|
|
|
|
|
listEvents(limit?: number): Promise<UsageEvent[]>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
getPlanId(): Promise<PlanId>;
|
2026-07-28 06:33:51 +00:00
|
|
|
|
/** 讀方案/無限;uid 需管理員權限,省略則為自己 */
|
2026-07-10 05:10:31 +00:00
|
|
|
|
getMemberPrefs(uid?: string): Promise<UsageMemberPrefs>;
|
|
|
|
|
|
/** 管理員:設某人方案/無限 */
|
|
|
|
|
|
setMemberPrefs(uid: string, patch: Partial<UsageMemberPrefs>): Promise<UsageMemberPrefs>;
|
|
|
|
|
|
/** 管理員:全體本月用量 */
|
|
|
|
|
|
getTenantSummary(monthKey?: string): Promise<TenantUsageSummary>;
|
|
|
|
|
|
/** 管理員:全體區間分析(日/月/年 + 購買 vs 消耗) */
|
|
|
|
|
|
getTenantAnalytics(query?: TenantAnalyticsQuery): Promise<TenantUsageAnalytics>;
|
2026-07-15 15:23:59 +00:00
|
|
|
|
createCheckoutSession(plan_id: PlanId, request_id: string): Promise<BillingCheckoutSession>;
|
|
|
|
|
|
getCheckoutSession(id: string): Promise<BillingCheckoutStatus>;
|
|
|
|
|
|
getBillingSubscription(): Promise<BillingSubscription>;
|
|
|
|
|
|
createBillingPortalSession(): Promise<BillingPortalSession>;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
/** 自己的購買紀錄 */
|
|
|
|
|
|
listMyPurchases(limit?: number): Promise<PlanPurchase[]>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-15 15:23:59 +00:00
|
|
|
|
export type BillingCheckoutSession = {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
session_id: string;
|
|
|
|
|
|
url: string;
|
|
|
|
|
|
expires_at: number;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type BillingCheckoutStatus = {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
plan_id: PlanId;
|
|
|
|
|
|
checkout_status: string;
|
|
|
|
|
|
payment_status: string;
|
|
|
|
|
|
fulfillment_status: string;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type BillingSubscription = {
|
|
|
|
|
|
plan_id: PlanId;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
current_period_end: number;
|
|
|
|
|
|
cancel_at_period_end: boolean;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type BillingPortalSession = { url: string };
|
|
|
|
|
|
|
2026-07-23 05:56:42 +00:00
|
|
|
|
export type GrowthRepo = {
|
|
|
|
|
|
getOutcomeSummary(range?: "week" | "month"): Promise<import("../domain/types").OutcomeSummary>;
|
|
|
|
|
|
listOutcomes(page?: number, pageSize?: number): Promise<import("../domain/types").OutcomeEvent[]>;
|
|
|
|
|
|
reportConversion(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
amount?: number,
|
|
|
|
|
|
note?: string,
|
|
|
|
|
|
currency?: string,
|
|
|
|
|
|
): Promise<import("../domain/types").OutcomeEvent>;
|
|
|
|
|
|
getLatestCheckup(): Promise<import("../domain/types").WeeklyCheckup>;
|
|
|
|
|
|
generateCheckupNow(): Promise<import("../domain/types").WeeklyCheckup>;
|
|
|
|
|
|
listAccountHealth(): Promise<import("../domain/types").AccountHealth[]>;
|
|
|
|
|
|
listWorkspaces(): Promise<{
|
|
|
|
|
|
list: import("../domain/types").Workspace[];
|
|
|
|
|
|
current_workspace_id: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
switchWorkspace(id: string): Promise<{
|
|
|
|
|
|
list: import("../domain/types").Workspace[];
|
|
|
|
|
|
current_workspace_id: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
createWorkspace(name: string): Promise<import("../domain/types").Workspace>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-08-03 05:52:02 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* demand-radar 加值層。契約見 docs/product/demand-radar/spec.md §5.2。
|
|
|
|
|
|
* 尚未實作的能力後端回 501010,`apiRequest` 會丟 ApiError,呼叫端不得當成空結果。
|
|
|
|
|
|
*/
|
|
|
|
|
|
export type RadarRepo = {
|
|
|
|
|
|
getServiceProfile(): Promise<import("../domain/types").ServiceProfile>;
|
|
|
|
|
|
saveServiceProfile(
|
|
|
|
|
|
patch: Omit<import("../domain/types").ServiceProfile, "exists" | "updated_at">,
|
|
|
|
|
|
): Promise<import("../domain/types").ServiceProfile>;
|
|
|
|
|
|
listWatches(
|
|
|
|
|
|
page?: number,
|
|
|
|
|
|
pageSize?: number,
|
|
|
|
|
|
status?: string,
|
2026-08-13 02:22:24 +00:00
|
|
|
|
contextMode?: import("../domain/types").RadarWatchContextMode,
|
|
|
|
|
|
brandId?: string,
|
|
|
|
|
|
productId?: string,
|
2026-08-03 05:52:02 +00:00
|
|
|
|
): Promise<{
|
|
|
|
|
|
list: import("../domain/types").RadarWatch[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
active_count: number;
|
|
|
|
|
|
max_active: number;
|
|
|
|
|
|
profile_exists: boolean;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
createWatch(input: {
|
|
|
|
|
|
terms: string[];
|
|
|
|
|
|
exclude_terms?: string[];
|
|
|
|
|
|
regions?: string[];
|
|
|
|
|
|
enabled?: boolean;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
brand_id?: string;
|
|
|
|
|
|
product_id?: string;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
}): Promise<import("../domain/types").RadarWatch>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
assignWatchProduct(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
brandId: string,
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
): Promise<import("../domain/types").RadarWatch>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
updateWatch(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
patch: { terms?: string[]; exclude_terms?: string[]; regions?: string[] },
|
|
|
|
|
|
): Promise<import("../domain/types").RadarWatch>;
|
|
|
|
|
|
pauseWatch(id: string): Promise<import("../domain/types").RadarWatch>;
|
|
|
|
|
|
resumeWatch(id: string): Promise<import("../domain/types").RadarWatch>;
|
|
|
|
|
|
archiveWatch(id: string): Promise<void>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
/** 永久刪除已封存的巡邏設定;歷史商機與巡邏紀錄保留。 */
|
|
|
|
|
|
deleteArchivedWatch(id: string): Promise<void>;
|
|
|
|
|
|
suggestWatchTerms(
|
|
|
|
|
|
input?: number | { limit?: number; brand_id?: string; product_id?: string },
|
|
|
|
|
|
): Promise<import("../domain/types").WatchTermSuggestion[]>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
triggerWatchSweep(id: string): Promise<{ job_id: string; sweep_id?: string }>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
getToday(filter?: {
|
|
|
|
|
|
brand_id?: string;
|
|
|
|
|
|
product_id?: string;
|
|
|
|
|
|
fit_band?: string;
|
|
|
|
|
|
}): Promise<import("../domain/types").RadarToday>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
listOpportunities(filter?: {
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
|
band?: string;
|
|
|
|
|
|
status?: string;
|
|
|
|
|
|
watch_id?: string;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
brand_id?: string;
|
|
|
|
|
|
product_id?: string;
|
|
|
|
|
|
fit_band?: string;
|
|
|
|
|
|
match_state?: string;
|
|
|
|
|
|
review_state?: import("../domain/types").OpportunityReviewState;
|
|
|
|
|
|
time_scope?: import("../domain/types").OpportunityTimeScope;
|
|
|
|
|
|
priority_band?: import("../domain/types").OpportunityPriorityBand;
|
|
|
|
|
|
sort?: string;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
from?: number;
|
|
|
|
|
|
to?: number;
|
|
|
|
|
|
}): Promise<{ list: import("../domain/types").Opportunity[]; total: number }>;
|
|
|
|
|
|
getOpportunity(id: string): Promise<import("../domain/types").Opportunity>;
|
|
|
|
|
|
acceptOpportunity(id: string): Promise<{
|
|
|
|
|
|
opportunity_id: string;
|
|
|
|
|
|
contact_id?: string;
|
|
|
|
|
|
status: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
dismissOpportunity(id: string, reason?: string): Promise<import("../domain/types").Opportunity>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
updateOpportunityReviewState(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
patch: {
|
|
|
|
|
|
state: import("../domain/types").OpportunityReviewState;
|
|
|
|
|
|
removal_reason?: import("../domain/types").OpportunityRemovalReason;
|
|
|
|
|
|
removal_note?: string;
|
|
|
|
|
|
duplicate_of_opportunity_id?: string;
|
|
|
|
|
|
},
|
|
|
|
|
|
): Promise<import("../domain/types").Opportunity>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
overrideOpportunity(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
patch: { band?: string; status?: string; note?: string },
|
|
|
|
|
|
): Promise<import("../domain/types").Opportunity>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
setPrimaryProduct(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
productId: string,
|
|
|
|
|
|
reason: string,
|
|
|
|
|
|
): Promise<import("../domain/types").Opportunity>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
listReplies(opportunityId: string): Promise<import("../domain/types").ReplyVariant[]>;
|
|
|
|
|
|
createReply(
|
|
|
|
|
|
opportunityId: string,
|
|
|
|
|
|
variant: import("../domain/types").ReplyVariantKind,
|
|
|
|
|
|
): Promise<import("../domain/types").ReplyVariant>;
|
2026-08-09 07:57:35 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* channel: outbox | manual_copy;dm 僅 manual_copy。
|
|
|
|
|
|
* accountId 在 channel=outbox 時必填:要用哪個已連的 Threads 帳號真的送出。
|
|
|
|
|
|
*/
|
2026-08-03 05:52:02 +00:00
|
|
|
|
markReplyUsed(
|
|
|
|
|
|
opportunityId: string,
|
|
|
|
|
|
replyId: string,
|
|
|
|
|
|
channel: "outbox" | "manual_copy",
|
2026-08-09 07:57:35 +00:00
|
|
|
|
accountId?: string,
|
2026-08-03 05:52:02 +00:00
|
|
|
|
): Promise<{
|
|
|
|
|
|
reply: import("../domain/types").ReplyVariant;
|
|
|
|
|
|
health_advice?: string;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
listSweeps(
|
|
|
|
|
|
page?: number,
|
|
|
|
|
|
pageSize?: number,
|
|
|
|
|
|
watchId?: string,
|
|
|
|
|
|
): Promise<{ list: import("../domain/types").RadarSweep[]; total: number }>;
|
2026-08-09 07:57:35 +00:00
|
|
|
|
/** 手動匯入商機(P1):貼 Threads/Facebook 貼文網址+內文或 CSV 批次,走同一套五問判定 */
|
|
|
|
|
|
importOpportunities(
|
|
|
|
|
|
items: import("../domain/types").ManualImportItem[],
|
2026-08-13 02:22:24 +00:00
|
|
|
|
context?: { brand_id?: string; product_id?: string },
|
2026-08-09 07:57:35 +00:00
|
|
|
|
): Promise<{ results: import("../domain/types").ManualImportResult[] }>;
|
|
|
|
|
|
/** 立即探索:短詞 fan-out 搜尋 → 五問判定,寫入今日商機 */
|
|
|
|
|
|
exploreOpportunities(
|
|
|
|
|
|
terms: string[],
|
2026-08-13 02:22:24 +00:00
|
|
|
|
context?: { brand_id?: string; product_id?: string; preview_id?: string; credit_ceiling?: number; demand_input_version?: string; demand_map_version?: number },
|
2026-08-09 07:57:35 +00:00
|
|
|
|
): Promise<import("../domain/types").ExploreResult>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
getDemandMap(productId: string): Promise<import("../domain/types").DemandMap>;
|
|
|
|
|
|
updateDemandMap(productId: string, patch: Omit<import("../domain/types").DemandMap, "product_id" | "updated_at"> & { expected_map_version: number }): Promise<import("../domain/types").DemandMap>;
|
|
|
|
|
|
enrichDemandMap(productId: string, input: { preview_id: string; credit_ceiling: number; expected_map_version: number }): Promise<import("../domain/types").DemandMap>;
|
|
|
|
|
|
getCostPreview(input: { action: string; watch_id?: string; product_id?: string; candidate_limit?: number }): Promise<import("../domain/types").CostPreview>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export type CrmRepo = {
|
|
|
|
|
|
listContacts(filter?: {
|
|
|
|
|
|
page?: number;
|
|
|
|
|
|
pageSize?: number;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
query?: string;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
stage?: string;
|
|
|
|
|
|
/** 留空=不篩選;true/false 才篩 */
|
|
|
|
|
|
follow_up?: boolean;
|
|
|
|
|
|
band?: string;
|
|
|
|
|
|
sort?: "last_touch_at" | "intent_score";
|
|
|
|
|
|
}): Promise<{
|
|
|
|
|
|
list: import("../domain/types").Contact[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
stage_counts: import("../domain/types").ContactStageCount[];
|
|
|
|
|
|
}>;
|
|
|
|
|
|
getContact(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
page?: number,
|
|
|
|
|
|
pageSize?: number,
|
|
|
|
|
|
): Promise<import("../domain/types").ContactDetail>;
|
2026-08-13 02:22:24 +00:00
|
|
|
|
/** 從工作名單移除;保留商機、接觸與成交歷史。 */
|
|
|
|
|
|
deleteContact(id: string): Promise<void>;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
/** 允許任意跳轉,含回退、跳級與終態重開 */
|
|
|
|
|
|
updateStage(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
stage: import("../domain/types").ContactStage,
|
|
|
|
|
|
note?: string,
|
|
|
|
|
|
): Promise<import("../domain/types").Contact>;
|
|
|
|
|
|
setFollowUp(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
needsFollowUp: boolean,
|
|
|
|
|
|
days?: number,
|
|
|
|
|
|
): Promise<import("../domain/types").Contact>;
|
|
|
|
|
|
addNote(id: string, body: string): Promise<import("../domain/types").ContactTouch>;
|
|
|
|
|
|
mergeContact(id: string, sourceContactId: string): Promise<import("../domain/types").Contact>;
|
|
|
|
|
|
unmergeContact(id: string, mergedContactId: string): Promise<import("../domain/types").Contact>;
|
|
|
|
|
|
/** 成交一律由人回報;系統不自動判定 */
|
|
|
|
|
|
reportConversion(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
input: { amount?: number; currency?: string; note?: string },
|
|
|
|
|
|
): Promise<{ contact_id: string; outcome_id?: string; stage: string }>;
|
|
|
|
|
|
updateConversion(
|
|
|
|
|
|
id: string,
|
|
|
|
|
|
input: { amount?: number; currency?: string; note?: string },
|
|
|
|
|
|
): Promise<{ contact_id: string; outcome_id?: string; stage: string }>;
|
|
|
|
|
|
deleteConversion(id: string): Promise<void>;
|
|
|
|
|
|
listFollowUps(
|
|
|
|
|
|
page?: number,
|
|
|
|
|
|
pageSize?: number,
|
|
|
|
|
|
status?: string,
|
|
|
|
|
|
): Promise<{ list: import("../domain/types").FollowUp[]; total: number }>;
|
|
|
|
|
|
doneFollowUp(id: string): Promise<import("../domain/types").FollowUp>;
|
|
|
|
|
|
snoozeFollowUp(id: string, days: number): Promise<import("../domain/types").FollowUp>;
|
|
|
|
|
|
generateFollowUpMessage(id: string): Promise<string>;
|
|
|
|
|
|
getStats(from?: number, to?: number): Promise<import("../domain/types").CrmStats>;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-10 05:10:31 +00:00
|
|
|
|
export type Repos = {
|
|
|
|
|
|
dataSource: DataSource;
|
|
|
|
|
|
auth: AuthRepo;
|
|
|
|
|
|
accounts: AccountsRepo;
|
|
|
|
|
|
plays: PlaysRepo;
|
|
|
|
|
|
outbox: OutboxRepo;
|
|
|
|
|
|
jobs: JobsRepo;
|
|
|
|
|
|
notifications: NotificationsRepo;
|
|
|
|
|
|
settings: SettingsRepo;
|
|
|
|
|
|
personas: PersonasRepo;
|
|
|
|
|
|
ownPosts: OwnPostsRepo;
|
|
|
|
|
|
mentions: MentionsRepo;
|
|
|
|
|
|
inspiration: InspirationRepo;
|
|
|
|
|
|
research: ResearchRepo;
|
|
|
|
|
|
media: MediaRepo;
|
|
|
|
|
|
compose: ComposeRepo;
|
|
|
|
|
|
scout: ScoutRepo;
|
|
|
|
|
|
usage: UsageRepo;
|
|
|
|
|
|
adminUsers: AdminUsersRepo;
|
2026-07-10 12:54:45 +00:00
|
|
|
|
invite: InviteRepo;
|
2026-07-23 05:56:42 +00:00
|
|
|
|
growth: GrowthRepo;
|
2026-08-03 05:52:02 +00:00
|
|
|
|
radar: RadarRepo;
|
|
|
|
|
|
crm: CrmRepo;
|
2026-07-10 05:10:31 +00:00
|
|
|
|
};
|