-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #38 from ios-osushi/try-use-gemini
Use Gemini for generate articles (Beta)
- Loading branch information
Showing
8 changed files
with
305 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import ArgumentParser | ||
import Foundation | ||
import ReleaseSummarizerCore | ||
import ReleaseSubscriptionsCore | ||
import Logging | ||
|
||
@main | ||
struct App: AsyncParsableCommand { | ||
|
||
static var configuration: CommandConfiguration { | ||
.init( | ||
commandName: "releaseSummarizer" | ||
) | ||
} | ||
|
||
@Option( | ||
name: .shortAndLong, | ||
help: "Gemini APIKey for generating summary of change log" | ||
) | ||
var apiToken: String? | ||
|
||
@Option( | ||
name: .long, | ||
help: "Generate article target date range from. yyyy/MM/dd date format string", | ||
transform: ISO8601DateFormatter().date(from:) | ||
) | ||
var from: Date? | ||
|
||
@Option( | ||
name: .long, | ||
help: "Generate article target date range to. yyyy/MM/dd date format string. default: today", | ||
transform: ISO8601DateFormatter().date(from:) | ||
) | ||
var to: Date? | ||
|
||
func run() async throws { | ||
guard let apiToken = apiToken else { | ||
Logger.app.error("An argument apiToken is needed. Please pass Gemini apiKey.") | ||
return | ||
} | ||
|
||
guard let from else { | ||
Logger.app.error("An argument from is needed or invalid. Please pass date range from.") | ||
return | ||
} | ||
|
||
let repositories = try ReleaseSubscriptionsParser.parse() | ||
let releases = try await ReleaseCollector.collect( | ||
for: repositories, | ||
from: from, | ||
to: to ?? Date() | ||
) | ||
let generatedContent = try await ReleaseSummarizeGenerator.generate(apiToken: apiToken, prompt: prompt, releases: releases) | ||
|
||
try ReleaseSummarizeFileHelper.writeToFile(fileContent: generatedContent) | ||
} | ||
} | ||
|
||
extension App { | ||
var prompt: String { | ||
""" | ||
次の文章はとあるライブラリのリリース情報です。 | ||
この内容を短くわかりやすい表現で要約してください。 | ||
要約の内容は | ||
・改善点 | ||
・修正点 | ||
・その他 | ||
に分けて日本語で行ってください。 | ||
内容は箇条書きの Markdown 形式で出力してください。 | ||
また、各箇条書きの項目は50文字以内で収めて、ですます調で出力してください。 | ||
出力例: | ||
##### 改善点 | ||
内容 | ||
##### 修正点 | ||
内容 | ||
##### その他 | ||
内容 | ||
""" | ||
} | ||
} | ||
|
||
extension Logger { | ||
fileprivate static let app = Logger(label: "io.github.ios-osushi.releasesummarizer") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// | ||
// ReleaseCollector.swift | ||
// ReleaseSubscriptions | ||
// | ||
// Created by 伊藤凌也 on 2024/09/28. | ||
// | ||
|
||
import Foundation | ||
import ReleaseSubscriptionsCore | ||
|
||
public struct ReleaseCollector { | ||
public static func collect(for repositories: [GitHubRepository], from: Date, to: Date) async throws -> [(GitHubRepository, Release)] { | ||
try OutputFileHelper.load(repositories: repositories) | ||
.flatMap { (repository, releases) in | ||
releases.map { (repository, $0) } | ||
} | ||
.filter { (repository, release) in | ||
guard let publishedAt = release.publishedAt else { | ||
return false | ||
} | ||
|
||
return (from...to).contains(publishedAt) | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
Sources/ReleaseSummarizerCore/ReleaseSummarizeFileHelper.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// | ||
// File.swift | ||
// ReleaseSubscriptions | ||
// | ||
// Created by 伊藤凌也 on 2024/09/28. | ||
// | ||
|
||
import Foundation | ||
import Logging | ||
|
||
public struct ReleaseSummarizeFileHelper { | ||
public static func writeToFile(fileContent: String) throws { | ||
defer { | ||
Logger.helper.info("🎉 \(#function) finished") | ||
} | ||
Logger.helper.info("ℹ️ \(#function) started") | ||
try fileContent.write(toFile: "./output.md", atomically: true, encoding: .utf8) | ||
} | ||
} | ||
|
||
extension Logger { | ||
fileprivate static let helper = Logger(label: "io.github.ios-osushi.releasesummarizer.filehelper") | ||
} |
125 changes: 125 additions & 0 deletions
125
Sources/ReleaseSummarizerCore/ReleaseSummarizeGenerator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
// | ||
// Generator.swift | ||
// ReleaseSubscriptions | ||
// | ||
// Created by 伊藤凌也 on 2024/09/28. | ||
// | ||
|
||
import GoogleGenerativeAI | ||
import Logging | ||
import ReleaseSubscriptionsCore | ||
|
||
public struct ReleaseSummarizeGenerator { | ||
|
||
// リリース情報に限った記事の内容を生成する | ||
// | ||
// フォーマット | ||
// | ||
// | ||
// ## OSS のリリース情報 | ||
// | ||
// iOS アプリ開発でよく使われている OSS のリリース情報です。 | ||
// | ||
// ### Apple | ||
// #### {バージョン} - ライブラリ名 | ||
// | ||
// [{release url}]({release url}) | ||
// | ||
// ##### 改善点 | ||
// | ||
// 内容 | ||
// | ||
// ##### 修正点 | ||
// | ||
// 内容 | ||
// | ||
// ##### その他 | ||
// | ||
// 内容 | ||
// | ||
// ### サードパーティ | ||
// | ||
// ... | ||
// | ||
public static func generate( | ||
apiToken: String, | ||
prompt: String, | ||
releases: [(GitHubRepository, Release)] | ||
) async throws -> String { | ||
let grouped: [SlackWebhookDestination: [Release]] = releases.reduce(into: [:]) { partialResult, release in | ||
let (repository, release) = release | ||
|
||
let destination = switch repository { | ||
case let .releases(destination, _): | ||
destination | ||
|
||
case let .tags(destination, _): | ||
destination | ||
} | ||
|
||
if partialResult[destination] != nil { | ||
partialResult[destination]?.append(release) | ||
} else { | ||
partialResult[destination] = [release] | ||
} | ||
} | ||
|
||
var builder = Builder() | ||
for (key, releases) in grouped { | ||
builder.addSection(destination: key) | ||
|
||
for release in releases { | ||
Logger.generator.info("Generating release content for \(release.owner)/\(release.repository) ...") | ||
|
||
let model = GenerativeModel(name: "gemini-1.5-flash-latest", apiKey: apiToken) | ||
let result = try await model.generateContent(prompt, release.body ?? "") | ||
|
||
if let content = result.text { | ||
builder.addRelease(release: release, generatedContent: content) | ||
} | ||
|
||
try await Task.sleep(nanoseconds: 5000_000_000) | ||
} | ||
} | ||
|
||
return builder.result | ||
} | ||
|
||
struct Builder { | ||
var result = """ | ||
## OSS のリリース情報 | ||
iOS アプリ開発でよく使われている OSS のリリース情報です。 | ||
""" | ||
|
||
mutating func addSection(destination: SlackWebhookDestination) { | ||
let title = switch destination { | ||
case .primary: | ||
"Apple" | ||
|
||
case .secondary: | ||
"サードパーティ" | ||
} | ||
|
||
result.append("### \(title)\n") | ||
} | ||
|
||
/// #### {バージョン} - ライブラリ名 | ||
mutating func addRelease(release: Release, generatedContent: String) { | ||
let content = """ | ||
#### \(release.version) - \(release.owner)/\(release.repository) | ||
[\(release.url)](\(release.url) | ||
\(generatedContent) | ||
""" | ||
result.append(content) | ||
} | ||
} | ||
} | ||
|
||
extension Logger { | ||
fileprivate static let generator = Logger(label: "io.github.ios-osushi.releasesummarizer.generator") | ||
} |