Skip to content

Commit

Permalink
Merge branch 'main' into herring/claudia
Browse files Browse the repository at this point in the history
  • Loading branch information
aiueo-1234 committed Apr 30, 2024
2 parents cf9828f + ab1eef1 commit 49e0aae
Show file tree
Hide file tree
Showing 15 changed files with 346 additions and 39 deletions.
32 changes: 19 additions & 13 deletions Epub/KoeBook.Epub/Services/AiStoryAnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,29 @@ public partial class AiStoryAnalyzerService(ISplitBraceService splitBraceService

public EpubDocument CreateEpubDocument(AiStory aiStory, Guid id)
{
int sectionNumber = 1;
return new EpubDocument(aiStory.Title, "AI", "", id)
{
Chapters = [new Chapter()
{
Sections = aiStory.Sections.Select(s => new Section($"{sectionNumber++}")
{
Elements = s.Paragraphs.SelectMany(p =>
_splitBraceService.SplitBrace(p.GetText())
.Zip(_splitBraceService.SplitBrace(p.GetScript()))
.Select(Element (p) => new Paragraph
{
Text = p.First,
ScriptLine = new(p.Second, "", "")
})
).ToList(),
}).ToList(),
Sections = [
new Section("本編")
{
Elements = aiStory.Lines.SelectMany(s =>
s.SelectMany(p => _splitBraceService.SplitBrace(p.GetText())
.Zip(_splitBraceService.SplitBrace(p.GetScript()))
.Select(Element (p) => new Paragraph
{
Text = p.First,
ScriptLine = new(p.Second, "", "")
}))
.Append(new Paragraph()
{
Text = "",
ScriptLine = new("", "", "")
})
).ToList(),
}
]
}]
};
}
Expand Down
4 changes: 2 additions & 2 deletions Epub/KoeBook.Epub/Services/AnalyzerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ public async ValueTask<BookScripts> AnalyzeAsync(BookProperties bookProperties,
var line = ReplaceBaseTextWithRuby(p.Text);
return p.ScriptLine = new ScriptLine(line, "", "");
}).ToArray();
}).Where(l => !string.IsNullOrEmpty(l.Text))
.ToArray();

// LLMによる話者、スタイル解析
var bookScripts = await _llmAnalyzerService.LlmAnalyzeScriptLinesAsync(bookProperties, scriptLines, cancellationToken)!;

return bookScripts;
}

Expand Down
9 changes: 9 additions & 0 deletions KoeBook.Core/Contracts/Services/IStoryCreatorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using KoeBook.Core.Models;

namespace KoeBook.Core.Contracts.Services;

public interface IStoryCreatorService
{
/// <returns>XML</returns>
public ValueTask<string> CreateStoryAsync(StoryGenre genre, string instruction, CancellationToken cancellationToken);
}
34 changes: 16 additions & 18 deletions KoeBook.Core/Models/AiStory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,38 @@
namespace KoeBook.Models;

