276 lines
7.2 KiB
PHP
276 lines
7.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class AIConversation extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'title',
|
|
'model',
|
|
'message_count',
|
|
'total_tokens',
|
|
'last_message_at',
|
|
'status',
|
|
];
|
|
|
|
protected $casts = [
|
|
'message_count' => 'integer',
|
|
'total_tokens' => 'integer',
|
|
'last_message_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* 关联用户
|
|
*/
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
/**
|
|
* 关联消息
|
|
*/
|
|
public function messages(): HasMany
|
|
{
|
|
return $this->hasMany(AIMessage::class);
|
|
}
|
|
|
|
/**
|
|
* 最后一条消息
|
|
*/
|
|
public function lastMessage()
|
|
{
|
|
return $this->hasOne(AIMessage::class)->latest();
|
|
}
|
|
|
|
/**
|
|
* 获取对话摘要
|
|
*/
|
|
public function getSummaryAttribute(): string
|
|
{
|
|
if ($this->title) {
|
|
return $this->title;
|
|
}
|
|
|
|
$firstMessage = $this->messages()->orderBy('created_at', 'asc')->first();
|
|
|
|
if ($firstMessage && $firstMessage->role === 'user') {
|
|
$content = $firstMessage->content;
|
|
return strlen($content) > 50 ? substr($content, 0, 50) . '...' : $content;
|
|
}
|
|
|
|
return 'AI对话';
|
|
}
|
|
|
|
/**
|
|
* 获取对话时长
|
|
*/
|
|
public function getDurationAttribute(): string
|
|
{
|
|
if (!$this->last_message_at) {
|
|
return '0分钟';
|
|
}
|
|
|
|
$duration = $this->last_message_at->diffInMinutes($this->created_at);
|
|
|
|
if ($duration < 60) {
|
|
return $duration . '分钟';
|
|
} elseif ($duration < 1440) {
|
|
return floor($duration / 60) . '小时';
|
|
} else {
|
|
return floor($duration / 1440) . '天';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取平均响应时间
|
|
*/
|
|
public function getAvgResponseTimeAttribute(): ?string
|
|
{
|
|
$aiMessages = $this->messages()->where('role', 'assistant')->get();
|
|
|
|
if ($aiMessages->isEmpty()) {
|
|
return null;
|
|
}
|
|
|
|
$totalSeconds = 0;
|
|
$count = 0;
|
|
|
|
foreach ($aiMessages as $message) {
|
|
$previousMessage = AIMessage::where('conversation_id', $this->id)
|
|
->where('created_at', '<', $message->created_at)
|
|
->orderBy('created_at', 'desc')
|
|
->first();
|
|
|
|
if ($previousMessage) {
|
|
$diff = $message->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) . '分钟';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取对话统计
|
|
*/
|
|
public function getStatisticsAttribute(): array
|
|
{
|
|
$userMessages = $this->messages()->where('role', 'user')->count();
|
|
$aiMessages = $this->messages()->where('role', 'assistant')->count();
|
|
|
|
$userTokens = $this->messages()->where('role', 'user')->sum('tokens');
|
|
$aiTokens = $this->messages()->where('role', 'assistant')->sum('tokens');
|
|
|
|
return [
|
|
'total_messages' => $userMessages + $aiMessages,
|
|
'user_messages' => $userMessages,
|
|
'ai_messages' => $aiMessages,
|
|
'total_tokens' => $userTokens + $aiTokens,
|
|
'user_tokens' => $userTokens,
|
|
'ai_tokens' => $aiTokens,
|
|
'avg_tokens_per_message' => $this->message_count > 0 ?
|
|
round(($userTokens + $aiTokens) / $this->message_count, 1) : 0,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 检查对话是否活跃
|
|
*/
|
|
public function getIsActiveAttribute(): bool
|
|
{
|
|
if (!$this->last_message_at) {
|
|
return false;
|
|
}
|
|
|
|
// 24小时内活跃
|
|
return $this->last_message_at->diffInHours(now()) < 24;
|
|
}
|
|
|
|
/**
|
|
* 获取对话标签
|
|
*/
|
|
public function getTagsAttribute(): array
|
|
{
|
|
$tags = [];
|
|
|
|
if ($this->is_active) {
|
|
$tags[] = ['label' => '活跃', 'color' => 'success'];
|
|
}
|
|
|
|
if ($this->message_count > 20) {
|
|
$tags[] = ['label' => '长篇', 'color' => 'warning'];
|
|
}
|
|
|
|
if ($this->total_tokens > 10000) {
|
|
$tags[] = ['label' => '深度', 'color' => 'primary'];
|
|
}
|
|
|
|
if ($this->status === 'archived') {
|
|
$tags[] = ['label' => '已归档', 'color' => 'secondary'];
|
|
}
|
|
|
|
return $tags;
|
|
}
|
|
|
|
/**
|
|
* 归档对话
|
|
*/
|
|
public function archive(): void
|
|
{
|
|
$this->update(['status' => 'archived']);
|
|
}
|
|
|
|
/**
|
|
* 恢复对话
|
|
*/
|
|
public function restore(): void
|
|
{
|
|
$this->update(['status' => 'active']);
|
|
}
|
|
|
|
/**
|
|
* 获取用户对话统计
|
|
*/
|
|
public static function getUserStatistics($userId)
|
|
{
|
|
$conversations = self::where('user_id', $userId)->get();
|
|
|
|
$totalConversations = $conversations->count();
|
|
$activeConversations = $conversations->where('is_active', true)->count();
|
|
$totalMessages = $conversations->sum('message_count');
|
|
$totalTokens = $conversations->sum('total_tokens');
|
|
|
|
$avgMessagesPerConversation = $totalConversations > 0 ?
|
|
round($totalMessages / $totalConversations, 1) : 0;
|
|
|
|
$avgTokensPerConversation = $totalConversations > 0 ?
|
|
round($totalTokens / $totalConversations, 1) : 0;
|
|
|
|
return [
|
|
'total_conversations' => $totalConversations,
|
|
'active_conversations' => $activeConversations,
|
|
'total_messages' => $totalMessages,
|
|
'total_tokens' => $totalTokens,
|
|
'avg_messages_per_conversation' => $avgMessagesPerConversation,
|
|
'avg_tokens_per_conversation' => $avgTokensPerConversation,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 清理过期对话
|
|
*/
|
|
public static function cleanupExpired($days = 30)
|
|
{
|
|
$expiredDate = now()->subDays($days);
|
|
|
|
$conversations = self::where('last_message_at', '<', $expiredDate)
|
|
->where('status', '!=', 'archived')
|
|
->get();
|
|
|
|
$deletedCount = 0;
|
|
$failedConversations = [];
|
|
|
|
foreach ($conversations as $conversation) {
|
|
try {
|
|
// 删除相关消息
|
|
$conversation->messages()->delete();
|
|
|
|
// 删除对话
|
|
$conversation->delete();
|
|
|
|
$deletedCount++;
|
|
} catch (\Exception $e) {
|
|
$failedConversations[] = [
|
|
'id' => $conversation->id,
|
|
'title' => $conversation->title,
|
|
'error' => $e->getMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
return [
|
|
'deleted_count' => $deletedCount,
|
|
'failed_conversations' => $failedConversations,
|
|
];
|
|
}
|
|
} |