feat: 迁移到pnpm-workspace

This commit is contained in:
digua authored and digua committed 2026-05-06 23:56:45 +08:00
1 parent f181c70402
commit 0f9f21db83
49 files changed
+220 -322

No files matched your search

+13 -1
View File
@@ -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({
+2 -2
View File
@@ -245,9 +245,9 @@ export class Agent {
ctx.api = lastMsg.api
}
// 从 model.baseUrl 和 api 类型构造完整请求 URL
const baseUrl = (this.piModel as Record<string, unknown>).baseUrl as string | undefined
const baseUrl = (this.piModel as unknown as Record<string, unknown>).baseUrl as string | undefined
if (baseUrl) {
const apiType = lastMsg?.api || (this.piModel as Record<string, unknown>).api
const apiType = lastMsg?.api || (this.piModel as unknown as Record<string, unknown>).api
const pathMap: Record<string, string> = {
'openai-completions': '/chat/completions',
'openai-responses': '/responses',
+1 -1
View File
@@ -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
}
@@ -51,14 +51,14 @@ export function createTool(context: ToolContext): AgentTool<typeof schema> {
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,
+2
View File
@@ -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, number> = {
[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,
}
+8 -8
View File
@@ -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)
+2 -7
View File
@@ -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 子句
+3 -5
View File
@@ -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 子句
@@ -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)
}
@@ -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<string, number>(a.topWords.map((w: any) => [w.word, w.count]))
const wordsB = new Map<string, number>(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)
+1 -5
View File
@@ -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 {
+1 -6
View File
@@ -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
+1 -6
View File
@@ -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<ClusterGraphOptions> = {
lookAhead: 3,
@@ -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
+1 -6
View File
@@ -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'
/**
* 构建时间和成员过滤条件
@@ -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
+1 -6
View File
@@ -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
+1 -6
View File
@@ -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'
/**
* 构建时间和成员过滤条件
+1 -6
View File
@@ -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
+1 -6
View File
@@ -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[] = []
@@ -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<{
@@ -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<{
@@ -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
@@ -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<{
@@ -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<{
@@ -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<{
+111
View File
@@ -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
}
+8
View File
@@ -0,0 +1,8 @@
{
"name": "@openchatlab/shared-types",
"version": "0.0.0",
"private": true,
"description": "ChatLab 跨平台共享类型定义",
"main": "./index.ts",
"types": "./index.ts"
}
+2
View File
@@ -0,0 +1,2 @@
packages:
- 'packages/**'
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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<{
@@ -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
@@ -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
+1 -5
View File
@@ -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<T> {
/** 是否立即加载数据(默认 true) */
immediate?: boolean
+2
View File
@@ -1,5 +1,7 @@
/// <reference types="vite/client" />
declare const __IS_ELECTRON__: boolean | undefined
declare module '*.vue' {
import type { DefineComponent } from 'vue'
// 使用 Record<string, never> 避免 {} 被 ESLint 判定为过宽类型。
@@ -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
+1 -5
View File
@@ -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
@@ -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
@@ -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
@@ -1,10 +1,6 @@
<script setup lang="ts">
import { KeywordAnalysis } from '@/components/analysis/quotes'
interface TimeFilter {
startTs?: number
endTs?: number
}
import type { TimeFilter } from '@openchatlab/shared-types'
const props = defineProps<{
sessionId: string
@@ -6,14 +6,10 @@ import UserSelect from '@/components/common/UserSelect.vue'
import MessageView from '@openchatlab/chart-message/MessageView.vue'
import RelationshipView from './view/RelationshipView.vue'
import { WordcloudTab, LanguagePreferenceTab } 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
sessionName?: string
@@ -6,14 +6,10 @@ import { ThemeCard, SectionCard, EmptyState, LoadingState } from '@/components/U
import { EChart } from '@/components/charts'
import RelationshipMetricCard from './RelationshipMetricCard.vue'
import type { EChartsOption } from 'echarts'
import type { TimeFilter } from '@openchatlab/shared-types'
const { t, locale } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
const props = defineProps<{
sessionId: string
timeFilter?: TimeFilter
@@ -1,13 +1,9 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import type { TimeFilter } from '@openchatlab/shared-types'
const { t } = useI18n()
interface TimeFilter {
startTs?: number
endTs?: number
}
// Props
defineProps<{
sessionId: string
+10 -120
View File
@@ -1,53 +1,23 @@
/**
* ChatLab 基础类型定义
* 包含:枚举、数据库模型、Parser 解析结果
*/
// ==================== 枚举定义 ====================
/**
* 消息类型枚举
*
* 分类说明:
* - 基础消息 (0-19): 常见的内容类型
* - 交互消息 (20-39): 涉及互动的消息类型
* - 系统消息 (80-89): 系统相关消息
* - 其他 (99): 未知或无法分类的消息
* 核心类型已迁移到 @openchatlab/shared-types,此处 re-export 保持兼容
*/
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, // 名片消息
import { MessageType, ChatType, KNOWN_PLATFORMS, STANDARD_ROLE_IDS } from '@openchatlab/shared-types'
// ========== 系统消息类型 (80-89) ==========
SYSTEM = 80, // 系统消息(入群/退群/群公告等)
RECALL = 81, // 撤回消息
import type { TimeFilter, ChatPlatform, MemberRole, ParsedMember, ParsedMessage } from '@openchatlab/shared-types'
// ========== 其他 (99) ==========
OTHER = 99, // 其他/未知
}
// Re-export all shared types for backward compatibility
export { MessageType, ChatType, KNOWN_PLATFORMS, STANDARD_ROLE_IDS }
export type { TimeFilter, ChatPlatform, MemberRole, ParsedMember, ParsedMessage }
/**
* 消息类型 i18n key 映射
*/
const MESSAGE_TYPE_KEYS: Record<number, string> = {
// 基础消息类型
[MessageType.TEXT]: 'text',
[MessageType.IMAGE]: 'image',
[MessageType.VOICE]: 'voice',
@@ -56,7 +26,6 @@ const MESSAGE_TYPE_KEYS: Record<number, string> = {
[MessageType.EMOJI]: 'emoji',
[MessageType.LINK]: 'link',
[MessageType.LOCATION]: 'location',
// 交互消息类型
[MessageType.RED_PACKET]: 'redPacket',
[MessageType.TRANSFER]: 'transfer',
[MessageType.POKE]: 'poke',
@@ -65,10 +34,8 @@ const MESSAGE_TYPE_KEYS: Record<number, string> = {
[MessageType.REPLY]: 'reply',
[MessageType.FORWARD]: 'forward',
[MessageType.CONTACT]: 'contact',
// 系统消息类型
[MessageType.SYSTEM]: 'system',
[MessageType.RECALL]: 'recall',
// 其他
[MessageType.OTHER]: 'other',
}
@@ -83,58 +50,6 @@ export function getMessageTypeName(type: MessageType | number, t?: (key: string)
return t ? t('common.messageType.unknown') : '未知'
}
/**
* 聊天平台类型(字符串,允许任意值)
* 常见平台示例: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', // 私聊
}
// ==================== 成员角色 ====================
/**
* 成员角色
* 一个成员可以有多个角色(如 Discord 的多角色系统)
*/
export interface MemberRole {
/** 角色标识:标准角色使用 "owner" | "admin",自定义角色使用任意字符串 */
id: string
/** 角色显示名称(自定义角色需要,标准角色可省略) */
name?: string
}
/**
* 标准角色 ID
*/
export const STANDARD_ROLE_IDS = {
/** 群主/创建者 */
OWNER: 'owner',
/** 管理员 */
ADMIN: 'admin',
} as const
// ==================== 数据库模型 ====================
/**
@@ -180,31 +95,6 @@ export interface DbMessage {
// ==================== Parser 解析结果 ====================
/**
* 解析后的成员信息
*/
export interface ParsedMember {
platformId: string // 平台标识
accountName: string // 账号名称(QQ原始昵称 sendNickName)
groupNickname?: string // 群昵称(sendMemberName,可为空)
avatar?: string // 头像(base64 Data URL,可为空)
roles?: MemberRole[] // 成员角色(可为空)
}
/**
* 解析后的消息
*/
export interface ParsedMessage {
platformMessageId?: string // 消息的平台原始 ID(用于回复关联查询)
senderPlatformId: string // 发送者平台ID
senderAccountName: string // 发送时的账号名称
senderGroupNickname?: string // 发送时的群昵称(可为空)
timestamp: number // 时间戳(秒)
type: MessageType // 消息类型
content: string | null // 内容
replyToMessageId?: string // 回复的目标消息 ID(平台原始 ID,可为空)
}
/**
* Parser 解析结果
*/
@@ -213,9 +103,9 @@ export interface ParseResult {
name: string
platform: ChatPlatform
type: ChatType
groupId?: string // 群ID(群聊类型有值)
groupAvatar?: string // 群头像(base64 Data URL)
ownerId?: string // 所有者/导出者的 platformId
groupId?: string
groupAvatar?: string
ownerId?: string
}
members: ParsedMember[]
messages: ParsedMessage[]
+10
View File
@@ -0,0 +1,10 @@
/**
* 平台检测工具
*
* 通过编译时注入的 __IS_ELECTRON__ 常量区分运行环境。
* Electron renderer 构建时注入 true,未来 Web 版构建时不定义或为 false。
*/
declare const __IS_ELECTRON__: boolean | undefined
export const IS_ELECTRON = typeof __IS_ELECTRON__ !== 'undefined' && __IS_ELECTRON__
+9 -2
View File
@@ -6,11 +6,18 @@
"electron/main/**/*",
"electron/preload/**/*",
"electron/shared/**/*",
"src/types/**/*"
"src/types/**/*",
"packages/shared-types/**/*"
],
"compilerOptions": {
"composite": true,
"types": ["electron-vite/node"],
"moduleResolution": "bundler"
"moduleResolution": "bundler",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@electron/*": ["electron/*"],
"@openchatlab/*": ["packages/*"]
}
}
}