fix(chat): improve history + polish SwiftUI panel
parent
01341d983c
commit
e0545e2f94
|
|
@ -43,7 +43,10 @@ protocol WebSocketSessioning: AnyObject {
|
||||||
|
|
||||||
extension URLSession: WebSocketSessioning {
|
extension URLSession: WebSocketSessioning {
|
||||||
func makeWebSocketTask(url: URL) -> WebSocketTaskBox {
|
func makeWebSocketTask(url: URL) -> WebSocketTaskBox {
|
||||||
WebSocketTaskBox(task: self.webSocketTask(with: url))
|
let task = self.webSocketTask(with: url)
|
||||||
|
// Avoid "Message too long" receive errors for large snapshots / history payloads.
|
||||||
|
task.maximumMessageSize = 16 * 1024 * 1024 // 16 MB
|
||||||
|
return WebSocketTaskBox(task: task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,35 +72,8 @@ struct MacGatewayChatTransport: ClawdisChatTransport, Sendable {
|
||||||
let stream = await GatewayConnection.shared.subscribe()
|
let stream = await GatewayConnection.shared.subscribe()
|
||||||
for await push in stream {
|
for await push in stream {
|
||||||
if Task.isCancelled { return }
|
if Task.isCancelled { return }
|
||||||
switch push {
|
if let evt = Self.mapPushToTransportEvent(push) {
|
||||||
case let .snapshot(hello):
|
continuation.yield(evt)
|
||||||
let ok = (try? JSONDecoder().decode(
|
|
||||||
ClawdisGatewayHealthOK.self,
|
|
||||||
from: JSONEncoder().encode(hello.snapshot.health)))?.ok ?? true
|
|
||||||
continuation.yield(.health(ok: ok))
|
|
||||||
case let .event(evt):
|
|
||||||
switch evt.event {
|
|
||||||
case "health":
|
|
||||||
guard let payload = evt.payload else { break }
|
|
||||||
let ok = (try? JSONDecoder().decode(
|
|
||||||
ClawdisGatewayHealthOK.self,
|
|
||||||
from: JSONEncoder().encode(payload)))?.ok ?? true
|
|
||||||
continuation.yield(.health(ok: ok))
|
|
||||||
case "tick":
|
|
||||||
continuation.yield(.tick)
|
|
||||||
case "chat":
|
|
||||||
guard let payload = evt.payload else { break }
|
|
||||||
if let chat = try? JSONDecoder().decode(
|
|
||||||
ClawdisChatEventPayload.self,
|
|
||||||
from: JSONEncoder().encode(payload))
|
|
||||||
{
|
|
||||||
continuation.yield(.chat(chat))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case .seqGap:
|
|
||||||
continuation.yield(.seqGap)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +83,42 @@ struct MacGatewayChatTransport: ClawdisChatTransport, Sendable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func mapPushToTransportEvent(_ push: GatewayPush) -> ClawdisChatTransportEvent? {
|
||||||
|
switch push {
|
||||||
|
case let .snapshot(hello):
|
||||||
|
let ok = (try? JSONDecoder().decode(
|
||||||
|
ClawdisGatewayHealthOK.self,
|
||||||
|
from: JSONEncoder().encode(hello.snapshot.health)))?.ok ?? true
|
||||||
|
return .health(ok: ok)
|
||||||
|
|
||||||
|
case let .event(evt):
|
||||||
|
switch evt.event {
|
||||||
|
case "health":
|
||||||
|
guard let payload = evt.payload else { return nil }
|
||||||
|
let ok = (try? JSONDecoder().decode(
|
||||||
|
ClawdisGatewayHealthOK.self,
|
||||||
|
from: JSONEncoder().encode(payload)))?.ok ?? true
|
||||||
|
return .health(ok: ok)
|
||||||
|
case "tick":
|
||||||
|
return .tick
|
||||||
|
case "chat":
|
||||||
|
guard let payload = evt.payload else { return nil }
|
||||||
|
guard let chat = try? JSONDecoder().decode(
|
||||||
|
ClawdisChatEventPayload.self,
|
||||||
|
from: JSONEncoder().encode(payload))
|
||||||
|
else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return .chat(chat)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case .seqGap:
|
||||||
|
return .seqGap
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Window controller
|
// MARK: - Window controller
|
||||||
|
|
@ -124,11 +133,19 @@ final class WebChatSwiftUIWindowController {
|
||||||
var onClosed: (() -> Void)?
|
var onClosed: (() -> Void)?
|
||||||
var onVisibilityChanged: ((Bool) -> Void)?
|
var onVisibilityChanged: ((Bool) -> Void)?
|
||||||
|
|
||||||
init(sessionKey: String, presentation: WebChatPresentation) {
|
convenience init(sessionKey: String, presentation: WebChatPresentation) {
|
||||||
|
self.init(sessionKey: sessionKey, presentation: presentation, transport: MacGatewayChatTransport())
|
||||||
|
}
|
||||||
|
|
||||||
|
init(sessionKey: String, presentation: WebChatPresentation, transport: any ClawdisChatTransport) {
|
||||||
self.sessionKey = sessionKey
|
self.sessionKey = sessionKey
|
||||||
self.presentation = presentation
|
self.presentation = presentation
|
||||||
let vm = ClawdisChatViewModel(sessionKey: sessionKey, transport: MacGatewayChatTransport())
|
let vm = ClawdisChatViewModel(sessionKey: sessionKey, transport: transport)
|
||||||
self.hosting = NSHostingController(rootView: ClawdisChatView(viewModel: vm))
|
self.hosting = NSHostingController(rootView: ClawdisChatView(viewModel: vm))
|
||||||
|
self.hosting.view.wantsLayer = true
|
||||||
|
self.hosting.view.layer?.cornerCurve = .continuous
|
||||||
|
self.hosting.view.layer?.cornerRadius = 16
|
||||||
|
self.hosting.view.layer?.masksToBounds = true
|
||||||
self.window = Self.makeWindow(for: presentation, contentViewController: self.hosting)
|
self.window = Self.makeWindow(for: presentation, contentViewController: self.hosting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,6 @@ public struct ClawdisChatView: View {
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, 16)
|
||||||
.frame(maxWidth: 1040)
|
.frame(maxWidth: 1040)
|
||||||
}
|
}
|
||||||
.background(
|
|
||||||
LinearGradient(
|
|
||||||
colors: [
|
|
||||||
Color(red: 0.96, green: 0.97, blue: 1.0),
|
|
||||||
Color(red: 0.93, green: 0.94, blue: 0.98),
|
|
||||||
],
|
|
||||||
startPoint: .top,
|
|
||||||
endPoint: .bottom)
|
|
||||||
.opacity(0.35)
|
|
||||||
.ignoresSafeArea())
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
||||||
.onAppear { self.viewModel.load() }
|
.onAppear { self.viewModel.load() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -458,6 +458,7 @@ export const CronRunLogEntrySchema = Type.Object(
|
||||||
export const ChatHistoryParamsSchema = Type.Object(
|
export const ChatHistoryParamsSchema = Type.Object(
|
||||||
{
|
{
|
||||||
sessionKey: NonEmptyString,
|
sessionKey: NonEmptyString,
|
||||||
|
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 500 })),
|
||||||
},
|
},
|
||||||
{ additionalProperties: false },
|
{ additionalProperties: false },
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -793,13 +793,19 @@ export async function startGatewayServer(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const { sessionKey } = params as { sessionKey: string };
|
const { sessionKey, limit } = params as {
|
||||||
|
sessionKey: string;
|
||||||
|
limit?: number;
|
||||||
|
};
|
||||||
const { storePath, entry } = loadSessionEntry(sessionKey);
|
const { storePath, entry } = loadSessionEntry(sessionKey);
|
||||||
const sessionId = entry?.sessionId;
|
const sessionId = entry?.sessionId;
|
||||||
const messages =
|
const rawMessages =
|
||||||
sessionId && storePath
|
sessionId && storePath
|
||||||
? readSessionMessages(sessionId, storePath)
|
? readSessionMessages(sessionId, storePath)
|
||||||
: [];
|
: [];
|
||||||
|
const max = typeof limit === "number" ? limit : 200;
|
||||||
|
const messages =
|
||||||
|
rawMessages.length > max ? rawMessages.slice(-max) : rawMessages;
|
||||||
const thinkingLevel =
|
const thinkingLevel =
|
||||||
entry?.thinkingLevel ??
|
entry?.thinkingLevel ??
|
||||||
loadConfig().inbound?.reply?.thinkingDefault ??
|
loadConfig().inbound?.reply?.thinkingDefault ??
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue