'integer',
'metadata' => 'array',
'created_at' => 'datetime',
];
/**
* 关联对话
*/
public function conversation(): BelongsTo
{
return $this->belongsTo(AIConversation::class);
}
/**
* 获取消息摘要
*/
public function getSummaryAttribute(): string
{
$content = $this->content;
if (strlen($content) <= 100) {
return $content;
}
return substr($content, 0, 100) . '...';
}
/**
* 获取消息类型
*/
public function getTypeAttribute(): string
{
$types = [
'system' => '系统',
'user' => '用户',
'assistant' => 'AI助手',
];
return $types[$this->role] ?? '未知';
}
/**
* 获取消息图标
*/
public function getIconAttribute(): string
{
$icons = [
'system' => 'settings',
'user' => 'person',
'assistant' => 'smart_toy',
];
return $icons[$this->role] ?? 'chat';
}
/**
* 获取消息颜色
*/
public function getColorAttribute(): string
{
$colors = [
'system' => 'secondary',
'user' => 'primary',
'assistant' => 'success',
];
return $colors[$this->role] ?? 'default';
}
/**
* 检查是否为AI消息
*/
public function getIsAiAttribute(): bool
{
return $this->role === 'assistant';
}
/**
* 检查是否为用户消息
*/
public function getIsUserAttribute(): bool
{
return $this->role === 'user';
}
/**
* 检查是否为系统消息
*/
public function getIsSystemAttribute(): bool
{
return $this->role === 'system';
}
/**
* 获取消息时间格式
*/
public function getTimeFormattedAttribute(): string
{
$now = now();
$messageTime = $this->created_at;
if ($messageTime->isToday()) {
return $messageTime->format('H:i');
} elseif ($messageTime->isYesterday()) {
return '昨天 ' . $messageTime->format('H:i');
} elseif ($messageTime->diffInDays($now) < 7) {
return $messageTime->format('m-d H:i');
} else {
return $messageTime->format('Y-m-d H:i');
}
}
/**
* 获取消息相对时间
*/
public function getRelativeTimeAttribute(): string
{
$diff = $this->created_at->diff(now());
if ($diff->y > 0) {
return $diff->y . '年前';
} elseif ($diff->m > 0) {
return $diff->m . '个月前';
} elseif ($diff->d > 0) {
return $diff->d . '天前';
} elseif ($diff->h > 0) {
return $diff->h . '小时前';
} elseif ($diff->i > 0) {
return $diff->i . '分钟前';
} else {
return '刚刚';
}
}
/**
* 获取消息内容格式(HTML安全)
*/
public function getContentFormattedAttribute(): string
{
$content = htmlspecialchars($this->content, ENT_QUOTES, 'UTF-8');
// 将换行转换为
$content = nl2br($content);
// 将URL转换为链接
$content = preg_replace(
'/(https?:\/\/[^\s]+)/',
'$1',
$content
);
// 将代码块格式化
$content = preg_replace_callback(
'/```(\w+)?\n([\s\S]*?)```/',
function ($matches) {
$language = $matches[1] ?? '';
$code = htmlspecialchars($matches[2], ENT_QUOTES, 'UTF-8');
return '
' . $code . '';
},
$content
);
// 将行内代码格式化
$content = preg_replace(
'/`([^`]+)`/',
'$1',
$content
);
return $content;
}
/**
* 获取消息中的代码块
*/
public function getCodeBlocksAttribute(): array
{
preg_match_all('/```(\w+)?\n([\s\S]*?)```/', $this->content, $matches, PREG_SET_ORDER);
$codeBlocks = [];
foreach ($matches as $match) {
$codeBlocks[] = [
'language' => $match[1] ?? '',
'code' => $match[2],
];
}
return $codeBlocks;
}
/**
* 获取消息中的链接
*/
public function getLinksAttribute(): array
{
preg_match_all('/https?:\/\/[^\s]+/', $this->content, $matches);
return $matches[0] ?? [];
}
/**
* 获取消息中的关键词
*/
public function getKeywordsAttribute(): array
{
// 简单关键词提取
$content = strtolower($this->content);
$words = preg_split('/\s+/', $content);
$wordCount = array_count_values($words);
arsort($wordCount);
// 过滤常见词
$commonWords = ['the', 'and', 'you', 'for', 'are', 'this', 'that', 'with', 'have', 'from'];
$keywords = array_diff(array_keys($wordCount), $commonWords);
return array_slice($keywords, 0, 10);
}
/**
* 获取消息情感分析(简单版)
*/
public function getSentimentAttribute(): string
{
$positiveWords = ['好', '优秀', '成功', '满意', '高兴', '喜欢', '感谢', '完美', '棒', '赞'];
$negativeWords = ['差', '失败', '不满意', '生气', '讨厌', '问题', '错误', '糟糕', '慢', '贵'];
$content = $this->content;
$positiveCount = 0;
$negativeCount = 0;
foreach ($positiveWords as $word) {
$positiveCount += substr_count($content, $word);
}
foreach ($negativeWords as $word) {
$negativeCount += substr_count($content, $word);
}
if ($positiveCount > $negativeCount) {
return 'positive';
} elseif ($negativeCount > $positiveCount) {
return 'negative';
} else {
return 'neutral';
}
}
/**
* 获取消息长度等级
*/
public function getLengthLevelAttribute(): string
{
$length = strlen($this->content);
if ($length < 50) {
return 'short';
} elseif ($length < 200) {
return 'medium';
} elseif ($length < 500) {
return 'long';
} else {
return 'very-long';
}
}
/**
* 获取消息复杂度
*/
public function getComplexityAttribute(): float
{
$content = $this->content;
// 计算平均句子长度
$sentences = preg_split('/[。!?.!?]/', $content);
$sentences = array_filter($sentences, function($sentence) {
return strlen(trim($sentence)) > 0;
});
if (empty($sentences)) {
return 0;
}
$totalWords = 0;
foreach ($sentences as $sentence) {
$words = preg_split('/\s+/', $sentence);
$totalWords += count($words);
}
$avgSentenceLength = $totalWords / count($sentences);
// 计算生词比例(简单版)
$commonWords = ['的', '了', '在', '是', '和', '与', '或', '等', '这个', '那个'];
$allWords = preg_split('/\s+/', $content);
$uniqueWords = array_unique($allWords);
$uncommonWords = array_diff($uniqueWords, $commonWords);
$uncommonRatio = count($uncommonWords) / max(1, count($uniqueWords));
// 综合复杂度
$complexity = ($avgSentenceLength * 0.6) + ($uncommonRatio * 40);
return min(100, max(0, $complexity));
}
/**
* 获取对话中的消息统计
*/
public static function getConversationStatistics($conversationId)
{
$messages = self::where('conversation_id', $conversationId)->get();
$totalMessages = $messages->count();
$userMessages = $messages->where('role', 'user')->count();
$aiMessages = $messages->where('role', 'assistant')->count();
$totalTokens = $messages->sum('tokens');
$userTokens = $messages->where('role', 'user')->sum('tokens');
$aiTokens = $messages->where('role', 'assistant')->sum('tokens');
$avgTokensPerMessage = $totalMessages > 0 ?
round($totalTokens / $totalMessages, 1) : 0;
$avgResponseTime = self::calculateAvgResponseTime($messages);
return [
'total_messages' => $totalMessages,
'user_messages' => $userMessages,
'ai_messages' => $aiMessages,
'message_ratio' => $userMessages > 0 ?
round($aiMessages / $userMessages, 2) : 0,
'total_tokens' => $totalTokens,
'user_tokens' => $userTokens,
'ai_tokens' => $aiTokens,
'avg_tokens_per_message' => $avgTokensPerMessage,
'avg_response_time' => $avgResponseTime,
];
}
/**
* 计算平均响应时间
*/
private static function calculateAvgResponseTime($messages)
{
$aiMessages = $messages->where('role', 'assistant');
if ($aiMessages->isEmpty()) {
return null;
}
$totalSeconds = 0;
$count = 0;
foreach ($aiMessages as $aiMessage) {
$previousMessage = $messages
->where('created_at', '<', $aiMessage->created_at)
->sortByDesc('created_at')
->first();
if ($previousMessage) {
$diff = $aiMessage->created_at->diffInSeconds($previousMessage->created_at);
$totalSeconds += $diff;
$count++;
}
}
if ($count === 0) {
return null;
}
$avgSeconds = $totalSeconds / $count;
if ($avgSeconds < 60) {
return round($avgSeconds) . '秒';
} else {
return round($avgSeconds / 60, 1) . '分钟';
}
}
}