import type { AiSettings, AppNotification, Brand, BrandProduct, DataSource, ExternalThreadTarget, InspirationIdea, InspireAngle, InspireChatMessage, InspireElement, InspireSession, Job, Member, MentionItem, OwnPost, OutboxBundle, Persona, PlacementSettings, ResearchHit, Role, ScoutHomeworkRecord, ScoutPost, ScoutRunBrief, ScoutScanContext, ScoutTopic, ThreadPlay, ThreadsAccount, TokenPair, TrendItem, TrendKind, ViralAnalysis, ViralSample, } 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; email?: string; bio?: string; timezone?: string; notify_email?: boolean; /** * 頭像 URL(data URL 或 https)。 * 傳 `null` 清除;`undefined` 不改。 */ avatar_url?: string | null; /** 改密時必填 */ current_password?: string; new_password?: string; }; export type AuthRepo = { login(email: string, password: string): Promise<{ tokens: TokenPair; member: Member }>; logout(): Promise; me(): Promise; /** 更新自己的會員資料(mock 寫 session) */ updateProfile(patch: MemberProfilePatch): Promise; /** * 申請重設密碼。mock 一律回成功(不洩漏帳號是否存在); * 若為已知帳號會給 mock_reset_path 方便本機測試。 */ requestPasswordReset(email: string): Promise<{ ok: true; message: string; /** 僅 mock:可點的重設路徑,例如 /reset-password?token=… */ mock_reset_path?: string; }>; /** 用 token 設定新密碼(mock) */ resetPassword(token: string, newPassword: string): Promise<{ ok: true; message: string }>; /** 寄送信箱驗證碼(需已登入) */ sendEmailVerificationCode(): Promise<{ ok: true; message: string; /** 僅 mock:畫面上顯示驗證碼方便測試 */ mock_code?: string; }>; /** 輸入驗證碼完成信箱驗證 */ verifyEmail(code: string): Promise; }; /** 管理者新增島民 */ export type AdminCreateMemberInput = { display_name: string; email: string; password?: string; roles?: Role[]; email_verified?: boolean; bio?: string; }; /** 管理員:島民列表與可寫操作 */ export type AdminUsersRepo = { /** @deprecated 請用 listUsersPage */ listUsers(): Promise; /** 分頁列表 page 從 1 起;query 可搜名稱 / Email / uid */ listUsersPage(opts?: { page?: number; pageSize?: number; query?: string; }): Promise; getUser(uid: string): Promise; /** 新增島民(回傳臨時密碼) */ createMember( input: AdminCreateMemberInput, ): Promise<{ user: MemberAdminView; temporary_password: string }>; /** 停權 / 復權 */ setSuspended(uid: string, suspended: boolean): Promise; /** 改驗證狀態 */ setEmailVerified(uid: string, verified: boolean): Promise; /** 指派角色(member / admin) */ setRoles(uid: string, roles: Role[]): Promise; /** * 幫使用者重設密碼。未傳 newPassword 則產生臨時密碼。 * 回傳 temporary_password 僅此次顯示。 */ resetPassword( uid: string, newPassword?: string, ): Promise<{ user: MemberAdminView; temporary_password: string }>; }; export type AccountsRepo = { list(): Promise; createMock(): Promise; remove(id: string): Promise; refreshSession(id: string): Promise; }; export type PlaysRepo = { list(): Promise; listByPost(ownPostId: string): Promise; /** 依外部 Threads 連結列出方案(URL 正規化後比對) */ listByExternalUrl(url: string): Promise; /** 貼連結 → mock 解析目標貼文 */ resolveExternalLink(url: string): Promise; get(id: string): Promise; save(play: ThreadPlay): Promise; remove(id: string): Promise; submit(id: string): Promise; }; export type OutboxRepo = { list(): Promise; get(id: string): Promise; simulateSuccess(id: string): Promise; simulateRootFail(id: string): Promise; retryStep(bundleId: string, stepId: string): Promise; /** 刪除整筆發送佇列項目 */ remove(id: string): Promise; }; export type JobsRepo = { list(): Promise; get(id: string): Promise; startDemo(): Promise; }; export type NotificationsRepo = { list(): Promise; unreadCount(): Promise; markRead(id: string): Promise; markAllRead(): Promise; }; export type SettingsRepo = { getDataSource(): DataSource; setDataSource(ds: DataSource): void; getAi(): Promise; saveAi(patch: Partial & { api_key?: string; research_api_key?: string }): Promise; getPlacement(): Promise; savePlacement( patch: Partial & { brave_api_key?: string; exa_api_key?: string }, ): Promise; listModels(provider: string): Promise; }; export type PersonasRepo = { list(): Promise; get(id: string): Promise; getActiveId(): Promise; setActiveId(id: string): Promise; save(persona: Persona): Promise; remove(id: string): Promise; analyzeFromText(id: string, rawText: string, sourceLabel?: string): Promise; analyzeFromAccount(id: string, username: string): Promise; }; export type OwnPostsRepo = { list(accountId?: string): Promise; lastSyncedAt(): Promise; sync(accountId: string): Promise; generateReply(opts: { postId: string; replyId?: string; personaId?: string; }): Promise; sendReply(opts: { postId: string; replyId?: string; text: string; /** 用哪個帳號送出(mock 寫入 username) */ accountId?: string; /** 附圖 URL(mock 記張數/可選縮圖) */ imageUrls?: string[]; }): Promise; analyzePost(postId: string): Promise; generateFromFormula(postId: string, personaId?: string): Promise<{ title: string; topic: string; root: string }>; }; export type MentionsRepo = { list(accountId?: string): Promise; generateReply(id: string, personaId?: string): Promise; markReplied(id: string, text?: string, imageUrls?: string[]): Promise; skip(id: string): Promise; }; export type InspirationRepo = { list(): Promise; /** 預設靈感榜:Threads 熱標(提示用) */ listTrends(kind?: TrendKind | "all"): Promise; refreshTrends(kind?: TrendKind | "all"): Promise; searchTrends(query: string): Promise; /** 元素庫 */ listElements(): Promise; saveElement(el: InspireElement): Promise; removeElement(id: string): Promise; /** 當前聊天 session(單一會話) */ getSession(): Promise; saveSession(session: InspireSession): Promise; clearSession(): Promise; /** * 聊天/產文。pinnedIds 為本輪套用元素;會寫回 session。 * mode=generate 時 assistant 帶 draft.body(乾淨正文)。 */ chat(opts: { message: string; pinnedIds: string[]; mode: "chat" | "generate"; }): Promise<{ session: InspireSession; messages: InspireChatMessage[] }>; /** @deprecated 舊角度流;保留相容 */ sparkAngles( trendId: string, opts?: { personaId?: string | null; brandId?: string | null }, ): Promise; /** @deprecated */ sparkAnglesForTopic( label: string, opts?: { personaId?: string | null; brandId?: string | null; samples?: string[] }, ): Promise<{ trend: TrendItem; angles: InspireAngle[] }>; bookmarkTrend(trendId: string): Promise; saveIdea(idea: InspirationIdea): Promise; generate(topic: string, personaId?: string): Promise; listViral(): Promise; /** @deprecated */ sparkFromTrend( trendId: string, personaId?: string, opts?: { save?: boolean }, ): Promise; }; export type ResearchRepo = { search(query: string): Promise; }; 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 }[]>; }; export type ComposeRepo = { mimic(sourceText: string, personaId?: string): Promise; analyzeViral(text: string): Promise; /** 單篇送出 Outbox */ publishSingle(opts: { accountId: string; text: string; title?: string; imageUrls?: string[]; }): Promise; }; export type ScoutRepo = { listBrands(): Promise; get(id: string): Promise; getActiveBrandId(): Promise; setActiveBrandId(id: string): Promise; /** 對齊舊 BrandsPage:建立牌子 */ createBrand(input?: { display_name?: string; brief?: string }): Promise; saveBrand(brand: Brand): Promise; removeBrand(id: string): Promise; listProducts(brandId: string): Promise; /** 全部產品(探查下拉用) */ listAllProducts(): Promise; getProduct(id: string): Promise; saveProduct(product: BrandProduct): Promise; removeProduct(id: string): Promise; /** * 反著做:貼商品連結 → 抓取/推估後回填表單草稿(尚未存檔)。 * 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; }>; listTopics(brandId?: string): Promise; saveTopic(topic: ScoutTopic): Promise; removeTopic(id: string): Promise; /** * 意圖 + 可選產品 → 可審知識 brief(痛點/周邊/掃描詞) * 無產品 = theme 模式 */ prepareBrief(opts: { intent: string; brandId?: string | null; productId?: string | null; /** value=痛點/置入(預設);activity=關鍵字活躍 */ purpose?: "value" | "activity"; /** * 是否做完整上網功課(摘要+分層來源)。 * 預設 false:只組痛點/掃描詞,不擋海巡;背景再 deep 補齊。 */ deep?: boolean; }): Promise; /** 依 brief(含使用者勾選後的 scan_terms)海巡 */ runScanFromBrief(brief: ScoutRunBrief): Promise; /** @deprecated 用 prepareBrief */ getScanContext(brandId: string, productId?: string | null): Promise; /** 列出命中;brandId 空 = 全部(含主題模式) */ listPosts(brandId?: string | null): Promise; /** @deprecated 用 runScanFromBrief */ runScan( brandId: string, productId?: string | null, extraTerms?: string[], ): Promise; draftOutreach(postId: string, personaId?: string): Promise; skipOutreach(postId: string): Promise; markPublished(postId: string): Promise; /** * 模擬發送外展回覆:寫入草稿、標記 published、可帶帳號 */ sendOutreach(opts: { postId: string; text: string; accountId?: string; personaId?: string; }): Promise; /** 刪除單則命中 */ removePost(postId: string): Promise; /** 刪除同一主題分組下全部命中 */ removeTheme(themeKey: string): Promise; /** 已完成的功課列表(持久化) */ listHomework(): Promise; /** 存一輪功課(同 theme_key 覆蓋) */ saveHomework(record: ScoutHomeworkRecord): Promise; getHomework(themeKey: string): Promise; removeHomework(themeKey: string): Promise; }; export type UsageRepo = { /** 目前登入者(或指定 uid)本月摘要 + 明細 */ getSummary(monthKey?: string, uid?: string): Promise; listEvents(limit?: number, uid?: string): Promise; getPlanId(): Promise; setPlanId(id: PlanId): Promise; getMemberPrefs(uid?: string): Promise; /** 管理員:設某人方案/無限 */ setMemberPrefs(uid: string, patch: Partial): Promise; /** 管理員:全體本月用量 */ getTenantSummary(monthKey?: string): Promise; /** 管理員:全體區間分析(日/月/年 + 購買 vs 消耗) */ getTenantAnalytics(query?: TenantAnalyticsQuery): Promise; /** * 會員自己購買方案(mock:假付款成功後才改 plan)。 * 管理員直接 setMemberPrefs 不算購買。 */ purchasePlan(plan_id: PlanId, opts?: { mock_ref?: string }): Promise; /** 自己的購買紀錄 */ listMyPurchases(limit?: number): Promise; }; 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; };