Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ストーリー作成タブを追加 #37

Merged
merged 8 commits into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
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("", "", "")
aiueo-1234 marked this conversation as resolved.
Show resolved Hide resolved
})
).ToList(),
}
]
}]
};
}
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 IStoryCreaterService

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creater->Creator

{
/// <returns>XML</returns>
public ValueTask<string> CreateStoryAsync(StoryGenre genre, string intruction, CancellationToken cancellationToken);
aiueo-1234 marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 9 additions & 11 deletions KoeBook.Core/Models/AiStory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,21 @@
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(
public record Line(
[property: XmlElement("Text", typeof(TextElement), 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));

Expand Down
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 @@ -93,6 +93,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 @@ -107,6 +109,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<IStoryCreaterService, StoryCreaterServiceMock>();
})
.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/StoryCreaterServiceMock.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 StoryCreaterServiceMock : IStoryCreaterService
{
public ValueTask<string> CreateStoryAsync(StoryGenre genre, string intruction, 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 IStoryCreaterService _storyCreaterService;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

誤字修正漏れですCreater->Creator


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, IStoryCreaterService storyCreaterService)
{
_selectedGenre = Genres[0];
_generationTaskService = generationTaskService;
_dialogService = dialogService;
_storyCreaterService = storyCreaterService;
}

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

[RelayCommand(CanExecute = nameof(CanCreateStory))]
private async Task OnCreateStoryAsync(CancellationToken cancellationToken)
{
using var sr = new StringReader(await _storyCreaterService.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));
}
}

105 changes: 105 additions & 0 deletions KoeBook/Views/CreateStoryPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<Page
x:Class="KoeBook.Views.CreateStoryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:KoeBook.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:CommunityToolkit.WinUI.UI.Controls"
aiueo-1234 marked this conversation as resolved.
Show resolved Hide resolved
xmlns:models="using:KoeBook.Core.Models"
mc:Ignorable="d">

<Grid RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="240" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition MinHeight="220" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>

<TextBlock
Grid.ColumnSpan="2"
Style="{StaticResource TitleTextBlockStyle}"
Text="物語をAIで生成する"/>

<StackPanel
Grid.Row="1"
Margin="5">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="ジャンル"/>
<ComboBox
Margin="{StaticResource SmallTopMargin}"
Width="200"
ItemsSource="{x:Bind ViewModel.Genres}"
SelectedValue="{x:Bind ViewModel.SelectedGenre, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="models:StoryGenre">
<TextBlock Text="{x:Bind Genre}" ToolTipService.ToolTip="{x:Bind Description}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Margin="{StaticResource MediumTopMargin}"
Text="生成"/>
<Button
Margin="{StaticResource SmallTopMargin}"
Content="物語を生成する"
Command="{x:Bind ViewModel.CreateStoryCommand}"
Width="160"/>
<Button
Margin="{StaticResource SmallTopMargin}"
Content="EPUBを生成する"
Command="{x:Bind ViewModel.StartGenerateTaskCommand}"
Width="160" />
</StackPanel>

<StackPanel
Grid.Column="1"
Grid.Row="1">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="物語の概要" />
<TextBox
FontSize="16"
AcceptsReturn="True"
TextWrapping="Wrap"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Margin="{StaticResource SmallTopMargin}"
Text="{x:Bind ViewModel.Instruction, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Height="256" />
</StackPanel>

<TextBlock
Grid.Row="2"
Grid.ColumnSpan="2"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="出力された物語"
Margin="{StaticResource SmallTopMargin}" />

<ScrollView
Grid.Row="3"
Grid.ColumnSpan="2"
Background="{ThemeResource CardBackgroundFillColorDefault}"
BorderBrush="{ThemeResource CardStrokeColorDefault}"
BorderThickness="1"
CornerRadius="12">
<StackPanel Padding="16,12,16,24">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.AiStoryTitle, Mode=OneWay}" />
<TextBlock
x:Name="StoryContnent"
Margin="{StaticResource XSmallTopMargin}"
TextWrapping="Wrap"/>
</StackPanel>
</ScrollView>
</Grid>
</Page>
Loading
Loading