[XmlRoot("Book")]
public record AiStory(
[property: XmlElement("Title", typeof(string), IsNullable = false)] string Title,
[property: XmlArray("Content", IsNullable = false), XmlArrayItem("Section", IsNullable = false)] AiStory.Section[] Sections)
public class AiStory
{
private AiStory() : this("", []) { }

public record Section(
[property: XmlArrayItem("Paragraph", IsNullable = false)] Paragraph[] Paragraphs)
{
private Section() : this([]) { }
}
[XmlElement("Title", typeof(string), IsNullable = false)]
public string Title { get; init; } = "";

[XmlArray("Content", IsNullable = false)]
[XmlArrayItem("Section", IsNullable = false)]
[XmlArrayItem("Paragraph", IsNullable = false, NestingLevel = 1)]
public Line[][] Lines { get; init; } = [];

public record Paragraph(
[property: XmlElement("Text", typeof(TextElement), IsNullable = false), XmlElement("Ruby", typeof(Ruby), IsNullable = false)] InlineElement[] Inlines)
public record Line(
[property: XmlElement("Text", typeof(Text), IsNullable = false), XmlElement("Ruby", typeof(Ruby), IsNullable = false)] InlineElement[] Inlines)
{
private Paragraph() : this([]) { }
private Line() : this([]) { }

public string GetText() => string.Concat(Inlines.Select(e => e.Text));
public string GetText() => string.Concat(Inlines.Select(e => e.Html));

public string GetScript() => string.Concat(Inlines.Select(e => e.Script));
}

public abstract record class InlineElement
{
public abstract string Text { get; }
public abstract string Html { get; }
public abstract string Script { get; }
}

public record TextElement([property: XmlText] string InnerText) : InlineElement
public record Text([property: XmlText] string InnerText) : InlineElement
{
private TextElement() : this("") { }
private Text() : this("") { }

public override string Text => InnerText;
public override string Html => InnerText;
public override string Script => InnerText;
}

Expand All @@ -46,7 +44,7 @@ public record Ruby(
{
private Ruby() : this("", "") { }

public override string Text => Rb;
public override string Html => $"<ruby>{Rb}<rt>{Rt}</rt></ruby>";
public override string Script => Rt;
}
}
4 changes: 4 additions & 0 deletions KoeBook.Core/Models/StoryGenre.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace KoeBook.Core.Models;

public record class StoryGenre(string Genre, string Description);

4 changes: 4 additions & 0 deletions KoeBook/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ public App()
services.AddTransient<ShellPage>();
services.AddTransient<ShellViewModel>();
services.AddTransient<EditDetailsViewModel>();
services.AddTransient<CreateStoryPage>();
services.AddTransient<CreateStoryViewModel>();
// Configuration
services.Configure<LocalSettingsOptions>(context.Configuration.GetSection(nameof(LocalSettingsOptions)));
Expand All @@ -106,6 +108,8 @@ public App()
services.AddSingleton<ISoundGenerationSelectorService, SoundGenerationSelectorServiceMock>();
if (mockOptions.ISoundGenerationService.HasValue && mockOptions.ISoundGenerationService.Value)
services.AddSingleton<ISoundGenerationService, SoundGenerationServiceMock>();
if (mockOptions.IStoryCreaterService.HasValue && mockOptions.IStoryCreaterService.Value)
services.AddSingleton<IStoryCreatorService, StoryCreatorServiceMock>();
})
.Build();

Expand Down
9 changes: 9 additions & 0 deletions KoeBook/KoeBook.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<None Remove="Components\Dialog\DialogContentControl.xaml" />
<None Remove="Components\Dialog\SharedContentDialog.xaml" />
<None Remove="Components\StateProgressBar.xaml" />
<None Remove="Views\CreateStoryPage.xaml" />
<None Remove="Views\EditDetailsTab.xaml" />
</ItemGroup>

Expand Down Expand Up @@ -68,4 +69,12 @@
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
</PropertyGroup>

<ItemGroup>
<CustomAdditionalCompileInputs Remove="Views\CreateStoryPage.xaml" />
</ItemGroup>

<ItemGroup>
<Resource Remove="Views\CreateStoryPage.xaml" />
</ItemGroup>
</Project>
2 changes: 2 additions & 0 deletions KoeBook/Models/MockOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ internal class MockOptions
public bool? IAnalyzerService { get; set; }

public bool? IEpubGenerateService { get; set; }

public bool? IStoryCreaterService { get; set; }
}
34 changes: 34 additions & 0 deletions KoeBook/Services/CoreMocks/StoryCreatorServiceMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;

