1→import Foundation 2→ 3→/// Service that transforms content from one platform format to all others 4→/// Newsletter -> YouTube -> LinkedIn -> Twitter -> Instagram 5→actor ContentPipelineService { 6→ static let shared = ContentPipelineService() 7→ 8→ private let claude = ClaudeService.shared 9→ private let lmstudio = LMStudioService.shared 10→ private let styleAnalyzer = StyleAnalyzer() 11→ 12→ private init() {} 13→ 14→ // MARK: - Transform Content 15→ 16→ /// Transform source content to a specific platform format 17→ func transform( 18→ sourceContent: String, 19→ sourcePlatform: Platform, 20→ targetPlatform: Platform, 21→ style: StyleProfile, 22→ topic: String 23→ ) async throws -> GeneratedContent { 24→ let stylePrompt = styleAnalyzer.buildStyleSystemPrompt(from: style) 25→ let transformPrompt = buildTransformPrompt( 26→ source: sourceContent, 27→ from: sourcePlatform, 28→ to: targetPlatform, 29→ topic: topic 30→ ) 31→ 32→ let content = try await generateWithFallback( 33→ prompt: transformPrompt, 34→ systemPrompt: stylePrompt 35→ ) 36→ 37→ return parseGeneratedContent(content, for: targetPlatform) 38→ } 39→ 40→ /// Transform newsletter to all platforms at once 41→ func transformToAllPlatforms( 42→ newsletter: String, 43→ style: StyleProfile, 44→ topic: String 45→ ) async throws -> [Platform: GeneratedContent] { 46→ var results: [Platform: GeneratedContent] = [:] 47→ 48→ // Newsletter is the source, transform to each target 49→ let targets: [Platform] = [.youtube, .linkedin, .twitter, .instagram] 50→ 51→ for target in targets { 52→ do { 53→ let content = try await transform( 54→ sourceContent: newsletter, 55→ sourcePlatform: .newsletter, 56→ targetPlatform: target, 57→ style: style, 58→ topic: topic 59→ ) 60→ results[target] = content 61→ } catch { 62→ // Continue with other platforms even if one fails 63→ print("Failed to transform to \(target.rawValue): \(error)") 64→ } 65→ } 66→ 67→ return results 68→ } 69→ 70→ // MARK: - Prompts 71→ 72→ private func buildTransformPrompt( 73→ source: String, 74→ from sourcePlatform: Platform, 75→ to targetPlatform: Platform, 76→ topic: String 77→ ) -> String { 78→ return """ 79→ Transform the following \(sourcePlatform.rawValue) content into a \(targetPlatform.rawValue) post. 80→ 81→ TOPIC: \(topic) 82→ 83→ SOURCE CONTENT (\(sourcePlatform.rawValue)): 84→ --- 85→ \(source) 86→ --- 87→ 88→ TARGET FORMAT: \(targetPlatform.promptContext) 89→ 90→ CHARACTER LIMIT: \(targetPlatform.characterLimit.map { String($0) } ?? "No limit") 91→ HASHTAG LIMIT: \(targetPlatform.hashtagLimit) 92→ 93→ Return the content in JSON format: 94→ { 95→ "hook": "The attention-grabbing opening line", 96→ "body": "The main content", 97→ "cta": "Call to action (if applicable)", 98→ "hashtags": ["hashtag1", "hashtag2"], 99→ "fullContent": "The complete formatted post ready to publish" 100→ } 101→ 102→ IMPORTANT: 103→ - Keep the core message but adapt the format 104→ - Match the platform's native style 105→ - Respect character limits 106→ - Make it engaging for the specific audience 107→ - The content should feel native to \(targetPlatform.rawValue), not like a copy-paste 108→ 109→ Return ONLY valid JSON. 110→ """ 111→ } 112→ 113→ // MARK: - Generate Newsletter 114→ 115→ func generateNewsletter( 116→ topic: String, 117→ style: StyleProfile, 118→ keyPoints: [String]? = nil, 119→ targetLength: Int = 1000 120→ ) async throws -> GeneratedContent { 121→ let stylePrompt = styleAnalyzer.buildStyleSystemPrompt(from: style) 122→ 123→ let keyPointsSection = keyPoints.map { points in 124→ "KEY POINTS TO COVER:\n" + points.map { "- \($0)" }.joined(separator: "\n") 125→ } ?? "" 126→ 127→ let prompt = """ 128→ Write a newsletter about: \(topic) 129→ 130→ \(keyPointsSection) 131→ 132→ TARGET LENGTH: ~\(targetLength) words 133→ 134→ STRUCTURE: 135→ 1. Hook/Opening that grabs attention 136→ 2. Main insight or story 137→ 3. Supporting points (2-3) 138→ 4. Practical takeaway or lesson 139→ 5. CTA or thought-provoking close 140→ 141→ FORMAT: 142→ - Use clear sections with line breaks 143→ - Include one powerful quote or statistic if relevant 144→ - Make it scannable with short paragraphs 145→ - End with something memorable 146→ 147→ Return in JSON format: 148→ { 149→ "title": "Newsletter title", 150→ "hook": "Opening hook", 151→ "body": "Full newsletter content with sections", 152→ "cta": "Closing CTA", 153→ "summary": "One-line summary of the main point", 154→ "fullContent": "Complete newsletter ready to send" 155→ } 156→ 157→ Return ONLY valid JSON. 158→ """ 159→ 160→ let content = try await generateWithFallback( 161→ prompt: prompt, 162→ systemPrompt: stylePrompt 163→ ) 164→ 165→ var result = parseGeneratedContent(content, for: .newsletter) 166→ 167→ // Extract title if present 168→ if let data = content.data(using: .utf8), 169→ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], 170→ let title = json["title"] as? String { 171→ result.title = title 172→ } 173→ 174→ return result 175→ } 176→ 177→ // MARK: - Helpers 178→ 179→ private func generateWithFallback( 180→ prompt: String, 181→ systemPrompt: String 182→ ) async throws -> String { 183→ // Try Claude first 184→ if await claude.hasAPIKey { 185→ do { 186→ return try await claude.generate( 187→ prompt: prompt, 188→ systemPrompt: systemPrompt 189→ ) 190→ } catch { 191→ print("Claude failed, trying LM Studio: \(error)") 192→ } 193→ } 194→ 195→ // Fallback to LM Studio 196→ if await lmstudio.isAvailable() { 197→ return try await lmstudio.generate( 198→ prompt: prompt, 199→ systemPrompt: systemPrompt 200→ ) 201→ } 202→ 203→ throw GhostAIError.missingAPIKey 204→ } 205→ 206→ private func parseGeneratedContent(_ content: String, for platform: Platform) -> GeneratedContent { 207→ // Clean JSON 208→ var jsonString = content 209→ .replacingOccurrences(of: "```json", with: "") 210→ .replacingOccurrences(of: "```", with: "") 211→ .trimmingCharacters(in: .whitespacesAndNewlines) 212→ 213→ if let startIndex = jsonString.firstIndex(of: "{"), 214→ let endIndex = jsonString.lastIndex(of: "}") { 215→ jsonString = String(jsonString[startIndex...endIndex]) 216→ } 217→ 218→ guard let data = jsonString.data(using: .utf8), 219→ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { 220→ // If parsing fails, treat entire content as body 221→ return GeneratedContent( 222→ platform: platform, 223→ hook: "", 224→ body: content, 225→ cta: "", 226→ hashtags: [], 227→ fullContent: content 228→ ) 229→ } 230→ 231→ return GeneratedContent( 232→ platform: platform, 233→ hook: json["hook"] as? String ?? "", 234→ body: json["body"] as? String ?? "", 235→ cta: json["cta"] as? String ?? "", 236→ hashtags: json["hashtags"] as? [String] ?? [], 237→ fullContent: json["fullContent"] as? String ?? content, 238→ title: json["title"] as? String 239→ ) 240→ } 241→} 242→ 243→// MARK: - Generated Content Model 244→ 245→struct GeneratedContent { 246→ let platform: Platform 247→ let hook: String 248→ let body: String 249→ let cta: String 250→ let hashtags: [String] 251→ let fullContent: String 252→ var title: String? 253→ 254→ var characterCount: Int { 255→ fullContent.count 256→ } 257→ 258→ var isWithinLimit: Bool { 259→ guard let limit = platform.characterLimit else { return true } 260→ return characterCount <= limit 261→ } 262→} 263→ Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.