Merge 4e860cc67d into da71eaebd2
This commit is contained in:
commit
70bf408251
@ -6,6 +6,23 @@ public enum ChatMarkdownVariant: String, CaseIterable, Sendable {
|
||||
case compact
|
||||
}
|
||||
|
||||
// MARK: - Textual Bundle Availability
|
||||
|
||||
/// Checks if the Textual syntax highlighting bundle is available.
|
||||
/// This must be called BEFORE any Textual types are accessed to avoid a crash
|
||||
/// from SPM's generated Bundle.module accessor when the bundle is missing.
|
||||
private let textualBundleAvailable: Bool = {
|
||||
let bundleNames = ["textual_Textual", "Textual_Textual"]
|
||||
guard let resourceURL = Bundle.main.resourceURL else { return false }
|
||||
for name in bundleNames {
|
||||
let bundleURL = resourceURL.appendingPathComponent("\(name).bundle")
|
||||
if FileManager.default.fileExists(atPath: bundleURL.path) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
@MainActor
|
||||
struct ChatMarkdownRenderer: View {
|
||||
enum Context {
|
||||
@ -22,12 +39,20 @@ struct ChatMarkdownRenderer: View {
|
||||
var body: some View {
|
||||
let processed = ChatMarkdownPreprocessor.preprocess(markdown: self.text)
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
StructuredText(markdown: processed.cleaned)
|
||||
.modifier(ChatMarkdownStyle(
|
||||
variant: self.variant,
|
||||
context: self.context,
|
||||
if textualBundleAvailable {
|
||||
StructuredText(markdown: processed.cleaned)
|
||||
.modifier(ChatMarkdownStyle(
|
||||
variant: self.variant,
|
||||
context: self.context,
|
||||
font: self.font,
|
||||
textColor: self.textColor))
|
||||
} else {
|
||||
// Fallback when Textual's resource bundle is missing (avoids crash).
|
||||
FallbackMarkdownText(
|
||||
text: processed.cleaned,
|
||||
font: self.font,
|
||||
textColor: self.textColor))
|
||||
textColor: self.textColor)
|
||||
}
|
||||
|
||||
if !processed.images.isEmpty {
|
||||
InlineImageList(images: processed.images)
|
||||
@ -88,3 +113,33 @@ private struct InlineImageList: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fallback Markdown Rendering
|
||||
|
||||
/// Fallback markdown renderer using SwiftUI's native AttributedString.
|
||||
/// Used when Textual's resource bundle is missing to avoid crashes.
|
||||
@MainActor
|
||||
private struct FallbackMarkdownText: View {
|
||||
let text: String
|
||||
let font: Font
|
||||
let textColor: Color
|
||||
|
||||
var body: some View {
|
||||
if let attributed = try? AttributedString(markdown: self.text, options: Self.markdownOptions) {
|
||||
Text(attributed)
|
||||
.font(self.font)
|
||||
.foregroundStyle(self.textColor)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
Text(self.text)
|
||||
.font(self.font)
|
||||
.foregroundStyle(self.textColor)
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
|
||||
private static let markdownOptions = AttributedString.MarkdownParsingOptions(
|
||||
allowsExtendedAttributes: true,
|
||||
interpretedSyntax: .inlineOnlyPreservingWhitespace,
|
||||
failurePolicy: .returnPartiallyParsedIfPossible)
|
||||
}
|
||||
|
||||
@ -25,11 +25,11 @@ type ParsedTtsCommand = {
|
||||
};
|
||||
|
||||
function parseTtsCommand(normalized: string): ParsedTtsCommand | null {
|
||||
// Accept `/tts` and `/tts <action> [args]` as a single control surface.
|
||||
if (normalized === "/tts") return { action: "status", args: "" };
|
||||
// Accept `/tts <action> [args]` - return null for `/tts` alone to trigger inline menu.
|
||||
if (normalized === "/tts") return null;
|
||||
if (!normalized.startsWith("/tts ")) return null;
|
||||
const rest = normalized.slice(5).trim();
|
||||
if (!rest) return { action: "status", args: "" };
|
||||
if (!rest) return null;
|
||||
const [action, ...tail] = rest.split(/\s+/);
|
||||
return { action: action.toLowerCase(), args: tail.join(" ").trim() };
|
||||
}
|
||||
|
||||
@ -485,6 +485,8 @@ export const registerTelegramNativeCommands = ({
|
||||
// Originating context for sub-agent announce routing
|
||||
OriginatingChannel: "telegram" as const,
|
||||
OriginatingTo: `telegram:${chatId}`,
|
||||
// Multi-account: preserve accountId for command confirmations (e.g., /reset)
|
||||
AccountId: route.accountId,
|
||||
});
|
||||
|
||||
const disableBlockStreaming =
|
||||
|
||||
Loading…
Reference in New Issue
Block a user