From 0f9f21db839270b566aa59e45033b0dfd13de04a Mon Sep 17 00:00:00 2001 From: digua Date: Wed, 6 May 2026 21:57:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=81=E7=A7=BB=E5=88=B0pnpm-workspa?= =?UTF-8?q?ce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron.vite.config.ts | 14 +- electron/main/ai/agent/index.ts | 4 +- electron/main/ai/llm/index.ts | 2 +- .../ai/tools/definitions/keyword-frequency.ts | 6 +- electron/main/api/errors.ts | 2 + electron/main/api/routes/import.ts | 16 +-- electron/main/database/analysis.ts | 9 +- electron/main/worker/core/dbCore.ts | 8 +- .../main/worker/import/incrementalImport.ts | 1 - .../query/advanced/languagePreference.ts | 4 +- electron/preload/index.d.ts | 6 +- packages/chart-cluster/ClusterView.vue | 7 +- packages/chart-cluster/queries.ts | 7 +- .../chart-interaction/InteractionView.vue | 7 +- packages/chart-interaction/queries.ts | 7 +- packages/chart-message/MessageProfileCard.vue | 7 +- packages/chart-message/MessageView.vue | 7 +- packages/chart-message/queries.ts | 7 +- packages/chart-ranking/RankingView.vue | 7 +- packages/chart-ranking/queries.ts | 7 +- .../chart-ranking/sections/ActivityRank.vue | 6 +- .../chart-ranking/sections/CheckInRank.vue | 6 +- .../chart-ranking/sections/DivingRank.vue | 6 +- .../chart-ranking/sections/MemeBattleRank.vue | 6 +- .../chart-ranking/sections/NightOwlRank.vue | 6 +- .../chart-ranking/sections/RepeatSection.vue | 6 +- packages/shared-types/index.ts | 111 +++++++++++++++ packages/shared-types/package.json | 8 ++ pnpm-workspace.yaml | 2 + .../analysis/quotes/CatchphraseTab.vue | 6 +- .../analysis/quotes/HotRepeatTab.vue | 6 +- .../analysis/quotes/KeywordAnalysis.vue | 6 +- .../analysis/quotes/LanguagePreferenceTab.vue | 6 +- .../analysis/quotes/SharedTopicsCard.vue | 6 +- .../analysis/quotes/TopicProfileCard.vue | 6 +- .../analysis/quotes/WordcloudTab.vue | 6 +- src/composables/useAsyncData.ts | 6 +- src/env.d.ts | 2 + src/pages/group-chat/components/QuotesTab.vue | 6 +- src/pages/group-chat/components/ViewTab.vue | 6 +- .../components/view/GroupRelationships.vue | 7 +- .../components/view/Relationships.vue | 6 +- .../private-chat/components/QuotesTab.vue | 6 +- src/pages/private-chat/components/ViewTab.vue | 6 +- .../components/view/RelationshipView.vue | 6 +- .../components/view/TimelineView.vue | 6 +- src/types/base.ts | 130 ++---------------- src/utils/platform.ts | 10 ++ tsconfig.node.json | 11 +- 49 files changed, 220 insertions(+), 322 deletions(-) create mode 100644 packages/shared-types/index.ts create mode 100644 packages/shared-types/package.json create mode 100644 pnpm-workspace.yaml create mode 100644 src/utils/platform.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index 6b8584fd..bc32857a 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -7,8 +7,12 @@ export default defineConfig(() => { return { main: { plugins: [externalizeDepsPlugin()], + resolve: { + alias: { + '@openchatlab': resolve('packages'), + }, + }, define: { - // 使用系统环境变量 'process.env.APTABASE_APP_KEY': JSON.stringify(process.env.APTABASE_APP_KEY || ''), }, build: { @@ -22,6 +26,11 @@ export default defineConfig(() => { }, preload: { plugins: [externalizeDepsPlugin()], + resolve: { + alias: { + '@openchatlab': resolve('packages'), + }, + }, build: { rollupOptions: { input: { @@ -38,6 +47,9 @@ export default defineConfig(() => { '@openchatlab': resolve('packages'), }, }, + define: { + __IS_ELECTRON__: JSON.stringify(true), + }, plugins: [ vue(), ui({ diff --git a/electron/main/ai/agent/index.ts b/electron/main/ai/agent/index.ts index 1f521872..c9a94a3b 100644 --- a/electron/main/ai/agent/index.ts +++ b/electron/main/ai/agent/index.ts @@ -245,9 +245,9 @@ export class Agent { ctx.api = lastMsg.api } // 从 model.baseUrl 和 api 类型构造完整请求 URL - const baseUrl = (this.piModel as Record).baseUrl as string | undefined + const baseUrl = (this.piModel as unknown as Record).baseUrl as string | undefined if (baseUrl) { - const apiType = lastMsg?.api || (this.piModel as Record).api + const apiType = lastMsg?.api || (this.piModel as unknown as Record).api const pathMap: Record = { 'openai-completions': '/chat/completions', 'openai-responses': '/responses', diff --git a/electron/main/ai/llm/index.ts b/electron/main/ai/llm/index.ts index d3668a65..c304e005 100644 --- a/electron/main/ai/llm/index.ts +++ b/electron/main/ai/llm/index.ts @@ -266,7 +266,7 @@ export function loadConfigStore(): AIConfigStore { } // Schema v2 → v3 迁移 - if (store.schemaVersion < 3) { + if ((store.schemaVersion ?? 0) < 3) { store = { ...migrateToSchemaV3(store), schemaVersion: CURRENT_SCHEMA_VERSION } as typeof store needsSchemaSave = true } diff --git a/electron/main/ai/tools/definitions/keyword-frequency.ts b/electron/main/ai/tools/definitions/keyword-frequency.ts index 542c9475..01114162 100644 --- a/electron/main/ai/tools/definitions/keyword-frequency.ts +++ b/electron/main/ai/tools/definitions/keyword-frequency.ts @@ -51,14 +51,14 @@ export function createTool(context: ToolContext): AgentTool { posFilterMode: 'meaningful', enableStopwords: true, } - const freqMap = batchSegmentWithFrequency(texts, segLocale, segOptions) + const freqResult = batchSegmentWithFrequency(texts, segLocale, segOptions) - if (freqMap.size === 0) { + if (freqResult.words.size === 0) { const text = isZh ? '分词后没有有意义的高频词' : 'No meaningful high-frequency words found after segmentation' return { content: [{ type: 'text', text }], details: null } } - const ranking = [...freqMap.entries()].map(([word, count], i) => ({ + const ranking = [...freqResult.words.entries()].map(([word, count], i) => ({ rank: i + 1, word, count, diff --git a/electron/main/api/errors.ts b/electron/main/api/errors.ts index 69cf74c2..00060835 100644 --- a/electron/main/api/errors.ts +++ b/electron/main/api/errors.ts @@ -13,6 +13,7 @@ export enum ApiErrorCode { BODY_TOO_LARGE = 'BODY_TOO_LARGE', IMPORT_IN_PROGRESS = 'IMPORT_IN_PROGRESS', IDEMPOTENCY_CONFLICT = 'IDEMPOTENCY_CONFLICT', + IDEMPOTENCY_PENDING = 'IDEMPOTENCY_PENDING', IMPORT_FAILED = 'IMPORT_FAILED', SERVER_ERROR = 'SERVER_ERROR', } @@ -28,6 +29,7 @@ const HTTP_STATUS: Record = { [ApiErrorCode.BODY_TOO_LARGE]: 413, [ApiErrorCode.IMPORT_IN_PROGRESS]: 409, [ApiErrorCode.IDEMPOTENCY_CONFLICT]: 409, + [ApiErrorCode.IDEMPOTENCY_PENDING]: 409, [ApiErrorCode.IMPORT_FAILED]: 500, [ApiErrorCode.SERVER_ERROR]: 500, } diff --git a/electron/main/api/routes/import.ts b/electron/main/api/routes/import.ts index 7e58ddb3..d82566ca 100644 --- a/electron/main/api/routes/import.ts +++ b/electron/main/api/routes/import.ts @@ -21,6 +21,8 @@ import { pipeline } from 'stream/promises' import { getTempDir } from '../../paths' import * as worker from '../../worker/workerManager' import { + ApiError, + ApiErrorCode, successResponse, importInProgress, importFailed, @@ -180,7 +182,7 @@ async function handleUnifiedImport(request: FastifyRequest, reply: FastifyReply, reply.code(err.statusCode).send(errorResponse(err)) return } - tempFile = writeResult.tempFile + tempFile = writeResult.tempFile! // Idempotency-Key check (after tempFile is written so we can hash JSONL content) if (cacheKey) { @@ -193,13 +195,11 @@ async function handleUnifiedImport(request: FastifyRequest, reply: FastifyReply, return } if (cached.status === 'pending') { - reply.code(409).send( - errorResponse({ - statusCode: 409, - code: 'IDEMPOTENCY_PENDING', - message: 'A request with this Idempotency-Key is still in progress. Please retry later.', - }) + const pendingErr = new ApiError( + ApiErrorCode.IDEMPOTENCY_PENDING, + 'A request with this Idempotency-Key is still in progress. Please retry later.' ) + reply.code(pendingErr.statusCode).send(errorResponse(pendingErr)) return } reply.send(cached.response) @@ -368,7 +368,7 @@ async function handleLegacyImport(request: FastifyRequest, reply: FastifyReply, reply.code(err.statusCode).send(errorResponse(err)) return } - tempFile = writeResult.tempFile + tempFile = writeResult.tempFile! if (sessionId) { const session = await worker.getSession(sessionId) diff --git a/electron/main/database/analysis.ts b/electron/main/database/analysis.ts index cb34a0bc..b71141fd 100644 --- a/electron/main/database/analysis.ts +++ b/electron/main/database/analysis.ts @@ -28,13 +28,8 @@ import type { } from '../../../src/types/analysis' import { openDatabase } from './core' -/** - * 时间过滤参数 - */ -export interface TimeFilter { - startTs?: number - endTs?: number -} +export type { TimeFilter } from '@openchatlab/shared-types' +import type { TimeFilter } from '@openchatlab/shared-types' /** * 构建时间过滤 WHERE 子句 diff --git a/electron/main/worker/core/dbCore.ts b/electron/main/worker/core/dbCore.ts index 7803de4c..01645234 100644 --- a/electron/main/worker/core/dbCore.ts +++ b/electron/main/worker/core/dbCore.ts @@ -86,11 +86,9 @@ export function getCacheDir(): string { // ==================== 时间过滤工具 ==================== -export interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null // 成员筛选,null 表示全部成员 -} +// Re-export from shared-types +export type { TimeFilter } from '@openchatlab/shared-types' +import type { TimeFilter } from '@openchatlab/shared-types' /** * 构建时间过滤 WHERE 子句 diff --git a/electron/main/worker/import/incrementalImport.ts b/electron/main/worker/import/incrementalImport.ts index 36c0bcdf..77a11215 100644 --- a/electron/main/worker/import/incrementalImport.ts +++ b/electron/main/worker/import/incrementalImport.ts @@ -196,7 +196,6 @@ export async function incrementalImport( id: number platform_id: string }> - const initialMemberCount = existingMembers.length for (const m of existingMembers) { memberIdMap.set(m.platform_id, m.id) } diff --git a/electron/main/worker/query/advanced/languagePreference.ts b/electron/main/worker/query/advanced/languagePreference.ts index c427e05a..0c364429 100644 --- a/electron/main/worker/query/advanced/languagePreference.ts +++ b/electron/main/worker/query/advanced/languagePreference.ts @@ -269,8 +269,8 @@ export function getLanguagePreferenceAnalysis(params: LanguagePreferenceParams): const b = memberProfiles[1] // 共同高频词 - const wordsA = new Map(a.topWords.map((w: any) => [w.word, w.count])) - const wordsB = new Map(b.topWords.map((w: any) => [w.word, w.count])) + const wordsA = new Map(a.topWords.map((w: any) => [w.word, w.count])) + const wordsB = new Map(b.topWords.map((w: any) => [w.word, w.count])) const shared: Array<{ word: string; countA: number; countB: number }> = [] for (const [word, countA] of wordsA) { const countB = wordsB.get(word) diff --git a/electron/preload/index.d.ts b/electron/preload/index.d.ts index ff6495f0..be68d8bd 100644 --- a/electron/preload/index.d.ts +++ b/electron/preload/index.d.ts @@ -20,11 +20,7 @@ import type { FileParseInfo, ConflictCheckResult, MergeParams, MergeResult } fro import type { LanguagePreferenceResult } from '../../src/types/quotes/languagePreference' import type { TableSchema, SQLResult } from '../../src/components/analysis/SQLLab/types' -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null // 成员筛选,null 表示全部成员 -} +import type { TimeFilter } from '@openchatlab/shared-types' // @ 互动关系图数据 interface MentionGraphData { diff --git a/packages/chart-cluster/ClusterView.vue b/packages/chart-cluster/ClusterView.vue index bf51db92..cf9a6b7e 100644 --- a/packages/chart-cluster/ClusterView.vue +++ b/packages/chart-cluster/ClusterView.vue @@ -13,15 +13,10 @@ import { CanvasRenderer } from 'echarts/renderers' import type { EChartsOption } from 'echarts' import { loadClusterGraph } from './queries' import type { ClusterGraphData, ClusterGraphOptions } from './types' +import type { TimeFilter } from '@openchatlab/shared-types' echarts.use([HeatmapChart, TooltipComponent, GridComponent, VisualMapComponent, CanvasRenderer]) -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/packages/chart-cluster/queries.ts b/packages/chart-cluster/queries.ts index 89f237a2..5c8045c8 100644 --- a/packages/chart-cluster/queries.ts +++ b/packages/chart-cluster/queries.ts @@ -8,12 +8,7 @@ */ import type { MemberRow, MessageRow, ClusterGraphData, ClusterGraphOptions, BuildClusterInput } from './types' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' const DEFAULT_OPTIONS: Required = { lookAhead: 3, diff --git a/packages/chart-interaction/InteractionView.vue b/packages/chart-interaction/InteractionView.vue index 94fbd434..0c70a2d8 100644 --- a/packages/chart-interaction/InteractionView.vue +++ b/packages/chart-interaction/InteractionView.vue @@ -8,12 +8,7 @@ import { useI18n } from 'vue-i18n' import { EChartGraph } from '@/components/charts' import type { EChartGraphData } from '@/components/charts' import { loadMentionGraph } from './queries' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = defineProps<{ sessionId: string diff --git a/packages/chart-interaction/queries.ts b/packages/chart-interaction/queries.ts index 0aa2fea6..6c709d30 100644 --- a/packages/chart-interaction/queries.ts +++ b/packages/chart-interaction/queries.ts @@ -7,12 +7,7 @@ */ import type { MemberInfo, NameHistory, MentionMessage, MentionGraphData, BuildGraphInput } from './types' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' /** * 构建时间和成员过滤条件 diff --git a/packages/chart-message/MessageProfileCard.vue b/packages/chart-message/MessageProfileCard.vue index 5a28dc25..413dfe17 100644 --- a/packages/chart-message/MessageProfileCard.vue +++ b/packages/chart-message/MessageProfileCard.vue @@ -10,6 +10,7 @@ import { ThemeCard } from '@/components/UI' import { MessageType, getMessageTypeName } from './types' import type { MessageTypeCount, HourlyActivity, WeekdayActivity, DailyActivity, TextStats } from './types' import { queryLongMessageCount } from './queries' +import type { TimeFilter } from '@openchatlab/shared-types' import dayjs from 'dayjs' echarts.use([PieChart, BarChart, TooltipComponent, GridComponent, CanvasRenderer]) @@ -17,12 +18,6 @@ echarts.use([PieChart, BarChart, TooltipComponent, GridComponent, CanvasRenderer const { t } = useI18n() const isDark = useDark() -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} - const props = defineProps<{ sessionId: string sessionName: string diff --git a/packages/chart-message/MessageView.vue b/packages/chart-message/MessageView.vue index 5bcb3482..fa4e3377 100644 --- a/packages/chart-message/MessageView.vue +++ b/packages/chart-message/MessageView.vue @@ -25,12 +25,7 @@ import type { TextStats, } from './types' import MessageProfileCard from './MessageProfileCard.vue' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = defineProps<{ sessionId: string diff --git a/packages/chart-message/queries.ts b/packages/chart-message/queries.ts index 4612b5e4..9f100d00 100644 --- a/packages/chart-message/queries.ts +++ b/packages/chart-message/queries.ts @@ -13,12 +13,7 @@ import type { LengthDistribution, TextStats, } from './types' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' /** * 构建时间和成员过滤条件 diff --git a/packages/chart-ranking/RankingView.vue b/packages/chart-ranking/RankingView.vue index 43a7918d..df78173f 100644 --- a/packages/chart-ranking/RankingView.vue +++ b/packages/chart-ranking/RankingView.vue @@ -7,12 +7,7 @@ import { PageAnchorsNav, TopNSelect } from '@/components/UI' import { usePageAnchors } from '@/composables' import type { MemberActivity } from './types' import { ActivityRank, CheckInRank, MemeBattleRank, RepeatSection, DivingRank, NightOwlRank } from './sections' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = defineProps<{ sessionId: string diff --git a/packages/chart-ranking/queries.ts b/packages/chart-ranking/queries.ts index 8d1e712b..3ee22ca2 100644 --- a/packages/chart-ranking/queries.ts +++ b/packages/chart-ranking/queries.ts @@ -14,12 +14,7 @@ import type { MemeBattleAnalysis, RepeatAnalysis, } from './types' - -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} +import type { TimeFilter } from '@openchatlab/shared-types' function buildFilter(filter?: TimeFilter): { conditions: string; params: any[] } { const parts: string[] = [] diff --git a/packages/chart-ranking/sections/ActivityRank.vue b/packages/chart-ranking/sections/ActivityRank.vue index ce04eae3..ea8f115d 100644 --- a/packages/chart-ranking/sections/ActivityRank.vue +++ b/packages/chart-ranking/sections/ActivityRank.vue @@ -5,11 +5,7 @@ import { EChartRank } from '@/components/charts' import type { RankItem } from '@/components/charts' import { SectionCard, LoadingState, Tabs, TopNSelect } from '@/components/UI' import { queryDragonKingAnalysis, queryCheckInAnalysis } from '../queries' - -interface TimeFilter { - startTs?: number - endTs?: number -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = withDefaults( defineProps<{ diff --git a/packages/chart-ranking/sections/CheckInRank.vue b/packages/chart-ranking/sections/CheckInRank.vue index fa673b74..af1d0c03 100644 --- a/packages/chart-ranking/sections/CheckInRank.vue +++ b/packages/chart-ranking/sections/CheckInRank.vue @@ -4,11 +4,7 @@ import type { CheckInAnalysis } from '../types' import { EChartStreakRank } from '../charts' import { SectionCard, LoadingState, EmptyState, Tabs, TopNSelect } from '@/components/UI' import { queryCheckInAnalysis } from '../queries' - -interface TimeFilter { - startTs?: number - endTs?: number -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = withDefaults( defineProps<{ diff --git a/packages/chart-ranking/sections/DivingRank.vue b/packages/chart-ranking/sections/DivingRank.vue index 68ca7789..76226d39 100644 --- a/packages/chart-ranking/sections/DivingRank.vue +++ b/packages/chart-ranking/sections/DivingRank.vue @@ -4,11 +4,7 @@ import type { DivingAnalysis } from '../types' import { EChartDivingRank } from '../charts' import { LoadingState } from '@/components/UI' import { queryDivingAnalysis } from '../queries' - -interface TimeFilter { - startTs?: number - endTs?: number -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = defineProps<{ sessionId: string diff --git a/packages/chart-ranking/sections/MemeBattleRank.vue b/packages/chart-ranking/sections/MemeBattleRank.vue index 2c034fab..f8b59a90 100644 --- a/packages/chart-ranking/sections/MemeBattleRank.vue +++ b/packages/chart-ranking/sections/MemeBattleRank.vue @@ -6,11 +6,7 @@ import type { RankItem } from '@/components/charts' import { LoadingState, Tabs, SectionCard, TopNSelect } from '@/components/UI' import { EChartBattleRank } from '../charts' import { queryMemeBattleAnalysis } from '../queries' - -interface TimeFilter { - startTs?: number - endTs?: number -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = withDefaults( defineProps<{ diff --git a/packages/chart-ranking/sections/NightOwlRank.vue b/packages/chart-ranking/sections/NightOwlRank.vue index f5a416b0..6b416a8f 100644 --- a/packages/chart-ranking/sections/NightOwlRank.vue +++ b/packages/chart-ranking/sections/NightOwlRank.vue @@ -5,11 +5,7 @@ import { EChartRank } from '@/components/charts' import { SectionCard, Tabs, TopNSelect, LoadingState } from '@/components/UI' import { EChartConsecutiveRank, EChartNightOwlRank } from '../charts' import { queryNightOwlAnalysis } from '../queries' - -interface TimeFilter { - startTs?: number - endTs?: number -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = withDefaults( defineProps<{ diff --git a/packages/chart-ranking/sections/RepeatSection.vue b/packages/chart-ranking/sections/RepeatSection.vue index c16f60f9..4795c69b 100644 --- a/packages/chart-ranking/sections/RepeatSection.vue +++ b/packages/chart-ranking/sections/RepeatSection.vue @@ -6,11 +6,7 @@ import type { RankItem, EChartBarData } from '@/components/charts' import { SectionCard, EmptyState, LoadingState, Tabs, TopNSelect } from '@/components/UI' import { EChartTimeRank } from '../charts' import { queryRepeatAnalysis } from '../queries' - -interface TimeFilter { - startTs?: number - endTs?: number -} +import type { TimeFilter } from '@openchatlab/shared-types' const props = withDefaults( defineProps<{ diff --git a/packages/shared-types/index.ts b/packages/shared-types/index.ts new file mode 100644 index 00000000..7b3212a9 --- /dev/null +++ b/packages/shared-types/index.ts @@ -0,0 +1,111 @@ +/** + * @openchatlab/shared-types + * 平台无关的共享类型定义,三端(Electron / Node 服务 / Web)统一使用 + */ + +// ==================== 时间筛选 ==================== + +export interface TimeFilter { + startTs?: number + endTs?: number + memberId?: number | null +} + +// ==================== 枚举与平台 ==================== + +/** + * 消息类型枚举 + * + * 分类说明: + * - 基础消息 (0-19): 常见的内容类型 + * - 交互消息 (20-39): 涉及互动的消息类型 + * - 系统消息 (80-89): 系统相关消息 + * - 其他 (99): 未知或无法分类的消息 + */ +export enum MessageType { + // ========== 基础消息类型 (0-19) ========== + TEXT = 0, + IMAGE = 1, + VOICE = 2, + VIDEO = 3, + FILE = 4, + EMOJI = 5, + LINK = 7, + LOCATION = 8, + + // ========== 交互消息类型 (20-39) ========== + RED_PACKET = 20, + TRANSFER = 21, + POKE = 22, + CALL = 23, + SHARE = 24, + REPLY = 25, + FORWARD = 26, + CONTACT = 27, + + // ========== 系统消息类型 (80-89) ========== + SYSTEM = 80, + RECALL = 81, + + // ========== 其他 (99) ========== + OTHER = 99, +} + +/** + * 聊天平台类型(字符串,允许任意值) + * 常见平台示例:qq, weixin, discord, whatsapp 等 + * 合并多平台记录时使用 'mixed' + */ +export type ChatPlatform = string + +export const KNOWN_PLATFORMS = { + QQ: 'qq', + WECHAT: 'weixin', + DISCORD: 'discord', + WHATSAPP: 'whatsapp', + TELEGRAM: 'telegram', + INSTAGRAM: 'instagram', + LINE: 'line', + UNKNOWN: 'unknown', +} as const + +/** + * 聊天类型枚举 + */ +export enum ChatType { + GROUP = 'group', + PRIVATE = 'private', +} + +// ==================== 成员角色 ==================== + +export interface MemberRole { + id: string + name?: string +} + +export const STANDARD_ROLE_IDS = { + OWNER: 'owner', + ADMIN: 'admin', +} as const + +// ==================== 标准协议(Parser 输出) ==================== + +export interface ParsedMember { + platformId: string + accountName: string + groupNickname?: string + avatar?: string + roles?: MemberRole[] +} + +export interface ParsedMessage { + platformMessageId?: string + senderPlatformId: string + senderAccountName: string + senderGroupNickname?: string + timestamp: number + type: MessageType + content: string | null + replyToMessageId?: string +} diff --git a/packages/shared-types/package.json b/packages/shared-types/package.json new file mode 100644 index 00000000..01a02aa6 --- /dev/null +++ b/packages/shared-types/package.json @@ -0,0 +1,8 @@ +{ + "name": "@openchatlab/shared-types", + "version": "0.0.0", + "private": true, + "description": "ChatLab 跨平台共享类型定义", + "main": "./index.ts", + "types": "./index.ts" +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..600b4bb4 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/**' diff --git a/src/components/analysis/quotes/CatchphraseTab.vue b/src/components/analysis/quotes/CatchphraseTab.vue index 1130023c..3389cafc 100644 --- a/src/components/analysis/quotes/CatchphraseTab.vue +++ b/src/components/analysis/quotes/CatchphraseTab.vue @@ -6,15 +6,11 @@ const EChartWordcloud = defineAsyncComponent(() => import('@/components/charts/E import type { EChartWordcloudData } from '@/components/charts' import { LoadingState, EmptyState, UITabs } from '@/components/UI' import { useLayoutStore } from '@/stores/layout' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() const layoutStore = useLayoutStore() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/src/components/analysis/quotes/HotRepeatTab.vue b/src/components/analysis/quotes/HotRepeatTab.vue index 0405e17a..eed2e0cf 100644 --- a/src/components/analysis/quotes/HotRepeatTab.vue +++ b/src/components/analysis/quotes/HotRepeatTab.vue @@ -7,14 +7,10 @@ import { ListPro } from '@/components/charts' import { LoadingState, EmptyState, SectionCard } from '@/components/UI' import { formatDate, getRankBadgeClass } from '@/utils' import { useLayoutStore } from '@/stores/layout' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/src/components/analysis/quotes/KeywordAnalysis.vue b/src/components/analysis/quotes/KeywordAnalysis.vue index 4429c5d7..c70a455a 100644 --- a/src/components/analysis/quotes/KeywordAnalysis.vue +++ b/src/components/analysis/quotes/KeywordAnalysis.vue @@ -7,14 +7,10 @@ import type { RankItem } from '@/components/charts' import { LoadingState } from '@/components/UI' import { getRankBadgeClass } from '@/utils' import { usePromptStore } from '@/stores/prompt' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number -} - // 扩展基础模板类型,添加组件内使用的字段 interface KeywordTemplate extends BaseKeywordTemplate { description?: string diff --git a/src/components/analysis/quotes/LanguagePreferenceTab.vue b/src/components/analysis/quotes/LanguagePreferenceTab.vue index 9b116006..17888d3f 100644 --- a/src/components/analysis/quotes/LanguagePreferenceTab.vue +++ b/src/components/analysis/quotes/LanguagePreferenceTab.vue @@ -9,15 +9,11 @@ import CatchphrasePKSection from './sections/CatchphrasePKSection.vue' import PosPortraitSection from './sections/PosPortraitSection.vue' import ModalParticleSection from './sections/ModalParticleSection.vue' import PunctuationSection from './sections/PunctuationSection.vue' +import type { TimeFilter } from '@openchatlab/shared-types' const { t, locale } = useI18n() const layoutStore = useLayoutStore() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/src/components/analysis/quotes/SharedTopicsCard.vue b/src/components/analysis/quotes/SharedTopicsCard.vue index d280e7ca..728b2134 100644 --- a/src/components/analysis/quotes/SharedTopicsCard.vue +++ b/src/components/analysis/quotes/SharedTopicsCard.vue @@ -5,15 +5,11 @@ import { ThemeCard } from '@/components/UI' import { useSettingsStore } from '@/stores/settings' import type { SharedWord } from '@/types/quotes/languagePreference' import type { MemberWithStats } from '@/types/analysis' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() const settingsStore = useSettingsStore() -interface TimeFilter { - startTs?: number - endTs?: number -} - type DictType = 'default' | 'zh-CN' | 'zh-TW' const props = defineProps<{ diff --git a/src/components/analysis/quotes/TopicProfileCard.vue b/src/components/analysis/quotes/TopicProfileCard.vue index 992bcbb0..be374e61 100644 --- a/src/components/analysis/quotes/TopicProfileCard.vue +++ b/src/components/analysis/quotes/TopicProfileCard.vue @@ -4,16 +4,12 @@ import { useI18n } from 'vue-i18n' import dayjs from 'dayjs' import { ThemeCard } from '@/components/UI' import type { WordFrequencyItem, PosTagStat } from './topicProfileTypes' +import type { TimeFilter } from '@openchatlab/shared-types' const EChartWordcloud = defineAsyncComponent(() => import('@/components/charts/EChartWordcloud.vue')) const { t } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ totalMessages: number totalWords: number diff --git a/src/components/analysis/quotes/WordcloudTab.vue b/src/components/analysis/quotes/WordcloudTab.vue index 41b957b9..281793b1 100644 --- a/src/components/analysis/quotes/WordcloudTab.vue +++ b/src/components/analysis/quotes/WordcloudTab.vue @@ -13,6 +13,7 @@ import { useSettingsStore } from '@/stores/settings' import { useLayoutStore } from '@/stores/layout' import { useWordFilterStore } from '@/stores/wordFilter' import { useToast } from '@/composables/useToast' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() const settingsStore = useSettingsStore() @@ -20,11 +21,6 @@ const layoutStore = useLayoutStore() const wordFilterStore = useWordFilterStore() const toast = useToast() -interface TimeFilter { - startTs?: number - endTs?: number -} - interface PosTagInfo { tag: string name: string diff --git a/src/composables/useAsyncData.ts b/src/composables/useAsyncData.ts index 0a256bb1..b607f9ac 100644 --- a/src/composables/useAsyncData.ts +++ b/src/composables/useAsyncData.ts @@ -1,15 +1,11 @@ import { ref, watch, type Ref, type ComputedRef } from 'vue' +import type { TimeFilter } from '@openchatlab/shared-types' /** * 异步数据加载 Composable * 统一处理数据加载、状态管理和错误处理 */ -interface TimeFilter { - startTs?: number - endTs?: number -} - interface UseAsyncDataOptions { /** 是否立即加载数据(默认 true) */ immediate?: boolean diff --git a/src/env.d.ts b/src/env.d.ts index 06d61db3..4a2d1660 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -1,5 +1,7 @@ /// +declare const __IS_ELECTRON__: boolean | undefined + declare module '*.vue' { import type { DefineComponent } from 'vue' // 使用 Record 避免 {} 被 ESLint 判定为过宽类型。 diff --git a/src/pages/group-chat/components/QuotesTab.vue b/src/pages/group-chat/components/QuotesTab.vue index 4812833d..06d3c3dc 100644 --- a/src/pages/group-chat/components/QuotesTab.vue +++ b/src/pages/group-chat/components/QuotesTab.vue @@ -3,14 +3,10 @@ import { ref, computed } from 'vue' import { useI18n } from 'vue-i18n' import { SubTabs } from '@/components/UI' import { CatchphraseTab, HotRepeatTab } from '@/components/analysis/quotes' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/src/pages/group-chat/components/ViewTab.vue b/src/pages/group-chat/components/ViewTab.vue index 44f23d3f..8dab38ad 100644 --- a/src/pages/group-chat/components/ViewTab.vue +++ b/src/pages/group-chat/components/ViewTab.vue @@ -8,14 +8,10 @@ import RankingView from '@openchatlab/chart-ranking/RankingView.vue' import GroupRelationships from './view/GroupRelationships.vue' import { WordcloudTab, CatchphraseTab, HotRepeatTab } from '@/components/analysis/quotes' import { isFeatureSupported, type LocaleType } from '@/i18n' +import type { TimeFilter } from '@openchatlab/shared-types' const { t, locale } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ sessionId: string sessionName?: string diff --git a/src/pages/group-chat/components/view/GroupRelationships.vue b/src/pages/group-chat/components/view/GroupRelationships.vue index ffcf146a..75c979f4 100644 --- a/src/pages/group-chat/components/view/GroupRelationships.vue +++ b/src/pages/group-chat/components/view/GroupRelationships.vue @@ -9,15 +9,10 @@ import { SubTabs } from '@/components/UI' import InteractionView from '@openchatlab/chart-interaction/InteractionView.vue' import Relationships from './Relationships.vue' import ClusterView from '@openchatlab/chart-cluster/ClusterView.vue' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number - memberId?: number | null -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/src/pages/group-chat/components/view/Relationships.vue b/src/pages/group-chat/components/view/Relationships.vue index dea4c7b3..7dbb5925 100644 --- a/src/pages/group-chat/components/view/Relationships.vue +++ b/src/pages/group-chat/components/view/Relationships.vue @@ -5,14 +5,10 @@ import type { MentionAnalysis, MemberMentionDetail } from '@/types/analysis' import { RankListPro } from '@/components/charts' import type { RankItem } from '@/components/charts' import { SectionCard, EmptyState, LoadingState } from '@/components/UI' +import type { TimeFilter } from '@openchatlab/shared-types' const { t } = useI18n() -interface TimeFilter { - startTs?: number - endTs?: number -} - const props = defineProps<{ sessionId: string timeFilter?: TimeFilter diff --git a/src/pages/private-chat/components/QuotesTab.vue b/src/pages/private-chat/components/QuotesTab.vue index 4c58b095..74646ee1 100644 --- a/src/pages/private-chat/components/QuotesTab.vue +++ b/src/pages/private-chat/components/QuotesTab.vue @@ -1,10 +1,6 @@