fix: M-CHAT audit 修复 7 个缺陷
- A1/A2(P0): listSessions 响应格式 + ChatEntryContext Hashable - A3: forceCreate 支持,新对话按钮创建新会话 - A4: loadSession 更新 entryContext (scope 标签) - A6: 死代码清理 (itemIds + AIMessageCitation) - A8: sessions sheet scope 过滤 - A10: deleteSession 错误处理 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2610bcf7f9
commit
491c3e7ef0
@ -480,7 +480,7 @@ enum ChatScopeType: String, Codable {
|
||||
case global = "global"
|
||||
}
|
||||
|
||||
struct ChatEntryContext {
|
||||
struct ChatEntryContext: Hashable {
|
||||
let scopeType: ChatScopeType
|
||||
let scopeId: String?
|
||||
let scopeName: String
|
||||
@ -541,6 +541,7 @@ struct CreateSessionRequest: Codable {
|
||||
let parentKnowledgeBaseId: String?
|
||||
let createdFrom: String
|
||||
let title: String?
|
||||
let forceCreate: Bool?
|
||||
}
|
||||
|
||||
struct UpdateChatSessionRequest: Codable {
|
||||
|
||||
@ -344,13 +344,14 @@ class RagChatService {
|
||||
static let shared = RagChatService()
|
||||
private let client = APIClient.shared
|
||||
|
||||
func createSession(ctx: ChatEntryContext) async throws -> ChatSession {
|
||||
func createSession(ctx: ChatEntryContext, forceCreate: Bool = false) async throws -> ChatSession {
|
||||
let body = CreateSessionRequest(
|
||||
scopeType: ctx.scopeType.rawValue,
|
||||
scopeId: ctx.scopeId,
|
||||
parentKnowledgeBaseId: ctx.parentKnowledgeBaseId,
|
||||
createdFrom: ctx.createdFrom,
|
||||
title: nil
|
||||
title: nil,
|
||||
forceCreate: forceCreate ? true : nil
|
||||
)
|
||||
return try await client.request("/rag-chat/sessions", method: "POST", body: body)
|
||||
}
|
||||
@ -360,7 +361,8 @@ class RagChatService {
|
||||
if let st = scopeType { items.append(URLQueryItem(name: "scopeType", value: st)) }
|
||||
if let si = scopeId { items.append(URLQueryItem(name: "scopeId", value: si)) }
|
||||
if let pk = parentKnowledgeBaseId { items.append(URLQueryItem(name: "parentKnowledgeBaseId", value: pk)) }
|
||||
return try await client.request("/rag-chat/sessions", queryItems: items.isEmpty ? nil : items)
|
||||
let page: PaginatedResponse<ChatSession> = try await client.request("/rag-chat/sessions", queryItems: items.isEmpty ? nil : items)
|
||||
return page.data
|
||||
}
|
||||
|
||||
func getMessages(sessionId: String) async throws -> [ChatMessage] {
|
||||
|
||||
@ -114,8 +114,12 @@ struct AIChatPage: View {
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
Button(role: .destructive) {
|
||||
Task {
|
||||
try? await RagChatService.shared.deleteSession(id: s.id)
|
||||
do {
|
||||
try await RagChatService.shared.deleteSession(id: s.id)
|
||||
sessions.removeAll { $0.id == s.id }
|
||||
} catch {
|
||||
// Keep session in list if delete failed
|
||||
}
|
||||
}
|
||||
} label: { Label("删除", systemImage: "trash") }
|
||||
}
|
||||
@ -128,7 +132,13 @@ struct AIChatPage: View {
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity).background(Color.zxBg0)
|
||||
.presentationDetents([.medium, .large])
|
||||
.task {
|
||||
if let s = try? await RagChatService.shared.listSessions() { sessions = s }
|
||||
let s: [ChatSession]
|
||||
if let filter = vm.sessionListFilter {
|
||||
s = (try? await RagChatService.shared.listSessions(scopeType: filter.scopeType, scopeId: filter.scopeId)) ?? []
|
||||
} else {
|
||||
s = (try? await RagChatService.shared.listSessions()) ?? []
|
||||
}
|
||||
sessions = s
|
||||
}
|
||||
}
|
||||
.task { await vm.load() }
|
||||
|
||||
@ -19,12 +19,6 @@ struct AIMessage: Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
struct AIMessageCitation: Identifiable {
|
||||
let id: String
|
||||
let title: String?
|
||||
let content: String?
|
||||
}
|
||||
|
||||
enum AIMessageRole {
|
||||
case user, ai
|
||||
}
|
||||
@ -39,7 +33,6 @@ final class AIChatViewModel: ObservableObject {
|
||||
|
||||
private var sessionId: String?
|
||||
private var entryContext: ChatEntryContext?
|
||||
private var itemIds: [String]?
|
||||
|
||||
var canSend: Bool {
|
||||
!inputText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isSending && sessionId != nil
|
||||
@ -52,6 +45,11 @@ final class AIChatViewModel: ObservableObject {
|
||||
return "提问范围:\(ctx.scopeName)"
|
||||
}
|
||||
|
||||
var sessionListFilter: (scopeType: String, scopeId: String)? {
|
||||
guard let ctx = entryContext, ctx.scopeType != .global, let sid = ctx.scopeId else { return nil }
|
||||
return (ctx.scopeType.rawValue, sid)
|
||||
}
|
||||
|
||||
var scopeIcon: String {
|
||||
guard let ctx = entryContext else { return "globe" }
|
||||
switch ctx.scopeType {
|
||||
@ -101,7 +99,7 @@ final class AIChatViewModel: ObservableObject {
|
||||
)
|
||||
|
||||
do {
|
||||
let session = try await RagChatService.shared.createSession(ctx: ctx)
|
||||
let session = try await RagChatService.shared.createSession(ctx: ctx, forceCreate: true)
|
||||
sessionId = session.id
|
||||
let scopeLabel = ctx.scopeType != .global ? "「\(ctx.scopeName)」的" : ""
|
||||
messages = [AIMessage(role: .ai, content: "你好!我是你的 AI 学习助手,基于\(scopeLabel)内容回答问题。")]
|
||||
@ -167,8 +165,27 @@ final class AIChatViewModel: ObservableObject {
|
||||
isCreatingSession = true
|
||||
do {
|
||||
let msgs: [ChatMessage] = try await RagChatService.shared.getMessages(sessionId: id)
|
||||
|
||||
// Update entryContext from the first message's scope snapshot (if available)
|
||||
if let snapshot = msgs.first?.scopeSnapshot,
|
||||
let scopeTypeStr = snapshot.scopeType,
|
||||
let scopeType = ChatScopeType(rawValue: scopeTypeStr) {
|
||||
entryContext = ChatEntryContext(
|
||||
scopeType: scopeType,
|
||||
scopeId: snapshot.scopeId,
|
||||
scopeName: scopeLabel ?? "AI 对话",
|
||||
parentKnowledgeBaseId: snapshot.parentKnowledgeBaseId,
|
||||
createdFrom: "session_switch"
|
||||
)
|
||||
}
|
||||
|
||||
let scopePrefix: String = {
|
||||
guard let ctx = entryContext, ctx.scopeType != .global else { return "" }
|
||||
return "「\(ctx.scopeName)」的"
|
||||
}()
|
||||
|
||||
if msgs.isEmpty {
|
||||
messages = [AIMessage(role: .ai, content: "你好!我是你的 AI 学习助手,基于你的知识库回答问题。")]
|
||||
messages = [AIMessage(role: .ai, content: "你好!我是你的 AI 学习助手,基于\(scopePrefix)内容回答问题。")]
|
||||
} else {
|
||||
messages = msgs.map { m in
|
||||
AIMessage(role: m.role == "user" ? .user : .ai, content: m.content, citations: m.citations)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user