| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- import Foundation
- enum AIHistoryToolKind: String, Codable, CaseIterable, Identifiable {
- case postGenerator
- case titleOptimizer
- case commentWriter
- var id: String { rawValue }
- var title: String {
- switch self {
- case .postGenerator: "Post Generator"
- case .titleOptimizer: "Title Optimizer"
- case .commentWriter: "Comment Writer"
- }
- }
- var iconKind: SidebarIconKind {
- switch self {
- case .postGenerator: .postGenerator
- case .titleOptimizer: .titleOptimizer
- case .commentWriter: .commentWriter
- }
- }
- }
- enum AIHistoryFilter: String, CaseIterable, Identifiable {
- case all
- case postGenerator
- case titleOptimizer
- case commentWriter
- var id: String { rawValue }
- var title: String {
- switch self {
- case .all: "All"
- case .postGenerator: "Posts"
- case .titleOptimizer: "Titles"
- case .commentWriter: "Comments"
- }
- }
- var toolKind: AIHistoryToolKind? {
- switch self {
- case .all: nil
- case .postGenerator: .postGenerator
- case .titleOptimizer: .titleOptimizer
- case .commentWriter: .commentWriter
- }
- }
- }
- struct StoredPollOption: Codable, Equatable {
- let id: UUID
- var text: String
- init(id: UUID = UUID(), text: String = "") {
- self.id = id
- self.text = text
- }
- init(from option: PollOption) {
- id = option.id
- text = option.text
- }
- var pollOption: PollOption {
- PollOption(id: id, text: text)
- }
- }
- struct StoredPostDraft: Codable, Equatable {
- var postType: RedditPostType
- var subreddit: String
- var topic: String
- var tone: PostTone
- var title: String
- var body: String
- var linkURL: String
- var videoURL: String
- var pollOptions: [StoredPollOption]
- var pollDuration: PollDuration
- var isNSFW: Bool
- var isSpoiler: Bool
- var isOC: Bool
- var flair: String
- init(from draft: PostDraft) {
- postType = draft.postType
- subreddit = draft.subreddit
- topic = draft.topic
- tone = draft.tone
- title = draft.title
- body = draft.body
- linkURL = draft.linkURL
- videoURL = draft.videoURL
- pollOptions = draft.pollOptions.map(StoredPollOption.init)
- pollDuration = draft.pollDuration
- isNSFW = draft.isNSFW
- isSpoiler = draft.isSpoiler
- isOC = draft.isOC
- flair = draft.flair
- }
- var postDraft: PostDraft {
- PostDraft(
- postType: postType,
- subreddit: subreddit,
- topic: topic,
- tone: tone,
- title: title,
- body: body,
- linkURL: linkURL,
- videoURL: videoURL,
- imageFileURL: nil,
- pollOptions: pollOptions.map(\.pollOption),
- pollDuration: pollDuration,
- isNSFW: isNSFW,
- isSpoiler: isSpoiler,
- isOC: isOC,
- flair: flair
- )
- }
- }
- struct StoredTitleDraft: Codable, Equatable {
- var subreddit: String
- var topic: String
- var tone: PostTone
- var titleGoal: TitleGoal
- var originalTitle: String
- var postType: RedditPostType
- var isNSFW: Bool
- var isSpoiler: Bool
- var isOC: Bool
- var flair: String
- var variantCount: TitleVariantCount
- init(from draft: TitleDraft) {
- subreddit = draft.subreddit
- topic = draft.topic
- tone = draft.tone
- titleGoal = draft.titleGoal
- originalTitle = draft.originalTitle
- postType = draft.postType
- isNSFW = draft.isNSFW
- isSpoiler = draft.isSpoiler
- isOC = draft.isOC
- flair = draft.flair
- variantCount = draft.variantCount
- }
- var titleDraft: TitleDraft {
- TitleDraft(
- subreddit: subreddit,
- topic: topic,
- tone: tone,
- titleGoal: titleGoal,
- originalTitle: originalTitle,
- postType: postType,
- isNSFW: isNSFW,
- isSpoiler: isSpoiler,
- isOC: isOC,
- flair: flair,
- variantCount: variantCount
- )
- }
- }
- struct StoredTitleVariant: Codable, Equatable {
- let id: UUID
- var title: String
- var score: Int
- var reasoning: String
- init(from variant: TitleVariant) {
- id = variant.id
- title = variant.title
- score = variant.score
- reasoning = variant.reasoning
- }
- var titleVariant: TitleVariant {
- TitleVariant(id: id, title: title, score: score, reasoning: reasoning)
- }
- }
- struct StoredTitleAnalysis: Codable, Equatable {
- var overallScore: Int
- var lengthScore: Int
- var engagementScore: Int
- var clarityScore: Int
- var suggestions: [String]
- init(from analysis: TitleAnalysis) {
- overallScore = analysis.overallScore
- lengthScore = analysis.lengthScore
- engagementScore = analysis.engagementScore
- clarityScore = analysis.clarityScore
- suggestions = analysis.suggestions
- }
- var titleAnalysis: TitleAnalysis {
- TitleAnalysis(
- overallScore: overallScore,
- lengthScore: lengthScore,
- engagementScore: engagementScore,
- clarityScore: clarityScore,
- suggestions: suggestions
- )
- }
- }
- struct StoredCommentDraft: Codable, Equatable {
- var commentType: CommentType
- var subreddit: String
- var postTitle: String
- var postBody: String
- var postURL: String
- var parentComment: String
- var topic: String
- var tone: PostTone
- var intent: CommentIntent
- var length: CommentLength
- var body: String
- var includeQuote: Bool
- var useMarkdown: Bool
- var variantCount: CommentVariantCount
- init(from draft: CommentDraft) {
- commentType = draft.commentType
- subreddit = draft.subreddit
- postTitle = draft.postTitle
- postBody = draft.postBody
- postURL = draft.postURL
- parentComment = draft.parentComment
- topic = draft.topic
- tone = draft.tone
- intent = draft.intent
- length = draft.length
- body = draft.body
- includeQuote = draft.includeQuote
- useMarkdown = draft.useMarkdown
- variantCount = draft.variantCount
- }
- var commentDraft: CommentDraft {
- CommentDraft(
- commentType: commentType,
- subreddit: subreddit,
- postTitle: postTitle,
- postBody: postBody,
- postURL: postURL,
- parentComment: parentComment,
- topic: topic,
- tone: tone,
- intent: intent,
- length: length,
- body: body,
- includeQuote: includeQuote,
- useMarkdown: useMarkdown,
- variantCount: variantCount
- )
- }
- }
- struct StoredCommentVariant: Codable, Equatable {
- let id: UUID
- var body: String
- var score: Int
- var reasoning: String
- init(from variant: CommentVariant) {
- id = variant.id
- body = variant.body
- score = variant.score
- reasoning = variant.reasoning
- }
- var commentVariant: CommentVariant {
- CommentVariant(id: id, body: body, score: score, reasoning: reasoning)
- }
- }
- enum AIHistoryPayload: Codable, Equatable {
- case postGenerator(StoredPostDraft)
- case titleOptimizer(
- draft: StoredTitleDraft,
- analysis: StoredTitleAnalysis?,
- variants: [StoredTitleVariant],
- selectedVariantID: UUID?
- )
- case commentWriter(
- draft: StoredCommentDraft,
- variants: [StoredCommentVariant],
- selectedVariantID: UUID?
- )
- }
- struct AIHistoryEntry: Identifiable, Codable, Equatable {
- let id: UUID
- let toolKind: AIHistoryToolKind
- let createdAt: Date
- let displayTitle: String
- let displaySubtitle: String
- let payload: AIHistoryPayload
- init(
- id: UUID = UUID(),
- toolKind: AIHistoryToolKind,
- createdAt: Date = .now,
- displayTitle: String,
- displaySubtitle: String,
- payload: AIHistoryPayload
- ) {
- self.id = id
- self.toolKind = toolKind
- self.createdAt = createdAt
- self.displayTitle = displayTitle
- self.displaySubtitle = displaySubtitle
- self.payload = payload
- }
- }
- extension AIHistoryEntry {
- static func fromPostGenerator(draft: PostDraft) -> AIHistoryEntry {
- let title = draft.title.trimmingCharacters(in: .whitespaces)
- let displayTitle = title.isEmpty ? "Untitled post" : title
- let subreddit = formattedSubredditName(draft.subreddit)
- return AIHistoryEntry(
- toolKind: .postGenerator,
- displayTitle: displayTitle,
- displaySubtitle: subreddit,
- payload: .postGenerator(StoredPostDraft(from: draft))
- )
- }
- static func fromTitleOptimizer(
- draft: TitleDraft,
- analysis: TitleAnalysis?,
- variants: [TitleVariant],
- selectedVariantID: UUID?
- ) -> AIHistoryEntry {
- let activeTitle: String
- if let selectedVariantID,
- let variant = variants.first(where: { $0.id == selectedVariantID }) {
- activeTitle = variant.title
- } else {
- activeTitle = draft.originalTitle
- }
- let title = activeTitle.trimmingCharacters(in: .whitespaces)
- let displayTitle = title.isEmpty ? "Untitled optimization" : title
- let subreddit = formattedSubredditName(draft.subreddit)
- return AIHistoryEntry(
- toolKind: .titleOptimizer,
- displayTitle: displayTitle,
- displaySubtitle: subreddit,
- payload: .titleOptimizer(
- draft: StoredTitleDraft(from: draft),
- analysis: analysis.map(StoredTitleAnalysis.init),
- variants: variants.map(StoredTitleVariant.init),
- selectedVariantID: selectedVariantID
- )
- )
- }
- static func fromCommentWriter(
- draft: CommentDraft,
- variants: [CommentVariant],
- selectedVariantID: UUID?
- ) -> AIHistoryEntry {
- let activeBody: String
- if let selectedVariantID,
- let variant = variants.first(where: { $0.id == selectedVariantID }) {
- activeBody = variant.body
- } else {
- activeBody = draft.body
- }
- let trimmed = activeBody.trimmingCharacters(in: .whitespacesAndNewlines)
- let displayTitle: String
- if trimmed.isEmpty {
- displayTitle = "Untitled comment"
- } else {
- let firstLine = trimmed.split(separator: "\n", maxSplits: 1).first.map(String.init) ?? trimmed
- displayTitle = firstLine.count > 80 ? String(firstLine.prefix(77)) + "…" : firstLine
- }
- let subreddit = formattedSubredditName(draft.subreddit)
- return AIHistoryEntry(
- toolKind: .commentWriter,
- displayTitle: displayTitle,
- displaySubtitle: subreddit,
- payload: .commentWriter(
- draft: StoredCommentDraft(from: draft),
- variants: variants.map(StoredCommentVariant.init),
- selectedVariantID: selectedVariantID
- )
- )
- }
- private static func formattedSubredditName(_ subreddit: String) -> String {
- let name = PostDraftValidator.normalizedSubreddit(subreddit)
- guard !name.isEmpty else { return "r/subreddit" }
- return "r/\(name)"
- }
- }
|