namespace KoeBook.Services.CoreMocks
{
public class StoryCreatorServiceMock : IStoryCreatorService
{
public ValueTask<string> CreateStoryAsync(StoryGenre genre, string instruction, CancellationToken cancellationToken)
{
return ValueTask.FromResult("""
<?xml version="1.0" encoding="UTF-8"?>
<Book>
<Title>境界線の向こう側</Title>
<Content>
<Section>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
</Section>
<Section>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
<Paragraph><Text>高校2年の夏、バレー部のエースで</Text><Ruby><Rb>端正</Rb><Rt>はんせい</Rt></Ruby><Text>な顔立ちの山田祐樹は、バスケ部のキャプテンで</Text><Ruby><Rb>凛</Rb><Rt>りん</Rt></Ruby><Text>とした佇まいの田中麻衣に密かに想いを寄せていた。しかし、両者の部活</Text><Ruby><Rb>仲間</Rb><Rt>なかま</Rt></Ruby><Text>たちの目を</Text><Ruby><Rb>憚</Rb><Rt>はばか</Rt></Ruby><Text>り、互いに素振りも見せずにいた。</Text></Paragraph>
</Section>
</Content>
</Book>
""");
}
}
}
79 changes: 79 additions & 0 deletions KoeBook/ViewModels/CreateStoryViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections.Immutable;
using System.Xml.Serialization;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using KoeBook.Contracts.Services;
using KoeBook.Core.Contracts.Services;
using KoeBook.Core.Models;
using KoeBook.Models;

namespace KoeBook.ViewModels;

public sealed partial class CreateStoryViewModel : ObservableObject
{
private readonly IGenerationTaskService _generationTaskService;
private readonly IDialogService _dialogService;
private readonly IStoryCreatorService _storyCreatorService;

public ImmutableArray<StoryGenre> Genres { get; } = [
new("青春小説", "学校生活、友情、恋愛など、若者の成長物語"),
new("ミステリー・サスペンス", "謎解きや犯罪、真相究明などのスリリングな物語"),
new("SF", "未来、科学技術、宇宙などを題材にした物語"),
new("ホラー", "恐怖や怪奇現象を扱った、読者の恐怖心をくすぐる物語"),
new("ロマンス", "恋愛や結婚、人間関係などを扱った、胸キュンな物語"),
new("コメディ", "ユーモアやギャグ、風刺などを交えた、読者を笑わせる物語"),
new("歴史小説", "過去の出来事や人物を題材にした、歴史の背景が感じられる物語"),
new("ノンフィクション・エッセイ", "実際の経験や知識、考えを綴った、リアルな物語"),
new("詩集", "感情や思考、風景などを言葉で表現した、韻文形式の作品集"),
];

[ObservableProperty]
private StoryGenre _selectedGenre;

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CreateStoryCommand))]
private string _instruction = "";

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(StartGenerateTaskCommand))]
[NotifyPropertyChangedFor(nameof(AiStoryTitle))]
private AiStory? _aiStory;

public string AiStoryTitle => AiStory?.Title ?? "";

public CreateStoryViewModel(IGenerationTaskService generationTaskService, IDialogService dialogService, IStoryCreatorService storyCreatorService)
{
_selectedGenre = Genres[0];
_generationTaskService = generationTaskService;
_dialogService = dialogService;
_storyCreatorService = storyCreatorService;
}

public bool CanCreateStory => !string.IsNullOrWhiteSpace(Instruction);

[RelayCommand(CanExecute = nameof(CanCreateStory))]
private async Task OnCreateStoryAsync(CancellationToken cancellationToken)
{
using var sr = new StringReader(await _storyCreatorService.CreateStoryAsync(SelectedGenre, Instruction, cancellationToken));
var serializer = new XmlSerializer(typeof(AiStory));
try
{
AiStory = (AiStory?)serializer.Deserialize(sr);
}
catch (InvalidOperationException)
{
await _dialogService.ShowAsync("生成失敗", "AIによるコードの生成に失敗しました", "OK", cancellationToken);
}
}

public bool CanStartGenerate => AiStory is not null;

[RelayCommand(CanExecute = nameof(CanStartGenerate))]
private void OnStartGenerateTask()
{
var aiStory = AiStory!;
AiStory = null;
_generationTaskService.Register(new GenerationTask(Guid.NewGuid(), aiStory, true));
}
}

Loading

0 comments on commit 49e0aae

Please sign in to comment.