thread-master/apps/web/src/data/repos.ts

790 lines
26 KiB
TypeScript
Raw Normal View History

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;
/**
* URLdata 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-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 livemock 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 URLkind 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
/** liveOAuth start回授權 URLfake 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-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;
/** 附圖 URLmock 記張數/可選縮圖) */
imageUrls?: string[];
}): Promise<OwnPost>;
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-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 mocklive
*/
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;
/** 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建立背景海巡任務。 */
runScanFromBrief(brief: ScoutRunBrief): Promise<{ job: Job }>;
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>;
/** 已完成的功課列表(持久化) */
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,
): 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;
}): Promise<import("../domain/types").RadarWatch>;
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>;
suggestWatchTerms(limit?: number): Promise<import("../domain/types").WatchTermSuggestion[]>;
triggerWatchSweep(id: string): Promise<{ job_id: string; sweep_id?: string }>;
getToday(): Promise<import("../domain/types").RadarToday>;
listOpportunities(filter?: {
page?: number;
pageSize?: number;
band?: string;
status?: string;
watch_id?: string;
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>;
overrideOpportunity(
id: string,
patch: { band?: string; status?: string; note?: string },
): Promise<import("../domain/types").Opportunity>;
listReplies(opportunityId: string): Promise<import("../domain/types").ReplyVariant[]>;
createReply(
opportunityId: string,
variant: import("../domain/types").ReplyVariantKind,
): Promise<import("../domain/types").ReplyVariant>;
/** channel: outbox | manual_copydm 僅 manual_copy */
markReplyUsed(
opportunityId: string,
replyId: string,
channel: "outbox" | "manual_copy",
): Promise<{
reply: import("../domain/types").ReplyVariant;
health_advice?: string;
}>;
listSweeps(
page?: number,
pageSize?: number,
watchId?: string,
): Promise<{ list: import("../domain/types").RadarSweep[]; total: number }>;
};
export type CrmRepo = {
listContacts(filter?: {
page?: number;
pageSize?: number;
stage?: string;
/** 留空不篩選truefalse 才篩 */
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>;
/** 允許任意跳轉,含回退、跳級與終態重開 */
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
};