Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Sources/InkletMac/Models/AnalysisCopy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ enum AnalysisTimeline {
var parts: [String] = []
if failedSteps > 0 { parts.append("\(AnalysisCopy.plural(failedSteps, "step")) failed") }
if deniedSteps > 0 { parts.append("\(AnalysisCopy.plural(deniedSteps, "step")) blocked") }
let suffix = parts.isEmpty ? "" : " · " + parts.joined(separator: " · ")
let suffix = parts.isEmpty ? "" : ". " + parts.joined(separator: ", ")

let state = data["state"]?.stringValue ?? "active"
let failed = state == "failed"
Expand All @@ -315,7 +315,7 @@ enum AnalysisTimeline {
guard let read = stats["notesRead"]?.numberValue.map({ Int($0) }) else {
return done ? "Read your notes" : "Reading your notes"
}
return done ? "Read \(AnalysisCopy.plural(read, "note"))" : "Reading your notes · \(read) read"
return done ? "Read \(AnalysisCopy.plural(read, "note"))" : "Reading your notes (\(read) read)"
case "checking_display":
return done ? "Checked the display" : "Checking the display"
case "choosing_layout":
Expand All @@ -326,11 +326,11 @@ enum AnalysisTimeline {
}
if done {
let looked = "Looked at \(AnalysisCopy.plural(seen, "layout"))"
return chosen.map { "\(looked) · chose \($0)" } ?? looked
return chosen.map { "\(looked), chose \($0)" } ?? looked
}
// A layout it has settled on outranks the running count: it is the
// answer the activity exists to produce.
return chosen.map { "Chose \($0)" } ?? "Looking at layouts · \(seen) so far"
return chosen.map { "Chose \($0)" } ?? "Looking at layouts (\(seen) so far)"
case "submitting_plan":
// "Checking" while it runs, because the backend is validating it
// and it may yet come back; "Submitted" once it has gone through.
Expand All @@ -339,7 +339,7 @@ enum AnalysisTimeline {
return done ? "Tried another layout" : "Trying another layout"
default:
if steps <= 0 { return done ? "Worked through it" : "Working" }
return done ? "Worked through \(AnalysisCopy.plural(steps, "step"))" : "Working · \(AnalysisCopy.plural(steps, "step"))"
return done ? "Worked through \(AnalysisCopy.plural(steps, "step"))" : "Working through \(AnalysisCopy.plural(steps, "step"))"
}
}

Expand Down
9 changes: 9 additions & 0 deletions Sources/InkletMac/Models/AskModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ final class AskModel {
}
}

func rename(_ id: String, title: String) async throws {
let expected = generation
let updated = try await InkletAPI.shared.renameConversation(id: id, title: title)
guard generation == expected else { return }
if let index = conversations.firstIndex(where: { $0.id == id }) {
conversations[index] = updated
}
}

func delete(_ id: String) async {
let expected = generation
do {
Expand Down
7 changes: 5 additions & 2 deletions Sources/InkletMac/Models/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ struct KnowledgeItem: Identifiable, Hashable {
} else if let firstLine {
title = firstLine
} else if let link = links.first?.url {
title = link
title = URL(string: link)?.host()?.replacingOccurrences(of: "www.", with: "") ?? link
} else if !images.isEmpty {
title = images.count == 1 ? (images.first?.filename ?? "Image") : "\(images.count) images"
} else if !files.isEmpty {
Expand All @@ -233,7 +233,10 @@ struct KnowledgeItem: Identifiable, Hashable {
if !images.isEmpty { parts.append("\(images.count) image\(images.count == 1 ? "" : "s")") }
if !files.isEmpty { parts.append("\(files.count) file\(files.count == 1 ? "" : "s")") }
if let failure = dto.failure, processStatus == .failed { parts.append(failure.message) }
detail = parts.isEmpty ? nil : parts.joined(separator: " · ")
detail = parts.isEmpty ? nil : parts.joined(separator: ", ")
if dto.assets.count == 1, let raw = links.first?.url, let url = URL(string: raw) {
detail = url.path.isEmpty || url.path == "/" ? raw : (url.path.removingPercentEncoding ?? url.path)
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions Sources/InkletMac/Services/InkletAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ actor InkletAPI {
return try await authed(path)
}

func content(id: String) async throws -> ContentDTO {
try await authed("api/app/v1/contents/\(id)")
}

func readContentAsset(contentID: String, index: Int, offset: Int = 0) async throws -> ContentAssetReading {
try await authed("api/app/v1/contents/\(contentID)/assets/\(index)/reading?offset=\(offset)")
}

// MARK: - Conversations (Ask inklet, `/api/app/v1/conversations`)

func conversations(cursor: String? = nil, limit: Int = 50) async throws -> ConversationPageDTO {
Expand All @@ -323,6 +331,10 @@ actor InkletAPI {
return try await authed(path)
}

func renameConversation(id: String, title: String) async throws -> ConversationDTO {
try await authed("api/app/v1/conversations/\(id)", method: "PATCH", jsonObject: ["title": title])
}

func deleteConversation(id: String) async throws {
try await authedVoid("api/app/v1/conversations/\(id)", method: "DELETE")
}
Expand Down
17 changes: 12 additions & 5 deletions Sources/InkletMac/Theme/InkletTheme.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,23 @@ struct InkCard<Content: View>: View {
}
}

/// Small caps eyebrow used above every section.
/// Shared type roles: serif page titles, readable section headings and quiet metadata.
enum InkType {
static let title = Font.brand(34)
static let section = Font.system(size: 16, weight: .semibold)
static let body = Font.system(size: 15)
static let metadata = Font.system(size: 12)
}

/// A section heading, never a decorative all-caps eyebrow.
struct SectionLabel: View {
let text: String

init(_ text: String) { self.text = text }

var body: some View {
Text(text.uppercased())
.font(.system(size: 11, weight: .medium))
.tracking(1.3)
.foregroundStyle(Ink.muted)
Text(text)
.font(InkType.section)
.foregroundStyle(Ink.text)
}
}
Loading
Loading