Merge d0eb74195a into 4583f88626
This commit is contained in:
commit
eb0cfc4cf5
@ -67,7 +67,7 @@ private struct EventRow: View {
|
|||||||
|
|
||||||
private var tint: Color {
|
private var tint: Color {
|
||||||
switch self.event.stream {
|
switch self.event.stream {
|
||||||
case "job": .blue
|
case "lifecycle": .blue
|
||||||
case "tool": .orange
|
case "tool": .orange
|
||||||
case "assistant": .green
|
case "assistant": .green
|
||||||
default: .gray
|
default: .gray
|
||||||
|
|||||||
@ -379,8 +379,11 @@ final class ControlChannel {
|
|||||||
let sessionKey = (event.data["sessionKey"]?.value as? String) ?? "main"
|
let sessionKey = (event.data["sessionKey"]?.value as? String) ?? "main"
|
||||||
|
|
||||||
switch event.stream.lowercased() {
|
switch event.stream.lowercased() {
|
||||||
case "job":
|
case "lifecycle":
|
||||||
if let state = event.data["state"]?.value as? String {
|
// Gateway emits phase: "start" | "end" | "error"
|
||||||
|
// WorkActivityStore expects state: "started" | "streaming" | (anything else = done)
|
||||||
|
if let phase = event.data["phase"]?.value as? String {
|
||||||
|
let state = phase.lowercased() == "start" ? "started" : "done"
|
||||||
WorkActivityStore.shared.handleJob(sessionKey: sessionKey, state: state)
|
WorkActivityStore.shared.handleJob(sessionKey: sessionKey, state: state)
|
||||||
}
|
}
|
||||||
case "tool":
|
case "tool":
|
||||||
|
|||||||
@ -6,6 +6,43 @@ public enum ChatMarkdownVariant: String, CaseIterable, Sendable {
|
|||||||
case compact
|
case compact
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if the Textual package's resource bundle is available.
|
||||||
|
/// When running from a packaged app, the bundle may not be in the expected location,
|
||||||
|
/// causing a fatal error when StructuredText tries to access Bundle.module.
|
||||||
|
private let textualBundleAvailable: Bool = {
|
||||||
|
// The Textual package looks for its bundle at specific paths.
|
||||||
|
// Check if we can find it before attempting to use StructuredText.
|
||||||
|
let bundleName = "textual_Textual.bundle"
|
||||||
|
|
||||||
|
// Check in main bundle (packaged app)
|
||||||
|
if Bundle.main.path(forResource: "textual_Textual", ofType: "bundle") != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check in app's PlugIns or Frameworks directories
|
||||||
|
if let pluginsURL = Bundle.main.builtInPlugInsURL,
|
||||||
|
FileManager.default.fileExists(atPath: pluginsURL.appendingPathComponent(bundleName).path)
|
||||||
|
{
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if let frameworksURL = Bundle.main.privateFrameworksURL,
|
||||||
|
FileManager.default.fileExists(atPath: frameworksURL.appendingPathComponent(bundleName).path)
|
||||||
|
{
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// In development (SwiftPM), Bundle.module should work
|
||||||
|
// We can't directly test Bundle.module without triggering the crash,
|
||||||
|
// so we check for a known development path pattern
|
||||||
|
#if DEBUG
|
||||||
|
return true
|
||||||
|
#else
|
||||||
|
// In release builds, if we haven't found the bundle, assume it's not available
|
||||||
|
return false
|
||||||
|
#endif
|
||||||
|
}()
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
struct ChatMarkdownRenderer: View {
|
struct ChatMarkdownRenderer: View {
|
||||||
enum Context {
|
enum Context {
|
||||||
@ -22,12 +59,20 @@ struct ChatMarkdownRenderer: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
let processed = ChatMarkdownPreprocessor.preprocess(markdown: self.text)
|
let processed = ChatMarkdownPreprocessor.preprocess(markdown: self.text)
|
||||||
VStack(alignment: .leading, spacing: 10) {
|
VStack(alignment: .leading, spacing: 10) {
|
||||||
StructuredText(markdown: processed.cleaned)
|
if textualBundleAvailable {
|
||||||
.modifier(ChatMarkdownStyle(
|
StructuredText(markdown: processed.cleaned)
|
||||||
variant: self.variant,
|
.modifier(ChatMarkdownStyle(
|
||||||
context: self.context,
|
variant: self.variant,
|
||||||
font: self.font,
|
context: self.context,
|
||||||
textColor: self.textColor))
|
font: self.font,
|
||||||
|
textColor: self.textColor))
|
||||||
|
} else {
|
||||||
|
// Fallback: render as plain text when Textual bundle is unavailable
|
||||||
|
Text(processed.cleaned)
|
||||||
|
.font(self.font)
|
||||||
|
.foregroundStyle(self.textColor)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
|
||||||
if !processed.images.isEmpty {
|
if !processed.images.isEmpty {
|
||||||
InlineImageList(images: processed.images)
|
InlineImageList(images: processed.images)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user