feat: add beta googlechat channel

This commit is contained in:
iHildy
2026-01-23 16:45:37 -06:00
committed by Peter Steinberger
parent 60661441b1
commit b76cd6695d
58 changed files with 3216 additions and 51 deletions

View File

@@ -40,6 +40,16 @@ extension ChannelsSettings {
return .orange
}
var googlechatTint: Color {
guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
else { return .secondary }
if !status.configured { return .secondary }
if status.lastError != nil { return .orange }
if status.probe?.ok == false { return .orange }
if status.running { return .green }
return .orange
}
var signalTint: Color {
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return .secondary }
@@ -85,6 +95,14 @@ extension ChannelsSettings {
return "Configured"
}
var googlechatSummary: String {
guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
else { return "Checking…" }
if !status.configured { return "Not configured" }
if status.running { return "Running" }
return "Configured"
}
var signalSummary: String {
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return "Checking…" }
@@ -193,6 +211,37 @@ extension ChannelsSettings {
return lines.isEmpty ? nil : lines.joined(separator: " · ")
}
var googlechatDetails: String? {
guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
else { return nil }
var lines: [String] = []
if let source = status.credentialSource {
lines.append("Credential: \(source)")
}
if let audienceType = status.audienceType {
let audience = status.audience ?? ""
let label = audience.isEmpty ? audienceType : "\(audienceType) \(audience)"
lines.append("Audience: \(label)")
}
if let probe = status.probe {
if probe.ok {
if let elapsed = probe.elapsedMs {
lines.append("Probe \(Int(elapsed))ms")
}
} else {
let code = probe.status.map { String($0) } ?? "unknown"
lines.append("Probe failed (\(code))")
}
}
if let last = self.date(fromMs: status.lastProbeAt) {
lines.append("Last probe \(relativeAge(from: last))")
}
if let err = status.lastError, !err.isEmpty {
lines.append("Error: \(err)")
}
return lines.isEmpty ? nil : lines.joined(separator: " · ")
}
var signalDetails: String? {
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return nil }
@@ -244,7 +293,7 @@ extension ChannelsSettings {
}
var orderedChannels: [ChannelItem] {
let fallback = ["whatsapp", "telegram", "discord", "slack", "signal", "imessage"]
let fallback = ["whatsapp", "telegram", "discord", "googlechat", "slack", "signal", "imessage"]
let order = self.store.snapshot?.channelOrder ?? fallback
let channels = order.enumerated().map { index, id in
ChannelItem(
@@ -307,6 +356,8 @@ extension ChannelsSettings {
return self.telegramTint
case "discord":
return self.discordTint
case "googlechat":
return self.googlechatTint
case "signal":
return self.signalTint
case "imessage":
@@ -326,6 +377,8 @@ extension ChannelsSettings {
return self.telegramSummary
case "discord":
return self.discordSummary
case "googlechat":
return self.googlechatSummary
case "signal":
return self.signalSummary
case "imessage":
@@ -345,6 +398,8 @@ extension ChannelsSettings {
return self.telegramDetails
case "discord":
return self.discordDetails
case "googlechat":
return self.googlechatDetails
case "signal":
return self.signalDetails
case "imessage":
@@ -377,6 +432,10 @@ extension ChannelsSettings {
return self
.date(fromMs: self.channelStatus("discord", as: ChannelsStatusSnapshot.DiscordStatus.self)?
.lastProbeAt)
case "googlechat":
return self
.date(fromMs: self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)?
.lastProbeAt)
case "signal":
return self
.date(fromMs: self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)?.lastProbeAt)
@@ -411,6 +470,10 @@ extension ChannelsSettings {
guard let status = self.channelStatus("discord", as: ChannelsStatusSnapshot.DiscordStatus.self)
else { return false }
return status.lastError?.isEmpty == false || status.probe?.ok == false
case "googlechat":
guard let status = self.channelStatus("googlechat", as: ChannelsStatusSnapshot.GoogleChatStatus.self)
else { return false }
return status.lastError?.isEmpty == false || status.probe?.ok == false
case "signal":
guard let status = self.channelStatus("signal", as: ChannelsStatusSnapshot.SignalStatus.self)
else { return false }

View File

@@ -85,6 +85,28 @@ struct ChannelsStatusSnapshot: Codable {
let lastProbeAt: Double?
}
struct GoogleChatProbe: Codable {
let ok: Bool
let status: Int?
let error: String?
let elapsedMs: Double?
}
struct GoogleChatStatus: Codable {
let configured: Bool
let credentialSource: String?
let audienceType: String?
let audience: String?
let webhookPath: String?
let webhookUrl: String?
let running: Bool
let lastStartAt: Double?
let lastStopAt: Double?
let lastError: String?
let probe: GoogleChatProbe?
let lastProbeAt: Double?
}
struct SignalProbe: Codable {
let ok: Bool
let status: Int?

View File

@@ -11,6 +11,7 @@ enum GatewayAgentChannel: String, Codable, CaseIterable, Sendable {
case whatsapp
case telegram
case discord
case googlechat
case slack
case signal
case imessage