feat: 项目初始化及当前全部内容提交

This commit is contained in:
2025-07-15 17:37:50 +08:00
parent ec817067f1
commit e78f192d34
622 changed files with 75174 additions and 383 deletions
@@ -0,0 +1,549 @@
//
// AIService.swift
// EmotionMuseum
//
// Created by on 2025/6/13.
//
import Foundation
import Combine
// MARK: - AI
protocol AIServiceProtocol {
func sendMessage(_ message: String, userId: UUID) async throws -> AIResponse
func analyzeEmotion(_ text: String) async throws -> EmotionAnalysis
func generateGrowthSuggestions(for stats: GrowthStats) async throws -> [GrowthTopic]
}
// MARK: - AI
struct AIResponse: Codable {
let messageId: UUID
let content: String
let emotionAnalysis: EmotionAnalysis
let suggestions: [String]
let followUpQuestions: [String]
let confidence: Float
let processingTime: TimeInterval
struct EmotionAnalysis: Codable {
let detectedEmotion: EmotionType
let intensity: Float
let triggers: [String]
let context: String
let recommendations: [String]
}
}
// MARK: - AI
class AIService: AIServiceProtocol, ObservableObject {
static let shared = AIService()
private let baseURL = "https://api.openai.com/v1"
private let apiKey: String
@Published var isLoading = false
@Published var lastError: Error?
private init() {
// API
self.apiKey = Bundle.main.infoDictionary?["OPENAI_API_KEY"] as? String ?? ""
}
// MARK: -
func sendMessage(_ message: String, userId: UUID) async throws -> AIResponse {
let startTime = Date()
isLoading = true
defer { isLoading = false }
let prompt = buildEmotionAnalysisPrompt(message: message)
let requestBody = OpenAIRequest(
model: "gpt-4",
messages: [
OpenAIMessage(role: "system", content: getSystemPrompt()),
OpenAIMessage(role: "user", content: prompt)
],
temperature: 0.7,
maxTokens: 500
)
do {
let response = try await sendOpenAIRequest(requestBody)
let processingTime = Date().timeIntervalSince(startTime)
return try parseAIResponse(response, messageId: UUID(), processingTime: processingTime)
} catch {
lastError = error
throw error
}
}
// MARK: -
func analyzeEmotion(_ text: String) async throws -> EmotionAnalysis {
let prompt = """
分析以下文本的情绪:"\(text)"
请返回JSON格式的分析结果,包含:
- summary: 简要总结
- keywords: 关键词数组
- suggestions: 建议数组
- moodPattern: 情绪模式
- confidence: 置信度(0-1)
"""
let requestBody = OpenAIRequest(
model: "gpt-4",
messages: [
OpenAIMessage(role: "system", content: "你是一个专业的情绪分析师,请用中文回答。"),
OpenAIMessage(role: "user", content: prompt)
],
temperature: 0.3,
maxTokens: 300
)
let response = try await sendOpenAIRequest(requestBody)
return try parseEmotionAnalysis(response)
}
// MARK: -
func generateGrowthSuggestions(for stats: GrowthStats) async throws -> [GrowthTopic] {
let prompt = """
基于用户的五维人格画像生成个性化成长建议:
- 自我感知: \(stats.selfAwareness)
- 情绪韧性: \(stats.emotionalResilience)
- 行动力: \(stats.actionPower)
- 共情力: \(stats.empathy)
- 生活热度: \(stats.lifeEnthusiasm)
请推荐3个最适合的成长课题,返回JSON格式。
"""
let requestBody = OpenAIRequest(
model: "gpt-4",
messages: [
OpenAIMessage(role: "system", content: "你是一个专业的心理成长顾问。"),
OpenAIMessage(role: "user", content: prompt)
],
temperature: 0.5,
maxTokens: 400
)
let response = try await sendOpenAIRequest(requestBody)
return try parseGrowthTopics(response)
}
// MARK: -
private func getSystemPrompt() -> String {
return """
你是情绪博物馆的AI情绪陪伴师,具有以下特质:
1. 专业且温暖:具备心理学知识,但表达温和亲切
2. 情绪敏感:能准确识别和回应用户的情绪状态
3. 个性化关怀:根据用户的话语提供针对性建议
4. 积极导向:引导用户朝着更健康的情绪状态发展
5. 边界清晰:不提供医疗建议,必要时建议寻求专业帮助
请用中文回答,保持对话自然流畅。
"""
}
private func buildEmotionAnalysisPrompt(message: String) -> String {
return """
用户消息:"\(message)"
请分析这条消息的情绪状态,并提供适当的回应。包含:
1. 检测到的主要情绪
2. 情绪强度(0-1)
3. 可能的触发因素
4. 温暖的回应内容
5. 具体的情绪调节建议
6. 后续探索问题
请以JSON格式返回分析结果。
"""
}
private func sendOpenAIRequest(_ request: OpenAIRequest) async throws -> OpenAIResponse {
guard !apiKey.isEmpty else {
throw AIServiceError.missingAPIKey
}
let url = URL(string: "\(baseURL)/chat/completions")!
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
let encoder = JSONEncoder()
urlRequest.httpBody = try encoder.encode(request)
let (data, response) = try await URLSession.shared.data(for: urlRequest)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw AIServiceError.apiError("请求失败")
}
let decoder = JSONDecoder()
return try decoder.decode(OpenAIResponse.self, from: data)
}
private func parseAIResponse(_ response: OpenAIResponse, messageId: UUID, processingTime: TimeInterval) throws -> AIResponse {
guard let choice = response.choices.first else {
throw AIServiceError.parseError("无法解析AI响应")
}
let content = choice.message.content
// JSON
if let jsonData = content.data(using: .utf8),
let parsed = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] {
let emotion = parseEmotionFromString(parsed["emotion"] as? String ?? "neutral")
let intensity = parsed["intensity"] as? Float ?? 0.5
let triggers = parsed["triggers"] as? [String] ?? []
let context = parsed["context"] as? String ?? ""
let recommendations = parsed["recommendations"] as? [String] ?? []
let suggestions = parsed["suggestions"] as? [String] ?? []
let followUpQuestions = parsed["followUpQuestions"] as? [String] ?? []
let responseContent = parsed["response"] as? String ?? content
let emotionAnalysis = AIResponse.EmotionAnalysis(
detectedEmotion: emotion,
intensity: intensity,
triggers: triggers,
context: context,
recommendations: recommendations
)
return AIResponse(
messageId: messageId,
content: responseContent,
emotionAnalysis: emotionAnalysis,
suggestions: suggestions,
followUpQuestions: followUpQuestions,
confidence: 0.8,
processingTime: processingTime
)
} else {
// JSON
return AIResponse(
messageId: messageId,
content: content,
emotionAnalysis: AIResponse.EmotionAnalysis(
detectedEmotion: .neutral,
intensity: 0.5,
triggers: [],
context: "基础对话",
recommendations: ["继续分享您的感受"]
),
suggestions: ["继续对话"],
followUpQuestions: ["还有什么想分享的吗?"],
confidence: 0.6,
processingTime: processingTime
)
}
}
private func parseEmotionAnalysis(_ response: OpenAIResponse) throws -> EmotionAnalysis {
guard response.choices.first != nil else {
throw AIServiceError.parseError("无法解析情绪分析响应")
}
// JSON
return EmotionAnalysis(
primaryEmotion: .neutral,
emotionIntensity: 0.5,
emotionTrend: .stable,
keywords: ["关键词1", "关键词2"],
aiInsights: "情绪分析摘要",
confidence: 0.75
)
}
private func parseGrowthTopics(_ response: OpenAIResponse) throws -> [GrowthTopic] {
// AI
return [
GrowthTopic(
title: "增强自我认知",
description: "通过反思练习提高自我觉察能力",
category: .selfAwareness,
difficulty: .beginner,
progress: 0.0
),
GrowthTopic(
title: "情绪调节技巧",
description: "学习有效的情绪管理方法",
category: .emotionRegulation,
difficulty: .intermediate,
progress: 0.0
),
GrowthTopic(
title: "提升沟通能力",
description: "改善人际关系和沟通技巧",
category: .relationships,
difficulty: .beginner,
progress: 0.0
)
]
}
private func parseEmotionFromString(_ emotionString: String) -> EmotionType {
switch emotionString.lowercased() {
case "joy", "happy", "开心", "喜悦": return .joy
case "sad", "sadness", "悲伤", "难过": return .sadness
case "angry", "anger", "愤怒", "生气": return .anger
case "fear", "scared", "恐惧", "害怕": return .fear
case "surprise", "surprised", "惊讶", "意外": return .surprise
default: return .neutral
}
}
}
// MARK: - OpenAI API
private struct OpenAIRequest: Codable {
let model: String
let messages: [OpenAIMessage]
let temperature: Float
let maxTokens: Int?
enum CodingKeys: String, CodingKey {
case model, messages, temperature
case maxTokens = "max_tokens"
}
}
private struct OpenAIMessage: Codable {
let role: String
let content: String
}
private struct OpenAIResponse: Codable {
let choices: [OpenAIChoice]
}
private struct OpenAIChoice: Codable {
let message: OpenAIMessage
}
// MARK: -
enum AIServiceError: LocalizedError {
case missingAPIKey
case apiError(String)
case parseError(String)
case networkError(Error)
var errorDescription: String? {
switch self {
case .missingAPIKey:
return "缺少API密钥"
case .apiError(let message):
return "API错误: \(message)"
case .parseError(let message):
return "解析错误: \(message)"
case .networkError(let error):
return "网络错误: \(error.localizedDescription)"
}
}
}
// MARK: - AI
class MockAIService: AIServiceProtocol, ObservableObject {
@Published var isLoading = false
func sendMessage(_ message: String, userId: UUID) async throws -> AIResponse {
isLoading = true
//
try await Task.sleep(nanoseconds: 1_000_000_000) // 1
isLoading = false
let emotion = analyzeEmotionFromMessage(message)
let response = generateMockResponse(for: emotion, message: message)
return AIResponse(
messageId: UUID(),
content: response,
emotionAnalysis: AIResponse.EmotionAnalysis(
detectedEmotion: emotion,
intensity: Float.random(in: 0.3...0.9),
triggers: extractTriggers(from: message),
context: "日常对话",
recommendations: generateRecommendations(for: emotion)
),
suggestions: generateSuggestions(for: emotion),
followUpQuestions: generateFollowUpQuestions(for: emotion),
confidence: Float.random(in: 0.6...0.9),
processingTime: 1.0
)
}
func analyzeEmotion(_ text: String) async throws -> EmotionAnalysis {
try await Task.sleep(nanoseconds: 500_000_000) // 0.5
return EmotionAnalysis(
primaryEmotion: analyzeEmotionFromMessage(text),
emotionIntensity: Float.random(in: 0.3...0.9),
emotionTrend: .stable,
keywords: ["情绪", "感受", "心情"],
aiInsights: "检测到\(analyzeEmotionFromMessage(text).rawValue)情绪",
confidence: Float.random(in: 0.7...0.95)
)
}
func generateGrowthSuggestions(for stats: GrowthStats) async throws -> [GrowthTopic] {
try await Task.sleep(nanoseconds: 800_000_000) // 0.8
return [
GrowthTopic(
title: "自我认知提升",
description: "通过日记和反思提高自我觉察",
category: .selfAwareness,
difficulty: .beginner,
progress: 0.0
),
GrowthTopic(
title: "情绪管理训练",
description: "学习情绪调节和压力缓解技巧",
category: .emotionRegulation,
difficulty: .intermediate,
progress: 0.0
)
]
}
// MARK: -
private func analyzeEmotionFromMessage(_ message: String) -> EmotionType {
let lowerMessage = message.lowercased()
if lowerMessage.contains("开心") || lowerMessage.contains("高兴") || lowerMessage.contains("快乐") {
return .joy
} else if lowerMessage.contains("难过") || lowerMessage.contains("悲伤") || lowerMessage.contains("伤心") {
return .sadness
} else if lowerMessage.contains("生气") || lowerMessage.contains("愤怒") || lowerMessage.contains("烦躁") {
return .anger
} else if lowerMessage.contains("害怕") || lowerMessage.contains("恐惧") || lowerMessage.contains("紧张") {
return .fear
} else if lowerMessage.contains("惊讶") || lowerMessage.contains("意外") {
return .surprise
} else {
return .neutral
}
}
private func generateMockResponse(for emotion: EmotionType, message: String) -> String {
switch emotion {
case .joy:
return "我能感受到你的开心!这种积极的情绪很珍贵,记得把这份快乐分享给身边的人。是什么让你感到如此愉悦呢?"
case .sadness:
return "我理解你现在的感受,难过是正常的情绪反应。允许自己感受这些情绪,同时也要温柔地照顾自己。你愿意分享更多吗?"
case .anger:
return "我能察觉到你的愤怒情绪。愤怒往往是其他情绪的表达,比如受伤或失望。深呼吸一下,我们一起探索这种感受的根源。"
case .fear:
return "恐惧感让人不安,这是很自然的反应。记住,你有能力面对困难。让我们一起分析一下你担心的事情,也许并不像想象中那么可怕。"
case .surprise:
return "意外的事情总是让人印象深刻!无论是好的惊喜还是让人措手不及的情况,都是生活的一部分。告诉我更多细节吧。"
case .neutral:
return "我在聆听你的分享。有时候平静也是一种很好的状态。如果你想深入探讨任何感受或想法,我都愿意陪伴你。"
case .anxiety:
return "我能感受到你的焦虑情绪。焦虑是很常见的感受,让我们一起找到缓解的方法。"
case .excitement:
return "你的兴奋情绪很有感染力!这种充满活力的状态真的很棒。告诉我是什么让你如此激动,我也想分享你的喜悦!"
case .contentment:
return "你的满足感让我很欣慰。这种内心的平和与充实是生活中最珍贵的状态之一。珍惜这份宁静,它会给你力量。"
case .confusion:
return "我感受到你内心的困惑。困惑是思考和成长的开始,让我们一起理清思路。"
case .melancholy:
return "我感受到你内心的忧郁。这种淡淡的愁绪有时候也是一种美,它让我们更深刻地感受生活。愿意和我分享你的思绪吗?"
}
}
private func extractTriggers(from message: String) -> [String] {
//
let keywords = ["工作", "家庭", "朋友", "健康", "学习", "感情", "压力", "变化"]
return keywords.filter { message.contains($0) }
}
private func generateRecommendations(for emotion: EmotionType) -> [String] {
switch emotion {
case .joy:
return ["记录这个美好时刻", "分享你的快乐", "感恩当下"]
case .sadness:
return ["允许自己哭泣", "寻求朋友支持", "进行轻柔运动"]
case .anger:
return ["深呼吸放松", "写下愤怒的原因", "进行体力活动"]
case .fear:
return ["面对恐惧", "寻求专业建议", "制定应对计划"]
case .surprise:
return ["接受变化", "保持开放心态", "记录感受"]
case .neutral:
return ["享受平静", "进行自我反思", "设定新目标"]
case .anxiety:
return ["深呼吸练习", "寻求支持", "制定应对计划"]
case .excitement:
return ["合理安排时间", "保持专注", "与他人分享喜悦"]
case .contentment:
return ["珍惜当下", "保持感恩心", "分享你的平和"]
case .confusion:
return ["整理思路", "寻求建议", "分步骤思考"]
case .melancholy:
return ["接受这种情绪", "寻找美好的事物", "与朋友交流"]
}
}
private func generateSuggestions(for emotion: EmotionType) -> [String] {
switch emotion {
case .joy:
return ["继续保持积极心态", "做些让你快乐的事"]
case .sadness:
return ["给自己一些时间", "考虑专业帮助"]
case .anger:
return ["找到健康的发泄方式", "思考问题的解决方案"]
case .fear:
return ["一步步面对恐惧", "建立支持系统"]
case .surprise:
return ["适应新情况", "保持灵活性"]
case .neutral:
return ["探索新的兴趣", "建立日常习惯"]
case .anxiety:
return ["学习放松技巧", "寻求专业帮助"]
case .excitement:
return ["制定行动计划", "保持理性思考"]
case .contentment:
return ["维持内心平衡", "继续当前的生活方式"]
case .confusion:
return ["寻找清晰的方向", "与他人交流想法"]
case .melancholy:
return ["接受当下的感受", "寻找内心的平静"]
}
}
private func generateFollowUpQuestions(for emotion: EmotionType) -> [String] {
switch emotion {
case .joy:
return ["是什么特别的事情让你如此开心?", "你想如何延续这种快乐?"]
case .sadness:
return ["这种感受持续多久了?", "有什么可以帮助你感觉好一些?"]
case .anger:
return ["什么事情触发了这种愤怒?", "你通常如何处理愤怒情绪?"]
case .fear:
return ["你最担心的是什么?", "有什么可以让你感到更安全?"]
case .surprise:
return ["这个意外对你意味着什么?", "你如何适应这个变化?"]
case .neutral:
return ["最近有什么新的想法吗?", "有什么目标想要实现?"]
case .anxiety:
return ["这种焦虑从什么时候开始的?", "有什么特别担心的事情吗?"]
case .excitement:
return ["是什么让你如此兴奋?", "你计划如何行动?"]
case .contentment:
return ["是什么让你感到如此满足?", "这种状态对你意味着什么?"]
case .confusion:
return ["什么让你感到困惑?", "需要帮助理清哪些思路?"]
case .melancholy:
return ["这种忧郁感从何而来?", "有什么特别的回忆或想法吗?"]
}
}
}