diff --git a/KoeBook.Core/Contracts/Services/ILlmStoryGeneratorService.cs b/KoeBook.Core/Contracts/Services/ILlmStoryGeneratorService.cs new file mode 100644 index 0000000..ea1ea9d --- /dev/null +++ b/KoeBook.Core/Contracts/Services/ILlmStoryGeneratorService.cs @@ -0,0 +1,8 @@ +using KoeBook.Core.Models; + +namespace KoeBook.Core.Contracts.Services; + +internal interface ILlmStoryGeneratorService +{ + ValueTask GenerateStoryAsync(StoryGenre storyGenre, string premise, CancellationToken cancellationToken); +} diff --git a/KoeBook.Core/Services/ClaudeStoryGeneratorService.cs b/KoeBook.Core/Services/ClaudeStoryGeneratorService.cs new file mode 100644 index 0000000..4e6a605 --- /dev/null +++ b/KoeBook.Core/Services/ClaudeStoryGeneratorService.cs @@ -0,0 +1,69 @@ +using KoeBook.Core.Contracts.Services; +using KoeBook.Core.Models; + +namespace KoeBook.Core.Services; + +internal class ClaudeStoryGeneratorService(IClaudeService claudeService) : ILlmStoryGeneratorService +{ + private readonly IClaudeService _claudeService = claudeService; + + public async ValueTask GenerateStoryAsync(StoryGenre storyGenre, string premise, CancellationToken cancellationToken) + { + if (_claudeService.Messages is null) + { + throw new EbookException(ExceptionType.ApiKeyNotSet); + } + try + { + var storyXml = await _claudeService.Messages.CreateAsync(new() + { + Model = Claudia.Models.Claude3Opus, + MaxTokens = 4000, + Temperature = 0.4, + Messages = [new() + { + Role = "user", + Content = CreateStoryPrompt(storyGenre, premise) + }] + }, + cancellationToken: cancellationToken + ); + return storyXml.ToString(); + } + catch (OperationCanceledException) { throw; } + catch (Exception e) + { + throw new EbookException(ExceptionType.ClaudeTalkerAndStyleSettingFailed, innerException: e); + } + } + + private string CreateStoryPrompt(StoryGenre storyGenre, string premise) + { + return $""" + You are a highly capable AI novelist that can write compelling 2500-character short stories and novellas in fluent, natural Japanese based on a given theme or plot points. + + When crafting the story, please focus on the following: + - Use dialogue extensively to advance the plot while revealing characters' personalities, motivations and relationships + - Write witty, revealing conversations authentic to each character's voice + - Vary dialogue tags and phrases to convey nuanced speech + - Aim to create a polished story where characters' distinct voices come through in the dialogue, within the 2500-character limit + - Words beyond the general vocabulary and proper nouns that are easily misread are given ruby. Not necessary for simple vocabulary. + + Please generate the full story in Japanese in a single output follow this example: + + + 境界線の向こう側 + +
+ 高校2年の夏、バレー部のエースで端正たんせいな顔立ちの山田祐樹は、バスケ部のキャプテンで凛とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活仲間たちの目をはばかり、互いに素振りも見せずにいた。 +
+
+
+ + Based on this prompt, please generate a ~2500-character Japanese story from the specified theme or plot points provided. + + theme: {storyGenre.Genre} + premise: {premise} + """; + } +}