- first commit
This commit is contained in:
commit
6abb069dd0
104 changed files with 62867 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/bin/*
|
||||
/obj/*
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1011
.vs/AuthorBuddy.Web.slnx/config/applicationhost.config
Normal file
1011
.vs/AuthorBuddy.Web.slnx/config/applicationhost.config
Normal file
File diff suppressed because it is too large
Load diff
BIN
.vs/AuthorBuddy.Web.slnx/v18/.wsuo
Normal file
BIN
.vs/AuthorBuddy.Web.slnx/v18/.wsuo
Normal file
Binary file not shown.
62
.vs/AuthorBuddy.Web.slnx/v18/DocumentLayout.backup.json
Normal file
62
.vs/AuthorBuddy.Web.slnx/v18/DocumentLayout.backup.json
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AuthorBuddy.Web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 0,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "AuthorBuddy.Web.csproj",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeDocumentMoniker": "AuthorBuddy.Web.csproj",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeToolTip": "AuthorBuddy.Web.csproj",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
|
||||
"WhenOpened": "2026-05-14T10:14:30.128Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "appsettings.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
|
||||
"RelativeDocumentMoniker": "appsettings.json",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
|
||||
"RelativeToolTip": "appsettings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2026-05-14T10:14:25.605Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
62
.vs/AuthorBuddy.Web.slnx/v18/DocumentLayout.json
Normal file
62
.vs/AuthorBuddy.Web.slnx/v18/DocumentLayout.json
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
{
|
||||
"Version": 1,
|
||||
"WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\",
|
||||
"Documents": [
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:AuthorBuddy.Web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}"
|
||||
},
|
||||
{
|
||||
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
|
||||
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:appsettings.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
|
||||
}
|
||||
],
|
||||
"DocumentGroupContainers": [
|
||||
{
|
||||
"Orientation": 0,
|
||||
"VerticalTabListWidth": 256,
|
||||
"DocumentGroups": [
|
||||
{
|
||||
"DockedWidth": 200,
|
||||
"SelectedChildIndex": 2,
|
||||
"Children": [
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{cce594b6-0c39-4442-ba28-10c64ac7e89f}"
|
||||
},
|
||||
{
|
||||
"$type": "Bookmark",
|
||||
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 0,
|
||||
"Title": "AuthorBuddy.Web.csproj",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeDocumentMoniker": "AuthorBuddy.Web.csproj",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeToolTip": "AuthorBuddy.Web.csproj",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
|
||||
"WhenOpened": "2026-05-14T10:14:30.128Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 1,
|
||||
"Title": "appsettings.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
|
||||
"RelativeDocumentMoniker": "appsettings.json",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
|
||||
"RelativeToolTip": "appsettings.json",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001642|",
|
||||
"WhenOpened": "2026-05-14T10:14:25.605Z",
|
||||
"EditorCaption": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
.vs/AuthorBuddy.Web/DesignTimeBuild/.dtbcache.v2
Normal file
BIN
.vs/AuthorBuddy.Web/DesignTimeBuild/.dtbcache.v2
Normal file
Binary file not shown.
Binary file not shown.
BIN
.vs/AuthorBuddy.Web/v18/.futdcache.v2
Normal file
BIN
.vs/AuthorBuddy.Web/v18/.futdcache.v2
Normal file
Binary file not shown.
BIN
.vs/AuthorBuddy.Web/v18/.suo
Normal file
BIN
.vs/AuthorBuddy.Web/v18/.suo
Normal file
Binary file not shown.
3
.vs/ProjectSettings.json
Normal file
3
.vs/ProjectSettings.json
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"CurrentProjectSetting": null
|
||||
}
|
||||
7
.vs/VSWorkspaceState.json
Normal file
7
.vs/VSWorkspaceState.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"ExpandedNodes": [
|
||||
""
|
||||
],
|
||||
"SelectedNode": "\\AuthorBuddy.Web.csproj",
|
||||
"PreviewInSolutionExplorer": false
|
||||
}
|
||||
BIN
.vs/slnx.sqlite
Normal file
BIN
.vs/slnx.sqlite
Normal file
Binary file not shown.
22
AuthorBuddy.Web.csproj
Normal file
22
AuthorBuddy.Web.csproj
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" />
|
||||
<PackageReference Include="Ollama" Version="1.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="vec0.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
23
Components/App.razor
Normal file
23
Components/App.razor
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<ResourcePreloader />
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link id="theme-css" rel="stylesheet" href="@Assets["css/themes/light.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["AuthorBuddy.Web.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes />
|
||||
<ReconnectModal />
|
||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
165
Components/Layout/MainLayout.razor
Normal file
165
Components/Layout/MainLayout.razor
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
@inherits LayoutComponentBase
|
||||
@inject IProjectService ProjectService
|
||||
|
||||
<div class="page">
|
||||
<div class="main-content">
|
||||
<aside class="sidebar">
|
||||
<div class="nav-section">
|
||||
<button class="nav-section-header" @onclick='() => ToggleSection("file")'>
|
||||
<span class="nav-icon">@(activeSection == "file" ? "▼" : "▶")</span>
|
||||
Datei
|
||||
</button>
|
||||
@if (activeSection == "file")
|
||||
{
|
||||
<div class="nav-section-body">
|
||||
<button class="nav-item" @onclick="CreateNewProject">Neues Projekt</button>
|
||||
<button class="nav-item" @onclick="OpenProject">Öffnen</button>
|
||||
<div class="nav-separator"></div>
|
||||
<button class="nav-item" @onclick="ExitApp">Beenden</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<button class="nav-section-header" @onclick='() => ToggleSection("view")'>
|
||||
<span class="nav-icon">@(activeSection == "view" ? "▼" : "▶")</span>
|
||||
Ansicht
|
||||
</button>
|
||||
@if (activeSection == "view")
|
||||
{
|
||||
<div class="nav-section-body">
|
||||
<button class="nav-item" @onclick="OpenEditor">Editor öffnen</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<button class="nav-section-header" @onclick='() => ToggleSection("extras")'>
|
||||
<span class="nav-icon">@(activeSection == "extras" ? "▼" : "▶")</span>
|
||||
Extras
|
||||
</button>
|
||||
@if (activeSection == "extras")
|
||||
{
|
||||
<div class="nav-section-body">
|
||||
<button class="nav-item" @onclick='() => NavigateTo("/settings")'>Einstellungen</button>
|
||||
<button class="nav-item" @onclick='() => NavigateTo("/models")'>Modelle verwalten</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="nav-section">
|
||||
<button class="nav-section-header" @onclick='() => ToggleSection("projects")'>
|
||||
<span class="nav-icon">@(activeSection == "projects" ? "▼" : "▶")</span>
|
||||
Projekte
|
||||
</button>
|
||||
@if (activeSection == "projects")
|
||||
{
|
||||
<div class="nav-section-body">
|
||||
@if (projects == null)
|
||||
{
|
||||
<span class="nav-hint">Lade Projekte...</span>
|
||||
}
|
||||
else if (projects.Count == 0)
|
||||
{
|
||||
<span class="nav-hint">Keine Projekte vorhanden.</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var p in projects)
|
||||
{
|
||||
<button class="nav-item" @onclick='() => SelectProject(p)'>@p.Name</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="detail-panel">
|
||||
@Body
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="status-bar">
|
||||
<span class="status-dot connected"></span>
|
||||
<span class="status-text">@statusText</span>
|
||||
@if (isBusy)
|
||||
{
|
||||
<progress value="@operationProgress" max="100"></progress>
|
||||
}
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string? activeSection;
|
||||
private List<Project>? projects;
|
||||
private string statusText = "Bereit";
|
||||
private bool isBusy;
|
||||
private double operationProgress;
|
||||
|
||||
[Inject]
|
||||
private NavigationManager Navigation { get; set; } = default!;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadProjects();
|
||||
}
|
||||
|
||||
private async Task LoadProjects()
|
||||
{
|
||||
try
|
||||
{
|
||||
projects = await ProjectService.GetAllProjectsAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
projects = new();
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSection(string section)
|
||||
{
|
||||
activeSection = activeSection == section ? null : section;
|
||||
}
|
||||
|
||||
private void ExitApp()
|
||||
{
|
||||
activeSection = null;
|
||||
Navigation.NavigateTo("/");
|
||||
}
|
||||
|
||||
private void CreateNewProject()
|
||||
{
|
||||
activeSection = null;
|
||||
Navigation.NavigateTo("/editor?action=newproject");
|
||||
}
|
||||
|
||||
private void OpenProject()
|
||||
{
|
||||
activeSection = null;
|
||||
Navigation.NavigateTo("/editor?action=open");
|
||||
}
|
||||
|
||||
private void OpenEditor()
|
||||
{
|
||||
activeSection = null;
|
||||
Navigation.NavigateTo("/editor");
|
||||
}
|
||||
|
||||
private void NavigateTo(string path)
|
||||
{
|
||||
activeSection = null;
|
||||
Navigation.NavigateTo(path);
|
||||
}
|
||||
|
||||
private void SelectProject(Project project)
|
||||
{
|
||||
activeSection = null;
|
||||
Navigation.NavigateTo($"/editor?projectId={project.Id}");
|
||||
}
|
||||
|
||||
public void SetStatus(string text, bool busy = false, double progress = 0)
|
||||
{
|
||||
statusText = text;
|
||||
isBusy = busy;
|
||||
operationProgress = progress;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
31
Components/Layout/MainLayout.razor.css
Normal file
31
Components/Layout/MainLayout.razor.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
1
Components/Layout/NavMenu.razor
Normal file
1
Components/Layout/NavMenu.razor
Normal file
|
|
@ -0,0 +1 @@
|
|||
@* NavMenu is integrated into MainLayout *@
|
||||
105
Components/Layout/NavMenu.razor.css
Normal file
105
Components/Layout/NavMenu.razor.css
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
.navbar-toggler {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
width: 3.5rem;
|
||||
height: 2.5rem;
|
||||
color: white;
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.navbar-toggler:checked {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.nav-item:first-of-type {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.nav-item:last-of-type {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link {
|
||||
color: #d7d7d7;
|
||||
background: none;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255,255,255,0.37);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item ::deep .nav-link:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.navbar-toggler:checked ~ .nav-scrollable {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
/* Never collapse the sidebar for wide screens */
|
||||
display: block;
|
||||
|
||||
/* Allow sidebar to scroll for tall menus */
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
31
Components/Layout/ReconnectModal.razor
Normal file
31
Components/Layout/ReconnectModal.razor
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet>
|
||||
<div class="components-reconnect-container">
|
||||
<div class="components-rejoining-animation" aria-hidden="true">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<p class="components-reconnect-first-attempt-visible">
|
||||
Rejoining the server...
|
||||
</p>
|
||||
<p class="components-reconnect-repeated-attempt-visible">
|
||||
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
|
||||
</p>
|
||||
<p class="components-reconnect-failed-visible">
|
||||
Failed to rejoin.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
|
||||
Retry
|
||||
</button>
|
||||
<p class="components-pause-visible">
|
||||
The session has been paused by the server.
|
||||
</p>
|
||||
<p class="components-resume-failed-visible">
|
||||
Failed to resume the session.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
|
||||
Resume
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
157
Components/Layout/ReconnectModal.razor.css
Normal file
157
Components/Layout/ReconnectModal.razor.css
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
.components-reconnect-first-attempt-visible,
|
||||
.components-reconnect-repeated-attempt-visible,
|
||||
.components-reconnect-failed-visible,
|
||||
.components-pause-visible,
|
||||
.components-resume-failed-visible,
|
||||
.components-rejoining-animation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#components-reconnect-modal {
|
||||
background-color: white;
|
||||
width: 20rem;
|
||||
margin: 20vh auto;
|
||||
padding: 2rem;
|
||||
border: 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||
&[open]
|
||||
|
||||
{
|
||||
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#components-reconnect-modal::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-slideUp {
|
||||
0% {
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeOutOpacity {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.components-reconnect-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#components-reconnect-modal p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button {
|
||||
border: 0;
|
||||
background-color: #6b9ed2;
|
||||
color: white;
|
||||
padding: 4px 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:hover {
|
||||
background-color: #3b6ea2;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:active {
|
||||
background-color: #6b9ed2;
|
||||
}
|
||||
|
||||
.components-rejoining-animation {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div {
|
||||
position: absolute;
|
||||
border: 3px solid #0087ff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes components-rejoining-animation {
|
||||
0% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
4.9% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
63
Components/Layout/ReconnectModal.razor.js
Normal file
63
Components/Layout/ReconnectModal.razor.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Set up event handlers
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
retryButton.addEventListener("click", retry);
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
resumeButton.addEventListener("click", resume);
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
// Reconnect will asynchronously return:
|
||||
// - true to mean success
|
||||
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
|
||||
// - exception to mean we didn't reach the server (this can be sync or async)
|
||||
const successful = await Blazor.reconnect();
|
||||
if (!successful) {
|
||||
// We have been able to reach the server, but the circuit is no longer available.
|
||||
// We'll reload the page so the user can continue using the app as quickly as possible.
|
||||
const resumeSuccessful = await Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// We got an exception, server is currently unavailable
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
const successful = await Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
} catch {
|
||||
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
123
Components/Pages/Editor.razor
Normal file
123
Components/Pages/Editor.razor
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
@page "/editor"
|
||||
@page "/editor/{action}"
|
||||
@page "/editor/{action}/{projectId:int}"
|
||||
@inject IOllamaService OllamaService
|
||||
@inject IProjectService ProjectService
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>Editor - Author Buddy</PageTitle>
|
||||
|
||||
<div class="editor-container">
|
||||
<textarea @bind="editorContent" placeholder="Schreibe hier deinen Text..."></textarea>
|
||||
<div class="editor-toolbar">
|
||||
<span style="flex: 1; font-size: 12px; color: var(--border-color);">
|
||||
@statusText
|
||||
</span>
|
||||
@if (isChecking)
|
||||
{
|
||||
<progress value="@checkProgress" max="100"></progress>
|
||||
}
|
||||
<button @onclick="CheckStyle" disabled="@isChecking">
|
||||
Stil prüfen
|
||||
</button>
|
||||
<button @onclick="SpellCheck" disabled="@isChecking">
|
||||
Rechtschreibung prüfen
|
||||
</button>
|
||||
<button @onclick="RagSearch">
|
||||
Fakten suchen (RAG)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(analysisResult))
|
||||
{
|
||||
<div style="border-top: 2px solid var(--border-color); padding: 12px; max-height: 300px; overflow-y: auto;">
|
||||
<h4 style="margin-bottom: 8px;">Ergebnis:</h4>
|
||||
<div style="white-space: pre-wrap; font-size: 13px; line-height: 1.5;">@analysisResult</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string? Action { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int? ProjectId { get; set; }
|
||||
|
||||
private string editorContent = string.Empty;
|
||||
private string statusText = "Bereit";
|
||||
private bool isChecking;
|
||||
private double checkProgress;
|
||||
private string analysisResult = string.Empty;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (Action == "newproject")
|
||||
{
|
||||
// The native folder dialog isn't available in Blazor.
|
||||
// In a real app, you'd provide a form to enter project name and path.
|
||||
statusText = "Bitte nutze die Einstellungen zum Erstellen eines neuen Projekts.";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckStyle()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editorContent)) return;
|
||||
|
||||
isChecking = true;
|
||||
statusText = "Ollama analysiert Stil...";
|
||||
checkProgress = 50;
|
||||
|
||||
try
|
||||
{
|
||||
analysisResult = await OllamaService.StreamStyleCheckAsync(editorContent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
analysisResult = $"Fehler: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
statusText = "Bereit";
|
||||
isChecking = false;
|
||||
checkProgress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SpellCheck()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editorContent)) return;
|
||||
|
||||
isChecking = true;
|
||||
statusText = "Ollama prüft Rechtschreibung...";
|
||||
checkProgress = 50;
|
||||
|
||||
try
|
||||
{
|
||||
analysisResult = await OllamaService.QuickSpellCheckAsync(editorContent);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
analysisResult = $"Fehler: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
statusText = "Bereit";
|
||||
isChecking = false;
|
||||
checkProgress = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RagSearch()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(editorContent)) return;
|
||||
|
||||
isChecking = true;
|
||||
statusText = "Suche verwandte Fakten...";
|
||||
|
||||
await Task.Delay(500);
|
||||
analysisResult = "RAG-Suche: Stelle sicher, dass ein Projekt geladen und indexiert wurde.";
|
||||
statusText = "Bereit";
|
||||
isChecking = false;
|
||||
}
|
||||
}
|
||||
21
Components/Pages/Error.razor
Normal file
21
Components/Pages/Error.razor
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
@page "/Error"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Fehler</PageTitle>
|
||||
|
||||
<h1>Ein Fehler ist aufgetreten</h1>
|
||||
<p>Entschuldigung, es ist ein unerwarteter Fehler aufgetreten.</p>
|
||||
<a href="/">Zurück zur Startseite</a>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (HttpContext is not null)
|
||||
{
|
||||
HttpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Components/Pages/Home.razor
Normal file
41
Components/Pages/Home.razor
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
@page "/"
|
||||
@inject IOllamaService OllamaService
|
||||
@inject IProjectService ProjectService
|
||||
<PageTitle>Dashboard - Author Buddy</PageTitle>
|
||||
|
||||
<div class="tab-bar">
|
||||
<div class="tab @GetTabClass("rag")" @onclick='() => activeTab = "rag"'>Gefundene Fakten (RAG)</div>
|
||||
<div class="tab @GetTabClass("style")" @onclick='() => activeTab = "style"'>Stil-Analyse</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
@if (activeTab == "rag")
|
||||
{
|
||||
@if (foundFacts.Count == 0)
|
||||
{
|
||||
<p style="color: var(--border-color);">Keine Fakten gefunden. Öffne ein Projekt und scanne die Dateien.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var fact in foundFacts)
|
||||
{
|
||||
<div class="fact-card">@fact</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (activeTab == "style")
|
||||
{
|
||||
<textarea style="width: 100%; min-height: 300px; background: var(--editor-bg); color: var(--text-color); border: 1px solid var(--border-color); padding: 8px; font-family: Consolas;" readonly>@styleSuggestions</textarea>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string activeTab = "rag";
|
||||
private List<string> foundFacts = new();
|
||||
private string styleSuggestions = string.Empty;
|
||||
|
||||
private string GetTabClass(string tab)
|
||||
{
|
||||
return activeTab == tab ? "active" : "";
|
||||
}
|
||||
}
|
||||
143
Components/Pages/Models.razor
Normal file
143
Components/Pages/Models.razor
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
@page "/models"
|
||||
@inject IOllamaService OllamaService
|
||||
@inject NavigationManager Navigation
|
||||
@using Ollama
|
||||
|
||||
<PageTitle>Modelle - Author Buddy</PageTitle>
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<h3>Ollama Modelle verwalten</h3>
|
||||
<button @onclick="RefreshModels" disabled="@isLoading">Aktualisieren</button>
|
||||
</div>
|
||||
|
||||
@if (isLoading)
|
||||
{
|
||||
<p>Lade Modelle...</p>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div style="color: red; margin-bottom: 12px;">@errorMessage</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="model-list">
|
||||
@foreach (var model in localModels)
|
||||
{
|
||||
<div class="model-card">
|
||||
<div class="model-name">@model.Model1</div>
|
||||
<div class="model-details">
|
||||
Größe: @FormatSize(model.Size) |
|
||||
Geändert: @model.ModifiedAt?.ToString("g")
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div style="margin-top: 24px; border-top: 1px solid var(--border-color); padding-top: 16px;">
|
||||
<h4 style="margin-bottom: 8px;">Neues Modell herunterladen</h4>
|
||||
<div style="display: flex; gap: 8px; align-items: flex-end;">
|
||||
<div class="form-group" style="flex: 1; margin-bottom: 0;">
|
||||
<label for="pullModelName">Modellname:</label>
|
||||
<input id="pullModelName" @bind="modelToPull" placeholder="z.B. llama3.2" />
|
||||
</div>
|
||||
<button @onclick="PullModel" disabled="@(isPulling || string.IsNullOrWhiteSpace(modelToPull))">
|
||||
Herunterladen
|
||||
</button>
|
||||
</div>
|
||||
@if (isPulling)
|
||||
{
|
||||
<div style="margin-top: 8px;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: @pullPercentDisplay"></div>
|
||||
</div>
|
||||
<div style="font-size: 12px; margin-top: 4px;">@pullStatus (@pullPercentDisplay)</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (pullResult != null)
|
||||
{
|
||||
<div style="margin-top: 12px; padding: 8px; background: var(--card-bg); border: 1px solid var(--accent-color); font-size: 13px;">
|
||||
@pullResult
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Ollama.Model> localModels = new();
|
||||
private bool isLoading;
|
||||
private string? errorMessage;
|
||||
private string modelToPull = string.Empty;
|
||||
private bool isPulling;
|
||||
private double pullProgress;
|
||||
private string pullStatus = string.Empty;
|
||||
private string? pullResult;
|
||||
private string pullPercentDisplay => $"{pullProgress:F0}%";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshModels();
|
||||
}
|
||||
|
||||
private async Task RefreshModels()
|
||||
{
|
||||
isLoading = true;
|
||||
errorMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
var models = await OllamaService.GetLocalModelsAsync();
|
||||
localModels = models.ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Fehler beim Laden der Modelle: {ex.Message}";
|
||||
localModels = new();
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PullModel()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(modelToPull)) return;
|
||||
|
||||
isPulling = true;
|
||||
pullProgress = 0;
|
||||
pullStatus = "Starte Download...";
|
||||
pullResult = null;
|
||||
|
||||
try
|
||||
{
|
||||
var progress = new Progress<ModelStatus>(status =>
|
||||
{
|
||||
pullProgress = status.Progress;
|
||||
pullStatus = status.Status;
|
||||
InvokeAsync(StateHasChanged);
|
||||
});
|
||||
|
||||
await OllamaService.PullModelAsync(modelToPull, progress);
|
||||
pullResult = $"Modell '{modelToPull}' erfolgreich heruntergeladen.";
|
||||
modelToPull = string.Empty;
|
||||
await RefreshModels();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
pullResult = $"Fehler: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
isPulling = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatSize(long? bytes)
|
||||
{
|
||||
if (!bytes.HasValue) return "Unbekannt";
|
||||
if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB";
|
||||
if (bytes < 1024 * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB";
|
||||
return $"{bytes / (1024.0 * 1024 * 1024):F1} GB";
|
||||
}
|
||||
}
|
||||
21
Components/Pages/NotFound.razor
Normal file
21
Components/Pages/NotFound.razor
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
@page "/not-found"
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Seite nicht gefunden</PageTitle>
|
||||
|
||||
<h1>404 - Seite nicht gefunden</h1>
|
||||
<p>Die angeforderte Seite existiert nicht.</p>
|
||||
<a href="/">Zurück zur Startseite</a>
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (HttpContext is not null)
|
||||
{
|
||||
HttpContext.Response.StatusCode = StatusCodes.Status404NotFound;
|
||||
}
|
||||
}
|
||||
}
|
||||
200
Components/Pages/Settings.razor
Normal file
200
Components/Pages/Settings.razor
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
@page "/settings"
|
||||
@inject IDbContextFactory<AppDbContext> ContextFactory
|
||||
@inject IOllamaService OllamaService
|
||||
@inject OllamaSettings OllamaConfig
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject IProjectService ProjectService
|
||||
|
||||
<PageTitle>Einstellungen - Author Buddy</PageTitle>
|
||||
|
||||
<div class="settings-page">
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Modelle</h3>
|
||||
<div class="form-group">
|
||||
<label for="ragModel">RAG-Modell (Faktenabfrage):</label>
|
||||
<select id="ragModel" @bind="ragModel">
|
||||
@if (availableModels.Count == 0)
|
||||
{
|
||||
<option value="">Keine Modelle gefunden</option>
|
||||
}
|
||||
@foreach (var m in availableModels)
|
||||
{
|
||||
<option value="@m.Model1">@m.Model1</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="styleModel">Stil-Modell (Lektorat):</label>
|
||||
<select id="styleModel" @bind="styleModel">
|
||||
@if (availableModels.Count == 0)
|
||||
{
|
||||
<option value="">Keine Modelle gefunden</option>
|
||||
}
|
||||
@foreach (var m in availableModels)
|
||||
{
|
||||
<option value="@m.Model1">@m.Model1</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spellingModel">Rechtschreibprüfungs-Modell:</label>
|
||||
<select id="spellingModel" @bind="spellingModel">
|
||||
@if (availableModels.Count == 0)
|
||||
{
|
||||
<option value="">Keine Modelle gefunden</option>
|
||||
}
|
||||
@foreach (var m in availableModels)
|
||||
{
|
||||
<option value="@m.Model1">@m.Model1</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Ollama Server</h3>
|
||||
<div class="form-group">
|
||||
<label for="ollamaUrl">Server-URL:</label>
|
||||
<input id="ollamaUrl" @bind="ollamaUrl" placeholder="http://localhost:11434" />
|
||||
<div class="form-hint">Adresse des Ollama-Servers (z. B. http://192.168.1.100:11434)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Design</h3>
|
||||
<div class="form-group">
|
||||
<label for="themeSelect">Theme:</label>
|
||||
<select id="themeSelect" @bind="selectedTheme">
|
||||
<option value="light">Hell</option>
|
||||
<option value="dark">Dunkel</option>
|
||||
<option value="w95">W95 (Retro)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">Neues Projekt</h3>
|
||||
<div class="form-group">
|
||||
<label for="projectName">Projektname:</label>
|
||||
<input id="projectName" @bind="newProjectName" placeholder="Projektname" />
|
||||
<div class="form-hint">Der Projektordner wird in "Dokumente\AuthorBuddy\" erstellt.</div>
|
||||
</div>
|
||||
<button @onclick="CreateProject" disabled="@(string.IsNullOrWhiteSpace(newProjectName))">Projekt erstellen</button>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="primary" @onclick="SaveSettings">Speichern</button>
|
||||
<button @onclick='() => Navigation.NavigateTo("/")'>Abbrechen</button>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(statusMessage))
|
||||
{
|
||||
<div class="settings-status">@statusMessage</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string ragModel = "llama3.2";
|
||||
private string styleModel = "llama3.2";
|
||||
private string spellingModel = "phi3:mini";
|
||||
private string selectedTheme = "light";
|
||||
private string ollamaUrl = "http://localhost:11434";
|
||||
private string newProjectName = string.Empty;
|
||||
private string statusMessage = string.Empty;
|
||||
private List<Ollama.Model> availableModels = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||
|
||||
ragModel = await GetSettingAsync(context, "ollama_rag_model") ?? "llama3.2";
|
||||
styleModel = await GetSettingAsync(context, "ollama_style_model") ?? "llama3.2";
|
||||
spellingModel = await GetSettingAsync(context, "ollama_spelling_model") ?? "phi3:mini";
|
||||
selectedTheme = await GetSettingAsync(context, "theme") ?? "light";
|
||||
ollamaUrl = await GetSettingAsync(context, "ollama_url") ?? "http://localhost:11434";
|
||||
|
||||
try
|
||||
{
|
||||
var models = await OllamaService.GetLocalModelsAsync();
|
||||
availableModels = models.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
availableModels = new();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<string?> GetSettingAsync(AppDbContext context, string key)
|
||||
{
|
||||
var setting = await context.Settings.FindAsync(key);
|
||||
return setting?.Value;
|
||||
}
|
||||
|
||||
private static async Task SaveSettingAsync(AppDbContext context, string key, string value)
|
||||
{
|
||||
var existing = await context.Settings.FindAsync(key);
|
||||
if (existing != null)
|
||||
{
|
||||
existing.Value = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Settings.Add(new SettingEntity { Key = key, Value = value });
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSettings()
|
||||
{
|
||||
await using var context = await ContextFactory.CreateDbContextAsync();
|
||||
|
||||
await SaveSettingAsync(context, "ollama_rag_model", ragModel);
|
||||
await SaveSettingAsync(context, "ollama_style_model", styleModel);
|
||||
await SaveSettingAsync(context, "ollama_spelling_model", spellingModel);
|
||||
await SaveSettingAsync(context, "theme", selectedTheme);
|
||||
await SaveSettingAsync(context, "ollama_url", ollamaUrl);
|
||||
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
OllamaConfig.OllamaUrl = ollamaUrl;
|
||||
OllamaConfig.ChatModel = ragModel;
|
||||
OllamaConfig.SpellingModel = spellingModel;
|
||||
await OllamaService.ReconfigureAsync(ollamaUrl);
|
||||
|
||||
statusMessage = "Einstellungen gespeichert.";
|
||||
await ApplyTheme(selectedTheme);
|
||||
}
|
||||
|
||||
private async Task CreateProject()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(newProjectName)) return;
|
||||
|
||||
try
|
||||
{
|
||||
string rootPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
||||
"AuthorBuddy",
|
||||
newProjectName);
|
||||
|
||||
await ProjectService.CreateNewProjectAsync(newProjectName, rootPath);
|
||||
statusMessage = $"Projekt '{newProjectName}' erstellt unter: {rootPath}";
|
||||
newProjectName = string.Empty;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
statusMessage = $"Fehler beim Erstellen: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ApplyTheme(string theme)
|
||||
{
|
||||
await JSRuntime.InvokeVoidAsync("eval",
|
||||
$"document.getElementById('theme-css')?.remove(); " +
|
||||
$"var link = document.createElement('link'); " +
|
||||
$"link.id = 'theme-css'; " +
|
||||
$"link.rel = 'stylesheet'; " +
|
||||
$"link.href = 'css/themes/{theme}.css'; " +
|
||||
$"document.head.appendChild(link);");
|
||||
}
|
||||
}
|
||||
6
Components/Routes.razor
Normal file
6
Components/Routes.razor
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
15
Components/_Imports.razor
Normal file
15
Components/_Imports.razor
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using AuthorBuddy.Web
|
||||
@using AuthorBuddy.Web.Components
|
||||
@using AuthorBuddy.Web.Components.Layout
|
||||
@using AuthorBuddy.Web.Models
|
||||
@using AuthorBuddy.Web.Services
|
||||
@using AuthorBuddy.Web.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
89
Data/AppDbContext.cs
Normal file
89
Data/AppDbContext.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AuthorBuddy.Web.Data;
|
||||
|
||||
public class AppDbContext : DbContext
|
||||
{
|
||||
public DbSet<ProjectEntity> Projects => Set<ProjectEntity>();
|
||||
public DbSet<SettingEntity> Settings => Set<SettingEntity>();
|
||||
public DbSet<DocumentMetaEntity> DocumentMeta => Set<DocumentMetaEntity>();
|
||||
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<ProjectEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("Projects");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.Name).IsRequired();
|
||||
entity.Property(e => e.RootPath).IsRequired();
|
||||
entity.Property(e => e.LastOpened).HasColumnName("last_opened");
|
||||
});
|
||||
|
||||
modelBuilder.Entity<SettingEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("Settings");
|
||||
entity.HasKey(e => e.Key);
|
||||
entity.Property(e => e.Key).HasColumnName("key");
|
||||
entity.Property(e => e.Value).HasColumnName("value").IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity<DocumentMetaEntity>(entity =>
|
||||
{
|
||||
entity.ToTable("DocumentMeta");
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.FilePath).HasColumnName("file_path").IsRequired();
|
||||
entity.Property(e => e.LastModified).HasColumnName("last_modified");
|
||||
entity.Property(e => e.FileType).HasColumnName("file_type");
|
||||
entity.Property(e => e.ProjectId).HasColumnName("project_id");
|
||||
});
|
||||
}
|
||||
|
||||
public static void InitializeDatabase(string connectionString)
|
||||
{
|
||||
using var connection = new SqliteConnection(connectionString);
|
||||
connection.Open();
|
||||
connection.EnableExtensions(true);
|
||||
|
||||
try
|
||||
{
|
||||
connection.LoadExtension("vec0");
|
||||
using var testCmd = connection.CreateCommand();
|
||||
testCmd.CommandText = "SELECT vec_version();";
|
||||
var version = testCmd.ExecuteScalar();
|
||||
Console.WriteLine($"sqlite-vec Version: {version}");
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
{
|
||||
Console.WriteLine($"Fehler beim Laden von vec0: {ex.Message}");
|
||||
}
|
||||
|
||||
using var createVecCmd = connection.CreateCommand();
|
||||
createVecCmd.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS Projects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
root_path TEXT NOT NULL,
|
||||
last_opened DATETIME
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS Settings (
|
||||
key TEXT NOT NULL UNIQUE,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS DocumentMeta (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
project_id INTEGER,
|
||||
file_path TEXT UNIQUE,
|
||||
last_modified DATETIME,
|
||||
hash TEXT,
|
||||
file_type TEXT,
|
||||
FOREIGN KEY(project_id) REFERENCES Projects(id)
|
||||
);
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS vec_documents USING vec0(
|
||||
embedding float[1024]
|
||||
);";
|
||||
createVecCmd.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
23
Data/DocumentMetaEntity.cs
Normal file
23
Data/DocumentMetaEntity.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AuthorBuddy.Web.Data;
|
||||
|
||||
[Table("DocumentMeta")]
|
||||
public class DocumentMetaEntity
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
public int? ProjectId { get; set; }
|
||||
|
||||
[Required]
|
||||
public string FilePath { get; set; } = string.Empty;
|
||||
|
||||
public DateTime? LastModified { get; set; }
|
||||
|
||||
public string? Hash { get; set; }
|
||||
|
||||
public string? FileType { get; set; }
|
||||
}
|
||||
20
Data/ProjectEntity.cs
Normal file
20
Data/ProjectEntity.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AuthorBuddy.Web.Data;
|
||||
|
||||
[Table("Projects")]
|
||||
public class ProjectEntity
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string RootPath { get; set; } = string.Empty;
|
||||
|
||||
public DateTime? LastOpened { get; set; }
|
||||
}
|
||||
14
Data/SettingEntity.cs
Normal file
14
Data/SettingEntity.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace AuthorBuddy.Web.Data;
|
||||
|
||||
[Table("Settings")]
|
||||
public class SettingEntity
|
||||
{
|
||||
[Key]
|
||||
public string Key { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
9
Models/Chapter.cs
Normal file
9
Models/Chapter.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace AuthorBuddy.Web.Models;
|
||||
|
||||
public class Chapter
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string FilePath { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public List<string> Characters { get; set; } = new();
|
||||
}
|
||||
14
Models/Constants.cs
Normal file
14
Models/Constants.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
namespace AuthorBuddy.Web.Models;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static readonly string[] SubFolders = {
|
||||
"00_Drafts",
|
||||
"01_Fakten/Personen",
|
||||
"01_Fakten/Orte",
|
||||
"02_Archive",
|
||||
"Exports"
|
||||
};
|
||||
|
||||
public const string SQLiteConnection = "Data Source=author_brain.db";
|
||||
}
|
||||
18
Models/ModelStatus.cs
Normal file
18
Models/ModelStatus.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
using Ollama;
|
||||
|
||||
namespace AuthorBuddy.Web.Models;
|
||||
|
||||
public class ModelStatus
|
||||
{
|
||||
public string Status { get; private set; } = "Unknown";
|
||||
public double Progress { get; private set; }
|
||||
|
||||
public void SetModelStatus(PullModelResponse modelResponse)
|
||||
{
|
||||
if (modelResponse.Total > 0 && modelResponse.Completed.HasValue && modelResponse.Completed > 0)
|
||||
{
|
||||
Progress = (double)modelResponse.Completed.Value / (double)modelResponse.Total * 100;
|
||||
Status = modelResponse.Status?.ToString() ?? "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Models/OllamaSettings.cs
Normal file
27
Models/OllamaSettings.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
namespace AuthorBuddy.Web.Models;
|
||||
|
||||
public class OllamaSettings
|
||||
{
|
||||
public string ChatModel { get; set; } = "llama3.2";
|
||||
public string EmbedModel { get; set; } = "mxbai-embed-large";
|
||||
public string SpellingModel { get; set; } = "phi3:mini";
|
||||
public string OllamaUrl { get; set; } = "http://localhost:11434";
|
||||
public string Theme { get; set; } = "light";
|
||||
|
||||
public string LektorSystemPrompt { get; private set; } = "Du bist ein erfahrener Lektor. Analysiere den Text des Autors. " +
|
||||
"Optimiere ihn auf einen lebendigen, anschaulichen und subjektiven Stil. " +
|
||||
"Nutze starke Verben, vermeide unnötige Adjektive und achte auf 'Show, don't tell'. " +
|
||||
"Antworte direkt mit dem verbesserten Text oder konkreten Vorschlägen.";
|
||||
|
||||
private string FactCheckTemplate { get; set; } = "FAKTEN AUS DER DATENBANK:\n{0}\n\n" +
|
||||
"FRAGE / KONTEXT:\n{1}\n\n" +
|
||||
"Basierend auf den Fakten, schreibe eine kurze, lebendige Ergänzung oder Korrektur.";
|
||||
|
||||
public string SpellingPrompt { get; set; } = "Du bist ein Rechtschreibprüfer. Korrigiere NUR Fehler (Rechtschreibung/Grammatik). " +
|
||||
"Ändere den Stil nicht. Gib nur den korrigierten Text zurück.";
|
||||
|
||||
public string GetFactCheckPrompt(string question, string facts)
|
||||
{
|
||||
return string.Format(FactCheckTemplate, question, facts);
|
||||
}
|
||||
}
|
||||
8
Models/Project.cs
Normal file
8
Models/Project.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
namespace AuthorBuddy.Web.Models;
|
||||
|
||||
public class Project
|
||||
{
|
||||
public int Id { get; internal set; }
|
||||
public string Name { get; internal set; } = string.Empty;
|
||||
public string RootPath { get; internal set; } = string.Empty;
|
||||
}
|
||||
35
Program.cs
Normal file
35
Program.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using AuthorBuddy.Web.Components;
|
||||
using AuthorBuddy.Web.Data;
|
||||
using AuthorBuddy.Web.Models;
|
||||
using AuthorBuddy.Web.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.Services.AddSingleton<OllamaSettings>();
|
||||
builder.Services.AddSingleton<IOllamaService, OllamaService>();
|
||||
builder.Services.AddSingleton<IFileScannerService, FileScannerService>();
|
||||
builder.Services.AddSingleton<IProjectService, ProjectService>();
|
||||
|
||||
builder.Services.AddDbContextFactory<AppDbContext>(options =>
|
||||
options.UseSqlite(Constants.SQLiteConnection));
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
AppDbContext.InitializeDatabase(Constants.SQLiteConnection);
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
}
|
||||
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
14
Properties/launchSettings.json
Normal file
14
Properties/launchSettings.json
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5216",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Services/FileScannerService.cs
Normal file
101
Services/FileScannerService.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using AuthorBuddy.Web.Data;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AuthorBuddy.Web.Services;
|
||||
|
||||
public class FileScannerService : IFileScannerService
|
||||
{
|
||||
private readonly IOllamaService _ollama;
|
||||
private readonly IDbContextFactory<AppDbContext> _contextFactory;
|
||||
private readonly string _connectionString;
|
||||
|
||||
public FileScannerService(IOllamaService ollama, IDbContextFactory<AppDbContext> contextFactory)
|
||||
{
|
||||
_ollama = ollama;
|
||||
_contextFactory = contextFactory;
|
||||
_connectionString = Models.Constants.SQLiteConnection;
|
||||
}
|
||||
|
||||
public async Task ScanProjectFolderAsync(int projectId, string rootPath, IProgress<string> progress)
|
||||
{
|
||||
var files = Directory.GetFiles(rootPath, "*.md", SearchOption.AllDirectories)
|
||||
.Where(f => f.Contains("00_Drafts") || f.Contains("01_Fakten"));
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var fileInfo = new FileInfo(file);
|
||||
bool needsUpdate = await CheckIfUpdateRequired(projectId, file, fileInfo.LastWriteTimeUtc);
|
||||
|
||||
if (needsUpdate)
|
||||
{
|
||||
progress?.Report($"Verarbeite: {fileInfo.Name}...");
|
||||
await ProcessAndVectorizeFile(projectId, file, fileInfo.LastWriteTimeUtc);
|
||||
}
|
||||
}
|
||||
progress?.Report("Scan abgeschlossen.");
|
||||
}
|
||||
|
||||
private async Task<bool> CheckIfUpdateRequired(int projectId, string file, DateTime lastWriteTimeUtc)
|
||||
{
|
||||
await using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
var meta = await context.DocumentMeta
|
||||
.Where(m => m.ProjectId == projectId && m.FilePath == file)
|
||||
.Select(m => m.LastModified)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (meta == null) return true;
|
||||
return meta.Value < lastWriteTimeUtc;
|
||||
}
|
||||
|
||||
private async Task<bool> ProcessAndVectorizeFile(int projectId, string path, DateTime lastModified)
|
||||
{
|
||||
string content = await File.ReadAllTextAsync(path);
|
||||
|
||||
var vector = await _ollama.GetEmbeddingAsync(content);
|
||||
|
||||
using var connection = new SqliteConnection(_connectionString);
|
||||
connection.Open();
|
||||
using var transaction = connection.BeginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
var cmdMeta = connection.CreateCommand();
|
||||
cmdMeta.CommandText = @"
|
||||
INSERT INTO DocumentMeta (project_id, file_path, last_modified)
|
||||
VALUES (@pid, @path, @mod)
|
||||
ON CONFLICT(file_path) DO UPDATE SET last_modified = @mod
|
||||
RETURNING id;";
|
||||
cmdMeta.Parameters.AddWithValue("@pid", projectId);
|
||||
cmdMeta.Parameters.AddWithValue("@path", path);
|
||||
cmdMeta.Parameters.AddWithValue("@mod", lastModified);
|
||||
|
||||
var docId = await cmdMeta.ExecuteScalarAsync();
|
||||
|
||||
byte[] blob = ConvertVectorToBytes(vector);
|
||||
var cmdVec = connection.CreateCommand();
|
||||
cmdVec.CommandText = "INSERT INTO vec_documents(rowid, embedding) VALUES (@id, @vec) " +
|
||||
"ON CONFLICT(rowid) DO UPDATE SET embedding = @vec;";
|
||||
cmdVec.Parameters.AddWithValue("@id", docId);
|
||||
cmdVec.Parameters.AddWithValue("@vec", blob);
|
||||
await cmdVec.ExecuteNonQueryAsync();
|
||||
|
||||
transaction.Commit();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
transaction.Rollback();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ConvertVectorToBytes(IList<double> vector)
|
||||
{
|
||||
float[] floatVector = vector.Select(d => (float)d).ToArray();
|
||||
byte[] bytes = new byte[floatVector.Length * sizeof(float)];
|
||||
Buffer.BlockCopy(floatVector, 0, bytes, 0, bytes.Length);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
6
Services/IFileScannerService.cs
Normal file
6
Services/IFileScannerService.cs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
namespace AuthorBuddy.Web.Services;
|
||||
|
||||
public interface IFileScannerService
|
||||
{
|
||||
Task ScanProjectFolderAsync(int projectId, string rootPath, IProgress<string> progress);
|
||||
}
|
||||
15
Services/IOllamaService.cs
Normal file
15
Services/IOllamaService.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using AuthorBuddy.Web.Models;
|
||||
using Ollama;
|
||||
|
||||
namespace AuthorBuddy.Web.Services;
|
||||
|
||||
public interface IOllamaService
|
||||
{
|
||||
Task<IEnumerable<Model>> GetLocalModelsAsync();
|
||||
Task PullModelAsync(string modelName, IProgress<ModelStatus> progress);
|
||||
Task<IList<double>> GetEmbeddingAsync(string text);
|
||||
Task<string> StreamStyleCheckAsync(string input);
|
||||
Task<string> AnswerBasedOnFactsAsync(string question, string facts);
|
||||
Task<string> QuickSpellCheckAsync(string text);
|
||||
Task ReconfigureAsync(string ollamaUrl);
|
||||
}
|
||||
10
Services/IProjectService.cs
Normal file
10
Services/IProjectService.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using AuthorBuddy.Web.Models;
|
||||
|
||||
namespace AuthorBuddy.Web.Services;
|
||||
|
||||
public interface IProjectService
|
||||
{
|
||||
Task<int> CreateNewProjectAsync(string projectName, string rootPath);
|
||||
Task<List<Project>> GetAllProjectsAsync();
|
||||
Task SyncProjectFilesAsync(int projectId, string directoryPath, IProgress<string> progress);
|
||||
}
|
||||
84
Services/OllamaService.cs
Normal file
84
Services/OllamaService.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using AuthorBuddy.Web.Models;
|
||||
using Ollama;
|
||||
|
||||
namespace AuthorBuddy.Web.Services;
|
||||
|
||||
public class OllamaService : IOllamaService, IDisposable
|
||||
{
|
||||
private readonly OllamaSettings _settings;
|
||||
private OllamaApiClient? _client;
|
||||
private string _currentUrl = "http://localhost:11434";
|
||||
|
||||
public OllamaService(OllamaSettings settings)
|
||||
{
|
||||
_settings = settings;
|
||||
_currentUrl = settings.OllamaUrl;
|
||||
_client = new OllamaApiClient(baseUri: new Uri(_currentUrl));
|
||||
}
|
||||
|
||||
public Task ReconfigureAsync(string ollamaUrl)
|
||||
{
|
||||
if (_currentUrl != ollamaUrl)
|
||||
{
|
||||
_currentUrl = ollamaUrl;
|
||||
_client?.Dispose();
|
||||
_client = new OllamaApiClient(baseUri: new Uri(ollamaUrl));
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private OllamaApiClient Client => _client ??= new OllamaApiClient(baseUri: new Uri(_currentUrl));
|
||||
|
||||
public async Task<IEnumerable<Model>> GetLocalModelsAsync()
|
||||
{
|
||||
var models = await Client.Models.ListModelsAsync();
|
||||
return models.Models ?? Enumerable.Empty<Ollama.Model>();
|
||||
}
|
||||
|
||||
public async Task PullModelAsync(string modelName, IProgress<ModelStatus> progress)
|
||||
{
|
||||
var p = new ModelStatus();
|
||||
await foreach (var response in Client.Models.PullModelAsync(modelName))
|
||||
{
|
||||
p.SetModelStatus(response);
|
||||
progress?.Report(p);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IList<double>> GetEmbeddingAsync(string text)
|
||||
{
|
||||
var response = await Client.Embeddings.GenerateEmbeddingAsync(
|
||||
model: _settings.EmbedModel,
|
||||
prompt: text);
|
||||
return response.Embedding ?? Array.Empty<double>();
|
||||
}
|
||||
|
||||
public async Task<string> StreamStyleCheckAsync(string input)
|
||||
{
|
||||
var systemPrompt = _settings.LektorSystemPrompt;
|
||||
var chat = Client.Chat(_settings.ChatModel, systemPrompt);
|
||||
var message = await chat.SendAsync(input);
|
||||
return message.Content;
|
||||
}
|
||||
|
||||
public async Task<string> AnswerBasedOnFactsAsync(string question, string facts)
|
||||
{
|
||||
var prompt = _settings.GetFactCheckPrompt(question, facts);
|
||||
var chat = Client.Chat(_settings.ChatModel);
|
||||
var message = await chat.SendAsync(prompt);
|
||||
return message.Content;
|
||||
}
|
||||
|
||||
public async Task<string> QuickSpellCheckAsync(string text)
|
||||
{
|
||||
var systemPrompt = _settings.SpellingPrompt;
|
||||
var chat = Client.Chat(_settings.SpellingModel, systemPrompt);
|
||||
var message = await chat.SendAsync(text);
|
||||
return message.Content;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_client?.Dispose();
|
||||
}
|
||||
}
|
||||
63
Services/ProjectService.cs
Normal file
63
Services/ProjectService.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using AuthorBuddy.Web.Data;
|
||||
using AuthorBuddy.Web.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace AuthorBuddy.Web.Services;
|
||||
|
||||
public class ProjectService : IProjectService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _contextFactory;
|
||||
private readonly IFileScannerService _fileScannerService;
|
||||
|
||||
public ProjectService(IDbContextFactory<AppDbContext> contextFactory, IFileScannerService fileScannerService)
|
||||
{
|
||||
_contextFactory = contextFactory;
|
||||
_fileScannerService = fileScannerService;
|
||||
}
|
||||
|
||||
public async Task<int> CreateNewProjectAsync(string projectName, string rootPath)
|
||||
{
|
||||
string[] subFolders = { "00_Drafts", "01_Fakten", "02_Characters", "03_Archive" };
|
||||
foreach (var subFolder in subFolders)
|
||||
{
|
||||
Directory.CreateDirectory(Path.Combine(rootPath, subFolder));
|
||||
}
|
||||
|
||||
string readmePath = Path.Combine(rootPath, "00_Projekt_Info.md");
|
||||
await File.WriteAllTextAsync(readmePath, $"# Projekt: {projectName}\nErstellt am: {DateTime.Now}");
|
||||
|
||||
await using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
var entity = new ProjectEntity
|
||||
{
|
||||
Name = projectName,
|
||||
RootPath = rootPath,
|
||||
LastOpened = DateTime.Now
|
||||
};
|
||||
|
||||
context.Projects.Add(entity);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
return entity.Id;
|
||||
}
|
||||
|
||||
public async Task<List<Project>> GetAllProjectsAsync()
|
||||
{
|
||||
await using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
return await context.Projects
|
||||
.OrderByDescending(p => p.LastOpened)
|
||||
.Select(p => new Project
|
||||
{
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
RootPath = p.RootPath
|
||||
})
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task SyncProjectFilesAsync(int projectId, string directoryPath, IProgress<string> progress)
|
||||
{
|
||||
await _fileScannerService.ScanProjectFolderAsync(projectId, directoryPath, progress);
|
||||
}
|
||||
}
|
||||
8
appsettings.Development.json
Normal file
8
appsettings.Development.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
appsettings.json
Normal file
9
appsettings.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
BIN
author_brain.db
Normal file
BIN
author_brain.db
Normal file
Binary file not shown.
BIN
vec0.dll
Normal file
BIN
vec0.dll
Normal file
Binary file not shown.
367
wwwroot/app.css
Normal file
367
wwwroot/app.css
Normal file
|
|
@ -0,0 +1,367 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background: var(--window-bg);
|
||||
color: var(--text-color);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Navigation panel (sidebar accordion) */
|
||||
.nav-section {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.nav-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
background: var(--menu-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nav-section-header:hover {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 10px;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.nav-section-body {
|
||||
border: 1px solid var(--border-color);
|
||||
border-top: none;
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 6px 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-separator {
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 4px 8px;
|
||||
}
|
||||
|
||||
.nav-hint {
|
||||
display: block;
|
||||
padding: 6px 16px;
|
||||
font-size: 12px;
|
||||
color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Status bar */
|
||||
.status-bar {
|
||||
background: var(--status-bar-bg);
|
||||
border-top: 2px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 3px 8px;
|
||||
font-size: 12px;
|
||||
min-height: 28px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-bar .status-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-bar .status-dot.connected {
|
||||
background: #00cc00;
|
||||
}
|
||||
|
||||
.status-bar .status-dot.disconnected {
|
||||
background: #cc0000;
|
||||
}
|
||||
|
||||
.status-bar .status-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.status-bar progress {
|
||||
width: 80px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Main content area */
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
min-width: 150px;
|
||||
border-right: 2px solid var(--border-color);
|
||||
padding: 4px;
|
||||
overflow-y: auto;
|
||||
background: var(--window-bg);
|
||||
}
|
||||
|
||||
/* Detail panel */
|
||||
.detail-panel {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.detail-panel .tab-bar {
|
||||
display: flex;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.detail-panel .tab-bar .tab {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
}
|
||||
|
||||
.detail-panel .tab-bar .tab.active {
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom-color: var(--window-bg);
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
.detail-panel .tab-content {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Cards for RAG facts */
|
||||
.fact-card {
|
||||
background: var(--card-bg);
|
||||
border-left: 3px solid var(--accent-color);
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Editor */
|
||||
.editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.editor-container textarea {
|
||||
flex: 1;
|
||||
font-family: 'Consolas', 'Courier New', monospace;
|
||||
font-size: 14pt;
|
||||
padding: 20px;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
background: var(--editor-bg);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.editor-container .editor-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px 16px;
|
||||
gap: 8px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
background: var(--menu-bg);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--menu-bg);
|
||||
color: var(--text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--editor-bg);
|
||||
color: var(--text-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
/* Project tree */
|
||||
.tree-node {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tree-node:hover {
|
||||
background: var(--accent-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tree-node .children {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
/* Grid splitter simulation */
|
||||
.grid-splitter {
|
||||
width: 5px;
|
||||
cursor: col-resize;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Progress */
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
background: var(--border-color);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-bar .progress-fill {
|
||||
height: 100%;
|
||||
background: var(--accent-color);
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
/* Model list */
|
||||
.model-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.model-card {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.model-card .model-name {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.model-card .model-details {
|
||||
font-size: 12px;
|
||||
color: var(--border-color);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* Settings page */
|
||||
.settings-page {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.settings-section-title {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
color: var(--accent-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 6px;
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
font-size: 11px;
|
||||
color: var(--border-color);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.settings-status {
|
||||
margin-top: 16px;
|
||||
padding: 10px;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--accent-color);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--window-bg);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--accent-color);
|
||||
}
|
||||
10
wwwroot/css/themes/dark.css
Normal file
10
wwwroot/css/themes/dark.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
:root {
|
||||
--window-bg: #1e1e1e;
|
||||
--editor-bg: #2d2d2d;
|
||||
--text-color: #d4d4d4;
|
||||
--border-color: #3c3c3c;
|
||||
--accent-color: #569cd6;
|
||||
--menu-bg: #252526;
|
||||
--card-bg: #2d2d2d;
|
||||
--status-bar-bg: #007acc;
|
||||
}
|
||||
10
wwwroot/css/themes/light.css
Normal file
10
wwwroot/css/themes/light.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
:root {
|
||||
--window-bg: #f0f0f0;
|
||||
--editor-bg: #ffffff;
|
||||
--text-color: #1a1a1a;
|
||||
--border-color: #cccccc;
|
||||
--accent-color: #2b5797;
|
||||
--menu-bg: #e0e0e0;
|
||||
--card-bg: #ffffff;
|
||||
--status-bar-bg: #dcdcdc;
|
||||
}
|
||||
10
wwwroot/css/themes/w95.css
Normal file
10
wwwroot/css/themes/w95.css
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
:root {
|
||||
--window-bg: #c0c0c0;
|
||||
--editor-bg: #ffffff;
|
||||
--text-color: #000000;
|
||||
--border-color: #808080;
|
||||
--accent-color: #000080;
|
||||
--menu-bg: #c0c0c0;
|
||||
--card-bg: #ffffff;
|
||||
--status-bar-bg: #c0c0c0;
|
||||
}
|
||||
BIN
wwwroot/favicon.png
Normal file
BIN
wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
4085
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css
vendored
Normal file
4085
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4084
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css
vendored
Normal file
4084
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
597
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css
vendored
Normal file
597
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css
vendored
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* rtl:raw:
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
*/
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
594
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css
vendored
Normal file
594
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css
vendored
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
/*!
|
||||
* Bootstrap Reboot v5.3.3 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2024 The Bootstrap Authors
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
:root,
|
||||
[data-bs-theme=light] {
|
||||
--bs-blue: #0d6efd;
|
||||
--bs-indigo: #6610f2;
|
||||
--bs-purple: #6f42c1;
|
||||
--bs-pink: #d63384;
|
||||
--bs-red: #dc3545;
|
||||
--bs-orange: #fd7e14;
|
||||
--bs-yellow: #ffc107;
|
||||
--bs-green: #198754;
|
||||
--bs-teal: #20c997;
|
||||
--bs-cyan: #0dcaf0;
|
||||
--bs-black: #000;
|
||||
--bs-white: #fff;
|
||||
--bs-gray: #6c757d;
|
||||
--bs-gray-dark: #343a40;
|
||||
--bs-gray-100: #f8f9fa;
|
||||
--bs-gray-200: #e9ecef;
|
||||
--bs-gray-300: #dee2e6;
|
||||
--bs-gray-400: #ced4da;
|
||||
--bs-gray-500: #adb5bd;
|
||||
--bs-gray-600: #6c757d;
|
||||
--bs-gray-700: #495057;
|
||||
--bs-gray-800: #343a40;
|
||||
--bs-gray-900: #212529;
|
||||
--bs-primary: #0d6efd;
|
||||
--bs-secondary: #6c757d;
|
||||
--bs-success: #198754;
|
||||
--bs-info: #0dcaf0;
|
||||
--bs-warning: #ffc107;
|
||||
--bs-danger: #dc3545;
|
||||
--bs-light: #f8f9fa;
|
||||
--bs-dark: #212529;
|
||||
--bs-primary-rgb: 13, 110, 253;
|
||||
--bs-secondary-rgb: 108, 117, 125;
|
||||
--bs-success-rgb: 25, 135, 84;
|
||||
--bs-info-rgb: 13, 202, 240;
|
||||
--bs-warning-rgb: 255, 193, 7;
|
||||
--bs-danger-rgb: 220, 53, 69;
|
||||
--bs-light-rgb: 248, 249, 250;
|
||||
--bs-dark-rgb: 33, 37, 41;
|
||||
--bs-primary-text-emphasis: #052c65;
|
||||
--bs-secondary-text-emphasis: #2b2f32;
|
||||
--bs-success-text-emphasis: #0a3622;
|
||||
--bs-info-text-emphasis: #055160;
|
||||
--bs-warning-text-emphasis: #664d03;
|
||||
--bs-danger-text-emphasis: #58151c;
|
||||
--bs-light-text-emphasis: #495057;
|
||||
--bs-dark-text-emphasis: #495057;
|
||||
--bs-primary-bg-subtle: #cfe2ff;
|
||||
--bs-secondary-bg-subtle: #e2e3e5;
|
||||
--bs-success-bg-subtle: #d1e7dd;
|
||||
--bs-info-bg-subtle: #cff4fc;
|
||||
--bs-warning-bg-subtle: #fff3cd;
|
||||
--bs-danger-bg-subtle: #f8d7da;
|
||||
--bs-light-bg-subtle: #fcfcfd;
|
||||
--bs-dark-bg-subtle: #ced4da;
|
||||
--bs-primary-border-subtle: #9ec5fe;
|
||||
--bs-secondary-border-subtle: #c4c8cb;
|
||||
--bs-success-border-subtle: #a3cfbb;
|
||||
--bs-info-border-subtle: #9eeaf9;
|
||||
--bs-warning-border-subtle: #ffe69c;
|
||||
--bs-danger-border-subtle: #f1aeb5;
|
||||
--bs-light-border-subtle: #e9ecef;
|
||||
--bs-dark-border-subtle: #adb5bd;
|
||||
--bs-white-rgb: 255, 255, 255;
|
||||
--bs-black-rgb: 0, 0, 0;
|
||||
--bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));
|
||||
--bs-body-font-family: var(--bs-font-sans-serif);
|
||||
--bs-body-font-size: 1rem;
|
||||
--bs-body-font-weight: 400;
|
||||
--bs-body-line-height: 1.5;
|
||||
--bs-body-color: #212529;
|
||||
--bs-body-color-rgb: 33, 37, 41;
|
||||
--bs-body-bg: #fff;
|
||||
--bs-body-bg-rgb: 255, 255, 255;
|
||||
--bs-emphasis-color: #000;
|
||||
--bs-emphasis-color-rgb: 0, 0, 0;
|
||||
--bs-secondary-color: rgba(33, 37, 41, 0.75);
|
||||
--bs-secondary-color-rgb: 33, 37, 41;
|
||||
--bs-secondary-bg: #e9ecef;
|
||||
--bs-secondary-bg-rgb: 233, 236, 239;
|
||||
--bs-tertiary-color: rgba(33, 37, 41, 0.5);
|
||||
--bs-tertiary-color-rgb: 33, 37, 41;
|
||||
--bs-tertiary-bg: #f8f9fa;
|
||||
--bs-tertiary-bg-rgb: 248, 249, 250;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #0d6efd;
|
||||
--bs-link-color-rgb: 13, 110, 253;
|
||||
--bs-link-decoration: underline;
|
||||
--bs-link-hover-color: #0a58ca;
|
||||
--bs-link-hover-color-rgb: 10, 88, 202;
|
||||
--bs-code-color: #d63384;
|
||||
--bs-highlight-color: #212529;
|
||||
--bs-highlight-bg: #fff3cd;
|
||||
--bs-border-width: 1px;
|
||||
--bs-border-style: solid;
|
||||
--bs-border-color: #dee2e6;
|
||||
--bs-border-color-translucent: rgba(0, 0, 0, 0.175);
|
||||
--bs-border-radius: 0.375rem;
|
||||
--bs-border-radius-sm: 0.25rem;
|
||||
--bs-border-radius-lg: 0.5rem;
|
||||
--bs-border-radius-xl: 1rem;
|
||||
--bs-border-radius-xxl: 2rem;
|
||||
--bs-border-radius-2xl: var(--bs-border-radius-xxl);
|
||||
--bs-border-radius-pill: 50rem;
|
||||
--bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
|
||||
--bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
||||
--bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175);
|
||||
--bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075);
|
||||
--bs-focus-ring-width: 0.25rem;
|
||||
--bs-focus-ring-opacity: 0.25;
|
||||
--bs-focus-ring-color: rgba(13, 110, 253, 0.25);
|
||||
--bs-form-valid-color: #198754;
|
||||
--bs-form-valid-border-color: #198754;
|
||||
--bs-form-invalid-color: #dc3545;
|
||||
--bs-form-invalid-border-color: #dc3545;
|
||||
}
|
||||
|
||||
[data-bs-theme=dark] {
|
||||
color-scheme: dark;
|
||||
--bs-body-color: #dee2e6;
|
||||
--bs-body-color-rgb: 222, 226, 230;
|
||||
--bs-body-bg: #212529;
|
||||
--bs-body-bg-rgb: 33, 37, 41;
|
||||
--bs-emphasis-color: #fff;
|
||||
--bs-emphasis-color-rgb: 255, 255, 255;
|
||||
--bs-secondary-color: rgba(222, 226, 230, 0.75);
|
||||
--bs-secondary-color-rgb: 222, 226, 230;
|
||||
--bs-secondary-bg: #343a40;
|
||||
--bs-secondary-bg-rgb: 52, 58, 64;
|
||||
--bs-tertiary-color: rgba(222, 226, 230, 0.5);
|
||||
--bs-tertiary-color-rgb: 222, 226, 230;
|
||||
--bs-tertiary-bg: #2b3035;
|
||||
--bs-tertiary-bg-rgb: 43, 48, 53;
|
||||
--bs-primary-text-emphasis: #6ea8fe;
|
||||
--bs-secondary-text-emphasis: #a7acb1;
|
||||
--bs-success-text-emphasis: #75b798;
|
||||
--bs-info-text-emphasis: #6edff6;
|
||||
--bs-warning-text-emphasis: #ffda6a;
|
||||
--bs-danger-text-emphasis: #ea868f;
|
||||
--bs-light-text-emphasis: #f8f9fa;
|
||||
--bs-dark-text-emphasis: #dee2e6;
|
||||
--bs-primary-bg-subtle: #031633;
|
||||
--bs-secondary-bg-subtle: #161719;
|
||||
--bs-success-bg-subtle: #051b11;
|
||||
--bs-info-bg-subtle: #032830;
|
||||
--bs-warning-bg-subtle: #332701;
|
||||
--bs-danger-bg-subtle: #2c0b0e;
|
||||
--bs-light-bg-subtle: #343a40;
|
||||
--bs-dark-bg-subtle: #1a1d20;
|
||||
--bs-primary-border-subtle: #084298;
|
||||
--bs-secondary-border-subtle: #41464b;
|
||||
--bs-success-border-subtle: #0f5132;
|
||||
--bs-info-border-subtle: #087990;
|
||||
--bs-warning-border-subtle: #997404;
|
||||
--bs-danger-border-subtle: #842029;
|
||||
--bs-light-border-subtle: #495057;
|
||||
--bs-dark-border-subtle: #343a40;
|
||||
--bs-heading-color: inherit;
|
||||
--bs-link-color: #6ea8fe;
|
||||
--bs-link-hover-color: #8bb9fe;
|
||||
--bs-link-color-rgb: 110, 168, 254;
|
||||
--bs-link-hover-color-rgb: 139, 185, 254;
|
||||
--bs-code-color: #e685b5;
|
||||
--bs-highlight-color: #dee2e6;
|
||||
--bs-highlight-bg: #664d03;
|
||||
--bs-border-color: #495057;
|
||||
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
|
||||
--bs-form-valid-color: #75b798;
|
||||
--bs-form-valid-border-color: #75b798;
|
||||
--bs-form-invalid-color: #ea868f;
|
||||
--bs-form-invalid-border-color: #ea868f;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
:root {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: var(--bs-body-font-family);
|
||||
font-size: var(--bs-body-font-size);
|
||||
font-weight: var(--bs-body-font-weight);
|
||||
line-height: var(--bs-body-line-height);
|
||||
color: var(--bs-body-color);
|
||||
text-align: var(--bs-body-text-align);
|
||||
background-color: var(--bs-body-bg);
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1rem 0;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
border-top: var(--bs-border-width) solid;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
h6, h5, h4, h3, h2, h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: calc(1.375rem + 1.5vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: calc(1.325rem + 0.9vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: calc(1.3rem + 0.6vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
h4 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 0.1875em;
|
||||
color: var(--bs-highlight-color);
|
||||
background-color: var(--bs-highlight-bg);
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 0.75em;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1));
|
||||
text-decoration: underline;
|
||||
}
|
||||
a:hover {
|
||||
--bs-link-color-rgb: var(--bs-link-hover-color-rgb);
|
||||
}
|
||||
|
||||
a:not([href]):not([class]), a:not([href]):not([class]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--bs-font-monospace);
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
pre code {
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-code-color);
|
||||
word-wrap: break-word;
|
||||
}
|
||||
a > code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
kbd {
|
||||
padding: 0.1875rem 0.375rem;
|
||||
font-size: 0.875em;
|
||||
color: var(--bs-body-bg);
|
||||
background-color: var(--bs-body-color);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
kbd kbd {
|
||||
padding: 0;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
caption-side: bottom;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
text-align: -webkit-match-parent;
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody,
|
||||
tfoot,
|
||||
tr,
|
||||
td,
|
||||
th {
|
||||
border-color: inherit;
|
||||
border-style: solid;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus:not(:focus-visible) {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
[role=button] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
select:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
button,
|
||||
[type=button],
|
||||
[type=reset],
|
||||
[type=submit] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
button:not(:disabled),
|
||||
[type=button]:not(:disabled),
|
||||
[type=reset]:not(:disabled),
|
||||
[type=submit]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
float: right;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: calc(1.275rem + 0.3vw);
|
||||
line-height: inherit;
|
||||
}
|
||||
@media (min-width: 1200px) {
|
||||
legend {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
legend + * {
|
||||
clear: right;
|
||||
}
|
||||
|
||||
::-webkit-datetime-edit-fields-wrapper,
|
||||
::-webkit-datetime-edit-text,
|
||||
::-webkit-datetime-edit-minute,
|
||||
::-webkit-datetime-edit-hour-field,
|
||||
::-webkit-datetime-edit-day-field,
|
||||
::-webkit-datetime-edit-month-field,
|
||||
::-webkit-datetime-edit-year-field {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-inner-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type=search] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="tel"],
|
||||
[type="url"],
|
||||
[type="email"],
|
||||
[type="number"] {
|
||||
direction: ltr;
|
||||
}
|
||||
::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
::file-selector-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.rtl.css.map */
|
||||
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
5402
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css
vendored
Normal file
5402
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
5393
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css
vendored
Normal file
5393
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12057
wwwroot/lib/bootstrap/dist/css/bootstrap.css
vendored
Normal file
12057
wwwroot/lib/bootstrap/dist/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
12030
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css
vendored
Normal file
12030
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css
vendored
Normal file
6
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map
vendored
Normal file
File diff suppressed because one or more lines are too long
6314
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js
vendored
Normal file
6314
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js
vendored
Normal file
7
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
4447
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js
vendored
Normal file
4447
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
7
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js
vendored
Normal file
7
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map
vendored
Normal file
1
wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue