Compare commits

..

4 commits

Author SHA1 Message Date
96f0e04381 fix 2026-09-21 10:50:05 +02:00
4af00fd978 - continue 2026-08-30 15:38:12 +02:00
5eba92e83a - continue 2026-08-30 08:11:01 +02:00
890b5bbf0b - improve mobile view 2026-07-05 07:47:01 +02:00
42 changed files with 3403 additions and 711 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
/obj/* /obj/*
/.vs/* /.vs/*
/.vs/* /.vs/*
/.vs/*

View file

@ -3,16 +3,28 @@
"WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\", "WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\",
"Documents": [ "Documents": [
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\localstorageentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\localstorageentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\authorbuddy.web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\authorbuddy.web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
@ -23,17 +35,29 @@
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\quickmarkdown.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\quickmarkdown.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamacloudbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamacloudbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
@ -54,10 +78,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
@ -74,10 +94,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
@ -94,10 +110,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
@ -188,10 +200,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
@ -214,34 +222,73 @@
"$type": "Bookmark", "$type": "Bookmark",
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
}, },
{
"$type": "Bookmark",
"Name": "ST:0:0:{0504ff91-9d61-11d0-a794-00a0c9110051}"
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}"
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}"
},
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 1, "DocumentIndex": 1,
"Title": "app.css", "Title": "UserEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeDocumentMoniker": "wwwroot\\app.css", "RelativeDocumentMoniker": "Data\\UserEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeToolTip": "wwwroot\\app.css", "RelativeToolTip": "Data\\UserEntity.cs",
"ViewState": "AgIAAJYCAAAAAAAAAAAiwKoCAAAJAAAAAAAAAA==", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAARAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003000|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-07-04T08:22:21.143Z", "WhenOpened": "2026-09-21T08:09:49.503Z",
"EditorCaption": "" "EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 5, "DocumentIndex": 0,
"Title": "Editor.razor", "Title": "SettingsService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs",
"RelativeDocumentMoniker": "Components\\Pages\\Editor.razor", "RelativeDocumentMoniker": "Services\\SettingsService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs*",
"RelativeToolTip": "Components\\Pages\\Editor.razor", "RelativeToolTip": "Services\\SettingsService.cs*",
"ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==", "ViewState": "AgIAAAADAAAAAAAAAAAMwBEDAAAlAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-16T13:01:59.743Z" "WhenOpened": "2026-09-21T06:22:02.245Z",
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 9, "DocumentIndex": 9,
"Title": "OllamaService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeDocumentMoniker": "Services\\OllamaService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeToolTip": "Services\\OllamaService.cs",
"ViewState": "AgIAAI0AAAAAAAAAAAAqwKMAAAAEAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-26T13:33:25.661Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "Program.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs",
"RelativeDocumentMoniker": "Program.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs",
"RelativeToolTip": "Program.cs",
"ViewState": "AgIAADEAAAAAAAAAAAAwwGcAAAARAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-20T04:07:21.54Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 15,
"Title": "IOllamaService.cs", "Title": "IOllamaService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaService.cs",
"RelativeDocumentMoniker": "Services\\IOllamaService.cs", "RelativeDocumentMoniker": "Services\\IOllamaService.cs",
@ -253,7 +300,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 14, "DocumentIndex": 19,
"Title": "Chat.razor", "Title": "Chat.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Chat.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Chat.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Chat.razor", "RelativeDocumentMoniker": "Components\\Pages\\Chat.razor",
@ -265,32 +312,98 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 0, "DocumentIndex": 5,
"Title": "ProjectEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeDocumentMoniker": "Data\\ProjectEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeToolTip": "Data\\ProjectEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-14T10:50:06.549Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "LocalStorageEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\LocalStorageEntity.cs",
"RelativeDocumentMoniker": "Data\\LocalStorageEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\LocalStorageEntity.cs",
"RelativeToolTip": "Data\\LocalStorageEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAqwAUAAAATAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-09-21T05:48:52.084Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "DbAdminService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs",
"RelativeDocumentMoniker": "Services\\DbAdminService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs",
"RelativeToolTip": "Services\\DbAdminService.cs",
"ViewState": "AgIAACcAAAAAAAAAAAAwwDkAAAAaAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-10T17:34:10.361Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 8,
"Title": "MainLayout.razor", "Title": "MainLayout.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor", "RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
"RelativeToolTip": "Components\\Layout\\MainLayout.razor", "RelativeToolTip": "Components\\Layout\\MainLayout.razor",
"ViewState": "AgIAADIAAAAAAAAAAAApwEUAAAByAAAAAAAAAA==", "ViewState": "AgIAABoCAAAAAAAAAAApwC0CAAAfAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-05-15T16:04:36.444Z", "WhenOpened": "2026-09-21T05:19:15.962Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "QuickMarkdown.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeDocumentMoniker": "Components\\Pages\\QuickMarkdown.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeToolTip": "Components\\Pages\\QuickMarkdown.razor",
"ViewState": "AgIAAF8BAAAAAAAAAAApwHIBAAAgAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-07-01T16:53:16.035Z",
"EditorCaption": "" "EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 6, "DocumentIndex": 6,
"Title": "Program.cs", "Title": "AuthorBuddy.Web.csproj",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
"RelativeDocumentMoniker": "Program.cs", "RelativeDocumentMoniker": "AuthorBuddy.Web.csproj",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
"RelativeToolTip": "Program.cs", "RelativeToolTip": "AuthorBuddy.Web.csproj",
"ViewState": "AgIAAEcAAAAAAAAAAAAQwFoAAAAAAAAAAAAAAA==", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
"WhenOpened": "2026-05-20T04:07:21.54Z" "WhenOpened": "2026-05-14T10:14:30.128Z",
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 8, "DocumentIndex": 10,
"Title": "OllamaCloudBackend.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaCloudBackend.cs",
"RelativeDocumentMoniker": "Services\\OllamaCloudBackend.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaCloudBackend.cs",
"RelativeToolTip": "Services\\OllamaCloudBackend.cs",
"ViewState": "AgIAAMMAAAAAAAAAAAAqwNgAAAA6AAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-07-12T07:08:37.774Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 14,
"Title": "OllamaBackend.cs", "Title": "OllamaBackend.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaBackend.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaBackend.cs",
"RelativeDocumentMoniker": "Services\\OllamaBackend.cs", "RelativeDocumentMoniker": "Services\\OllamaBackend.cs",
@ -302,68 +415,44 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 2, "DocumentIndex": 11,
"Title": "Settings.razor", "Title": "Settings.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Settings.razor", "RelativeDocumentMoniker": "Components\\Pages\\Settings.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
"RelativeToolTip": "Components\\Pages\\Settings.razor", "RelativeToolTip": "Components\\Pages\\Settings.razor",
"ViewState": "AgIAABIAAAAAAAAAAAAAADMAAAAfAAAAAAAAAA==", "ViewState": "AgIAAIsCAAAAAAAAAAArwKACAAAQAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-05-15T16:43:26.6Z", "WhenOpened": "2026-05-15T16:43:26.6Z",
"EditorCaption": "" "EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 23, "DocumentIndex": 13,
"Title": "DbAdminService.cs", "Title": "Editor.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
"RelativeDocumentMoniker": "Services\\DbAdminService.cs", "RelativeDocumentMoniker": "Components\\Pages\\Editor.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
"RelativeToolTip": "Services\\DbAdminService.cs", "RelativeToolTip": "Components\\Pages\\Editor.razor",
"ViewState": "AgIAABUAAAAAAAAAAAAowAAAAAAAAAAAAAAAAA==", "ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-10T17:34:10.361Z"
},
{
"$type": "Document",
"DocumentIndex": 3,
"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"
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "OllamaService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeDocumentMoniker": "Services\\OllamaService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeToolTip": "Services\\OllamaService.cs",
"ViewState": "AgIAAK8AAAAAAAAAAAAqwLwAAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-26T13:33:25.661Z"
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "QuickMarkdown.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeDocumentMoniker": "Components\\Pages\\QuickMarkdown.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeToolTip": "Components\\Pages\\QuickMarkdown.razor",
"ViewState": "AgIAACkAAAAAAAAAAAAiwEoAAABfAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-07-01T16:53:16.035Z" "WhenOpened": "2026-05-16T13:01:59.743Z"
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 19, "DocumentIndex": 12,
"Title": "app.css",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css",
"RelativeDocumentMoniker": "wwwroot\\app.css",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css",
"RelativeToolTip": "wwwroot\\app.css",
"ViewState": "AgIAAJYCAAAAAAAAAAAiwKoCAAAJAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003000|",
"WhenOpened": "2026-07-04T08:22:21.143Z"
},
{
"$type": "Document",
"DocumentIndex": 23,
"Title": "Login.razor", "Title": "Login.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Login.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Login.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Login.razor", "RelativeDocumentMoniker": "Components\\Pages\\Login.razor",
@ -375,7 +464,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 20, "DocumentIndex": 24,
"Title": "FirstRunSetup.razor", "Title": "FirstRunSetup.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\FirstRunSetup.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\FirstRunSetup.razor",
"RelativeDocumentMoniker": "Components\\Pages\\FirstRunSetup.razor", "RelativeDocumentMoniker": "Components\\Pages\\FirstRunSetup.razor",
@ -387,19 +476,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 13, "DocumentIndex": 16,
"Title": "SettingsService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs",
"RelativeDocumentMoniker": "Services\\SettingsService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs",
"RelativeToolTip": "Services\\SettingsService.cs",
"ViewState": "AgIAANwBAAAAAAAAAAAAAO4BAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-21T17:37:24.777Z"
},
{
"$type": "Document",
"DocumentIndex": 10,
"Title": "ChatService.cs", "Title": "ChatService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ChatService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ChatService.cs",
"RelativeDocumentMoniker": "Services\\ChatService.cs", "RelativeDocumentMoniker": "Services\\ChatService.cs",
@ -411,7 +488,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 12, "DocumentIndex": 18,
"Title": "de.json", "Title": "de.json",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\de.json", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\de.json",
"RelativeDocumentMoniker": "Resources\\de.json", "RelativeDocumentMoniker": "Resources\\de.json",
@ -423,7 +500,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 11, "DocumentIndex": 17,
"Title": "en.json", "Title": "en.json",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\en.json", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\en.json",
"RelativeDocumentMoniker": "Resources\\en.json", "RelativeDocumentMoniker": "Resources\\en.json",
@ -435,7 +512,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 16, "DocumentIndex": 21,
"Title": "NavMenu.razor", "Title": "NavMenu.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\NavMenu.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\NavMenu.razor",
"RelativeDocumentMoniker": "Components\\Layout\\NavMenu.razor", "RelativeDocumentMoniker": "Components\\Layout\\NavMenu.razor",
@ -447,7 +524,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 15, "DocumentIndex": 20,
"Title": "MainLayout.razor.css", "Title": "MainLayout.razor.css",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor.css", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor.css",
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor.css", "RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor.css",
@ -459,7 +536,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 22, "DocumentIndex": 26,
"Title": "DbAdmin.razor", "Title": "DbAdmin.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\DbAdmin.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\DbAdmin.razor",
"RelativeDocumentMoniker": "Components\\Pages\\DbAdmin.razor", "RelativeDocumentMoniker": "Components\\Pages\\DbAdmin.razor",
@ -471,7 +548,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 17, "DocumentIndex": 22,
"Title": "ILLMBackend.cs", "Title": "ILLMBackend.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ILLMBackend.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ILLMBackend.cs",
"RelativeDocumentMoniker": "Services\\ILLMBackend.cs", "RelativeDocumentMoniker": "Services\\ILLMBackend.cs",
@ -483,19 +560,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 18, "DocumentIndex": 25,
"Title": "UserEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeDocumentMoniker": "Data\\UserEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeToolTip": "Data\\UserEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAQAAAAUAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-24T04:52:42.227Z"
},
{
"$type": "Document",
"DocumentIndex": 21,
"Title": "OllamaAssistant.razor", "Title": "OllamaAssistant.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\OllamaAssistant.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\OllamaAssistant.razor",
"RelativeDocumentMoniker": "Components\\Pages\\OllamaAssistant.razor", "RelativeDocumentMoniker": "Components\\Pages\\OllamaAssistant.razor",
@ -507,7 +572,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 27, "DocumentIndex": 30,
"Title": "ProjectService.cs", "Title": "ProjectService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ProjectService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ProjectService.cs",
"RelativeDocumentMoniker": "Services\\ProjectService.cs", "RelativeDocumentMoniker": "Services\\ProjectService.cs",
@ -519,7 +584,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 28, "DocumentIndex": 31,
"Title": "IOllamaSetupService.cs", "Title": "IOllamaSetupService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaSetupService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaSetupService.cs",
"RelativeDocumentMoniker": "Services\\IOllamaSetupService.cs", "RelativeDocumentMoniker": "Services\\IOllamaSetupService.cs",
@ -531,7 +596,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 24, "DocumentIndex": 27,
"Title": "SafeFileHandle.Windows.cs", "Title": "SafeFileHandle.Windows.cs",
"DocumentMoniker": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs", "DocumentMoniker": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs",
"ToolTip": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs [Schreibgesch\u00FCtzt]", "ToolTip": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs [Schreibgesch\u00FCtzt]",
@ -542,7 +607,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 25, "DocumentIndex": 28,
"Title": "A7405018.jpg", "Title": "A7405018.jpg",
"DocumentMoniker": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg", "DocumentMoniker": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
"ToolTip": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg", "ToolTip": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
@ -551,7 +616,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 29, "DocumentIndex": 32,
"Title": "INSTALL.md", "Title": "INSTALL.md",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\INSTALL.md", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\INSTALL.md",
"RelativeDocumentMoniker": "INSTALL.md", "RelativeDocumentMoniker": "INSTALL.md",
@ -563,7 +628,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 30, "DocumentIndex": 33,
"Title": "IThemeService.cs", "Title": "IThemeService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IThemeService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IThemeService.cs",
"RelativeDocumentMoniker": "Services\\IThemeService.cs", "RelativeDocumentMoniker": "Services\\IThemeService.cs",
@ -575,7 +640,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 31, "DocumentIndex": 34,
"Title": "RedirectToLogin.razor", "Title": "RedirectToLogin.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\RedirectToLogin.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\RedirectToLogin.razor",
"RelativeDocumentMoniker": "Components\\RedirectToLogin.razor", "RelativeDocumentMoniker": "Components\\RedirectToLogin.razor",
@ -587,7 +652,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 32, "DocumentIndex": 35,
"Title": "ISettingsService.cs", "Title": "ISettingsService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ISettingsService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ISettingsService.cs",
"RelativeDocumentMoniker": "Services\\ISettingsService.cs", "RelativeDocumentMoniker": "Services\\ISettingsService.cs",
@ -595,11 +660,12 @@
"RelativeToolTip": "Services\\ISettingsService.cs", "RelativeToolTip": "Services\\ISettingsService.cs",
"ViewState": "AgIAABwAAAAAAAAAAAA9wC4AAAAZAAAAAAAAAA==", "ViewState": "AgIAABwAAAAAAAAAAAA9wC4AAAAZAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-21T16:35:36.655Z" "WhenOpened": "2026-05-21T16:35:36.655Z",
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 33, "DocumentIndex": 36,
"Title": "IRagService.cs", "Title": "IRagService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IRagService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IRagService.cs",
"RelativeDocumentMoniker": "Services\\IRagService.cs", "RelativeDocumentMoniker": "Services\\IRagService.cs",
@ -611,7 +677,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 34, "DocumentIndex": 37,
"Title": "ThemeService.cs", "Title": "ThemeService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ThemeService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ThemeService.cs",
"RelativeDocumentMoniker": "Services\\ThemeService.cs", "RelativeDocumentMoniker": "Services\\ThemeService.cs",
@ -623,7 +689,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 35, "DocumentIndex": 38,
"Title": "logo.jpg", "Title": "logo.jpg",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Assets\\logo.jpg", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Assets\\logo.jpg",
"RelativeDocumentMoniker": "Assets\\logo.jpg", "RelativeDocumentMoniker": "Assets\\logo.jpg",
@ -634,7 +700,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 36, "DocumentIndex": 39,
"Title": "OllamaSettings.cs", "Title": "OllamaSettings.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\OllamaSettings.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\OllamaSettings.cs",
"RelativeDocumentMoniker": "Models\\OllamaSettings.cs", "RelativeDocumentMoniker": "Models\\OllamaSettings.cs",
@ -646,7 +712,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 26, "DocumentIndex": 29,
"Title": "AppDbContext.cs", "Title": "AppDbContext.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\AppDbContext.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\AppDbContext.cs",
"RelativeDocumentMoniker": "Data\\AppDbContext.cs", "RelativeDocumentMoniker": "Data\\AppDbContext.cs",
@ -657,7 +723,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 37, "DocumentIndex": 40,
"Title": "FileScannerService.cs", "Title": "FileScannerService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\FileScannerService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\FileScannerService.cs",
"RelativeDocumentMoniker": "Services\\FileScannerService.cs", "RelativeDocumentMoniker": "Services\\FileScannerService.cs",
@ -669,7 +735,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 38, "DocumentIndex": 41,
"Title": "TotpHelper.cs", "Title": "TotpHelper.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\TotpHelper.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\TotpHelper.cs",
"RelativeDocumentMoniker": "Services\\TotpHelper.cs", "RelativeDocumentMoniker": "Services\\TotpHelper.cs",
@ -681,7 +747,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 39, "DocumentIndex": 42,
"Title": "Project.cs", "Title": "Project.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\Project.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\Project.cs",
"RelativeDocumentMoniker": "Models\\Project.cs", "RelativeDocumentMoniker": "Models\\Project.cs",
@ -693,7 +759,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 40, "DocumentIndex": 43,
"Title": "RagService.cs", "Title": "RagService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\RagService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\RagService.cs",
"RelativeDocumentMoniker": "Services\\RagService.cs", "RelativeDocumentMoniker": "Services\\RagService.cs",
@ -705,7 +771,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 41, "DocumentIndex": 44,
"Title": "App.razor", "Title": "App.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\App.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\App.razor",
"RelativeDocumentMoniker": "Components\\App.razor", "RelativeDocumentMoniker": "Components\\App.razor",
@ -717,7 +783,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 42, "DocumentIndex": 45,
"Title": "Models.razor", "Title": "Models.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Models.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Models.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Models.razor", "RelativeDocumentMoniker": "Components\\Pages\\Models.razor",
@ -729,7 +795,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 43, "DocumentIndex": 46,
"Title": "LLM_Model.cs", "Title": "LLM_Model.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\LLM_Model.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\LLM_Model.cs",
"RelativeDocumentMoniker": "Models\\LLM_Model.cs", "RelativeDocumentMoniker": "Models\\LLM_Model.cs",
@ -741,7 +807,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 44, "DocumentIndex": 47,
"Title": "ModelStatus.cs", "Title": "ModelStatus.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\ModelStatus.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\ModelStatus.cs",
"RelativeDocumentMoniker": "Models\\ModelStatus.cs", "RelativeDocumentMoniker": "Models\\ModelStatus.cs",
@ -753,7 +819,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 45, "DocumentIndex": 48,
"Title": "IFileScannerService.cs", "Title": "IFileScannerService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IFileScannerService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IFileScannerService.cs",
"RelativeDocumentMoniker": "Services\\IFileScannerService.cs", "RelativeDocumentMoniker": "Services\\IFileScannerService.cs",
@ -765,7 +831,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 46, "DocumentIndex": 49,
"Title": "bootstrap-utilities.css", "Title": "bootstrap-utilities.css",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css",
"RelativeDocumentMoniker": "wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css", "RelativeDocumentMoniker": "wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css",
@ -777,19 +843,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 47, "DocumentIndex": 50,
"Title": "ProjectEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeDocumentMoniker": "Data\\ProjectEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeToolTip": "Data\\ProjectEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-14T10:50:06.549Z"
},
{
"$type": "Document",
"DocumentIndex": 48,
"Title": "IProjectService.cs", "Title": "IProjectService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IProjectService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IProjectService.cs",
"RelativeDocumentMoniker": "Services\\IProjectService.cs", "RelativeDocumentMoniker": "Services\\IProjectService.cs",
@ -801,7 +855,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 49, "DocumentIndex": 51,
"Title": "appsettings.json", "Title": "appsettings.json",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
"RelativeDocumentMoniker": "appsettings.json", "RelativeDocumentMoniker": "appsettings.json",

View file

@ -3,16 +3,28 @@
"WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\", "WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\",
"Documents": [ "Documents": [
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\localstorageentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\localstorageentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\authorbuddy.web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\authorbuddy.web.csproj||{FA3CD31E-987B-443A-9B81-186104E8DAC1}",
@ -23,17 +35,29 @@
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\quickmarkdown.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\quickmarkdown.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\layout\\mainlayout.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamaservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamacloudbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamacloudbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\settings.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\app.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}"
},
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\editor.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\ollamabackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
@ -54,10 +78,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:resources\\de.json||{90A6B3A7-C1A3-4009-A288-E2FF89E96FA0}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\settingsservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\chat.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
@ -74,10 +94,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\illmbackend.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\userentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\login.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
@ -94,10 +110,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:components\\pages\\dbadmin.razor||{40D31677-CBC0-4297-A9EF-89D907823A98}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\dbadminservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
}, },
@ -188,10 +200,6 @@
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css||{A5401142-F49D-43DB-90B1-F57BA349E55C}"
}, },
{
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:data\\projectentity.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{ {
"AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", "AbsoluteMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|e:\\ollama_net\\ollama\\authorbuddy.web\\services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" "RelativeMoniker": "D:0:0:{4F4697EF-EFFA-26AC-C8A1-C9859805D247}|AuthorBuddy.Web.csproj|solutionrelative:services\\iprojectservice.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
@ -214,34 +222,73 @@
"$type": "Bookmark", "$type": "Bookmark",
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
}, },
{
"$type": "Bookmark",
"Name": "ST:0:0:{0504ff91-9d61-11d0-a794-00a0c9110051}"
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{ecb7191a-597b-41f5-9843-03a4cf275dde}"
},
{
"$type": "Bookmark",
"Name": "ST:0:0:{605322a2-17ae-43f4-b60f-766556e46c87}"
},
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 1, "DocumentIndex": 1,
"Title": "app.css", "Title": "UserEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeDocumentMoniker": "wwwroot\\app.css", "RelativeDocumentMoniker": "Data\\UserEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeToolTip": "wwwroot\\app.css", "RelativeToolTip": "Data\\UserEntity.cs",
"ViewState": "AgIAAJYCAAAAAAAAAAAiwKoCAAAJAAAAAAAAAA==", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAARAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003000|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-07-04T08:22:21.143Z", "WhenOpened": "2026-09-21T08:09:49.503Z",
"EditorCaption": "" "EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 5, "DocumentIndex": 0,
"Title": "Editor.razor", "Title": "SettingsService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs",
"RelativeDocumentMoniker": "Components\\Pages\\Editor.razor", "RelativeDocumentMoniker": "Services\\SettingsService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs*",
"RelativeToolTip": "Components\\Pages\\Editor.razor", "RelativeToolTip": "Services\\SettingsService.cs*",
"ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==", "ViewState": "AgIAAPoCAAAAAAAAAAAIwBUDAAAmAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-16T13:01:59.743Z" "WhenOpened": "2026-09-21T06:22:02.245Z",
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 9, "DocumentIndex": 9,
"Title": "OllamaService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeDocumentMoniker": "Services\\OllamaService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeToolTip": "Services\\OllamaService.cs",
"ViewState": "AgIAAI0AAAAAAAAAAAAqwKMAAAAEAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-26T13:33:25.661Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "Program.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs",
"RelativeDocumentMoniker": "Program.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs",
"RelativeToolTip": "Program.cs",
"ViewState": "AgIAADEAAAAAAAAAAAAwwGcAAAARAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-20T04:07:21.54Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 15,
"Title": "IOllamaService.cs", "Title": "IOllamaService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaService.cs",
"RelativeDocumentMoniker": "Services\\IOllamaService.cs", "RelativeDocumentMoniker": "Services\\IOllamaService.cs",
@ -253,7 +300,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 14, "DocumentIndex": 19,
"Title": "Chat.razor", "Title": "Chat.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Chat.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Chat.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Chat.razor", "RelativeDocumentMoniker": "Components\\Pages\\Chat.razor",
@ -265,32 +312,98 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 0, "DocumentIndex": 5,
"Title": "ProjectEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeDocumentMoniker": "Data\\ProjectEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeToolTip": "Data\\ProjectEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-14T10:50:06.549Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "LocalStorageEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\LocalStorageEntity.cs",
"RelativeDocumentMoniker": "Data\\LocalStorageEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\LocalStorageEntity.cs",
"RelativeToolTip": "Data\\LocalStorageEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAqwAUAAAATAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-09-21T05:48:52.084Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "DbAdminService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs",
"RelativeDocumentMoniker": "Services\\DbAdminService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs",
"RelativeToolTip": "Services\\DbAdminService.cs",
"ViewState": "AgIAACcAAAAAAAAAAAAwwDkAAAAaAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-10T17:34:10.361Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 8,
"Title": "MainLayout.razor", "Title": "MainLayout.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor", "RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
"RelativeToolTip": "Components\\Layout\\MainLayout.razor", "RelativeToolTip": "Components\\Layout\\MainLayout.razor",
"ViewState": "AgIAADIAAAAAAAAAAAApwEUAAAByAAAAAAAAAA==", "ViewState": "AgIAABoCAAAAAAAAAAApwC0CAAAfAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-05-15T16:04:36.444Z", "WhenOpened": "2026-09-21T05:19:15.962Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "QuickMarkdown.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeDocumentMoniker": "Components\\Pages\\QuickMarkdown.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeToolTip": "Components\\Pages\\QuickMarkdown.razor",
"ViewState": "AgIAAF8BAAAAAAAAAAApwHIBAAAgAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-07-01T16:53:16.035Z",
"EditorCaption": "" "EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 6, "DocumentIndex": 6,
"Title": "Program.cs", "Title": "AuthorBuddy.Web.csproj",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
"RelativeDocumentMoniker": "Program.cs", "RelativeDocumentMoniker": "AuthorBuddy.Web.csproj",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Program.cs", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj*",
"RelativeToolTip": "Program.cs", "RelativeToolTip": "AuthorBuddy.Web.csproj*",
"ViewState": "AgIAAEcAAAAAAAAAAAAQwFoAAAAAAAAAAAAAAA==", "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
"WhenOpened": "2026-05-20T04:07:21.54Z" "WhenOpened": "2026-05-14T10:14:30.128Z",
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 8, "DocumentIndex": 10,
"Title": "OllamaCloudBackend.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaCloudBackend.cs",
"RelativeDocumentMoniker": "Services\\OllamaCloudBackend.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaCloudBackend.cs",
"RelativeToolTip": "Services\\OllamaCloudBackend.cs",
"ViewState": "AgIAAMMAAAAAAAAAAAAqwNgAAAA6AAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-07-12T07:08:37.774Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 14,
"Title": "OllamaBackend.cs", "Title": "OllamaBackend.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaBackend.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaBackend.cs",
"RelativeDocumentMoniker": "Services\\OllamaBackend.cs", "RelativeDocumentMoniker": "Services\\OllamaBackend.cs",
@ -302,68 +415,44 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 2, "DocumentIndex": 11,
"Title": "Settings.razor", "Title": "Settings.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Settings.razor", "RelativeDocumentMoniker": "Components\\Pages\\Settings.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
"RelativeToolTip": "Components\\Pages\\Settings.razor", "RelativeToolTip": "Components\\Pages\\Settings.razor",
"ViewState": "AgIAABIAAAAAAAAAAAAAADMAAAAfAAAAAAAAAA==", "ViewState": "AgIAAIsCAAAAAAAAAAArwKACAAAQAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-05-15T16:43:26.6Z", "WhenOpened": "2026-05-15T16:43:26.6Z",
"EditorCaption": "" "EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 23, "DocumentIndex": 13,
"Title": "DbAdminService.cs", "Title": "Editor.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
"RelativeDocumentMoniker": "Services\\DbAdminService.cs", "RelativeDocumentMoniker": "Components\\Pages\\Editor.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\DbAdminService.cs", "ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
"RelativeToolTip": "Services\\DbAdminService.cs", "RelativeToolTip": "Components\\Pages\\Editor.razor",
"ViewState": "AgIAABUAAAAAAAAAAAAowAAAAAAAAAAAAAAAAA==", "ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-10T17:34:10.361Z"
},
{
"$type": "Document",
"DocumentIndex": 3,
"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"
},
{
"$type": "Document",
"DocumentIndex": 7,
"Title": "OllamaService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeDocumentMoniker": "Services\\OllamaService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaService.cs",
"RelativeToolTip": "Services\\OllamaService.cs",
"ViewState": "AgIAAK8AAAAAAAAAAAAqwLwAAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-06-26T13:33:25.661Z"
},
{
"$type": "Document",
"DocumentIndex": 4,
"Title": "QuickMarkdown.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeDocumentMoniker": "Components\\Pages\\QuickMarkdown.razor",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\QuickMarkdown.razor",
"RelativeToolTip": "Components\\Pages\\QuickMarkdown.razor",
"ViewState": "AgIAACkAAAAAAAAAAAAiwEoAAABfAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
"WhenOpened": "2026-07-01T16:53:16.035Z" "WhenOpened": "2026-05-16T13:01:59.743Z"
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 19, "DocumentIndex": 12,
"Title": "app.css",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css",
"RelativeDocumentMoniker": "wwwroot\\app.css",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\app.css",
"RelativeToolTip": "wwwroot\\app.css",
"ViewState": "AgIAAJYCAAAAAAAAAAAiwKoCAAAJAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003000|",
"WhenOpened": "2026-07-04T08:22:21.143Z"
},
{
"$type": "Document",
"DocumentIndex": 23,
"Title": "Login.razor", "Title": "Login.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Login.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Login.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Login.razor", "RelativeDocumentMoniker": "Components\\Pages\\Login.razor",
@ -375,7 +464,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 20, "DocumentIndex": 24,
"Title": "FirstRunSetup.razor", "Title": "FirstRunSetup.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\FirstRunSetup.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\FirstRunSetup.razor",
"RelativeDocumentMoniker": "Components\\Pages\\FirstRunSetup.razor", "RelativeDocumentMoniker": "Components\\Pages\\FirstRunSetup.razor",
@ -387,19 +476,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 13, "DocumentIndex": 16,
"Title": "SettingsService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs",
"RelativeDocumentMoniker": "Services\\SettingsService.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\SettingsService.cs",
"RelativeToolTip": "Services\\SettingsService.cs",
"ViewState": "AgIAANwBAAAAAAAAAAAAAO4BAAAIAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-21T17:37:24.777Z"
},
{
"$type": "Document",
"DocumentIndex": 10,
"Title": "ChatService.cs", "Title": "ChatService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ChatService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ChatService.cs",
"RelativeDocumentMoniker": "Services\\ChatService.cs", "RelativeDocumentMoniker": "Services\\ChatService.cs",
@ -411,7 +488,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 12, "DocumentIndex": 18,
"Title": "de.json", "Title": "de.json",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\de.json", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\de.json",
"RelativeDocumentMoniker": "Resources\\de.json", "RelativeDocumentMoniker": "Resources\\de.json",
@ -423,7 +500,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 11, "DocumentIndex": 17,
"Title": "en.json", "Title": "en.json",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\en.json", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\en.json",
"RelativeDocumentMoniker": "Resources\\en.json", "RelativeDocumentMoniker": "Resources\\en.json",
@ -435,7 +512,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 16, "DocumentIndex": 21,
"Title": "NavMenu.razor", "Title": "NavMenu.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\NavMenu.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\NavMenu.razor",
"RelativeDocumentMoniker": "Components\\Layout\\NavMenu.razor", "RelativeDocumentMoniker": "Components\\Layout\\NavMenu.razor",
@ -447,7 +524,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 15, "DocumentIndex": 20,
"Title": "MainLayout.razor.css", "Title": "MainLayout.razor.css",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor.css", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor.css",
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor.css", "RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor.css",
@ -459,7 +536,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 22, "DocumentIndex": 26,
"Title": "DbAdmin.razor", "Title": "DbAdmin.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\DbAdmin.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\DbAdmin.razor",
"RelativeDocumentMoniker": "Components\\Pages\\DbAdmin.razor", "RelativeDocumentMoniker": "Components\\Pages\\DbAdmin.razor",
@ -471,7 +548,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 17, "DocumentIndex": 22,
"Title": "ILLMBackend.cs", "Title": "ILLMBackend.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ILLMBackend.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ILLMBackend.cs",
"RelativeDocumentMoniker": "Services\\ILLMBackend.cs", "RelativeDocumentMoniker": "Services\\ILLMBackend.cs",
@ -483,19 +560,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 18, "DocumentIndex": 25,
"Title": "UserEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeDocumentMoniker": "Data\\UserEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\UserEntity.cs",
"RelativeToolTip": "Data\\UserEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAQAAAAUAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-24T04:52:42.227Z"
},
{
"$type": "Document",
"DocumentIndex": 21,
"Title": "OllamaAssistant.razor", "Title": "OllamaAssistant.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\OllamaAssistant.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\OllamaAssistant.razor",
"RelativeDocumentMoniker": "Components\\Pages\\OllamaAssistant.razor", "RelativeDocumentMoniker": "Components\\Pages\\OllamaAssistant.razor",
@ -507,7 +572,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 27, "DocumentIndex": 30,
"Title": "ProjectService.cs", "Title": "ProjectService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ProjectService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ProjectService.cs",
"RelativeDocumentMoniker": "Services\\ProjectService.cs", "RelativeDocumentMoniker": "Services\\ProjectService.cs",
@ -519,7 +584,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 28, "DocumentIndex": 31,
"Title": "IOllamaSetupService.cs", "Title": "IOllamaSetupService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaSetupService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaSetupService.cs",
"RelativeDocumentMoniker": "Services\\IOllamaSetupService.cs", "RelativeDocumentMoniker": "Services\\IOllamaSetupService.cs",
@ -531,7 +596,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 24, "DocumentIndex": 27,
"Title": "SafeFileHandle.Windows.cs", "Title": "SafeFileHandle.Windows.cs",
"DocumentMoniker": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs", "DocumentMoniker": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs",
"ToolTip": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs [Schreibgesch\u00FCtzt]", "ToolTip": "C:\\Users\\HP\\AppData\\Local\\SourceServer\\dddeb33c46e3c746b90c5f64463103c08ec03b9a85e92eab3b21c11218135d28\\src\\runtime\\src\\libraries\\System.Private.CoreLib\\src\\Microsoft\\Win32\\SafeHandles\\SafeFileHandle.Windows.cs [Schreibgesch\u00FCtzt]",
@ -542,7 +607,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 25, "DocumentIndex": 28,
"Title": "A7405018.jpg", "Title": "A7405018.jpg",
"DocumentMoniker": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg", "DocumentMoniker": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
"ToolTip": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg", "ToolTip": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
@ -551,7 +616,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 29, "DocumentIndex": 32,
"Title": "INSTALL.md", "Title": "INSTALL.md",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\INSTALL.md", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\INSTALL.md",
"RelativeDocumentMoniker": "INSTALL.md", "RelativeDocumentMoniker": "INSTALL.md",
@ -563,7 +628,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 30, "DocumentIndex": 33,
"Title": "IThemeService.cs", "Title": "IThemeService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IThemeService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IThemeService.cs",
"RelativeDocumentMoniker": "Services\\IThemeService.cs", "RelativeDocumentMoniker": "Services\\IThemeService.cs",
@ -575,7 +640,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 31, "DocumentIndex": 34,
"Title": "RedirectToLogin.razor", "Title": "RedirectToLogin.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\RedirectToLogin.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\RedirectToLogin.razor",
"RelativeDocumentMoniker": "Components\\RedirectToLogin.razor", "RelativeDocumentMoniker": "Components\\RedirectToLogin.razor",
@ -587,7 +652,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 32, "DocumentIndex": 35,
"Title": "ISettingsService.cs", "Title": "ISettingsService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ISettingsService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ISettingsService.cs",
"RelativeDocumentMoniker": "Services\\ISettingsService.cs", "RelativeDocumentMoniker": "Services\\ISettingsService.cs",
@ -595,11 +660,12 @@
"RelativeToolTip": "Services\\ISettingsService.cs", "RelativeToolTip": "Services\\ISettingsService.cs",
"ViewState": "AgIAABwAAAAAAAAAAAA9wC4AAAAZAAAAAAAAAA==", "ViewState": "AgIAABwAAAAAAAAAAAA9wC4AAAAZAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-21T16:35:36.655Z" "WhenOpened": "2026-05-21T16:35:36.655Z",
"EditorCaption": ""
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 33, "DocumentIndex": 36,
"Title": "IRagService.cs", "Title": "IRagService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IRagService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IRagService.cs",
"RelativeDocumentMoniker": "Services\\IRagService.cs", "RelativeDocumentMoniker": "Services\\IRagService.cs",
@ -611,7 +677,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 34, "DocumentIndex": 37,
"Title": "ThemeService.cs", "Title": "ThemeService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ThemeService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ThemeService.cs",
"RelativeDocumentMoniker": "Services\\ThemeService.cs", "RelativeDocumentMoniker": "Services\\ThemeService.cs",
@ -623,7 +689,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 35, "DocumentIndex": 38,
"Title": "logo.jpg", "Title": "logo.jpg",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Assets\\logo.jpg", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Assets\\logo.jpg",
"RelativeDocumentMoniker": "Assets\\logo.jpg", "RelativeDocumentMoniker": "Assets\\logo.jpg",
@ -634,7 +700,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 36, "DocumentIndex": 39,
"Title": "OllamaSettings.cs", "Title": "OllamaSettings.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\OllamaSettings.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\OllamaSettings.cs",
"RelativeDocumentMoniker": "Models\\OllamaSettings.cs", "RelativeDocumentMoniker": "Models\\OllamaSettings.cs",
@ -646,7 +712,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 26, "DocumentIndex": 29,
"Title": "AppDbContext.cs", "Title": "AppDbContext.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\AppDbContext.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\AppDbContext.cs",
"RelativeDocumentMoniker": "Data\\AppDbContext.cs", "RelativeDocumentMoniker": "Data\\AppDbContext.cs",
@ -657,7 +723,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 37, "DocumentIndex": 40,
"Title": "FileScannerService.cs", "Title": "FileScannerService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\FileScannerService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\FileScannerService.cs",
"RelativeDocumentMoniker": "Services\\FileScannerService.cs", "RelativeDocumentMoniker": "Services\\FileScannerService.cs",
@ -669,7 +735,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 38, "DocumentIndex": 41,
"Title": "TotpHelper.cs", "Title": "TotpHelper.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\TotpHelper.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\TotpHelper.cs",
"RelativeDocumentMoniker": "Services\\TotpHelper.cs", "RelativeDocumentMoniker": "Services\\TotpHelper.cs",
@ -681,7 +747,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 39, "DocumentIndex": 42,
"Title": "Project.cs", "Title": "Project.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\Project.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\Project.cs",
"RelativeDocumentMoniker": "Models\\Project.cs", "RelativeDocumentMoniker": "Models\\Project.cs",
@ -693,7 +759,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 40, "DocumentIndex": 43,
"Title": "RagService.cs", "Title": "RagService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\RagService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\RagService.cs",
"RelativeDocumentMoniker": "Services\\RagService.cs", "RelativeDocumentMoniker": "Services\\RagService.cs",
@ -705,7 +771,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 41, "DocumentIndex": 44,
"Title": "App.razor", "Title": "App.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\App.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\App.razor",
"RelativeDocumentMoniker": "Components\\App.razor", "RelativeDocumentMoniker": "Components\\App.razor",
@ -717,7 +783,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 42, "DocumentIndex": 45,
"Title": "Models.razor", "Title": "Models.razor",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Models.razor", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Models.razor",
"RelativeDocumentMoniker": "Components\\Pages\\Models.razor", "RelativeDocumentMoniker": "Components\\Pages\\Models.razor",
@ -729,7 +795,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 43, "DocumentIndex": 46,
"Title": "LLM_Model.cs", "Title": "LLM_Model.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\LLM_Model.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\LLM_Model.cs",
"RelativeDocumentMoniker": "Models\\LLM_Model.cs", "RelativeDocumentMoniker": "Models\\LLM_Model.cs",
@ -741,7 +807,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 44, "DocumentIndex": 47,
"Title": "ModelStatus.cs", "Title": "ModelStatus.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\ModelStatus.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\ModelStatus.cs",
"RelativeDocumentMoniker": "Models\\ModelStatus.cs", "RelativeDocumentMoniker": "Models\\ModelStatus.cs",
@ -753,7 +819,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 45, "DocumentIndex": 48,
"Title": "IFileScannerService.cs", "Title": "IFileScannerService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IFileScannerService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IFileScannerService.cs",
"RelativeDocumentMoniker": "Services\\IFileScannerService.cs", "RelativeDocumentMoniker": "Services\\IFileScannerService.cs",
@ -765,7 +831,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 46, "DocumentIndex": 49,
"Title": "bootstrap-utilities.css", "Title": "bootstrap-utilities.css",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css",
"RelativeDocumentMoniker": "wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css", "RelativeDocumentMoniker": "wwwroot\\lib\\bootstrap\\dist\\css\\bootstrap-utilities.css",
@ -777,19 +843,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 47, "DocumentIndex": 50,
"Title": "ProjectEntity.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeDocumentMoniker": "Data\\ProjectEntity.cs",
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\ProjectEntity.cs",
"RelativeToolTip": "Data\\ProjectEntity.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-14T10:50:06.549Z"
},
{
"$type": "Document",
"DocumentIndex": 48,
"Title": "IProjectService.cs", "Title": "IProjectService.cs",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IProjectService.cs", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IProjectService.cs",
"RelativeDocumentMoniker": "Services\\IProjectService.cs", "RelativeDocumentMoniker": "Services\\IProjectService.cs",
@ -801,7 +855,7 @@
}, },
{ {
"$type": "Document", "$type": "Document",
"DocumentIndex": 49, "DocumentIndex": 51,
"Title": "appsettings.json", "Title": "appsettings.json",
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json", "DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
"RelativeDocumentMoniker": "appsettings.json", "RelativeDocumentMoniker": "appsettings.json",

View file

@ -26,10 +26,35 @@
else else
{ {
<div class="page"> <div class="page">
@if (OpState.IsBusy)
{
<div class="llm-overlay">
<div class="llm-modal">
<div class="llm-title">
<span class="llm-title-spinner"></span>
@Loc["llm.overlay.title"]
</div>
<div class="llm-body">
<div class="llm-status">@OpState.StatusText</div>
@if (!string.IsNullOrEmpty(OpState.ThoughtText))
{
<div class="llm-thoughts">@OpState.ThoughtText</div>
}
else
{
<div class="llm-thoughts llm-thoughts-empty">@Loc["llm.overlay.placeholder"]</div>
}
</div>
</div>
</div>
}
<div class="main-content"> <div class="main-content">
@if (_isAuthenticated) @if (_isAuthenticated)
{ {
<aside class="sidebar"> <aside class="sidebar @(_showMobileSidebar ? "mobile-visible" : "")">
<div class="mobile-sidebar-close" @onclick="CloseMobileSidebar">✕</div>
<div class="sidebar-logo"> <div class="sidebar-logo">
<img src="/Assets/logo.jpg" alt="Author Buddy" /> <img src="/Assets/logo.jpg" alt="Author Buddy" />
</div> </div>
@ -343,6 +368,14 @@ else
</aside> </aside>
} }
@if (_isAuthenticated)
{
<button class="hamburger" @onclick="ToggleMobileSidebar">☰</button>
}
@if (_showMobileSidebar)
{
<div class="mobile-sidebar-backdrop" @onclick="CloseMobileSidebar"></div>
}
<main class="detail-panel"> <main class="detail-panel">
@if (currentProjectId > 0) @if (currentProjectId > 0)
{ {
@ -452,12 +485,15 @@ else
private string newFileName = string.Empty; private string newFileName = string.Empty;
private FileTreeItem? newFileParentDir; private FileTreeItem? newFileParentDir;
private bool showCopyProjectDialog; private bool showCopyProjectDialog;
private bool _showMobileSidebar;
private Action? _opStateHandler; private Action? _opStateHandler;
private string? _editingCategoryPath; private string? _editingCategoryPath;
private HashSet<string> _selectedFiles = new(); private HashSet<string> _selectedFiles = new();
private bool _showExportDialog; private bool _showExportDialog;
private string exportFileName = string.Empty; private string exportFileName = string.Empty;
private string exportFormat = "Html"; private string exportFormat = "Html";
private long _lastThoughtScrollTicks;
private string? _lastVisitedPage;
[Inject] [Inject]
private NavigationManager Navigation { get; set; } = default!; private NavigationManager Navigation { get; set; } = default!;
@ -517,23 +553,30 @@ else
if (firstRender) if (firstRender)
{ {
// Restore last visited page from localStorage
_lastVisitedPage = await JS.InvokeAsync<string>("localStorage.getItem", "last_visited_page");
if (!string.IsNullOrEmpty(_lastVisitedPage))
{
Navigation.NavigateTo(_lastVisitedPage);
}
await ThemeService.ApplyTheme(OllamaSettings.Theme); await ThemeService.ApplyTheme(OllamaSettings.Theme);
// Auto-login runs AFTER circuit establishment (correct scoped services) // Auto-login runs AFTER circuit establishment (correct scoped services)
await LoadCurrentUser(); await LoadCurrentUser();
// #if DEBUG // #if DEBUG
// if (!_isAuthenticated && Program.DebugAutoLogin) // if (!_isAuthenticated && Program.DebugAutoLogin)
// { // {
// var users = await SettingsService.GetUsersAsync(); // var users = await SettingsService.GetUsersAsync();
// var first = users.FirstOrDefault(u => u.Role == UserRole.Administrator); // var first = users.FirstOrDefault(u => u.Role == UserRole.Administrator);
// if (first != null) // if (first != null)
// { // {
// await AuthService.AdminBypassLoginAsync(first.Id); // await AuthService.AdminBypassLoginAsync(first.Id);
// AuthStateProvider.NotifyStateChanged(); // AuthStateProvider.NotifyStateChanged();
// await LoadCurrentUser(); // await LoadCurrentUser();
// } // }
// } // }
// #endif // #endif
if (_isAuthenticated) if (_isAuthenticated)
{ {
await LoadProjects(); await LoadProjects();
@ -544,6 +587,16 @@ else
_isLoading = false; _isLoading = false;
StateHasChanged(); StateHasChanged();
} }
if (OpState.IsBusy && OpState.ThoughtText.Length > 0)
{
var now = DateTime.UtcNow.Ticks;
if (now - _lastThoughtScrollTicks > TimeSpan.TicksPerMillisecond * 80)
{
_lastThoughtScrollTicks = now;
await JS.InvokeVoidAsync("eval", "var el=document.querySelector('.llm-thoughts'); if(el){el.scrollTop=el.scrollHeight;}");
}
}
} }
public void Dispose() public void Dispose()
@ -560,6 +613,14 @@ else
{ {
await LoadProjects(); await LoadProjects();
await LoadProjectContext(); await LoadProjectContext();
if (Navigation.Uri.Contains("quick-markdown") ||
Navigation.Uri.Contains("chat") ||
Navigation.Uri.Contains("editor/"))
{
// Save current page to localStorage
await JS.InvokeVoidAsync("localStorage.setItem", "last_visited_page", Navigation.Uri);
}
} }
StateHasChanged(); StateHasChanged();
} }
@ -710,9 +771,20 @@ else
private void NavigateTo(string path) private void NavigateTo(string path)
{ {
activeSection = null; activeSection = null;
_showMobileSidebar = false;
Navigation.NavigateTo(path); Navigation.NavigateTo(path);
} }
private void ToggleMobileSidebar()
{
_showMobileSidebar = !_showMobileSidebar;
}
private void CloseMobileSidebar()
{
_showMobileSidebar = false;
}
private void ToggleInfoOverlay() private void ToggleInfoOverlay()
{ {
_showInfoOverlay = !_showInfoOverlay; _showInfoOverlay = !_showInfoOverlay;

View file

@ -7,6 +7,7 @@
@inject IAuthService AuthService @inject IAuthService AuthService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IJSRuntime JS @inject IJSRuntime JS
@inject IFactCheckService FactCheckService
@rendermode InteractiveServer @rendermode InteractiveServer
@ -38,6 +39,12 @@
<span>kreativ (2)</span> <span>kreativ (2)</span>
</div> </div>
</div> </div>
<div class="form-group">
<label class="factcheck-toggle">
<input type="checkbox" @bind="_factCheckEnabled" />
Antwort automatisch per DuckDuckGo verifizieren
</label>
</div>
<div class="form-group"> <div class="form-group">
<button class="btn btn-sm btn-primary" @onclick="SaveSettings">System-Prompt & Temperatur speichern</button> <button class="btn btn-sm btn-primary" @onclick="SaveSettings">System-Prompt & Temperatur speichern</button>
<button class="btn btn-sm" @onclick="ResetToDefault">Auf Standard zurücksetzen</button> <button class="btn btn-sm" @onclick="ResetToDefault">Auf Standard zurücksetzen</button>
@ -50,22 +57,36 @@
{ {
<div class="chat-empty">Noch keine Nachrichten. Stelle eine Frage.</div> <div class="chat-empty">Noch keine Nachrichten. Stelle eine Frage.</div>
} }
@foreach (var msg in _messages) @for (var i = 0; i < _messages.Count; i++)
{ {
var msg = _messages[i];
var checks = _factChecks.Count > i ? _factChecks[i] : null;
<div class="chat-message chat-user"> <div class="chat-message chat-user">
<div class="chat-avatar">U</div> <div class="chat-avatar">U</div>
<div class="chat-bubble">@msg.UserMessage</div> <div class="chat-bubble">@msg.UserMessage</div>
</div> </div>
<div class="chat-message chat-assistant"> <div class="chat-message chat-assistant">
<div class="chat-avatar">A</div> <div class="chat-avatar">A</div>
<div class="chat-bubble">@MarkdownToHtml(msg.AssistantResponse)</div> <div class="chat-bubble">
@RenderAssistantMessage(msg.AssistantResponse)
@if (checks != null && checks.Count > 0)
{
<div class="factcheck-section">
<div class="factcheck-title">Faktenprüfung</div>
@foreach (var check in checks)
{
@RenderFactCheck(check)
}
</div>
}
</div>
</div> </div>
} }
@if (_isBusy) @if (_isBusy)
{ {
<div class="chat-message chat-assistant"> <div class="chat-message chat-assistant">
<div class="chat-avatar">A</div> <div class="chat-avatar">A</div>
<div class="chat-bubble chat-thinking">…</div> <div class="chat-bubble">@RenderAssistantMessage(_streamingResponse, true)</div>
</div> </div>
} }
</div> </div>
@ -97,6 +118,9 @@
private string _systemPrompt = string.Empty; private string _systemPrompt = string.Empty;
private double _temperature = 0.7; private double _temperature = 0.7;
private bool _needsScroll; private bool _needsScroll;
private string _streamingResponse = string.Empty;
private bool _factCheckEnabled;
private List<List<FactCheckResult>?> _factChecks = new();
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -108,9 +132,11 @@
} }
_userId = user.Id; _userId = user.Id;
_messages = await ChatService.GetHistoryAsync(_userId); _messages = await ChatService.GetHistoryAsync(_userId);
_factChecks = _messages.Select(_ => (List<FactCheckResult>?)null).ToList();
_systemPrompt = await SettingsService.GetChatPromptAsync() _systemPrompt = await SettingsService.GetChatPromptAsync()
?? Settings.GetChatDefaultPrompt(Settings.Language); ?? Settings.GetChatDefaultPrompt(Settings.Language);
_temperature = await SettingsService.GetChatTempAsync(); _temperature = await SettingsService.GetChatTempAsync();
_factCheckEnabled = await SettingsService.GetChatFactCheckEnabledAsync();
_needsScroll = true; _needsScroll = true;
} }
@ -132,6 +158,7 @@
{ {
await SettingsService.SaveChatPromptAsync(_systemPrompt); await SettingsService.SaveChatPromptAsync(_systemPrompt);
await SettingsService.SaveChatTempAsync(_temperature); await SettingsService.SaveChatTempAsync(_temperature);
await SettingsService.SaveChatFactCheckEnabledAsync(_factCheckEnabled);
} }
private async Task ResetToDefault() private async Task ResetToDefault()
@ -157,22 +184,40 @@
_input = string.Empty; _input = string.Empty;
_error = string.Empty; _error = string.Empty;
_isBusy = true; _isBusy = true;
_streamingResponse = string.Empty;
_needsScroll = true; _needsScroll = true;
try try
{ {
var response = await OllamaService.Chat(Settings.StyleModel, text, _systemPrompt, _temperature); var model = await SettingsService.GetStyleModelAsync() ?? "llama3.2";
await OllamaService.ChatStream(model, text, async chunk =>
{
_streamingResponse += chunk;
_needsScroll = true;
await InvokeAsync(StateHasChanged);
}, _systemPrompt, _temperature, Settings.StyleBackend);
var msg = new ChatEntity var msg = new ChatEntity
{ {
UserId = _userId, UserId = _userId,
UserMessage = text, UserMessage = text,
AssistantResponse = response, AssistantResponse = _streamingResponse,
Model = Settings.StyleModel, Model = model,
Timestamp = DateTime.UtcNow Timestamp = DateTime.UtcNow
}; };
await ChatService.AddMessageAsync(msg); await ChatService.AddMessageAsync(msg);
_messages.Add(msg); _messages.Add(msg);
if (_factCheckEnabled && !string.IsNullOrWhiteSpace(_streamingResponse))
{
_needsScroll = true;
_factChecks.Add(await FactCheckService.VerifyAsync(_streamingResponse, Settings.StyleBackend));
}
else
{
_factChecks.Add(null);
}
_needsScroll = true; _needsScroll = true;
} }
catch (Exception ex) catch (Exception ex)
@ -182,7 +227,6 @@
finally finally
{ {
_isBusy = false; _isBusy = false;
_input = string.Empty;
_needsScroll = true; _needsScroll = true;
} }
} }
@ -198,13 +242,99 @@
await JS.InvokeVoidAsync("eval", "document.getElementById('chat-messages').scrollTop = document.getElementById('chat-messages').scrollHeight"); await JS.InvokeVoidAsync("eval", "document.getElementById('chat-messages').scrollTop = document.getElementById('chat-messages').scrollHeight");
} }
private MarkupString MarkdownToHtml(string text) private static (string thinking, string answer) ParseResponse(string text)
{ {
if (string.IsNullOrEmpty(text)) return new MarkupString(string.Empty); if (string.IsNullOrEmpty(text)) return ("", "");
var html = System.Web.HttpUtility.HtmlEncode(text);
html = System.Text.RegularExpressions.Regex.Replace(html, @"\*\*(.+?)\*\*", "<strong>$1</strong>"); var closeTag = "</thinking>";
html = System.Text.RegularExpressions.Regex.Replace(html, @"\*(.+?)\*", "<em>$1</em>"); var closeIdx = text.IndexOf(closeTag, StringComparison.Ordinal);
html = html.Replace("\n", "<br />"); if (closeIdx >= 0)
return new MarkupString(html); {
var openTag = "<thinking>";
var openIdx = text.IndexOf(openTag, StringComparison.Ordinal);
if (openIdx >= 0 && openIdx < closeIdx)
{
var thinking = text[(openIdx + openTag.Length)..closeIdx].Trim();
var answer = text[(closeIdx + closeTag.Length)..].Trim();
return (thinking, answer);
}
}
var lastOpenIdx = text.LastIndexOf("<thinking>", StringComparison.Ordinal);
if (lastOpenIdx >= 0)
return (text[(lastOpenIdx + "<thinking>".Length)..], "");
return ("", text);
}
private MarkupString RenderAssistantMessage(string text, bool isStreaming = false)
{
if (string.IsNullOrEmpty(text))
return new MarkupString(isStreaming ? "<span class=\"chat-thinking\">…</span>" : string.Empty);
var (thinking, answer) = ParseResponse(text);
var html = new System.Text.StringBuilder();
if (!string.IsNullOrEmpty(thinking))
{
html.Append("<details class=\"thinking-block\"");
if (isStreaming || !string.IsNullOrEmpty(answer))
html.Append(" open");
html.Append("><summary>Gedankengang</summary><div class=\"thinking-content\">");
html.Append(System.Web.HttpUtility.HtmlEncode(thinking));
html.Append("</div></details>");
}
if (!string.IsNullOrEmpty(answer))
{
var encoded = System.Web.HttpUtility.HtmlEncode(answer);
encoded = System.Text.RegularExpressions.Regex.Replace(encoded, @"\*\*(.+?)\*\*", "<strong>$1</strong>");
encoded = System.Text.RegularExpressions.Regex.Replace(encoded, @"\*(.+?)\*", "<em>$1</em>");
encoded = encoded.Replace("\n", "<br />");
html.Append(encoded);
}
else if (string.IsNullOrEmpty(thinking))
{
html.Append("<span class=\"chat-thinking\">…</span>");
}
return new MarkupString(html.ToString());
}
private MarkupString RenderFactCheck(FactCheckResult check)
{
var (icon, label, cssClass) = check.Verdict switch
{
FactCheckVerdict.Confirmed => ("✓", "Bestätigt", "verdict-confirmed"),
FactCheckVerdict.Contradicted => ("✗", "Widersprochen", "verdict-contradicted"),
FactCheckVerdict.Partial => ("⚠", "Teilweise bestätigt", "verdict-partial"),
_ => ("?", "Keine Information", "verdict-none")
};
var html = new System.Text.StringBuilder();
html.Append("<div class=\"factcheck-item\">");
html.Append($"<div class=\"factcheck-header\"><span class=\"factcheck-verdict {cssClass}\">{icon} {label}</span></div>");
html.Append("<div class=\"factcheck-claim\">");
html.Append(System.Web.HttpUtility.HtmlEncode(check.Claim));
html.Append("</div>");
if (!string.IsNullOrEmpty(check.Reasoning))
{
html.Append("<div class=\"factcheck-reasoning\">");
html.Append(System.Web.HttpUtility.HtmlEncode(check.Reasoning));
html.Append("</div>");
}
if (check.Sources.Count > 0)
{
html.Append("<div class=\"factcheck-sources\">");
foreach (var source in check.Sources)
{
html.Append("<div class=\"factcheck-source\">");
html.Append(System.Web.HttpUtility.HtmlEncode(source));
html.Append("</div>");
}
html.Append("</div>");
}
html.Append("</div>");
return new MarkupString(html.ToString());
} }
} }

View file

@ -4,7 +4,7 @@
@using System.Threading @using System.Threading
@inject IOllamaService OllamaService @inject IOllamaService OllamaService
@inject IProjectService ProjectService @inject IProjectService ProjectService
@inject IRagService RagService @inject ILlmWikiService WikiService
@inject ISettingsService SettingsService @inject ISettingsService SettingsService
@inject IWikipediaService WikipediaService @inject IWikipediaService WikipediaService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@ -62,7 +62,7 @@
<button @onclick="ShowVersionDialog" disabled="@(string.IsNullOrEmpty(currentFile))">@Loc["editor.version.button.save"]</button> <button @onclick="ShowVersionDialog" disabled="@(string.IsNullOrEmpty(currentFile))">@Loc["editor.version.button.save"]</button>
<button @onclick="ShowVersionBrowser" disabled="@(string.IsNullOrEmpty(currentFile))">@Loc["editor.version.button.browse"]</button> <button @onclick="ShowVersionBrowser" disabled="@(string.IsNullOrEmpty(currentFile))">@Loc["editor.version.button.browse"]</button>
<button @onclick="OpenAssistant">@Loc["editor.button.assistant"]</button> <button @onclick="OpenAssistant">@Loc["editor.button.assistant"]</button>
<button @onclick="IndexProject" disabled="@isChecking">@Loc["editor.button.index_project"]</button> <button @onclick="CompileWiki" disabled="@isChecking">@Loc["editor.button.compile_wiki"]</button>
</div> </div>
@if (showVersionDialog) @if (showVersionDialog)
@ -87,7 +87,7 @@
<div class="search-section"> <div class="search-section">
<input value="@searchQuery" @oninput="HandleSearchInput" placeholder="@Loc["editor.search.placeholder"]" <input value="@searchQuery" @oninput="HandleSearchInput" placeholder="@Loc["editor.search.placeholder"]"
disabled="@isChecking" /> disabled="@isChecking" />
<button @onclick="RagSearch" disabled=@buttonDisabled>@Loc["editor.button.search_rag"]</button> <button @onclick="QueryWiki" disabled=@buttonDisabled>@Loc["editor.button.search_wiki"]</button>
<button @onclick="SearchWikipedia" disabled=@buttonDisabled>@Loc["editor.button.search_wikipedia"]</button> <button @onclick="SearchWikipedia" disabled=@buttonDisabled>@Loc["editor.button.search_wikipedia"]</button>
</div> </div>
@ -153,18 +153,11 @@
<button class="btn btn-secondary" @onclick="CloseVersionBrowser">@Loc["common.cancel"]</button> <button class="btn btn-secondary" @onclick="CloseVersionBrowser">@Loc["common.cancel"]</button>
</div> </div>
} }
else if (ragResults != null && ragResults.Count > 0) else if (!string.IsNullOrEmpty(wikiResult))
{ {
<div class="results-area"> <div class="results-area">
<h4>@Loc["editor.rag.heading"]</h4> <h4>@Loc["editor.wiki.heading"]</h4>
@foreach (var r in ragResults) <div class="wiki-result">@Markup(wikiResult)</div>
{
<div class="fact-card">
<div class="fact-similarity">@Loc["editor.rag.label.similarity"] @r.Similarity.ToString("P1")</div>
<div class="fact-path">@r.FilePath</div>
<div class="fact-text">@r.Text</div>
</div>
}
</div> </div>
} }
else if (wikipediaResults != null && wikipediaResults.Count > 0) else if (wikipediaResults != null && wikipediaResults.Count > 0)
@ -261,7 +254,7 @@
private string originalText = string.Empty; private string originalText = string.Empty;
private string correctedText = string.Empty; private string correctedText = string.Empty;
private bool showSpellDiff; private bool showSpellDiff;
private List<RagSearchResult>? ragResults; private string? wikiResult;
private List<WikipediaResult>? wikipediaResults; private List<WikipediaResult>? wikipediaResults;
private int currentProjectId; private int currentProjectId;
@ -414,7 +407,7 @@
showSpellDiff = false; showSpellDiff = false;
showVersionBrowser = false; showVersionBrowser = false;
showVersionDiff = false; showVersionDiff = false;
ragResults = null; wikiResult = null;
wikipediaResults = null; wikipediaResults = null;
analysisResult = string.Empty; analysisResult = string.Empty;
} }
@ -464,7 +457,7 @@
correctedText += chunk; correctedText += chunk;
OpState.AppendThought(chunk); OpState.AppendThought(chunk);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
}, OllamaSettings.SpellingPrompt, OllamaSettings.SpellingTemp); }, OllamaSettings.SpellingPrompt, OllamaSettings.SpellingTemp, OllamaSettings.SpellingBackend);
showSpellDiff = true; showSpellDiff = true;
OpState.SetStatus(Loc["common.status.ready"], false); OpState.SetStatus(Loc["common.status.ready"], false);
@ -493,12 +486,12 @@
statusText = Loc["editor.status.corrections_applied"]; statusText = Loc["editor.status.corrections_applied"];
} }
private async Task RagSearch() private async Task QueryWiki()
{ {
if (string.IsNullOrWhiteSpace(searchQuery)) return; if (string.IsNullOrWhiteSpace(searchQuery)) return;
isChecking = true; isChecking = true;
statusText = Loc["editor.status.searching_rag"]; statusText = Loc["editor.status.searching_wiki"];
ClearResults(); ClearResults();
analysisResult = string.Empty; analysisResult = string.Empty;
@ -506,20 +499,16 @@
{ {
if (currentProjectId == 0) if (currentProjectId == 0)
{ {
analysisResult = Loc["editor.rag.error.no_project"]; analysisResult = Loc["editor.wiki.error.no_project"];
return; return;
} }
ragResults = await RagService.SearchAsync(currentProjectId, searchQuery, 5); var answer = await WikiService.QueryWikiAsync(currentProjectId, searchQuery);
wikiResult = answer;
if (ragResults.Count == 0)
{
analysisResult = Loc["editor.rag.error.no_facts"];
}
} }
catch (Exception ex) catch (Exception ex)
{ {
analysisResult = $"{Loc["editor.rag.error.generic"]} {ex.Message}"; analysisResult = $"{Loc["editor.wiki.error.generic"]} {ex.Message}";
} }
finally finally
{ {
@ -682,18 +671,18 @@
} }
} }
private async Task IndexProject() private async Task CompileWiki()
{ {
if (currentProjectId == 0) if (currentProjectId == 0)
{ {
analysisResult = Loc["editor.index.error.no_project"]; analysisResult = Loc["editor.wiki.error.no_project"];
return; return;
} }
isChecking = true; isChecking = true;
statusText = Loc["editor.status.indexing"]; statusText = Loc["editor.status.compiling_wiki"];
analysisResult = string.Empty; analysisResult = string.Empty;
ragResults = null; wikiResult = null;
try try
{ {
@ -701,7 +690,7 @@
var project = projects.FirstOrDefault(p => p.Id == currentProjectId); var project = projects.FirstOrDefault(p => p.Id == currentProjectId);
if (project == null) if (project == null)
{ {
analysisResult = Loc["editor.index.error.not_found"]; analysisResult = Loc["editor.wiki.error.not_found"];
return; return;
} }
@ -711,12 +700,12 @@
InvokeAsync(StateHasChanged); InvokeAsync(StateHasChanged);
}); });
await RagService.IndexProjectAsync(currentProjectId, project.RootPath, p); await WikiService.CompileWikiAsync(currentProjectId, project.RootPath, p);
analysisResult = Loc["editor.index.status.success"]; analysisResult = Loc["editor.wiki.status.success"];
} }
catch (Exception ex) catch (Exception ex)
{ {
analysisResult = $"{Loc["editor.index.error.generic"]} {ex.Message}"; analysisResult = $"{Loc["editor.wiki.error.generic"]} {ex.Message}";
} }
finally finally
{ {
@ -900,4 +889,6 @@
comment comment
); );
} }
private static MarkupString Markup(string text) => new(text);
} }

View file

@ -6,39 +6,18 @@
<PageTitle>@Loc["page.title.home"]</PageTitle> <PageTitle>@Loc["page.title.home"]</PageTitle>
<div class="tab-bar">
<div class="tab @GetTabClass("rag")" @onclick='() => activeTab = "rag"'>@Loc["home.tab.rag_facts"]</div>
<div class="tab @GetTabClass("style")" @onclick='() => activeTab = "style"'>@Loc["home.tab.style_analysis"]</div>
</div>
<div class="tab-content"> <div class="tab-content">
@if (activeTab == "rag") @if (styleSuggestions.Length == 0)
{ {
@if (foundFacts.Count == 0) <p style="color: var(--border-color);">Willkommen bei Author Buddy. Öffne ein Projekt im Editor, um loszulegen.</p>
{
<p style="color: var(--border-color);">@Loc["home.rag.empty"]</p>
} }
else else
{ {
@foreach (var fact in foundFacts) <h3>@Loc["home.tab.style_analysis"]</h3>
{
<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> <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> </div>
@code { @code {
private string activeTab = "rag";
private List<string> foundFacts = new();
private string styleSuggestions = string.Empty; private string styleSuggestions = string.Empty;
private string GetTabClass(string tab)
{
return activeTab == tab ? "active" : "";
}
} }

View file

@ -7,21 +7,30 @@
<PageTitle>@Loc["page.title.models"]</PageTitle> <PageTitle>@Loc["page.title.models"]</PageTitle>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;"> @if (_isCloudBackend)
<h3>@Loc["models.heading"]</h3>
<button @onclick="RefreshModels" disabled="@isLoading">@Loc["models.button.refresh"]</button>
</div>
@if (isLoading)
{ {
<p>@Loc["models.status.loading"]</p> <div style="padding: 24px; text-align: center; color: var(--text-muted);">
} <p>@Loc["models.cloud.not_available"]</p>
else if (!string.IsNullOrEmpty(errorMessage)) <p style="font-size: 12px; margin-top: 8px;">@Loc["models.cloud.hint"]</p>
{ </div>
<div style="color: red; margin-bottom: 12px;">@errorMessage</div>
} }
else else
{ {
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3>@Loc["models.heading"]</h3>
<button @onclick="RefreshModels" disabled="@isLoading">@Loc["models.button.refresh"]</button>
</div>
@if (isLoading)
{
<p>@Loc["models.status.loading"]</p>
}
else if (!string.IsNullOrEmpty(errorMessage))
{
<div style="color: red; margin-bottom: 12px;">@errorMessage</div>
}
else
{
<div class="model-list"> <div class="model-list">
@foreach (var model in localModels) @foreach (var model in localModels)
{ {
@ -41,9 +50,9 @@ else
</div> </div>
} }
</div> </div>
} }
<div style="margin-top: 24px; border-top: 1px solid var(--border-color); padding-top: 16px;"> <div style="margin-top: 24px; border-top: 1px solid var(--border-color); padding-top: 16px;">
<h4 style="margin-bottom: 8px;">@Loc["models.pull.heading"]</h4> <h4 style="margin-bottom: 8px;">@Loc["models.pull.heading"]</h4>
<div style="display: flex; gap: 8px; align-items: flex-end;"> <div style="display: flex; gap: 8px; align-items: flex-end;">
<div class="form-group" style="flex: 1; margin-bottom: 0;"> <div class="form-group" style="flex: 1; margin-bottom: 0;">
@ -63,17 +72,17 @@ else
<div style="font-size: 12px; margin-top: 4px;">@pullStatus (@pullPercentDisplay)</div> <div style="font-size: 12px; margin-top: 4px;">@pullStatus (@pullPercentDisplay)</div>
</div> </div>
} }
</div> </div>
@if (pullResult != null) @if (pullResult != null)
{ {
<div style="margin-top: 12px; padding: 8px; background: var(--card-bg); border: 1px solid var(--accent-color); font-size: 13px;"> <div style="margin-top: 12px; padding: 8px; background: var(--card-bg); border: 1px solid var(--accent-color); font-size: 13px;">
@pullResult @pullResult
</div> </div>
} }
@if (_confirmDeleteModel != null) @if (_confirmDeleteModel != null)
{ {
<div class="modal-overlay"> <div class="modal-overlay">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-title">Modell löschen</div> <div class="modal-title">Modell löschen</div>
@ -86,6 +95,7 @@ else
</div> </div>
</div> </div>
</div> </div>
}
} }
@code { @code {
@ -100,9 +110,12 @@ else
private string? pullResult; private string? pullResult;
private string pullPercentDisplay => $"{pullProgress:F0}%"; private string pullPercentDisplay => $"{pullProgress:F0}%";
private string? _confirmDeleteModel; private string? _confirmDeleteModel;
private bool _isCloudBackend;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_isCloudBackend = !OllamaService.SupportsModelManagement;
if (!_isCloudBackend)
await RefreshModels(); await RefreshModels();
} }

View file

@ -2,11 +2,12 @@
@attribute [Authorize] @attribute [Authorize]
@inject IOllamaService OllamaService @inject IOllamaService OllamaService
@inject ISettingsService SettingsService @inject ISettingsService SettingsService
@inject IRagService RagService @inject ILlmWikiService WikiService
@inject IProjectService ProjectService @inject IProjectService ProjectService
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject ILocalizationService Loc @inject ILocalizationService Loc
@inject OperationState OpState @inject OperationState OpState
@inject OllamaSettings OllamaConfig
<PageTitle>@Loc["page.title.assistant"]</PageTitle> <PageTitle>@Loc["page.title.assistant"]</PageTitle>
@ -18,7 +19,7 @@
<option value="hookfinder">@Loc["assistant.mode.hookfinder"]</option> <option value="hookfinder">@Loc["assistant.mode.hookfinder"]</option>
<option value="eli5">@Loc["assistant.mode.eli5"]</option> <option value="eli5">@Loc["assistant.mode.eli5"]</option>
<option value="coauthor">@Loc["assistant.mode.coauthor"]</option> <option value="coauthor">@Loc["assistant.mode.coauthor"]</option>
<option value="rag">@Loc["assistant.mode.rag"]</option> <option value="wiki">@Loc["assistant.mode.wiki"]</option>
@if (currentTemplate == ProjectTemplate.Technical) @if (currentTemplate == ProjectTemplate.Technical)
{ {
<option disabled style="font-style:italic;">── Requirements ──</option> <option disabled style="font-style:italic;">── Requirements ──</option>
@ -46,7 +47,7 @@
} }
</div> </div>
@if (showPromptEditor && selectedMode != "rag") @if (showPromptEditor && selectedMode != "wiki")
{ {
<div class="assistant-prompt-editor"> <div class="assistant-prompt-editor">
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;"> <div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
@ -131,7 +132,7 @@
private async Task LoadPromptForMode() private async Task LoadPromptForMode()
{ {
if (selectedMode == "rag") if (selectedMode == "wiki")
{ {
promptOverride = string.Empty; promptOverride = string.Empty;
temperatureOverride = 0.3; temperatureOverride = 0.3;
@ -186,7 +187,7 @@
try try
{ {
if (selectedMode == "rag") if (selectedMode == "wiki")
{ {
var projectId = SettingsService.CurrentProjectId; var projectId = SettingsService.CurrentProjectId;
if (projectId == null) if (projectId == null)
@ -197,18 +198,10 @@
OpState.SetStatus(Loc["assistant.status.searching"], true); OpState.SetStatus(Loc["assistant.status.searching"], true);
progress = 30; progress = 30;
var results = await RagService.SearchAsync(projectId.Value, inputText, 5); var answer = await WikiService.QueryWikiAsync(projectId.Value, inputText);
if (results.Count == 0) progress = 100;
{ outputText = answer;
errorMessage = Loc["assistant.error.no_facts"]; OpState.SetThought(answer);
return;
}
var facts = string.Join("\n\n---\n\n", results.Select(r => r.Text));
progress = 70;
OpState.SetStatus(Loc["assistant.status.processing"], true);
outputText = await OllamaService.AnswerBasedOnFactsAsync(inputText, facts);
OpState.SetThought(outputText);
statusText = Loc["assistant.status.done"]; statusText = Loc["assistant.status.done"];
OpState.SetStatus(Loc["assistant.status.done"], false); OpState.SetStatus(Loc["assistant.status.done"], false);
} }
@ -254,7 +247,7 @@
outputText += chunk; outputText += chunk;
OpState.AppendThought(chunk); OpState.AppendThought(chunk);
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
}, systemPrompt, temperatureOverride); }, systemPrompt, temperatureOverride, OllamaConfig.StyleBackend);
statusText = Loc["assistant.status.done"]; statusText = Loc["assistant.status.done"];
OpState.SetStatus(Loc["assistant.status.done"], false); OpState.SetStatus(Loc["assistant.status.done"], false);

View file

@ -6,12 +6,55 @@
@inject IAuthService AuthService @inject IAuthService AuthService
@inject ISettingsService SettingsService @inject ISettingsService SettingsService
@inject IQuickMarkdownService QuickMarkdownService @inject IQuickMarkdownService QuickMarkdownService
@inject IPluginManager PluginManager
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IFactSearchToolService FactSearchToolService
@rendermode InteractiveServer
<PageTitle>Quick Markdown</PageTitle> <PageTitle>Quick Markdown</PageTitle>
<style>
.qm-fullscreen-textarea {
width: 100%;
height: calc(100vh - 140px);
font-size: 16px;
}
.qm-fullscreen-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background: var(--bs-dark);
color: var(--bs-light);
}
.qm-fullscreen-container .qm-container {
max-width: 100%;
margin: 0;
height: 100%;
display: flex;
flex-direction: column;
}
.qm-fullscreen-container .qm-output-panel {
height: 40%;
border-top: 1px solid var(--bs-gray);
}
.qm-fullscreen-container .qm-input-panel {
height: 60%;
overflow-y: auto;
}
.qm-fullscreen-container .qm-prompt-selector,
.qm-fullscreen-container .qm-generate-row,
.qm-fullscreen-container .qm-range-labels {
display: none;
}
</style>
<div class="qm-container"> <div class="qm-container">
<h2>Quick Markdown</h2> <h2>Quick Markdown</h2>
<p>Erzeugt aus einem Text und einem System-Prompt direkt formatiertes Markdown – ohne Projekt oder Speicherung.</p> <p>Erzeugt aus einem Text und einem System-Prompt direkt formatiertes Markdown – ohne Projekt oder Speicherung.</p>
@ -29,6 +72,7 @@
<option value="@p.Id">@p.Name</option> <option value="@p.Id">@p.Name</option>
} }
</select> </select>
<button class="btn btn-sm @(_fullscreenMode ? "btn-primary" : "")" @onclick="ToggleFullscreen">Vollbild</button>
<button class="btn btn-sm" @onclick="ShowSavePromptDialog">+ Als Prompt speichern</button> <button class="btn btn-sm" @onclick="ShowSavePromptDialog">+ Als Prompt speichern</button>
<button class="btn btn-sm" @onclick="DeleteCurrentPrompt" disabled="@(_selectedPromptId == 0)">Löschen</button> <button class="btn btn-sm" @onclick="DeleteCurrentPrompt" disabled="@(_selectedPromptId == 0)">Löschen</button>
</div> </div>
@ -44,12 +88,12 @@
<div class="form-group"> <div class="form-group">
<label>System-Prompt</label> <label>System-Prompt</label>
<textarea class="qm-textarea qm-prompt" @bind="_systemPrompt" rows="4"></textarea> <textarea class="qm-textarea qm-prompt @(_fullscreenMode ? "qm-fullscreen-textarea" : "")" @bind="_systemPrompt" @bind:after="PersistPromptDraft" rows="4"></textarea>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="tempRange">Temperatur: @_temperature.ToString("F2")</label> <label for="tempRange">Temperatur: @_temperature.ToString("F2")</label>
<input id="tempRange" type="range" min="0" max="2" step="0.05" @bind="_temperature" /> <input id="tempRange" type="range" min="0" max="2" step="0.05" @bind="_temperature" @bind:event="oninput" @bind:after="PersistPromptDraft" />
<div class="qm-range-labels"> <div class="qm-range-labels">
<span>präzise (0)</span> <span>präzise (0)</span>
<span>kreativ (2)</span> <span>kreativ (2)</span>
@ -58,7 +102,7 @@
<div class="form-group"> <div class="form-group">
<label>Text</label> <label>Text</label>
<textarea class="qm-textarea" @bind="_inputText" @bind:event="oninput" rows="12" placeholder="Text hier eingeben…"></textarea> <textarea class="qm-textarea" @bind="_inputText" @bind:event="oninput" @bind:after="PersistTextDraft" rows="12" placeholder="Text hier eingeben…"></textarea>
</div> </div>
<div class="qm-generate-row"> <div class="qm-generate-row">
@ -75,6 +119,9 @@
<button class="btn btn-sm" @onclick="SaveEntry" disabled="@_saveEntryDisabled"> <button class="btn btn-sm" @onclick="SaveEntry" disabled="@_saveEntryDisabled">
Eingabe & Ergebnis speichern Eingabe & Ergebnis speichern
</button> </button>
<button class="btn btn-sm" @onclick="StartFactSearch" disabled="@(_isBusy || string.IsNullOrWhiteSpace(_inputText))" title="Prüft die Zeile mit [Fakt] im Text per Websuche">
🔍 Fakten prüfen
</button>
</div> </div>
</div> </div>
@ -84,7 +131,24 @@
<div class="qm-output-toolbar"> <div class="qm-output-toolbar">
<button class="btn btn-small" @onclick="CopyOutput" disabled="@string.IsNullOrEmpty(_output)">📋 Kopieren</button> <button class="btn btn-small" @onclick="CopyOutput" disabled="@string.IsNullOrEmpty(_output)">📋 Kopieren</button>
<button class="btn btn-small" @onclick="ClearOutput">🗑 Leeren</button> <button class="btn btn-small" @onclick="ClearOutput">🗑 Leeren</button>
@if (_availablePlugins.Count > 0)
{
<select @bind="_selectedPluginId" style="flex:1;max-width:180px;font-size:12px;">
<option value="">Export via Plugin…</option>
@foreach (var p in _availablePlugins)
{
<option value="@p.Id">@p.Name</option>
}
</select>
<button class="btn btn-small btn-primary" @onclick="ExportViaPlugin" disabled="@(_selectedPluginId == "" || string.IsNullOrEmpty(_output) || _isExporting)">
@(_isExporting ? "Exportiere…" : "Export")
</button>
}
</div> </div>
@if (!string.IsNullOrEmpty(_exportResult))
{
<div class="qm-notice" style="margin-top:4px;font-size:12px;">@_exportResult</div>
}
</div> </div>
<textarea class="qm-textarea qm-output" @bind="_output" rows="30" readonly></textarea> <textarea class="qm-textarea qm-output" @bind="_output" rows="30" readonly></textarea>
</div> </div>
@ -116,9 +180,83 @@
{ {
<div class="qm-notice">@_modelNotice</div> <div class="qm-notice">@_modelNotice</div>
} }
@if (_showFactSearch)
{
<div class="qm-fact-drawer">
<div class="qm-fact-drawer-header">
<span>🔍 Fakten prüfen</span>
<button class="btn btn-sm" @onclick="CloseFactSearch">✕</button>
</div>
<div class="qm-fact-drawer-body">
<div class="form-group">
<label>Behauptung</label>
<div class="qm-fact-claim">@_factText</div>
</div>
<div class="form-group">
<label for="factQuery">Suchanfrage</label>
<div class="qm-fact-query-row">
<input id="factQuery" @bind="_searchQuery" @bind:event="oninput"
placeholder="Suchanfrage bearbeiten…" />
<button class="btn btn-sm btn-primary" @onclick="RunFactSearch"
disabled="@(_factSearching || string.IsNullOrWhiteSpace(_searchQuery))">
Suchen
</button>
</div>
<button class="btn btn-sm" @onclick="RegenerateFactQuery" disabled="@_factSearching">
Anfrage neu generieren
</button>
</div>
@if (_factSearching)
{
<div class="qm-fact-loading">Suche läuft…</div>
}
@if (!string.IsNullOrEmpty(_factSearchError))
{
<div class="qm-fact-error">@_factSearchError</div>
}
@if (_factResults.Count > 0)
{
<div class="qm-fact-results">
@foreach (var r in _factResults)
{
<div class="qm-fact-result">
<a href="@r.Url" target="_blank" rel="noopener noreferrer">@r.Title</a>
@if (!string.IsNullOrEmpty(r.Snippet))
{
<div class="qm-fact-snippet">@r.Snippet</div>
}
<div class="qm-fact-result-actions">
<button class="btn btn-sm btn-primary" @onclick="() => SelectFactResult(r)"
disabled="@_factSummarizing">
@(_factSummarizing ? "Fasse zusammen…" : "Übernehmen")
</button>
</div>
</div>
}
</div>
}
else if (!_factSearching && _factSearchSearched && _factSearchError.Length == 0)
{
<div class="qm-fact-hint">Keine Ergebnisse gefunden. Versuche die Suchanfrage anzupassen.</div>
}
else if (!_factSearching && !_factSearchSearched)
{
<div class="qm-fact-hint">Klicke auf „Suchen", um Ergebnisse zu laden.</div>
}
</div>
</div>
}
</div> </div>
@code { @code {
private const string DraftKeyText = "qm_draft_text";
private const string DraftKeyPrompt = "qm_draft_prompt";
private const string DraftKeyTemp = "qm_draft_temp";
private const string DraftKeyPromptId = "qm_draft_prompt_id";
private bool _fullscreenMode;
private string _systemPrompt = string.Empty; private string _systemPrompt = string.Empty;
private string _inputText = string.Empty; private string _inputText = string.Empty;
private string _output = string.Empty; private string _output = string.Empty;
@ -134,6 +272,20 @@
private int _selectedPromptId; private int _selectedPromptId;
private bool _showPromptNameInput; private bool _showPromptNameInput;
private string _newPromptName = string.Empty; private string _newPromptName = string.Empty;
private List<IExportPlugin> _availablePlugins = new();
private string _selectedPluginId = string.Empty;
private bool _isExporting;
private string _exportResult = string.Empty;
private bool _draftRestored;
private bool _showFactSearch;
private bool _factSearching;
private bool _factSearchSearched;
private bool _factSummarizing;
private string _factSearchError = string.Empty;
private string _factLine = string.Empty;
private string _factText = string.Empty;
private string _searchQuery = string.Empty;
private List<FactSearchResult> _factResults = new();
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -150,10 +302,77 @@
?? Settings.GetQuickMarkdownDefaultPrompt(Settings.Language); ?? Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
_temperature = await SettingsService.GetQuickMarkdownTempAsync(); _temperature = await SettingsService.GetQuickMarkdownTempAsync();
_savedEntries = await QuickMarkdownService.GetAllAsync(_userId); _savedEntries = await QuickMarkdownService.GetAllAsync(_userId);
_availablePlugins = PluginManager.GetPlugins().ToList();
Navigation.LocationChanged += OnLocationChanged;
_modelNotice = $"Verwendetes Modell: {Settings.StyleModel} (via {Settings.Backend})"; _modelNotice = $"Verwendetes Modell: {Settings.StyleModel} (via {Settings.Backend})";
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !_draftRestored)
{
_draftRestored = true;
await RestoreDraftAsync();
await InvokeAsync(StateHasChanged);
}
}
public void Dispose()
{
Navigation.LocationChanged -= OnLocationChanged;
}
private void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
_ = PersistDraftAsync();
}
private async Task PersistTextDraft()
{
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyText, _inputText ?? string.Empty);
}
private async Task PersistPromptDraft()
{
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyPrompt, _systemPrompt ?? string.Empty);
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyTemp, _temperature.ToString(System.Globalization.CultureInfo.InvariantCulture));
await JS.InvokeVoidAsync("localStorage.setItem", DraftKeyPromptId, _selectedPromptId.ToString());
}
private async Task PersistDraftAsync()
{
await PersistTextDraft();
await PersistPromptDraft();
}
private async Task RestoreDraftAsync()
{
var text = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyText);
if (!string.IsNullOrEmpty(text))
_inputText = text;
var promptIdStr = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyPromptId);
if (int.TryParse(promptIdStr, out var promptId) && _savedPrompts.Any(p => p.Id == promptId))
{
_selectedPromptId = promptId;
var prompt = _savedPrompts.First(p => p.Id == promptId);
_systemPrompt = prompt.PromptText;
_temperature = prompt.Temperature;
}
else
{
var prompt = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyPrompt);
if (!string.IsNullOrEmpty(prompt))
_systemPrompt = prompt;
var tempStr = await JS.InvokeAsync<string>("localStorage.getItem", DraftKeyTemp);
if (double.TryParse(tempStr, System.Globalization.CultureInfo.InvariantCulture, out var temp))
_temperature = temp;
}
}
private async Task SwitchPrompt(ChangeEventArgs e) private async Task SwitchPrompt(ChangeEventArgs e)
{ {
var id = int.Parse(e.Value?.ToString() ?? "0"); var id = int.Parse(e.Value?.ToString() ?? "0");
@ -172,6 +391,8 @@
_temperature = prompt.Temperature; _temperature = prompt.Temperature;
} }
} }
await PersistPromptDraft();
} }
private void ShowSavePromptDialog() private void ShowSavePromptDialog()
@ -185,6 +406,20 @@
if (string.IsNullOrWhiteSpace(_newPromptName)) if (string.IsNullOrWhiteSpace(_newPromptName))
return; return;
if (_fullscreenMode && _selectedPromptId > 0)
{
var prompt = _savedPrompts.FirstOrDefault(p => p.Id == _selectedPromptId);
if (prompt != null)
{
prompt.PromptText = _systemPrompt;
prompt.Temperature = _temperature;
prompt.Timestamp = DateTime.UtcNow;
await QuickMarkdownService.SavePromptAsync(prompt);
_selectedPromptId = prompt.Id;
}
}
else
{
var existing = _savedPrompts.FirstOrDefault(p => var existing = _savedPrompts.FirstOrDefault(p =>
p.Name == _newPromptName && p.Id != _selectedPromptId); p.Name == _newPromptName && p.Id != _selectedPromptId);
if (existing != null) if (existing != null)
@ -208,10 +443,13 @@
await QuickMarkdownService.SavePromptAsync(prompt); await QuickMarkdownService.SavePromptAsync(prompt);
_selectedPromptId = prompt.Id; _selectedPromptId = prompt.Id;
} }
}
_savedPrompts = await QuickMarkdownService.GetPromptsAsync(_userId); _savedPrompts = await QuickMarkdownService.GetPromptsAsync(_userId);
_showPromptNameInput = false; _showPromptNameInput = false;
_newPromptName = string.Empty; _newPromptName = string.Empty;
await PersistPromptDraft();
} }
private void CancelSavePrompt() private void CancelSavePrompt()
@ -220,6 +458,11 @@
_newPromptName = string.Empty; _newPromptName = string.Empty;
} }
private void ToggleFullscreen()
{
_fullscreenMode = !_fullscreenMode;
}
private async Task DeleteCurrentPrompt() private async Task DeleteCurrentPrompt()
{ {
if (_selectedPromptId == 0) if (_selectedPromptId == 0)
@ -230,6 +473,8 @@
_selectedPromptId = 0; _selectedPromptId = 0;
_systemPrompt = Settings.GetQuickMarkdownDefaultPrompt(Settings.Language); _systemPrompt = Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
_temperature = Settings.QuickMarkdownTemp; _temperature = Settings.QuickMarkdownTemp;
await PersistPromptDraft();
} }
private async Task SaveEntry() private async Task SaveEntry()
@ -263,6 +508,8 @@
_systemPrompt = entry.SystemPrompt; _systemPrompt = entry.SystemPrompt;
_temperature = entry.Temperature; _temperature = entry.Temperature;
} }
await PersistDraftAsync();
} }
private async Task DeleteEntry(int id) private async Task DeleteEntry(int id)
@ -275,13 +522,15 @@
{ {
if (string.IsNullOrWhiteSpace(_inputText)) return; if (string.IsNullOrWhiteSpace(_inputText)) return;
await PersistDraftAsync();
_isBusy = true; _isBusy = true;
_error = string.Empty; _error = string.Empty;
_output = string.Empty; _output = string.Empty;
try try
{ {
var result = await OllamaService.Chat(Settings.StyleModel, _inputText, _systemPrompt, _temperature); var result = await OllamaService.Chat(Settings.StyleModel, _inputText, _systemPrompt, _temperature, Settings.StyleBackend);
_output = result; _output = result;
} }
catch (Exception ex) catch (Exception ex)
@ -305,4 +554,181 @@
_output = string.Empty; _output = string.Empty;
_error = string.Empty; _error = string.Empty;
} }
private async Task StartFactSearch()
{
if (_isBusy || string.IsNullOrWhiteSpace(_inputText)) return;
var found = FindFirstFact(_inputText);
if (found == null)
{
_error = "Keine Zeile mit [Fakt] gefunden. Schreibe in eine Zeile z.B.: [Fakt] Paris ist die Hauptstadt von Frankreich.";
return;
}
_error = string.Empty;
_factLine = found.Value.Line;
_factText = found.Value.Fact;
_showFactSearch = true;
_factResults = new();
_factSearchError = string.Empty;
_factSearchSearched = false;
_factSearching = true;
await InvokeAsync(StateHasChanged);
try
{
_searchQuery = await FactSearchToolService.CreateSearchQueryAsync(_factText);
await SearchCoreAsync();
}
catch (Exception ex)
{
_factSearchError = $"Fehler: {ex.Message}";
}
finally
{
_factSearching = false;
}
}
private async Task RunFactSearch()
{
if (_factSearching || string.IsNullOrWhiteSpace(_searchQuery)) return;
_factSearching = true;
_factSearchError = string.Empty;
_factSearchSearched = false;
await InvokeAsync(StateHasChanged);
try
{
await SearchCoreAsync();
}
catch (Exception ex)
{
_factSearchError = $"Fehler: {ex.Message}";
}
finally
{
_factSearching = false;
}
}
private async Task RegenerateFactQuery()
{
if (_factSearching) return;
_factSearching = true;
_factSearchError = string.Empty;
await InvokeAsync(StateHasChanged);
try
{
_searchQuery = await FactSearchToolService.CreateSearchQueryAsync(_factText);
_factSearchSearched = false;
await SearchCoreAsync();
}
catch (Exception ex)
{
_factSearchError = $"Fehler: {ex.Message}";
}
finally
{
_factSearching = false;
}
}
private async Task SearchCoreAsync()
{
_factResults = await FactSearchToolService.SearchAsync(_searchQuery);
_factSearchSearched = true;
}
private async Task SelectFactResult(FactSearchResult result)
{
if (_factSummarizing) return;
_factSummarizing = true;
_factSearchError = string.Empty;
await InvokeAsync(StateHasChanged);
try
{
if (!string.IsNullOrWhiteSpace(result.Url))
await JS.InvokeVoidAsync("window.open", result.Url, "_blank");
var info = string.IsNullOrWhiteSpace(result.Snippet) ? result.Title : result.Snippet;
var summary = await FactSearchToolService.SummarizeAsync(_searchQuery, info, result.Url);
var replacement = string.IsNullOrWhiteSpace(summary)
? $"({result.Title}, Quelle: {result.Url})"
: $"{summary} (Quelle: {result.Url})";
_inputText = _inputText.Replace(_factLine, replacement);
await PersistDraftAsync();
_showFactSearch = false;
}
catch (Exception ex)
{
_factSearchError = $"Fehler: {ex.Message}";
}
finally
{
_factSummarizing = false;
}
}
private void CloseFactSearch()
{
_showFactSearch = false;
}
private static (string Line, string Fact)? FindFirstFact(string text)
{
if (string.IsNullOrEmpty(text)) return null;
foreach (var line in text.Split('\n'))
{
var idx = line.IndexOf("[Fakt]", StringComparison.OrdinalIgnoreCase);
if (idx < 0) continue;
var fact = line.Remove(idx, "[Fakt]".Length);
fact = fact.Trim().TrimEnd('.', '!', '?', ':', ';', ',', '-').Trim();
if (!string.IsNullOrWhiteSpace(fact))
return (line, fact);
}
return null;
}
private async Task ExportViaPlugin()
{
if (string.IsNullOrEmpty(_selectedPluginId) || string.IsNullOrEmpty(_output)) return;
var plugin = PluginManager.GetPlugin(_selectedPluginId);
if (plugin == null) return;
_isExporting = true;
_exportResult = string.Empty;
try
{
var title = _inputText.Length > 80 ? _inputText[..80] + "…" : _inputText;
title = string.IsNullOrWhiteSpace(title) ? "Quick Markdown Export" : title;
var context = new ExportPluginContext
{
Title = title,
Content = _output
};
var result = await plugin.ExportAsync(context);
_exportResult = result.Message;
}
catch (Exception ex)
{
_exportResult = $"Fehler: {ex.Message}";
}
finally
{
_isExporting = false;
}
}
} }

View file

@ -4,11 +4,13 @@
@inject ISettingsService SettingsService @inject ISettingsService SettingsService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IOllamaService OllamaService @inject IOllamaService OllamaService
@inject OllamaSettings OllamaConfig
@inject NavigationManager Navigation @inject NavigationManager Navigation
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject IProjectService ProjectService @inject IProjectService ProjectService
@inject ILocalizationService Loc @inject ILocalizationService Loc
@inject IAuthService AuthService @inject IAuthService AuthService
@inject IPluginManager PluginManager
<PageTitle>@Loc["page.title.settings"] - Author Buddy</PageTitle> <PageTitle>@Loc["page.title.settings"] - Author Buddy</PageTitle>
@ -73,40 +75,55 @@
<div class="settings-section"> <div class="settings-section">
<h3 class="settings-section-title">@Loc["settings.section.models"]</h3> <h3 class="settings-section-title">@Loc["settings.section.models"]</h3>
<div class="form-group"> <div class="form-group settings-model-row">
<label for="ragModel">@Loc["settings.models.label.rag"]</label> <label for="ragModel">@Loc["settings.models.label.rag"]</label>
<select id="ragBackend" value="@ragBackend" @onchange="OnRagBackendChanged">
<option value="Ollama">Ollama</option>
<option value="Ollama Cloud">Ollama Cloud</option>
<option value="llama.cpp">llama.cpp</option>
</select>
<select id="ragModel" @bind="ragModel"> <select id="ragModel" @bind="ragModel">
@if (availableModels.Count == 0) @if (availableRagModels.Count == 0)
{ {
<option value="">@Loc["settings.models.empty"]</option> <option value="">@Loc["settings.models.empty"]</option>
} }
@foreach (var m in availableModels) @foreach (var m in availableRagModels)
{ {
<option value="@m.Name">@m.Name</option> <option value="@m.Name">@m.Name</option>
} }
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group settings-model-row">
<label for="styleModel">@Loc["settings.models.label.style"]</label> <label for="styleModel">@Loc["settings.models.label.style"]</label>
<select id="styleBackend" value="@styleBackend" @onchange="OnStyleBackendChanged">
<option value="Ollama">Ollama</option>
<option value="Ollama Cloud">Ollama Cloud</option>
<option value="llama.cpp">llama.cpp</option>
</select>
<select id="styleModel" @bind="styleModel"> <select id="styleModel" @bind="styleModel">
@if (availableModels.Count == 0) @if (availableStyleModels.Count == 0)
{ {
<option value="">@Loc["settings.models.empty"]</option> <option value="">@Loc["settings.models.empty"]</option>
} }
@foreach (var m in availableModels) @foreach (var m in availableStyleModels)
{ {
<option value="@m.Name">@m.Name</option> <option value="@m.Name">@m.Name</option>
} }
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group settings-model-row">
<label for="spellingModel">@Loc["settings.models.label.spellcheck"]</label> <label for="spellingModel">@Loc["settings.models.label.spellcheck"]</label>
<select id="spellingBackend" value="@spellingBackend" @onchange="OnSpellingBackendChanged">
<option value="Ollama">Ollama</option>
<option value="Ollama Cloud">Ollama Cloud</option>
<option value="llama.cpp">llama.cpp</option>
</select>
<select id="spellingModel" @bind="spellingModel"> <select id="spellingModel" @bind="spellingModel">
@if (availableModels.Count == 0) @if (availableSpellingModels.Count == 0)
{ {
<option value="">@Loc["settings.models.empty"]</option> <option value="">@Loc["settings.models.empty"]</option>
} }
@foreach (var m in availableModels) @foreach (var m in availableSpellingModels)
{ {
<option value="@m.Name">@m.Name</option> <option value="@m.Name">@m.Name</option>
} }
@ -116,13 +133,6 @@
<div class="settings-section"> <div class="settings-section">
<h3 class="settings-section-title">@Loc["settings.section.ollama_server"]</h3> <h3 class="settings-section-title">@Loc["settings.section.ollama_server"]</h3>
<div class="form-group">
<label for="backendSelect">Backend</label>
<select id="backendSelect" @bind="selectedBackend">
<option value="Ollama">Ollama</option>
<option value="llama.cpp">llama.cpp</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<label for="ollamaUrl">@Loc["settings.ollama.label.url"] (Ollama)</label> <label for="ollamaUrl">@Loc["settings.ollama.label.url"] (Ollama)</label>
<div class="input-with-button"> <div class="input-with-button">
@ -144,6 +154,16 @@
<input id="llamaCppUrl" @bind="llamaCppUrl" placeholder="http://localhost:8080" /> <input id="llamaCppUrl" @bind="llamaCppUrl" placeholder="http://localhost:8080" />
<div class="form-hint">Nur aktiv wenn Backend auf "llama.cpp" gesetzt ist</div> <div class="form-hint">Nur aktiv wenn Backend auf "llama.cpp" gesetzt ist</div>
</div> </div>
<div class="form-group">
<label for="ollamaCloudUrl">URL (Ollama Cloud)</label>
<input id="ollamaCloudUrl" @bind="_ollamaCloudUrl" placeholder="https://api.ollama.com" />
<div class="form-hint">Nur aktiv wenn Backend auf "Ollama Cloud" gesetzt ist</div>
</div>
<div class="form-group">
<label for="ollamaCloudApiKey">API-Key (Ollama Cloud)</label>
<input id="ollamaCloudApiKey" type="password" @bind="_ollamaCloudApiKey" placeholder="Bearer-Token" />
<div class="form-hint">Wird nur lokal gespeichert, nie ausgeloggt</div>
</div>
</div> </div>
</div> </div>
@ -163,6 +183,30 @@
</div> </div>
} }
</div> </div>
@if (_availablePlugins.Count > 0)
{
<div class="settings-section">
<h3 class="settings-section-title">Plugins</h3>
@foreach (var p in _availablePlugins)
{
<div class="form-group" style="border:1px solid var(--border-color);padding:8px;border-radius:4px;margin-bottom:8px;">
<strong>@p.Name</strong>
<div style="font-size:12px;opacity:.7;margin-bottom:6px;">@p.Description</div>
@if (p.Id == "wordpress")
{
<label>WordPress-URL</label>
<input @bind="_wpUrl" placeholder="https://example.com" />
<label>Benutzername</label>
<input @bind="_wpUsername" placeholder="admin" />
<label>App-Passwort</label>
<input type="password" @bind="_wpPassword" placeholder="xxxx xxxx xxxx xxxx" />
}
</div>
}
<button class="btn btn-sm btn-primary" @onclick="SavePluginConfig">Plugin-Konfiguration speichern</button>
<span style="margin-left:8px;font-size:12px;">@_pluginStatus</span>
</div>
}
<div class="settings-section"> <div class="settings-section">
<h3 class="settings-section-title">@Loc["settings.section.appearance"]</h3> <h3 class="settings-section-title">@Loc["settings.section.appearance"]</h3>
<div class="form-group"> <div class="form-group">
@ -409,12 +453,14 @@
private void SwitchToTechnicalTab() => _activePromptTab = "technical"; private void SwitchToTechnicalTab() => _activePromptTab = "technical";
private void SwitchToAdminTab() => _activePromptTab = "admin"; private void SwitchToAdminTab() => _activePromptTab = "admin";
private string ragModel = "llama3.2"; private string ragModel = "llama3.2";
private string ragBackend = "Ollama";
private string styleModel = "llama3.2"; private string styleModel = "llama3.2";
private string styleBackend = "Ollama";
private string spellingModel = "phi3:mini"; private string spellingModel = "phi3:mini";
private string spellingBackend = "Ollama";
private string selectedTheme = "light"; private string selectedTheme = "light";
private string ollamaUrl = "http://localhost:11434"; private string ollamaUrl = "http://localhost:11434";
private string llamaCppUrl = "http://localhost:8080"; private string llamaCppUrl = "http://localhost:8080";
private string selectedBackend = "Ollama";
private string lektorPrompt = string.Empty; private string lektorPrompt = string.Empty;
private string factCheckTemplate = string.Empty; private string factCheckTemplate = string.Empty;
private string spellingPrompt = string.Empty; private string spellingPrompt = string.Empty;
@ -447,7 +493,9 @@
private string newProjectName = string.Empty; private string newProjectName = string.Empty;
private string newUserName = string.Empty; private string newUserName = string.Empty;
private string statusMessage = string.Empty; private string statusMessage = string.Empty;
private List<LLM_Model> availableModels = new(); private List<LLM_Model> availableRagModels = new();
private List<LLM_Model> availableStyleModels = new();
private List<LLM_Model> availableSpellingModels = new();
private List<UserEntity> users = new(); private List<UserEntity> users = new();
private int selectedUserId; private int selectedUserId;
private string? currentProjectName; private string? currentProjectName;
@ -459,6 +507,13 @@
private bool _isTestingConnection; private bool _isTestingConnection;
private List<string>? _diagnosticsResults; private List<string>? _diagnosticsResults;
private bool _isDiagnosing; private bool _isDiagnosing;
private string _ollamaCloudUrl = string.Empty;
private string _ollamaCloudApiKey = string.Empty;
private List<IExportPlugin> _availablePlugins = new();
private string _wpUrl = string.Empty;
private string _wpUsername = string.Empty;
private string _wpPassword = string.Empty;
private string _pluginStatus = string.Empty;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -481,27 +536,24 @@
if (_isAdmin) if (_isAdmin)
{ {
try await RefreshModelsForAllBackends();
{
var models = await OllamaService.GetLocalModelsAsync(); _availablePlugins = PluginManager.GetPlugins().ToList();
availableModels = models.ToList(); await LoadPluginConfig();
}
catch
{
availableModels = new();
}
} }
} }
private async Task LoadUserSettings() private async Task LoadUserSettings()
{ {
ragModel = await SettingsService.GetRagModelAsync() ?? "llama3.2"; ragModel = await SettingsService.GetRagModelAsync() ?? "llama3.2";
ragBackend = await SettingsService.GetRagBackendAsync() ?? "Ollama";
styleModel = await SettingsService.GetStyleModelAsync() ?? "llama3.2"; styleModel = await SettingsService.GetStyleModelAsync() ?? "llama3.2";
styleBackend = await SettingsService.GetStyleBackendAsync() ?? "Ollama";
spellingModel = await SettingsService.GetSpellingModelAsync() ?? "phi3:mini"; spellingModel = await SettingsService.GetSpellingModelAsync() ?? "phi3:mini";
spellingBackend = await SettingsService.GetSpellingBackendAsync() ?? "Ollama";
selectedTheme = await SettingsService.GetThemeAsync() ?? "light"; selectedTheme = await SettingsService.GetThemeAsync() ?? "light";
ollamaUrl = await SettingsService.GetOllamaUrlAsync() ?? "http://localhost:11434"; ollamaUrl = await SettingsService.GetOllamaUrlAsync() ?? "http://localhost:11434";
llamaCppUrl = await SettingsService.GetLlamaCppUrlAsync() ?? "http://localhost:8080"; llamaCppUrl = await SettingsService.GetLlamaCppUrlAsync() ?? "http://localhost:8080";
selectedBackend = await SettingsService.GetBackendAsync() ?? "Ollama";
lektorPrompt = await SettingsService.GetLektorSystemPromptAsync() ?? string.Empty; lektorPrompt = await SettingsService.GetLektorSystemPromptAsync() ?? string.Empty;
factCheckTemplate = await SettingsService.GetFactCheckTemplateAsync() ?? string.Empty; factCheckTemplate = await SettingsService.GetFactCheckTemplateAsync() ?? string.Empty;
spellingPrompt = await SettingsService.GetSpellingPromptAsync() ?? string.Empty; spellingPrompt = await SettingsService.GetSpellingPromptAsync() ?? string.Empty;
@ -531,6 +583,8 @@
techQualitaetTemp = await SettingsService.GetTechQualitaetTempAsync(); techQualitaetTemp = await SettingsService.GetTechQualitaetTempAsync();
techRisikoTemp = await SettingsService.GetTechRisikoTempAsync(); techRisikoTemp = await SettingsService.GetTechRisikoTempAsync();
selectedLanguage = await SettingsService.GetLanguageAsync() ?? "de"; selectedLanguage = await SettingsService.GetLanguageAsync() ?? "de";
_ollamaCloudUrl = await SettingsService.GetOllamaCloudUrlAsync() ?? OllamaConfig.OllamaCloudUrl;
_ollamaCloudApiKey = await SettingsService.GetOllamaCloudApiKeyAsync() ?? OllamaConfig.OllamaCloudApiKey;
} }
private async Task SwitchUser(ChangeEventArgs e) private async Task SwitchUser(ChangeEventArgs e)
@ -552,29 +606,84 @@
statusMessage = string.Format(Loc["settings.status.user_created"], user.Name); statusMessage = string.Format(Loc["settings.status.user_created"], user.Name);
} }
private async Task OnRagBackendChanged(ChangeEventArgs e)
{
ragBackend = e.Value?.ToString() ?? "Ollama";
await RefreshModelsForRag();
}
private async Task OnStyleBackendChanged(ChangeEventArgs e)
{
styleBackend = e.Value?.ToString() ?? "Ollama";
await RefreshModelsForStyle();
}
private async Task OnSpellingBackendChanged(ChangeEventArgs e)
{
spellingBackend = e.Value?.ToString() ?? "Ollama";
await RefreshModelsForSpelling();
}
private async Task RefreshModelsForRag()
{
OllamaConfig.OllamaCloudUrl = _ollamaCloudUrl;
OllamaConfig.OllamaCloudApiKey = _ollamaCloudApiKey;
try { availableRagModels = (await OllamaService.GetModelsForBackendAsync(ragBackend)).ToList(); }
catch { availableRagModels = new(); }
}
private async Task RefreshModelsForStyle()
{
OllamaConfig.OllamaCloudUrl = _ollamaCloudUrl;
OllamaConfig.OllamaCloudApiKey = _ollamaCloudApiKey;
try { availableStyleModels = (await OllamaService.GetModelsForBackendAsync(styleBackend)).ToList(); }
catch { availableStyleModels = new(); }
}
private async Task RefreshModelsForSpelling()
{
OllamaConfig.OllamaCloudUrl = _ollamaCloudUrl;
OllamaConfig.OllamaCloudApiKey = _ollamaCloudApiKey;
try { availableSpellingModels = (await OllamaService.GetModelsForBackendAsync(spellingBackend)).ToList(); }
catch { availableSpellingModels = new(); }
}
private async Task RefreshModelsForAllBackends()
{
await Task.WhenAll(RefreshModelsForRag(), RefreshModelsForStyle(), RefreshModelsForSpelling());
}
private async Task SaveSettings() private async Task SaveSettings()
{ {
if (_isAdmin) if (_isAdmin)
{ {
await SettingsService.SaveOllamaUrlAsync(ollamaUrl); await SettingsService.SaveOllamaUrlAsync(ollamaUrl);
await SettingsService.SaveLlamaCppUrlAsync(llamaCppUrl); await SettingsService.SaveLlamaCppUrlAsync(llamaCppUrl);
await SettingsService.SaveBackendAsync(selectedBackend);
OllamaService.Reconfigure(ollamaUrl); OllamaService.Reconfigure(ollamaUrl);
await SettingsService.SaveOllamaCloudUrlAsync(_ollamaCloudUrl);
await SettingsService.SaveOllamaCloudApiKeyAsync(_ollamaCloudApiKey);
await SettingsService.SaveRagBackendAsync(ragBackend);
await SettingsService.SaveStyleBackendAsync(styleBackend);
await SettingsService.SaveSpellingBackendAsync(spellingBackend);
if (!string.IsNullOrWhiteSpace(ragModel)) if (!string.IsNullOrWhiteSpace(ragModel))
{ {
var ok = await OllamaService.TestEmbeddingAsync(ragModel); var ok = await OllamaService.TestEmbeddingAsync(ragModel, ragBackend);
if (!ok) if (!ok)
{ {
statusMessage = selectedLanguage == "en" statusMessage = selectedLanguage == "en"
? $"Model '{ragModel}' does not support embeddings. RAG model not saved." ? $"Model '{ragModel}' does not support embeddings. RAG model not saved."
: $"Modell '{ragModel}' unterstützt keine Embeddings. RAG-Modell nicht gespeichert."; : $"Modell '{ragModel}' unterstützt keine Embeddings. RAG-Modell nicht gespeichert.";
return; //return;
} }
} }
await SettingsService.SaveRagModelAsync(ragModel); await SettingsService.SaveRagModelAsync(ragModel);
await SettingsService.SaveStyleModelAsync(styleModel); await SettingsService.SaveStyleModelAsync(styleModel);
await SettingsService.SaveSpellingModelAsync(spellingModel); await SettingsService.SaveSpellingModelAsync(spellingModel);
} }
await SettingsService.SaveThemeAsync(selectedTheme); await SettingsService.SaveThemeAsync(selectedTheme);
await SettingsService.SaveLektorSystemPromptAsync(lektorPrompt); await SettingsService.SaveLektorSystemPromptAsync(lektorPrompt);
@ -605,7 +714,7 @@
await SettingsService.SaveTechGlossarTempAsync(techGlossarTemp); await SettingsService.SaveTechGlossarTempAsync(techGlossarTemp);
await SettingsService.SaveTechQualitaetTempAsync(techQualitaetTemp); await SettingsService.SaveTechQualitaetTempAsync(techQualitaetTemp);
await SettingsService.SaveTechRisikoTempAsync(techRisikoTemp); await SettingsService.SaveTechRisikoTempAsync(techRisikoTemp);
await SettingsService.SaveLanguageAsync(selectedLanguage);
if (selectedLanguage != Loc.CurrentLanguage) if (selectedLanguage != Loc.CurrentLanguage)
await Loc.SetLanguageAsync(selectedLanguage); await Loc.SetLanguageAsync(selectedLanguage);
@ -614,6 +723,33 @@
await ThemeService.ApplyTheme(selectedTheme); await ThemeService.ApplyTheme(selectedTheme);
} }
private async Task LoadPluginConfig()
{
var cfg = await PluginManager.GetConfigAsync("wordpress");
if (cfg != null)
{
if (cfg.Values.TryGetValue("url", out var url)) _wpUrl = url;
if (cfg.Values.TryGetValue("username", out var user)) _wpUsername = user;
if (cfg.Values.TryGetValue("password", out var pass)) _wpPassword = pass;
}
}
private async Task SavePluginConfig()
{
var cfg = new PluginConfig
{
PluginId = "wordpress",
Values = new()
{
["url"] = _wpUrl ?? "",
["username"] = _wpUsername ?? "",
["password"] = _wpPassword ?? ""
}
};
await PluginManager.SaveConfigAsync(cfg);
_pluginStatus = "Gespeichert.";
}
private string GetTempCategory(double temp) private string GetTempCategory(double temp)
{ {
if (temp <= 0.2) return Loc["settings.temperature.category.precise"]; if (temp <= 0.2) return Loc["settings.temperature.category.precise"];
@ -675,33 +811,33 @@
} }
var models = new[] { var models = new[] {
(Loc["settings.models.label.style"] ?? "Style", styleModel), (Label: Loc["settings.models.label.style"] ?? "Style", Model: styleModel, Backend: styleBackend),
(Loc["settings.models.label.rag"] ?? "RAG", ragModel), (Label: Loc["settings.models.label.rag"] ?? "RAG", Model: ragModel, Backend: ragBackend),
(Loc["settings.models.label.spellcheck"] ?? "Spelling", spellingModel) (Label: Loc["settings.models.label.spellcheck"] ?? "Spelling", Model: spellingModel, Backend: spellingBackend)
}; };
foreach (var (label, model) in models) foreach (var entry in models)
{ {
if (string.IsNullOrWhiteSpace(model)) if (string.IsNullOrWhiteSpace(entry.Model))
{ {
results.Add($"? {label}: {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not configured" : "nicht konfiguriert")}"); results.Add($"? {entry.Label}: {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not configured" : "nicht konfiguriert")}");
continue; continue;
} }
var exists = await OllamaService.ModelExistsAsync(model); var exists = await OllamaService.ModelExistsAsync(entry.Model, entry.Backend);
if (!exists) if (!exists)
{ {
results.Add($"✗ {label} ({model}): {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not found" : "nicht gefunden")}"); results.Add($"✗ {entry.Label} ({entry.Model}): {(!string.IsNullOrEmpty(lang) && lang == "en" ? "not found" : "nicht gefunden")}");
continue; continue;
} }
var latency = await OllamaService.MeasureLatencyAsync(model); var latency = await OllamaService.MeasureLatencyAsync(entry.Model, entry.Backend);
var latencyStr = latency >= 0 ? $"{latency} ms" : "—"; var latencyStr = latency >= 0 ? $"{latency} ms" : "—";
var ctx = await OllamaService.GetModelContextWindowAsync(model); var ctx = await OllamaService.GetModelContextWindowAsync(entry.Model, entry.Backend);
var ctxStr = ctx.Success ? $"{ctx.ContextWindow}" : "?"; var ctxStr = ctx.Success ? $"{ctx.ContextWindow}" : "?";
results.Add($"✓ {label} ({model}): {latencyStr}, {(!string.IsNullOrEmpty(lang) && lang == "en" ? "Context" : "Kontext")}: {ctxStr}"); results.Add($"✓ {entry.Label} ({entry.Model}): {latencyStr}, {(!string.IsNullOrEmpty(lang) && lang == "en" ? "Context" : "Kontext")}: {ctxStr}");
} }
_diagnosticsResults = results; _diagnosticsResults = results;

View file

@ -0,0 +1,11 @@
namespace AuthorBuddy.Web.Models;
public class DuckDuckGoResult
{
public string Heading { get; set; } = string.Empty;
public string AbstractText { get; set; } = string.Empty;
public string AbstractUrl { get; set; } = string.Empty;
public string Answer { get; set; } = string.Empty;
public List<string> RelatedTexts { get; set; } = new();
public List<string> RelatedUrls { get; set; } = new();
}

View file

@ -0,0 +1,22 @@
namespace AuthorBuddy.Web.Models;
public class ExportPluginContext
{
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public string? Tags { get; set; }
public string? Status { get; set; } = "draft";
public Dictionary<string, string> Extra { get; set; } = new();
}
public class PluginResult
{
public bool Success { get; set; }
public string Message { get; set; } = string.Empty;
}
public class PluginConfig
{
public string PluginId { get; set; } = string.Empty;
public Dictionary<string, string> Values { get; set; } = new();
}

17
Models/FactCheckResult.cs Normal file
View file

@ -0,0 +1,17 @@
namespace AuthorBuddy.Web.Models;
public class FactCheckResult
{
public string Claim { get; set; } = string.Empty;
public FactCheckVerdict Verdict { get; set; } = FactCheckVerdict.NoInformation;
public string Reasoning { get; set; } = string.Empty;
public List<string> Sources { get; set; } = new();
}
public enum FactCheckVerdict
{
Confirmed,
Contradicted,
Partial,
NoInformation
}

View file

@ -0,0 +1,8 @@
namespace AuthorBuddy.Web.Models;
public class FactSearchResult
{
public string Title { get; set; } = string.Empty;
public string Snippet { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
}

View file

@ -0,0 +1,8 @@
namespace AuthorBuddy.Web.Models;
public class McpToolDefinition
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string InputSchemaJson { get; set; } = string.Empty;
}

9
Models/McpToolResult.cs Normal file
View file

@ -0,0 +1,9 @@
namespace AuthorBuddy.Web.Models;
public class McpToolResult
{
public bool IsError { get; set; }
public string ToolName { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public object? Data { get; set; }
}

View file

@ -8,8 +8,13 @@ public class OllamaSettings
public string OllamaUrl { get; set; } = "http://localhost:11434"; public string OllamaUrl { get; set; } = "http://localhost:11434";
public string LlamaCppUrl { get; set; } = "http://localhost:8080"; public string LlamaCppUrl { get; set; } = "http://localhost:8080";
public string Backend { get; set; } = "Ollama"; public string Backend { get; set; } = "Ollama";
public string RagBackend { get; set; } = "Ollama";
public string StyleBackend { get; set; } = "Ollama";
public string SpellingBackend { get; set; } = "Ollama";
public string Theme { get; set; } = "light"; public string Theme { get; set; } = "light";
public string Language { get; set; } = "de"; public string Language { get; set; } = "de";
public string OllamaCloudUrl { get; set; } = "https://api.ollama.com";
public string OllamaCloudApiKey { get; set; } = string.Empty;
public string LektorSystemPrompt { get; set; } = "Du bist ein erfahrener Lektor. Analysiere den Text des Autors. " + public string LektorSystemPrompt { get; set; } = "Du bist ein erfahrener Lektor. Analysiere den Text des Autors. " +
"Optimiere ihn auf einen lebendigen, anschaulichen und subjektiven Stil. " + "Optimiere ihn auf einen lebendigen, anschaulichen und subjektiven Stil. " +
@ -108,11 +113,17 @@ public class OllamaSettings
public string ChatPrompt { get; set; } = "Du bist ein hilfreicher Assistent. Antworte freundlich und präzise auf Deutsch."; public string ChatPrompt { get; set; } = "Du bist ein hilfreicher Assistent. Antworte freundlich und präzise auf Deutsch.";
public string ChatPromptEn { get; } = "You are a helpful assistant. Answer friendly and precisely in English."; public string ChatPromptEn { get; } = "You are a helpful assistant. Answer friendly and precisely in English.";
public double ChatTemp { get; set; } = 0.7; public double ChatTemp { get; set; } = 0.7;
public bool ChatFactCheckEnabled { get; set; } = false;
public string FactSearchPrompt { get; set; } = "Du bist ein Recherche-Assistent. Erstelle aus der folgenden Behauptung eine präzise Suchanfrage für eine Websuche (maximal 10 Wörter, nur Schlüsselbegriffe). Gib NUR die Suchanfrage zurück – ohne Anführungszeichen, ohne Vor- oder Nachbemerkungen.";
public string FactSearchPromptEn { get; } = "You are a research assistant. From the following claim, create a precise web-search query (max 10 words, keywords only). Return ONLY the query – no quotes, no preamble.";
public double FactSearchTemp { get; set; } = 0.2;
public string GetChatDefaultPrompt(string currentLanguage) => GetDefaultPrompt(ChatPrompt, ChatPromptEn, currentLanguage); public string GetChatDefaultPrompt(string currentLanguage) => GetDefaultPrompt(ChatPrompt, ChatPromptEn, currentLanguage);
public string GetQuickMarkdownDefaultPrompt(string currentLanguage) => GetDefaultPrompt(QuickMarkdownPrompt, QuickMarkdownPromptEn, currentLanguage); public string GetQuickMarkdownDefaultPrompt(string currentLanguage) => GetDefaultPrompt(QuickMarkdownPrompt, QuickMarkdownPromptEn, currentLanguage);
public string GetFactSearchDefaultPrompt(string currentLanguage) => GetDefaultPrompt(FactSearchPrompt, FactSearchPromptEn, currentLanguage);
public string GetDefaultPrompt(string dePrompt, string enPrompt, string currentLanguage) public string GetDefaultPrompt(string dePrompt, string enPrompt, string currentLanguage)
{ {
return currentLanguage == "en" ? enPrompt : dePrompt; return currentLanguage == "en" ? enPrompt : dePrompt;

View file

@ -0,0 +1,94 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using AuthorBuddy.Web.Models;
using AuthorBuddy.Web.Services;
namespace AuthorBuddy.Web.Plugins;
public class WordPressPlugin : IExportPlugin
{
private readonly ISettingsService _settings;
private readonly IHttpClientFactory _httpFactory;
public string Id => "wordpress";
public string Name => "WordPress";
public string Description => "Erstellt einen neuen WordPress-Beitrag als Draft mit Titel, Inhalt und Stichwörtern.";
public WordPressPlugin(ISettingsService settings, IHttpClientFactory httpFactory)
{
_settings = settings;
_httpFactory = httpFactory;
}
public async Task<PluginResult> ExportAsync(ExportPluginContext context)
{
var json = await _settings.GetPluginConfigAsync(Id);
PluginConfig? cfg = null;
if (!string.IsNullOrEmpty(json))
cfg = JsonSerializer.Deserialize<PluginConfig>(json);
if (cfg == null)
return new PluginResult { Success = false, Message = "WordPress-Plugin nicht konfiguriert." };
cfg.Values.TryGetValue("url", out var wpUrl);
cfg.Values.TryGetValue("username", out var username);
cfg.Values.TryGetValue("password", out var password);
if (string.IsNullOrEmpty(wpUrl) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password))
return new PluginResult { Success = false, Message = "WordPress-URL, Benutzername oder App-Passwort fehlt." };
wpUrl = wpUrl.TrimEnd('/');
var tags = new List<string>();
if (!string.IsNullOrWhiteSpace(context.Tags))
tags.AddRange(context.Tags.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries));
var post = new Dictionary<string, object>
{
["title"] = context.Title,
["content"] = context.Content,
["status"] = context.Status ?? "draft"
};
if (tags.Count > 0)
post["tags"] = tags;
var body = JsonSerializer.Serialize(post);
var request = new HttpRequestMessage(HttpMethod.Post, $"{wpUrl}/wp/v2/posts")
{
Content = new StringContent(body, Encoding.UTF8, "application/json")
};
var auth = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"));
request.Headers.Authorization = new AuthenticationHeaderValue("Basic", auth);
try
{
var http = _httpFactory.CreateClient();
var response = await http.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var resBody = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(resBody);
var id = doc.RootElement.TryGetProperty("id", out var idProp) ? idProp.GetInt32() : 0;
var link = doc.RootElement.TryGetProperty("link", out var linkProp) ? linkProp.GetString() : "";
return new PluginResult
{
Success = true,
Message = $"WordPress-Beitrag als Draft erstellt (ID: {id}).{(string.IsNullOrEmpty(link) ? "" : $" {link}")}"
};
}
var err = await response.Content.ReadAsStringAsync();
return new PluginResult { Success = false, Message = $"WordPress-Fehler ({response.StatusCode}): {Truncate(err, 200)}" };
}
catch (Exception ex)
{
return new PluginResult { Success = false, Message = $"Verbindungsfehler: {ex.Message}" };
}
}
private static string Truncate(string s, int max) => s.Length <= max ? s : s[..max] + "…";
}

View file

@ -1,6 +1,7 @@
using AuthorBuddy.Web.Components; using AuthorBuddy.Web.Components;
using AuthorBuddy.Web.Data; using AuthorBuddy.Web.Data;
using AuthorBuddy.Web.Models; using AuthorBuddy.Web.Models;
using AuthorBuddy.Web.Plugins;
using AuthorBuddy.Web.Services; using AuthorBuddy.Web.Services;
using LiteDB; using LiteDB;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
@ -29,14 +30,18 @@ builder.Services.AddSingleton<OllamaSettings>();
builder.Services.AddSingleton<IOllamaService, OllamaService>(); builder.Services.AddSingleton<IOllamaService, OllamaService>();
builder.Services.AddSingleton<ILLMBackend, OllamaBackend>(); builder.Services.AddSingleton<ILLMBackend, OllamaBackend>();
builder.Services.AddSingleton<ILLMBackend, LlamaCppBackend>(); builder.Services.AddSingleton<ILLMBackend, LlamaCppBackend>();
builder.Services.AddSingleton<ILLMBackend, OllamaCloudBackend>();
builder.Services.AddSingleton<IOllamaSetupService, OllamaSetupService>(); builder.Services.AddSingleton<IOllamaSetupService, OllamaSetupService>();
builder.Services.AddSingleton<IFileScannerService, FileScannerService>(); builder.Services.AddSingleton<IFileScannerService, FileScannerService>();
builder.Services.AddScoped<ISettingsService, SettingsService>(); builder.Services.AddScoped<ISettingsService, SettingsService>();
builder.Services.AddScoped<IThemeService, ThemeService>(); builder.Services.AddScoped<IThemeService, ThemeService>();
builder.Services.AddSingleton<IProjectService, ProjectService>(); builder.Services.AddSingleton<IProjectService, ProjectService>();
builder.Services.AddSingleton<IExportService, ExportService>(); builder.Services.AddSingleton<IExportService, ExportService>();
builder.Services.AddSingleton<IRagService, RagService>();
builder.Services.AddSingleton<IWikipediaService, WikipediaService>(); builder.Services.AddSingleton<IWikipediaService, WikipediaService>();
builder.Services.AddSingleton<ILlmWikiService, LlmWikiService>();
builder.Services.AddSingleton<IDuckDuckGoService, DuckDuckGoService>();
builder.Services.AddSingleton<IFactCheckService, FactCheckService>();
builder.Services.AddScoped<IFactSearchToolService, FactSearchToolService>();
builder.Services.AddSingleton<OperationState>(); builder.Services.AddSingleton<OperationState>();
builder.Services.AddSingleton<IDbAdminService, DbAdminService>(); builder.Services.AddSingleton<IDbAdminService, DbAdminService>();
builder.Services.AddScoped<IAuthService, AuthService>(); builder.Services.AddScoped<IAuthService, AuthService>();
@ -44,6 +49,10 @@ builder.Services.AddSingleton<IEmailService, EmailService>();
builder.Services.AddSingleton<IChatService, ChatService>(); builder.Services.AddSingleton<IChatService, ChatService>();
builder.Services.AddSingleton<IQuickMarkdownService, QuickMarkdownService>(); builder.Services.AddSingleton<IQuickMarkdownService, QuickMarkdownService>();
builder.Services.AddHttpClient();
builder.Services.AddScoped<IExportPlugin, WordPressPlugin>();
builder.Services.AddScoped<IPluginManager, PluginManager>();
var smtpSettings = new SmtpSettings(); var smtpSettings = new SmtpSettings();
builder.Configuration.GetSection("Smtp").Bind(smtpSettings); builder.Configuration.GetSection("Smtp").Bind(smtpSettings);
builder.Services.AddSingleton(smtpSettings); builder.Services.AddSingleton(smtpSettings);

View file

@ -3,7 +3,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<_PublishTargetUrl>E:\ollama_net\ollama\AuthorBuddy.Web\bin\Release\net10.0\publish\</_PublishTargetUrl> <_PublishTargetUrl>E:\ollama_net\ollama\AuthorBuddy.Web\bin\Release\net10.0\publish\</_PublishTargetUrl>
<History>True|2026-07-04T08:31:55.1105019Z||;True|2026-07-03T19:42:19.2725521+02:00||;True|2026-07-03T19:14:41.2291817+02:00||;True|2026-06-26T17:29:16.2419779+02:00||;True|2026-06-14T08:09:44.9432726+02:00||;</History> <History>True|2026-08-02T07:53:31.4907144Z||;True|2026-07-12T15:04:45.0075628+02:00||;True|2026-07-12T14:50:27.1555269+02:00||;True|2026-07-12T14:49:01.4372949+02:00||;True|2026-07-05T07:05:59.5910017+02:00||;True|2026-07-04T10:31:55.1105019+02:00||;True|2026-07-03T19:42:19.2725521+02:00||;True|2026-07-03T19:14:41.2291817+02:00||;True|2026-06-26T17:29:16.2419779+02:00||;True|2026-06-14T08:09:44.9432726+02:00||;</History>
<LastFailureDetails /> <LastFailureDetails />
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -4,6 +4,8 @@
"common.error": "Fehler:", "common.error": "Fehler:",
"common.copy": "Kopieren", "common.copy": "Kopieren",
"common.status.error": "Fehler", "common.status.error": "Fehler",
"llm.overlay.title": "KI-Kommunikation",
"llm.overlay.placeholder": "Die KI denkt nach…",
"page.title.login": "Anmelden - Author Buddy", "page.title.login": "Anmelden - Author Buddy",
"page.title.register": "Registrieren - Author Buddy", "page.title.register": "Registrieren - Author Buddy",
"page.title.setup2fa": "2FA einrichten - Author Buddy", "page.title.setup2fa": "2FA einrichten - Author Buddy",
@ -156,7 +158,7 @@
"assistant.hint.empty_result": "Ergebnis erscheint hier nach der Ausführung.", "assistant.hint.empty_result": "Ergebnis erscheint hier nach der Ausführung.",
"assistant.status.processing": "Verarbeite...", "assistant.status.processing": "Verarbeite...",
"assistant.mode.coauthor": "Co-Author Brainstorming", "assistant.mode.coauthor": "Co-Author Brainstorming",
"assistant.mode.rag": "RAG-Faktenabfrage", "assistant.mode.wiki": "Wiki-Wissen",
"assistant.mode.tech_lastenheft": "📋 Lastenheft", "assistant.mode.tech_lastenheft": "📋 Lastenheft",
"assistant.mode.tech_pflichtenheft": "⚙️ Pflichtenheft", "assistant.mode.tech_pflichtenheft": "⚙️ Pflichtenheft",
"assistant.mode.tech_usecase": "🔄 Use Cases", "assistant.mode.tech_usecase": "🔄 Use Cases",
@ -197,17 +199,16 @@
"editor.button.save": "Speichern", "editor.button.save": "Speichern",
"editor.button.version": "Version", "editor.button.version": "Version",
"editor.button.assistant": "Assistent", "editor.button.assistant": "Assistent",
"editor.button.index_project": "Projekt indexieren", "editor.button.compile_wiki": "Wiki kompilieren",
"editor.version.title": "Version speichern", "editor.version.title": "Version speichern",
"editor.version.label.comment": "Kommentar (optional)", "editor.version.label.comment": "Kommentar (optional)",
"editor.version.placeholder.comment": "Was hat sich geändert?", "editor.version.placeholder.comment": "Was hat sich geändert?",
"editor.version.button.save": "Version speichern", "editor.version.button.save": "Version speichern",
"editor.search.placeholder": "Suchbegriff für Fakten-/Wikipedia-Recherche...", "editor.search.placeholder": "Suchbegriff für Fakten-/Wikipedia-Recherche...",
"editor.button.search_rag": "Fakten suchen (RAG)", "editor.button.search_wiki": "Wiki fragen",
"editor.button.search_wikipedia": "Wikipedia-Recherche", "editor.button.search_wikipedia": "Wikipedia-Recherche",
"editor.spellcheck.heading": "Rechtschreibprüfung - Änderungen:", "editor.spellcheck.heading": "Rechtschreibprüfung - Änderungen:",
"editor.rag.heading": "Gefundene Fakten (RAG):", "editor.wiki.heading": "Wiki-Antwort:",
"editor.rag.label.similarity": "Ähnlichkeit:",
"editor.wikipedia.heading": "Wikipedia-Recherche:", "editor.wikipedia.heading": "Wikipedia-Recherche:",
"editor.wikipedia.status.loaded": "✓ Geladen", "editor.wikipedia.status.loaded": "✓ Geladen",
"editor.wikipedia.button.load": "Vollständigen Artikel laden", "editor.wikipedia.button.load": "Vollständigen Artikel laden",
@ -233,18 +234,19 @@
"settings.temperature.desc.chaos": "Unbrauchbar für produktives Schreiben", "settings.temperature.desc.chaos": "Unbrauchbar für produktives Schreiben",
"editor.status.analyzing_style": "Ollama analysiert Stil...", "editor.status.analyzing_style": "Ollama analysiert Stil...",
"editor.status.checking_spelling": "Ollama prüft Rechtschreibung...", "editor.status.checking_spelling": "Ollama prüft Rechtschreibung...",
"editor.status.searching_rag": "Suche verwandte Fakten...", "editor.status.searching_wiki": "Frage Wiki...",
"editor.status.searching_wikipedia": "Suche auf Wikipedia...", "editor.status.searching_wikipedia": "Suche auf Wikipedia...",
"editor.status.loading_article": "Lade Artikel:", "editor.status.loading_article": "Lade Artikel:",
"editor.status.saving_fact": "Speichere als Fakt...", "editor.status.saving_fact": "Speichere als Fakt...",
"editor.status.creating_project": "Erstelle Projekt...", "editor.status.creating_project": "Erstelle Projekt...",
"editor.status.indexing": "Indexiere Projekt...", "editor.status.compiling_wiki": "Komprimiere Wiki...",
"editor.status.saved": "Gespeichert:", "editor.status.saved": "Gespeichert:",
"editor.status.version_saved": "Version gespeichert:", "editor.status.version_saved": "Version gespeichert:",
"editor.status.saved_as": "Gespeichert als:", "editor.status.saved_as": "Gespeichert als:",
"editor.rag.error.no_project": "Kein Projekt geladen. Öffne zuerst ein Projekt über die Sidebar.", "editor.wiki.error.no_project": "Kein Projekt geladen. Öffne zuerst ein Projekt über die Sidebar.",
"editor.rag.error.no_facts": "Keine relevanten Fakten gefunden. Indexiere das Projekt ggf. über 'Projekt indexieren'.", "editor.wiki.error.not_found": "Projekt nicht gefunden.",
"editor.rag.error.generic": "RAG-Fehler:", "editor.wiki.status.success": "Wiki erfolgreich kompiliert.",
"editor.wiki.error.generic": "Wiki-Fehler:",
"editor.wikipedia.error.not_found": "Keine Wikipedia-Artikel gefunden.", "editor.wikipedia.error.not_found": "Keine Wikipedia-Artikel gefunden.",
"editor.wikipedia.error.generic": "Wikipedia-Fehler:", "editor.wikipedia.error.generic": "Wikipedia-Fehler:",
"editor.wikipedia.error.load_failed": "Konnte Artikel nicht laden.", "editor.wikipedia.error.load_failed": "Konnte Artikel nicht laden.",
@ -252,9 +254,6 @@
"editor.error.save_failed": "Fehler beim Speichern:", "editor.error.save_failed": "Fehler beim Speichern:",
"editor.error.create_failed": "Fehler beim Erstellen:", "editor.error.create_failed": "Fehler beim Erstellen:",
"editor.index.error.no_project": "Kein Projekt geladen. Öffne zuerst ein Projekt über die Sidebar.", "editor.index.error.no_project": "Kein Projekt geladen. Öffne zuerst ein Projekt über die Sidebar.",
"editor.index.error.not_found": "Projekt nicht gefunden.",
"editor.index.status.success": "Projekt erfolgreich indexiert.",
"editor.index.error.generic": "Indexierungsfehler:",
"editor.version.status_loaded_prefix": "Version vom", "editor.version.status_loaded_prefix": "Version vom",
"editor.version.status_loaded_suffix": "geladen.", "editor.version.status_loaded_suffix": "geladen.",
"home.tab.rag_facts": "Gefundene Fakten (RAG)", "home.tab.rag_facts": "Gefundene Fakten (RAG)",
@ -282,6 +281,8 @@
"usermanagement.status.deleted": "Benutzer gelöscht.", "usermanagement.status.deleted": "Benutzer gelöscht.",
"common.back": "Zurück", "common.back": "Zurück",
"export.title": "Dokumente exportieren", "export.title": "Dokumente exportieren",
"models.cloud.not_available": "Modellverwaltung ist nur für das lokale Ollama-Backend verfügbar.",
"models.cloud.hint": "Wechsle in den Einstellungen auf das Ollama-Backend, um Modelle zu verwalten.",
"export.file_count": "Ausgewählte Dateien", "export.file_count": "Ausgewählte Dateien",
"export.label.filename": "Dateiname", "export.label.filename": "Dateiname",
"export.placeholder.filename": "z.B. MeinExport", "export.placeholder.filename": "z.B. MeinExport",

View file

@ -4,6 +4,8 @@
"common.error": "Error:", "common.error": "Error:",
"common.copy": "Copy", "common.copy": "Copy",
"common.status.error": "Error", "common.status.error": "Error",
"llm.overlay.title": "AI Communication",
"llm.overlay.placeholder": "The AI is thinking…",
"page.title.login": "Login - Author Buddy", "page.title.login": "Login - Author Buddy",
"page.title.register": "Register - Author Buddy", "page.title.register": "Register - Author Buddy",
"page.title.setup2fa": "2FA setup - Author Buddy", "page.title.setup2fa": "2FA setup - Author Buddy",
@ -156,7 +158,7 @@
"assistant.hint.empty_result": "Result appears here after execution.", "assistant.hint.empty_result": "Result appears here after execution.",
"assistant.status.processing": "Processing...", "assistant.status.processing": "Processing...",
"assistant.mode.coauthor": "Co-Author Brainstorming", "assistant.mode.coauthor": "Co-Author Brainstorming",
"assistant.mode.rag": "RAG fact query", "assistant.mode.wiki": "Wiki knowledge",
"assistant.mode.tech_lastenheft": "📋 Requirements Spec", "assistant.mode.tech_lastenheft": "📋 Requirements Spec",
"assistant.mode.tech_pflichtenheft": "⚙️ Functional Spec", "assistant.mode.tech_pflichtenheft": "⚙️ Functional Spec",
"assistant.mode.tech_usecase": "🔄 Use Cases", "assistant.mode.tech_usecase": "🔄 Use Cases",
@ -197,17 +199,16 @@
"editor.button.save": "Save", "editor.button.save": "Save",
"editor.button.version": "Version", "editor.button.version": "Version",
"editor.button.assistant": "Assistant", "editor.button.assistant": "Assistant",
"editor.button.index_project": "Index project", "editor.button.compile_wiki": "Compile wiki",
"editor.version.title": "Save version", "editor.version.title": "Save version",
"editor.version.label.comment": "Comment (optional)", "editor.version.label.comment": "Comment (optional)",
"editor.version.placeholder.comment": "What changed?", "editor.version.placeholder.comment": "What changed?",
"editor.version.button.save": "Save version", "editor.version.button.save": "Save version",
"editor.search.placeholder": "Search term for fact/Wikipedia research...", "editor.search.placeholder": "Search term for fact/Wikipedia research...",
"editor.button.search_rag": "Search facts (RAG)", "editor.button.search_wiki": "Ask wiki",
"editor.button.search_wikipedia": "Wikipedia research", "editor.button.search_wikipedia": "Wikipedia research",
"editor.spellcheck.heading": "Spell check - Changes:", "editor.spellcheck.heading": "Spell check - Changes:",
"editor.rag.heading": "Found facts (RAG):", "editor.wiki.heading": "Wiki answer:",
"editor.rag.label.similarity": "Similarity:",
"editor.wikipedia.heading": "Wikipedia research:", "editor.wikipedia.heading": "Wikipedia research:",
"editor.wikipedia.status.loaded": "\u2713 Loaded", "editor.wikipedia.status.loaded": "\u2713 Loaded",
"editor.wikipedia.button.load": "Load full article", "editor.wikipedia.button.load": "Load full article",
@ -233,18 +234,19 @@
"settings.temperature.desc.chaos": "Unusable for productive writing", "settings.temperature.desc.chaos": "Unusable for productive writing",
"editor.status.analyzing_style": "Ollama analyzing style...", "editor.status.analyzing_style": "Ollama analyzing style...",
"editor.status.checking_spelling": "Ollama checking spelling...", "editor.status.checking_spelling": "Ollama checking spelling...",
"editor.status.searching_rag": "Searching related facts...", "editor.status.searching_wiki": "Asking wiki...",
"editor.status.searching_wikipedia": "Searching Wikipedia...", "editor.status.searching_wikipedia": "Searching Wikipedia...",
"editor.status.loading_article": "Loading article:", "editor.status.loading_article": "Loading article:",
"editor.status.saving_fact": "Saving as fact...", "editor.status.saving_fact": "Saving as fact...",
"editor.status.creating_project": "Creating project...", "editor.status.creating_project": "Creating project...",
"editor.status.indexing": "Indexing project...", "editor.status.compiling_wiki": "Compiling wiki...",
"editor.status.saved": "Saved:", "editor.status.saved": "Saved:",
"editor.status.version_saved": "Version saved:", "editor.status.version_saved": "Version saved:",
"editor.status.saved_as": "Saved as:", "editor.status.saved_as": "Saved as:",
"editor.rag.error.no_project": "No project loaded. Open a project via the sidebar first.", "editor.wiki.error.no_project": "No project loaded. Open a project via the sidebar first.",
"editor.rag.error.no_facts": "No relevant facts found. Index the project if needed via 'Index project'.", "editor.wiki.error.not_found": "Project not found.",
"editor.rag.error.generic": "RAG error:", "editor.wiki.status.success": "Wiki compiled successfully.",
"editor.wiki.error.generic": "Wiki error:",
"editor.wikipedia.error.not_found": "No Wikipedia articles found.", "editor.wikipedia.error.not_found": "No Wikipedia articles found.",
"editor.wikipedia.error.generic": "Wikipedia error:", "editor.wikipedia.error.generic": "Wikipedia error:",
"editor.wikipedia.error.load_failed": "Could not load article.", "editor.wikipedia.error.load_failed": "Could not load article.",
@ -252,9 +254,6 @@
"editor.error.save_failed": "Error saving:", "editor.error.save_failed": "Error saving:",
"editor.error.create_failed": "Error creating:", "editor.error.create_failed": "Error creating:",
"editor.index.error.no_project": "No project loaded. Open a project via the sidebar first.", "editor.index.error.no_project": "No project loaded. Open a project via the sidebar first.",
"editor.index.error.not_found": "Project not found.",
"editor.index.status.success": "Project indexed successfully.",
"editor.index.error.generic": "Indexing error:",
"editor.version.status_loaded_prefix": "Version from", "editor.version.status_loaded_prefix": "Version from",
"editor.version.status_loaded_suffix": "loaded.", "editor.version.status_loaded_suffix": "loaded.",
"home.tab.rag_facts": "Found facts (RAG)", "home.tab.rag_facts": "Found facts (RAG)",
@ -282,6 +281,8 @@
"usermanagement.status.deleted": "User deleted.", "usermanagement.status.deleted": "User deleted.",
"common.back": "Back", "common.back": "Back",
"export.title": "Export documents", "export.title": "Export documents",
"models.cloud.not_available": "Model management is only available for the local Ollama backend.",
"models.cloud.hint": "Switch to the Ollama backend in settings to manage models.",
"export.file_count": "Selected files", "export.file_count": "Selected files",
"export.label.filename": "Filename", "export.label.filename": "Filename",
"export.placeholder.filename": "e.g. MyExport", "export.placeholder.filename": "e.g. MyExport",

View file

@ -0,0 +1,166 @@
using AuthorBuddy.Web.Models;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Web;
namespace AuthorBuddy.Web.Services;
public class DuckDuckGoService : IDuckDuckGoService
{
private readonly HttpClient _http;
private readonly HttpClient _htmlHttp;
public DuckDuckGoService()
{
_http = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
_http.DefaultRequestHeaders.UserAgent.ParseAdd("AuthorBuddy/1.0 (fact-checker)");
_htmlHttp = new HttpClient { Timeout = TimeSpan.FromSeconds(20) };
_htmlHttp.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
_htmlHttp.DefaultRequestHeaders.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
_htmlHttp.DefaultRequestHeaders.Add("Accept-Language", "de,en;q=0.8");
}
public async Task<List<DuckDuckGoResult>> SearchWebAsync(string query, int maxResults = 5)
{
var results = new List<DuckDuckGoResult>();
try
{
var url = "https://html.duckduckgo.com/html/?q=" + HttpUtility.UrlEncode(query);
var html = await _htmlHttp.GetStringAsync(url);
if (html.Contains("anomaly", StringComparison.OrdinalIgnoreCase) ||
html.Contains("challenge-form", StringComparison.OrdinalIgnoreCase))
{
return results;
}
var blocks = Regex.Split(html, "<div[^>]*class=\"result[^\"]*\"", RegexOptions.IgnoreCase);
foreach (var block in blocks.Skip(1))
{
var link = Regex.Match(block, "<a[^>]*class=\"result__a\"[^>]*href=\"([^\"]+)\"[^>]*>(.*?)</a>", RegexOptions.Singleline);
if (!link.Success) continue;
var realUrl = DecodeDdgUrl(System.Net.WebUtility.HtmlDecode(link.Groups[1].Value));
if (string.IsNullOrWhiteSpace(realUrl)) continue;
var title = StripHtml(link.Groups[2].Value);
var snippet = string.Empty;
var snip = Regex.Match(block, "<a[^>]*class=\"result__snippet\"[^>]*>(.*?)</a>", RegexOptions.Singleline);
if (snip.Success) snippet = StripHtml(snip.Groups[1].Value);
results.Add(new DuckDuckGoResult { Heading = title, AbstractText = snippet, AbstractUrl = realUrl });
if (results.Count >= maxResults) break;
}
}
catch
{
// Keine Ergebnisse bei Fehler
}
return results;
}
public async Task<List<DuckDuckGoResult>> SearchAsync(string query, int maxResults = 3)
{
var results = new List<DuckDuckGoResult>();
try
{
var url = "https://api.duckduckgo.com/" +
$"?q={HttpUtility.UrlEncode(query)}" +
"&format=json" +
"&no_html=1" +
"&skip_disambig=1" +
"&t=AuthorBuddy";
var response = await _http.GetStringAsync(url);
using var doc = JsonDocument.Parse(response);
var root = doc.RootElement;
var result = new DuckDuckGoResult
{
Heading = GetString(root, "Heading"),
AbstractText = GetString(root, "AbstractText"),
AbstractUrl = GetString(root, "AbstractURL"),
Answer = GetString(root, "Answer")
};
if (root.TryGetProperty("RelatedTopics", out var related) && related.ValueKind == JsonValueKind.Array)
{
foreach (var topic in related.EnumerateArray())
{
if (topic.TryGetProperty("Text", out var text))
{
var t = text.GetString();
if (!string.IsNullOrWhiteSpace(t))
result.RelatedTexts.Add(t);
if (topic.TryGetProperty("FirstURL", out var firstUrl))
{
var u = firstUrl.GetString();
if (!string.IsNullOrWhiteSpace(u))
result.RelatedUrls.Add(u);
}
}
else if (topic.TryGetProperty("Topics", out var subTopics) && subTopics.ValueKind == JsonValueKind.Array)
{
foreach (var sub in subTopics.EnumerateArray())
{
if (sub.TryGetProperty("Text", out var subText))
{
var t = subText.GetString();
if (!string.IsNullOrWhiteSpace(t))
result.RelatedTexts.Add(t);
if (sub.TryGetProperty("FirstURL", out var firstUrl))
{
var u = firstUrl.GetString();
if (!string.IsNullOrWhiteSpace(u))
result.RelatedUrls.Add(u);
}
}
}
}
}
}
if (!string.IsNullOrWhiteSpace(result.AbstractText) ||
!string.IsNullOrWhiteSpace(result.Answer) ||
result.RelatedTexts.Count > 0)
{
results.Add(result);
}
}
catch
{
// Kein Ergebnis bei Fehler
}
return results.Take(maxResults).ToList();
}
private static string GetString(JsonElement element, string property)
{
if (element.TryGetProperty(property, out var value) && value.ValueKind == JsonValueKind.String)
return value.GetString() ?? string.Empty;
return string.Empty;
}
private static string DecodeDdgUrl(string rawUrl)
{
if (string.IsNullOrWhiteSpace(rawUrl)) return rawUrl;
var uddg = Regex.Match(rawUrl, "[?&]uddg=([^&]+)");
if (uddg.Success)
return HttpUtility.UrlDecode(uddg.Groups[1].Value);
if (rawUrl.StartsWith("//"))
return "https:" + rawUrl;
return rawUrl;
}
private static string StripHtml(string html)
{
var text = Regex.Replace(html, "<[^>]+>", string.Empty);
return System.Net.WebUtility.HtmlDecode(text).Trim();
}
}

View file

@ -0,0 +1,146 @@
using AuthorBuddy.Web.Models;
using System.Text;
namespace AuthorBuddy.Web.Services;
public class FactCheckService : IFactCheckService
{
private readonly IDuckDuckGoService _duckDuckGo;
private readonly IOllamaService _ollama;
private readonly OllamaSettings _settings;
public FactCheckService(IDuckDuckGoService duckDuckGo, IOllamaService ollama, OllamaSettings settings)
{
_duckDuckGo = duckDuckGo;
_ollama = ollama;
_settings = settings;
}
public async Task<List<FactCheckResult>> VerifyAsync(string answer, string? backendName = null, int maxClaims = 3)
{
var results = new List<FactCheckResult>();
var claims = ExtractClaims(answer);
foreach (var claim in claims.Take(maxClaims))
{
var searchResults = await _duckDuckGo.SearchAsync(claim);
if (searchResults.Count == 0)
{
results.Add(new FactCheckResult
{
Claim = claim,
Verdict = FactCheckVerdict.NoInformation,
Reasoning = "Keine Quellen gefunden."
});
continue;
}
var verdict = await GetVerdictAsync(claim, searchResults, backendName);
results.Add(verdict);
}
return results;
}
private async Task<FactCheckResult> GetVerdictAsync(string claim, List<DuckDuckGoResult> searchResults, string? backendName)
{
var sourcesText = new StringBuilder();
var sources = new List<string>();
foreach (var r in searchResults)
{
if (!string.IsNullOrWhiteSpace(r.AbstractText))
sourcesText.AppendLine($"- {r.AbstractText}");
if (!string.IsNullOrWhiteSpace(r.Answer))
sourcesText.AppendLine($"- {r.Answer}");
foreach (var related in r.RelatedTexts)
sourcesText.AppendLine($"- {related}");
if (!string.IsNullOrWhiteSpace(r.AbstractUrl))
sources.Add(r.AbstractUrl);
}
if (sourcesText.Length == 0)
{
return new FactCheckResult
{
Claim = claim,
Verdict = FactCheckVerdict.NoInformation,
Reasoning = "Keine verwertbaren Quellen gefunden.",
Sources = sources
};
}
var prompt = $@"Du bist ein Faktenprüfer. Vergleiche die folgende Behauptung mit den DuckDuckGo-Suchergebnissen.
BEHAUPTUNG: {claim}
SUCHERGEBNISSE:
{sourcesText}
Bewerte, ob die Behauptung durch die Suchergebnisse bestätigt oder widerlegt wird.
Antworte NUR in diesem Format:
URTEIL: BESTAETIGT oder WIDERSPROCHEN oder TEILWEISE oder KEINE_INFORMATION
BEGRUENDUNG: Maximal 2 Sätze.";
var raw = await _ollama.Chat(_settings.StyleModel, prompt, temperature: 0.1, backendName: backendName ?? _settings.StyleBackend);
return new FactCheckResult
{
Claim = claim,
Verdict = ParseVerdict(raw),
Reasoning = ExtractReasoning(raw),
Sources = sources
};
}
private static List<string> ExtractClaims(string text)
{
var claims = new List<string>();
if (string.IsNullOrWhiteSpace(text)) return claims;
var sentences = System.Text.RegularExpressions.Regex.Split(text, @"(?<=[.!?])\s+");
foreach (var sentence in sentences)
{
var trimmed = sentence.Trim().Trim('"');
if (trimmed.Length < 25) continue;
if (trimmed.EndsWith("?")) continue;
if (trimmed.StartsWith("```") || trimmed.StartsWith("#")) continue;
claims.Add(trimmed.Length > 200 ? trimmed[..200] + "..." : trimmed);
if (claims.Count >= 5) break;
}
return claims;
}
private static FactCheckVerdict ParseVerdict(string raw)
{
var upper = raw.ToUpperInvariant();
if (upper.Contains("BESTAETIGT") || upper.Contains("CONFIRMED") || upper.Contains("BESTÄTIGT"))
return FactCheckVerdict.Confirmed;
if (upper.Contains("WIDERSPROCHEN") || upper.Contains("CONTRADICT") || upper.Contains("REFUTED"))
return FactCheckVerdict.Contradicted;
if (upper.Contains("TEILWEISE") || upper.Contains("PARTIAL"))
return FactCheckVerdict.Partial;
return FactCheckVerdict.NoInformation;
}
private static string ExtractReasoning(string raw)
{
var idx = raw.IndexOf("BEGRUENDUNG:", StringComparison.OrdinalIgnoreCase);
if (idx < 0)
idx = raw.IndexOf("BEGRÜNDUNG:", StringComparison.OrdinalIgnoreCase);
if (idx < 0)
idx = raw.IndexOf("REASONING:", StringComparison.OrdinalIgnoreCase);
if (idx >= 0)
{
var reasoning = raw[(idx + 1)..];
if (reasoning.Contains(':'))
reasoning = reasoning[(reasoning.IndexOf(':') + 1)..];
return reasoning.Trim();
}
return raw.Trim();
}
}

View file

@ -0,0 +1,209 @@
using AuthorBuddy.Web.Models;
using System.Text.Json;
namespace AuthorBuddy.Web.Services;
public class FactSearchToolService : IFactSearchToolService
{
private readonly IOllamaService _ollama;
private readonly IDuckDuckGoService _duckDuckGo;
private readonly IWikipediaService _wikipedia;
private readonly ISettingsService _settingsService;
private readonly OllamaSettings _settings;
public IReadOnlyList<McpToolDefinition> Tools { get; }
public FactSearchToolService(
IOllamaService ollama,
IDuckDuckGoService duckDuckGo,
IWikipediaService wikipedia,
ISettingsService settingsService,
OllamaSettings settings)
{
_ollama = ollama;
_duckDuckGo = duckDuckGo;
_wikipedia = wikipedia;
_settingsService = settingsService;
_settings = settings;
Tools = new List<McpToolDefinition>
{
new()
{
Name = "fact_search_query",
Description = "Erzeugt aus einer Behauptung (Fakt) eine präzise Suchanfrage für eine Websuche.",
InputSchemaJson = "{\"type\":\"object\",\"properties\":{\"fact\":{\"type\":\"string\"}},\"required\":[\"fact\"]}"
},
new()
{
Name = "fact_web_search",
Description = "Führt eine Websuche aus (DuckDuckGo, ggf. Wikipedia) und liefert Titel, Snippets und URLs.",
InputSchemaJson = "{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"},\"maxResults\":{\"type\":\"integer\"}},\"required\":[\"query\"]}"
},
new()
{
Name = "fact_summarize",
Description = "Erstellt eine kurze Zusammenfassung über ein Thema anhand von Suchinformationen.",
InputSchemaJson = "{\"type\":\"object\",\"properties\":{\"query\":{\"type\":\"string\"},\"text\":{\"type\":\"string\"},\"url\":{\"type\":\"string\"}},\"required\":[\"query\",\"text\"]}"
}
};
}
public async Task<McpToolResult> CallAsync(string toolName, string argumentsJson)
{
try
{
using var doc = JsonDocument.Parse(string.IsNullOrWhiteSpace(argumentsJson) ? "{}" : argumentsJson);
var args = doc.RootElement;
switch (toolName)
{
case "fact_search_query":
{
var fact = GetString(args, "fact");
if (string.IsNullOrWhiteSpace(fact))
return Error(toolName, "Argument 'fact' fehlt.");
var query = await CreateSearchQueryAsync(fact);
return new McpToolResult { ToolName = toolName, Content = JsonSerializer.Serialize(new { query }), Data = query };
}
case "fact_web_search":
{
var query = GetString(args, "query");
if (string.IsNullOrWhiteSpace(query))
return Error(toolName, "Argument 'query' fehlt.");
var maxResults = args.TryGetProperty("maxResults", out var m) && m.TryGetInt32(out var mi) ? mi : 5;
var results = await SearchAsync(query, maxResults);
return new McpToolResult { ToolName = toolName, Content = JsonSerializer.Serialize(results), Data = results };
}
case "fact_summarize":
{
var query = GetString(args, "query");
var text = GetString(args, "text");
var url = GetString(args, "url");
if (string.IsNullOrWhiteSpace(query) || string.IsNullOrWhiteSpace(text))
return Error(toolName, "Argumente 'query' und 'text' sind erforderlich.");
var summary = await SummarizeAsync(query, text, url);
return new McpToolResult { ToolName = toolName, Content = JsonSerializer.Serialize(new { summary, url }), Data = summary };
}
default:
return Error(toolName, $"Unbekanntes Tool '{toolName}'.");
}
}
catch (Exception ex)
{
return Error(toolName, ex.Message);
}
}
public async Task<string> CreateSearchQueryAsync(string factText)
{
var prompt = await _settingsService.GetFactSearchPromptAsync()
?? _settings.GetFactSearchDefaultPrompt(_settings.Language);
var temperature = await _settingsService.GetFactSearchTempAsync();
var result = await _ollama.Chat(_settings.StyleModel, factText, prompt, temperature, _settings.StyleBackend);
return SanitizeQuery(result);
}
public async Task<List<FactSearchResult>> SearchAsync(string query, int maxResults = 5)
{
var results = new List<FactSearchResult>();
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
void Add(FactSearchResult r)
{
if (string.IsNullOrWhiteSpace(r.Url)) return;
if (!seen.Add(r.Url)) return;
results.Add(r);
}
// 1) Echte Websuche über DuckDuckGo HTML (liefert Titel, Snippet, URL)
try
{
var web = await _duckDuckGo.SearchWebAsync(query, maxResults);
foreach (var r in web)
Add(new FactSearchResult { Title = r.Heading, Snippet = r.AbstractText, Url = r.AbstractUrl });
}
catch
{
// Weiter mit nächster Quelle
}
// 2) DuckDuckGo Instant Answer (enzyklopädische Antworten)
if (results.Count < maxResults)
{
try
{
var ddg = await _duckDuckGo.SearchAsync(query, maxResults);
foreach (var r in ddg)
{
var url = string.IsNullOrWhiteSpace(r.AbstractUrl) ? r.RelatedUrls.FirstOrDefault() ?? string.Empty : r.AbstractUrl;
if (string.IsNullOrWhiteSpace(url)) continue;
var snippet = string.IsNullOrWhiteSpace(r.AbstractText)
? string.Join(" | ", r.RelatedTexts)
: r.AbstractText;
Add(new FactSearchResult
{
Title = string.IsNullOrWhiteSpace(r.Heading) ? url : r.Heading,
Snippet = snippet,
Url = url
});
}
}
catch
{
// Weiter
}
}
// 3) Wikipedia-Fallback
if (results.Count < maxResults)
{
try
{
var wiki = await _wikipedia.SearchAsync(query, maxResults);
foreach (var w in wiki)
Add(new FactSearchResult { Title = w.Title, Snippet = w.Snippet, Url = w.Url });
}
catch
{
// Keine Ergebnisse
}
}
return results.Take(maxResults).ToList();
}
public async Task<string> SummarizeAsync(string query, string text, string url)
{
var info = string.IsNullOrWhiteSpace(text) ? query : text;
if (info.Length > 2500) info = info[..2500];
var system = $"Du bist ein Faktenprüfer und Autor. Erstelle eine kurze, präzise Zusammenfassung (maximal 3 Sätze) über das Thema \"{query}\" auf Deutsch. Nutze nur die gegebenen Informationen. Gib NUR die Zusammenfassung zurück, ohne Anführungszeichen.";
var result = await _ollama.Chat(_settings.StyleModel, info, system, 0.3, _settings.StyleBackend);
return (result ?? string.Empty).Trim().Trim('"', '\'', '\n', '\r');
}
private static string SanitizeQuery(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return raw ?? string.Empty;
var q = raw.Trim();
q = q.Trim('"', '\'', '.', ':');
q = System.Text.RegularExpressions.Regex.Replace(q, @"^Suche\s*:\s*", "", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
q = System.Text.RegularExpressions.Regex.Replace(q, @"^Query\s*:\s*", "", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
return q;
}
private static string GetString(JsonElement element, string property)
{
if (element.TryGetProperty(property, out var value) && value.ValueKind == JsonValueKind.String)
return value.GetString() ?? string.Empty;
return string.Empty;
}
private static McpToolResult Error(string toolName, string message) =>
new() { IsError = true, ToolName = toolName, Content = JsonSerializer.Serialize(new { error = message }) };
}

View file

@ -0,0 +1,9 @@
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public interface IDuckDuckGoService
{
Task<List<DuckDuckGoResult>> SearchAsync(string query, int maxResults = 3);
Task<List<DuckDuckGoResult>> SearchWebAsync(string query, int maxResults = 5);
}

11
Services/IExportPlugin.cs Normal file
View file

@ -0,0 +1,11 @@
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public interface IExportPlugin
{
string Id { get; }
string Name { get; }
string Description { get; }
Task<PluginResult> ExportAsync(ExportPluginContext context);
}

View file

@ -0,0 +1,8 @@
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public interface IFactCheckService
{
Task<List<FactCheckResult>> VerifyAsync(string answer, string? backendName = null, int maxClaims = 3);
}

View file

@ -0,0 +1,12 @@
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public interface IFactSearchToolService
{
IReadOnlyList<McpToolDefinition> Tools { get; }
Task<McpToolResult> CallAsync(string toolName, string argumentsJson);
Task<string> CreateSearchQueryAsync(string factText);
Task<List<FactSearchResult>> SearchAsync(string query, int maxResults = 5);
Task<string> SummarizeAsync(string query, string text, string url);
}

View file

@ -0,0 +1,8 @@
namespace AuthorBuddy.Web.Services;
public interface ILlmWikiService
{
Task CompileWikiAsync(int projectId, string rootPath, IProgress<string> progress);
Task<string> QueryWikiAsync(int projectId, string question);
bool WikiExists(int projectId, string rootPath);
}

View file

@ -6,18 +6,20 @@ namespace AuthorBuddy.Web.Services;
public interface IOllamaService public interface IOllamaService
{ {
bool SupportsModelManagement { get; }
Task<IEnumerable<LLM_Model>> GetLocalModelsAsync(); Task<IEnumerable<LLM_Model>> GetLocalModelsAsync();
Task<IEnumerable<LLM_Model>> GetModelsForBackendAsync(string backendName);
Task<bool> PullModelAsync(string modelName, IProgress<string> progress); Task<bool> PullModelAsync(string modelName, IProgress<string> progress);
Task<string> QuickSpellCheckAsync(string text); Task<string> QuickSpellCheckAsync(string text);
Task<float[]> GetEmbeddingAsync(string text); Task<float[]> GetEmbeddingAsync(string text, string? backendName = null);
Task<bool> TestEmbeddingAsync(string model); Task<bool> TestEmbeddingAsync(string model, string? backendName = null);
Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null); Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null, string? backendName = null);
Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null); Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null, string? backendName = null);
Task<string> AnswerBasedOnFactsAsync(string question, string facts); Task<string> AnswerBasedOnFactsAsync(string question, string facts);
Task<(bool Success, long LatencyMs)> TestConnectionAsync(); Task<(bool Success, long LatencyMs)> TestConnectionAsync();
Task<bool> ModelExistsAsync(string model); Task<bool> ModelExistsAsync(string model, string? backendName = null);
Task<bool> DeleteModelAsync(string modelName); Task<bool> DeleteModelAsync(string modelName);
Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model); Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model, string? backendName = null);
Task<long> MeasureLatencyAsync(string model); Task<long> MeasureLatencyAsync(string model, string? backendName = null);
void Reconfigure(string ollamaUrl); void Reconfigure(string ollamaUrl);
} }

View file

@ -0,0 +1,11 @@
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public interface IPluginManager
{
IReadOnlyList<IExportPlugin> GetPlugins();
IExportPlugin? GetPlugin(string id);
Task<PluginConfig?> GetConfigAsync(string pluginId);
Task SaveConfigAsync(PluginConfig config);
}

View file

@ -14,9 +14,16 @@ public interface ISettingsService
Task SaveOllamaUrlAsync(string url); Task SaveOllamaUrlAsync(string url);
Task SaveLlamaCppUrlAsync(string url); Task SaveLlamaCppUrlAsync(string url);
Task SaveBackendAsync(string backend); Task SaveBackendAsync(string backend);
void SetBackend(string backend);
Task SaveRagModelAsync(string model); Task SaveRagModelAsync(string model);
Task SaveStyleModelAsync(string model); Task SaveStyleModelAsync(string model);
Task SaveSpellingModelAsync(string model); Task SaveSpellingModelAsync(string model);
Task<string?> GetRagBackendAsync();
Task<string?> GetStyleBackendAsync();
Task<string?> GetSpellingBackendAsync();
Task SaveRagBackendAsync(string backend);
Task SaveStyleBackendAsync(string backend);
Task SaveSpellingBackendAsync(string backend);
Task SaveThemeAsync(string theme); Task SaveThemeAsync(string theme);
Task<string?> GetLektorSystemPromptAsync(); Task<string?> GetLektorSystemPromptAsync();
@ -86,10 +93,24 @@ public interface ISettingsService
Task SaveChatPromptAsync(string prompt); Task SaveChatPromptAsync(string prompt);
Task<double> GetChatTempAsync(); Task<double> GetChatTempAsync();
Task SaveChatTempAsync(double temp); Task SaveChatTempAsync(double temp);
Task<bool> GetChatFactCheckEnabledAsync();
Task SaveChatFactCheckEnabledAsync(bool enabled);
Task<string?> GetFactSearchPromptAsync();
Task SaveFactSearchPromptAsync(string prompt);
Task<double> GetFactSearchTempAsync();
Task SaveFactSearchTempAsync(double temp);
Task<string?> GetLanguageAsync(); Task<string?> GetLanguageAsync();
Task SaveLanguageAsync(string language); Task SaveLanguageAsync(string language);
Task<string?> GetOllamaCloudUrlAsync();
Task SaveOllamaCloudUrlAsync(string url);
Task<string?> GetOllamaCloudApiKeyAsync();
Task SaveOllamaCloudApiKeyAsync(string apiKey);
Task<string?> GetPluginConfigAsync(string pluginId);
Task SavePluginConfigAsync(string pluginId, string json);
Task<List<UserEntity>> GetUsersAsync(); Task<List<UserEntity>> GetUsersAsync();
Task<UserEntity> GetCurrentUserAsync(); Task<UserEntity> GetCurrentUserAsync();
void SetCurrentUser(int userId); void SetCurrentUser(int userId);

137
Services/LlmWikiService.cs Normal file
View file

@ -0,0 +1,137 @@
using System.Text;
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public class LlmWikiService : ILlmWikiService
{
private readonly IOllamaService _ollama;
private readonly OllamaSettings _settings;
private readonly IProjectService _projectService;
public LlmWikiService(IOllamaService ollama, OllamaSettings settings, IProjectService projectService)
{
_ollama = ollama;
_settings = settings;
_projectService = projectService;
}
public bool WikiExists(int projectId, string rootPath)
{
var wikiDir = GetWikiDir(rootPath);
return Directory.Exists(wikiDir) && Directory.GetFiles(wikiDir, "*.md").Length > 0;
}
public async Task CompileWikiAsync(int projectId, string rootPath, IProgress<string> progress)
{
var wikiDir = GetWikiDir(rootPath);
Directory.CreateDirectory(wikiDir);
var files = Directory.GetFiles(rootPath, "*.md", SearchOption.AllDirectories)
.Where(f => !f.Contains("\\wiki\\") && !f.Contains("/wiki/"))
.ToList();
if (files.Count == 0)
{
progress?.Report("Keine Markdown-Dateien gefunden.");
return;
}
var indexEntries = new List<string>();
foreach (var file in files)
{
var fileName = Path.GetFileNameWithoutExtension(file);
var safeName = SanitizeName(fileName);
progress?.Report($"Komprimiere: {fileName}...");
var content = await File.ReadAllTextAsync(file);
if (string.IsNullOrWhiteSpace(content))
{
progress?.Report(" ↳ leer, übersprungen");
continue;
}
var wikiContent = await CompilePage(content, fileName);
var wikiPath = Path.Combine(wikiDir, $"{safeName}.md");
await File.WriteAllTextAsync(wikiPath, wikiContent);
indexEntries.Add($"- [{fileName}]({safeName}.md)");
progress?.Report(" ↳ ✓ Wiki-Seite erstellt");
}
var indexContent = "# Wiki-Index\n\n" +
$"Erstellt am {DateTime.Now:dd.MM.yyyy HH:mm} aus {files.Count} Quellen.\n\n" +
string.Join("\n", indexEntries) + "\n";
await File.WriteAllTextAsync(Path.Combine(wikiDir, "index.md"), indexContent);
progress?.Report($"Wiki kompiliert: {indexEntries.Count} Seiten.");
}
public async Task<string> QueryWikiAsync(int projectId, string question)
{
var project = await _projectService.GetProjectAsync(projectId);
if (project == null)
return "Projekt nicht gefunden.";
var wikiDir = GetWikiDir(project.RootPath);
if (!Directory.Exists(wikiDir))
return "Wiki noch nicht kompiliert. Bitte zuerst das Wiki erstellen.";
var wikiFiles = Directory.GetFiles(wikiDir, "*.md");
var wikiContent = new StringBuilder();
foreach (var file in wikiFiles.OrderBy(f => f))
{
var content = await File.ReadAllTextAsync(file);
wikiContent.AppendLine($"--- {Path.GetFileName(file)} ---");
wikiContent.AppendLine(content);
wikiContent.AppendLine();
}
var prompt = $@"Du bist ein Wiki-Assistent. Beantworte die folgende Frage ausschließlich basierend auf dem untenstehenden Wiki. Wenn die Antwort nicht im Wiki zu finden ist, sage das deutlich.
WIKI:
{wikiContent}
FRAGE: {question}";
return await _ollama.Chat(_settings.StyleModel, prompt, temperature: 0.3, backendName: _settings.StyleBackend);
}
private async Task<string> CompilePage(string content, string title)
{
var prompt = $@"Du bist ein Wiki-Redakteur. Erstelle eine strukturierte Wiki-Seite aus dem folgenden Dokument.
FORMAT:
# {title}
## Zusammenfassung
Kurze Zusammenfassung (2-3 Sätze)
## Kernkonzepte
- Konzept 1: Beschreibung
- Konzept 2: Beschreibung
## Details
Wichtige Fakten und Details aus dem Dokument
## Verknüpfungen
Verweise auf verwandte Themen (falls erkennbar)
DOKUMENT:
{content}";
return await _ollama.Chat(_settings.StyleModel, prompt, temperature: 0.3, backendName: _settings.StyleBackend);
}
private static string GetWikiDir(string rootPath) =>
Path.Combine(rootPath, "wiki");
private static string SanitizeName(string name)
{
var invalid = Path.GetInvalidFileNameChars();
var sanitized = new string(name.Where(c => !invalid.Contains(c)).ToArray());
return string.IsNullOrWhiteSpace(sanitized) ? "unnamed" : sanitized;
}
}

View file

@ -0,0 +1,225 @@
using System.Diagnostics;
using System.Net.Http.Headers;
using System.Text.Json;
using System.Text.RegularExpressions;
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public class OllamaCloudBackend : ILLMBackend
{
private readonly IHttpClientFactory _httpFactory;
private readonly OllamaSettings _settings;
public string Name => "Ollama Cloud";
public bool SupportsModelManagement => false;
public OllamaCloudBackend(OllamaSettings settings, IHttpClientFactory httpFactory)
{
_settings = settings;
_httpFactory = httpFactory;
}
private HttpClient CreateClient()
{
var client = _httpFactory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(300);
client.BaseAddress = new Uri(_settings.OllamaCloudUrl.TrimEnd('/') + "/");
if (!string.IsNullOrEmpty(_settings.OllamaCloudApiKey))
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _settings.OllamaCloudApiKey);
}
return client;
}
public async Task<bool> TestConnectionAsync()
{
try
{
var http = CreateClient();
var sw = Stopwatch.StartNew();
var response = await http.GetAsync("api/tags");
sw.Stop();
return response.IsSuccessStatusCode;
}
catch { return false; }
}
public async Task<IEnumerable<LLM_Model>> GetModelsAsync()
{
try
{
var http = CreateClient();
var response = await http.GetAsync("api/tags");
if (!response.IsSuccessStatusCode) return Enumerable.Empty<LLM_Model>();
var content = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(content);
if (!doc.RootElement.TryGetProperty("models", out var models))
return Enumerable.Empty<LLM_Model>();
var list = new List<LLM_Model>();
foreach (var m in models.EnumerateArray())
list.Add(new LLM_Model(m));
return list;
}
catch { return Enumerable.Empty<LLM_Model>(); }
}
public async Task<string> ChatAsync(string model, string prompt, string? systemPrompt = null, double? temperature = null)
{
try
{
var request = new Dictionary<string, object?>
{
["model"] = model, ["prompt"] = prompt, ["stream"] = false
};
if (!string.IsNullOrEmpty(systemPrompt)) request["system"] = systemPrompt;
if (temperature.HasValue) request["temperature"] = Math.Clamp(temperature.Value, 0.0, 2.0);
var http = CreateClient();
var response = await http.PostAsJsonAsync("api/generate", request);
if (!response.IsSuccessStatusCode) return "Fehler bei der Verarbeitung.";
var content = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(content);
if (doc.RootElement.TryGetProperty("response", out var resp))
return resp.GetString() ?? "";
return "Fehler bei der Verarbeitung.";
}
catch (Exception ex) { return $"Error: {ex.Message}"; }
}
public async Task ChatStreamAsync(string model, string prompt, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null)
{
var request = new Dictionary<string, object?>
{
["model"] = model, ["prompt"] = prompt, ["stream"] = true
};
if (!string.IsNullOrEmpty(systemPrompt)) request["system"] = systemPrompt;
if (temperature.HasValue) request["temperature"] = Math.Clamp(temperature.Value, 0.0, 2.0);
var http = CreateClient();
using var response = await http.PostAsync(
"api/generate",
new StringContent(JsonSerializer.Serialize(request), System.Text.Encoding.UTF8, "application/json")
);
if (!response.IsSuccessStatusCode) return;
using var stream = await response.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);
string? line;
while ((line = await reader.ReadLineAsync()) != null)
{
var text = ExtractResponseText(line);
if (!string.IsNullOrEmpty(text))
await onToken(text);
}
}
private static string ExtractResponseText(string jsonLine)
{
if (string.IsNullOrWhiteSpace(jsonLine)) return string.Empty;
try
{
using var doc = JsonDocument.Parse(jsonLine);
if (doc.RootElement.TryGetProperty("response", out var resp))
return resp.GetString() ?? string.Empty;
}
catch { }
return string.Empty;
}
public async Task<float[]> GetEmbeddingAsync(string text, string? model = null)
{
try
{
var m = model ?? _settings.EmbedModel;
var http = CreateClient();
var response = await http.PostAsJsonAsync("api/embeddings", new { model = m, prompt = text });
if (!response.IsSuccessStatusCode) return Array.Empty<float>();
var content = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(content);
if (!doc.RootElement.TryGetProperty("embedding", out var emb)) return Array.Empty<float>();
var list = new List<float>();
foreach (var v in emb.EnumerateArray()) list.Add(v.GetSingle());
return list.ToArray();
}
catch { return Array.Empty<float>(); }
}
public Task<bool> PullModelAsync(string name, IProgress<string>? progress = null)
{
return Task.FromResult(false);
}
public Task<bool> DeleteModelAsync(string name)
{
return Task.FromResult(false);
}
public async Task<int?> GetContextSizeAsync(string model)
{
try
{
var http = CreateClient();
var response = await http.PostAsJsonAsync("api/show", new { model });
if (!response.IsSuccessStatusCode) return null;
var content = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(content);
if (doc.RootElement.TryGetProperty("modelfile", out var mf))
{
var modelfile = mf.GetString() ?? "";
var match = Regex.Match(modelfile, @"num_ctx\s+(\d+)");
if (match.Success && int.TryParse(match.Groups[1].Value, out var ctx))
return ctx;
}
return 2048;
}
catch { return null; }
}
public async Task<long> MeasureLatencyAsync(string model)
{
try
{
var http = CreateClient();
var sw = Stopwatch.StartNew();
var response = await http.PostAsJsonAsync("api/generate", new { model, prompt = "Hallo", stream = false });
sw.Stop();
return response.IsSuccessStatusCode ? sw.ElapsedMilliseconds : -1;
}
catch { return -1; }
}
public async Task<bool> ModelExistsAsync(string model)
{
if (string.IsNullOrWhiteSpace(model)) return false;
try
{
var models = await GetModelsAsync();
return models.Any(m => m.Name == model || m.Name.StartsWith(model.Split(':')[0]));
}
catch { return false; }
}
public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model)
{
var ctx = await GetContextSizeAsync(model);
return (ctx.HasValue, ctx ?? 0);
}
public bool TestEmbedding(string model)
{
try
{
var http = CreateClient();
var response = http.PostAsJsonAsync("api/embeddings", new { model, prompt = "test" }).GetAwaiter().GetResult();
if (!response.IsSuccessStatusCode) return false;
var content = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
using var doc = JsonDocument.Parse(content);
if (doc.RootElement.TryGetProperty("embedding", out var emb))
return emb.EnumerateArray().Any();
return false;
}
catch { return false; }
}
}

View file

@ -18,18 +18,19 @@ public class OllamaService : IOllamaService
_opState = opState; _opState = opState;
} }
private ILLMBackend ActiveBackend =>
_backends.FirstOrDefault(b => b.Name == _settings.Backend) ?? _backends.First();
private readonly object _warmLock = new(); private readonly object _warmLock = new();
private bool _warmupAttempted; private bool _warmupAttempted;
private async Task EnsureRunningAsync() private ILLMBackend GetBackend(string? name) =>
_backends.FirstOrDefault(b => b.Name == (name ?? _settings.Backend)) ?? _backends.First();
private async Task EnsureRunningAsync(string? backendName = null)
{ {
if (ActiveBackend.Name != "Ollama") var backend = GetBackend(backendName);
if (backend.Name != "Ollama")
return; return;
var reachable = await ActiveBackend.TestConnectionAsync(); var reachable = await backend.TestConnectionAsync();
if (reachable) if (reachable)
{ {
await WarmupModelAsync(); await WarmupModelAsync();
@ -62,7 +63,7 @@ public class OllamaService : IOllamaService
for (int i = 0; i < 30; i++) for (int i = 0; i < 30; i++)
{ {
await Task.Delay(1000); await Task.Delay(1000);
reachable = await ActiveBackend.TestConnectionAsync(); reachable = await backend.TestConnectionAsync();
if (reachable) if (reachable)
{ {
_opState.AppendThought(" Ollama ist bereit"); _opState.AppendThought(" Ollama ist bereit");
@ -83,21 +84,28 @@ public class OllamaService : IOllamaService
_warmupAttempted = true; _warmupAttempted = true;
} }
var modelsToWarm = new[] { _settings.StyleModel, _settings.EmbedModel } var modelsToWarm = new[]
.Where(m => !string.IsNullOrWhiteSpace(m)) {
.Distinct() (Model: _settings.StyleModel, Backend: _settings.StyleBackend),
(Model: _settings.EmbedModel, Backend: _settings.RagBackend)
}
.Where(m => !string.IsNullOrWhiteSpace(m.Model))
.DistinctBy(m => m.Model)
.ToList(); .ToList();
if (modelsToWarm.Count == 0) return; if (modelsToWarm.Count == 0) return;
_opState.SetThought("Lade Modelle in den Arbeitsspeicher..."); _opState.SetThought("Lade Modelle in den Arbeitsspeicher...");
foreach (var model in modelsToWarm) foreach (var (model, backendName) in modelsToWarm)
{ {
_opState.AppendThought($" {model}..."); var backend = GetBackend(backendName);
if (backend.Name != "Ollama")
continue;
_opState.AppendThought($" {model} ({backendName})...");
try try
{ {
var warmCts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); var dummy = await backend.ChatAsync(model, " ", null, 0.0);
var dummy = await ActiveBackend.ChatAsync(model, " ", null, 0.0);
_opState.AppendThought(" ✓"); _opState.AppendThought(" ✓");
} }
catch (Exception ex) catch (Exception ex)
@ -115,89 +123,183 @@ public class OllamaService : IOllamaService
public async Task<(bool Success, long LatencyMs)> TestConnectionAsync() public async Task<(bool Success, long LatencyMs)> TestConnectionAsync()
{ {
var backend = GetBackend(null);
await EnsureRunningAsync(); await EnsureRunningAsync();
_opState.SetThought($"Teste Verbindung zu {ActiveBackend.Name}..."); _opState.SetThought($"Teste Verbindung zu {backend.Name}...");
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
var result = await ActiveBackend.TestConnectionAsync(); var result = await backend.TestConnectionAsync();
sw.Stop(); sw.Stop();
_opState.AppendThought(result ? " OK" : " Fehlgeschlagen"); _opState.AppendThought(result ? " OK" : " Fehlgeschlagen");
return (result, result ? sw.ElapsedMilliseconds : 0); return (result, result ? sw.ElapsedMilliseconds : 0);
} }
public bool SupportsModelManagement => GetBackend(null).SupportsModelManagement;
public async Task<IEnumerable<LLM_Model>> GetLocalModelsAsync() public async Task<IEnumerable<LLM_Model>> GetLocalModelsAsync()
{ {
var backend = GetBackend(null);
await EnsureRunningAsync(); await EnsureRunningAsync();
_opState.SetThought($"Lade Modelle von {ActiveBackend.Name}..."); _opState.SetThought($"Lade Modelle von {backend.Name}...");
var models = await ActiveBackend.GetModelsAsync(); var models = await backend.GetModelsAsync();
_opState.AppendThought($" {models.Count()} gefunden"); _opState.AppendThought($" {models.Count()} gefunden");
return models; return models;
} }
public async Task<bool> TestEmbeddingAsync(string model) public async Task<IEnumerable<LLM_Model>> GetModelsForBackendAsync(string backendName)
{ {
var backend = GetBackend(backendName);
if (backend.Name == "Ollama")
await EnsureRunningAsync(); await EnsureRunningAsync();
return ActiveBackend.TestEmbedding(model); _opState.SetThought($"Lade Modelle von {backend.Name}...");
var models = await backend.GetModelsAsync();
_opState.AppendThought($" {models.Count()} gefunden");
return models;
} }
public async Task<float[]> GetEmbeddingAsync(string text) public async Task<bool> TestEmbeddingAsync(string model, string? backendName = null)
{ {
await EnsureRunningAsync(); var backend = GetBackend(backendName);
return await ActiveBackend.GetEmbeddingAsync(text, _settings.EmbedModel); await EnsureRunningAsync(backendName);
return backend.TestEmbedding(model);
}
public async Task<float[]> GetEmbeddingAsync(string text, string? backendName = null)
{
var backend = GetBackend(backendName);
_opState.SetBusy(true);
_opState.SetThought($"{backend.Name}: Erzeuge Embeddings...");
try
{
await EnsureRunningAsync(backendName);
return await backend.GetEmbeddingAsync(text, _settings.EmbedModel);
}
catch
{
_opState.SetThought("Fehler bei Embeddings");
throw;
}
finally
{
_opState.SetBusy(false);
}
} }
public async Task<string> QuickSpellCheckAsync(string text) public async Task<string> QuickSpellCheckAsync(string text)
{ {
await EnsureRunningAsync(); _opState.SetBusy(true);
return await Chat(_settings.SpellingModel, text, _settings.SpellingPrompt, _settings.SpellingTemp); _opState.SetThought("Rechtschreibprüfung läuft...");
try
{
await EnsureRunningAsync(_settings.SpellingBackend);
return await Chat(_settings.SpellingModel, text, _settings.SpellingPrompt, _settings.SpellingTemp, _settings.SpellingBackend);
}
catch
{
_opState.SetThought("Fehler bei Rechtschreibprüfung");
throw;
}
finally
{
_opState.SetBusy(false);
}
} }
public async Task<string> StreamStyleCheckAsync(string text) public async Task<string> StreamStyleCheckAsync(string text)
{ {
await EnsureRunningAsync(); _opState.SetBusy(true);
return await Chat(_settings.StyleModel, text, _settings.LektorSystemPrompt, _settings.LektorTemp); _opState.SetThought("Stilprüfung läuft...");
try
{
await EnsureRunningAsync(_settings.StyleBackend);
return await Chat(_settings.StyleModel, text, _settings.LektorSystemPrompt, _settings.LektorTemp, _settings.StyleBackend);
}
catch
{
_opState.SetThought("Fehler bei Stilprüfung");
throw;
}
finally
{
_opState.SetBusy(false);
}
} }
public async Task<string> AnswerBasedOnFactsAsync(string question, string facts) public async Task<string> AnswerBasedOnFactsAsync(string question, string facts)
{ {
await EnsureRunningAsync(); _opState.SetBusy(true);
_opState.SetThought("Faktencheck läuft...");
try
{
await EnsureRunningAsync(_settings.RagBackend);
var prompt = _settings.GetFactCheckPrompt(question, facts); var prompt = _settings.GetFactCheckPrompt(question, facts);
return await Chat(_settings.EmbedModel, prompt, temperature: _settings.FactCheckTemp); return await Chat(_settings.EmbedModel, prompt, temperature: _settings.FactCheckTemp, backendName: _settings.RagBackend);
}
catch
{
_opState.SetThought("Fehler bei Faktencheck");
throw;
}
finally
{
_opState.SetBusy(false);
}
} }
public async Task<bool> ModelExistsAsync(string model) public async Task<bool> ModelExistsAsync(string model, string? backendName = null)
{ {
await EnsureRunningAsync(); var backend = GetBackend(backendName);
return await ActiveBackend.ModelExistsAsync(model); await EnsureRunningAsync(backendName);
return await backend.ModelExistsAsync(model);
} }
public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model) public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model, string? backendName = null)
{ {
await EnsureRunningAsync(); var backend = GetBackend(backendName);
return await ActiveBackend.GetModelContextWindowAsync(model); await EnsureRunningAsync(backendName);
return await backend.GetModelContextWindowAsync(model);
} }
public async Task<long> MeasureLatencyAsync(string model) public async Task<long> MeasureLatencyAsync(string model, string? backendName = null)
{ {
await EnsureRunningAsync(); var backend = GetBackend(backendName);
return await ActiveBackend.MeasureLatencyAsync(model); await EnsureRunningAsync(backendName);
return await backend.MeasureLatencyAsync(model);
} }
public async Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null) public async Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null, string? backendName = null)
{ {
await EnsureRunningAsync(); var backend = GetBackend(backendName);
_opState.SetThought($"{ActiveBackend.Name}: Sende Anfrage an {model}..."); _opState.SetBusy(true);
var result = await ActiveBackend.ChatAsync(model, userMessage, systemPrompt, temperature); _opState.SetThought($"{backend.Name}: Sende Anfrage an {model}...");
try
{
await EnsureRunningAsync(backendName);
var result = await backend.ChatAsync(model, userMessage, systemPrompt, temperature);
_opState.AppendThought(" Fertig"); _opState.AppendThought(" Fertig");
return result; return result;
} }
catch
public async Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null)
{ {
await EnsureRunningAsync(); _opState.SetThought($"Fehler bei {model}");
_opState.SetThought($"{ActiveBackend.Name}: Verbinde mit Modell {model}..."); throw;
}
finally
{
_opState.SetBusy(false);
}
}
public async Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null, string? backendName = null)
{
var backend = GetBackend(backendName);
_opState.SetBusy(true);
_opState.SetThought($"{backend.Name}: Verbinde mit Modell {model}...");
try
{
await EnsureRunningAsync(backendName);
int tokenCount = 0; int tokenCount = 0;
await ActiveBackend.ChatStreamAsync(model, userMessage, async chunk => await backend.ChatStreamAsync(model, userMessage, async chunk =>
{ {
tokenCount++; tokenCount++;
_opState.AppendThought(chunk); _opState.AppendThought(chunk);
@ -206,21 +308,33 @@ public class OllamaService : IOllamaService
_opState.AppendThought($" Fertig ({tokenCount} Token)"); _opState.AppendThought($" Fertig ({tokenCount} Token)");
} }
catch
{
_opState.SetThought($"Fehler bei {model}");
throw;
}
finally
{
_opState.SetBusy(false);
}
}
public async Task<bool> PullModelAsync(string modelName, IProgress<string> progress) public async Task<bool> PullModelAsync(string modelName, IProgress<string> progress)
{ {
var backend = GetBackend(null);
await EnsureRunningAsync(); await EnsureRunningAsync();
_opState.SetThought($"Lade Modell {modelName}..."); _opState.SetThought($"Lade Modell {modelName}...");
var result = await ActiveBackend.PullModelAsync(modelName, progress); var result = await backend.PullModelAsync(modelName, progress);
_opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen"); _opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen");
return result; return result;
} }
public async Task<bool> DeleteModelAsync(string modelName) public async Task<bool> DeleteModelAsync(string modelName)
{ {
var backend = GetBackend(null);
await EnsureRunningAsync(); await EnsureRunningAsync();
_opState.SetThought($"Lösche Modell {modelName}..."); _opState.SetThought($"Lösche Modell {modelName}...");
var result = await ActiveBackend.DeleteModelAsync(modelName); var result = await backend.DeleteModelAsync(modelName);
_opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen"); _opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen");
return result; return result;
} }

View file

@ -17,6 +17,12 @@ public class OperationState
Notify(); Notify();
} }
public void SetBusy(bool busy)
{
IsBusy = busy;
Notify();
}
public void SetThought(string text) public void SetThought(string text)
{ {
ThoughtText = text; ThoughtText = text;

38
Services/PluginManager.cs Normal file
View file

@ -0,0 +1,38 @@
using AuthorBuddy.Web.Models;
namespace AuthorBuddy.Web.Services;
public class PluginManager : IPluginManager
{
private readonly List<IExportPlugin> _plugins = new();
private readonly ISettingsService _settings;
public PluginManager(ISettingsService settings, IEnumerable<IExportPlugin> plugins)
{
_settings = settings;
_plugins.AddRange(plugins);
}
public void Register(IExportPlugin plugin)
{
if (!_plugins.Any(p => p.Id == plugin.Id))
_plugins.Add(plugin);
}
public IReadOnlyList<IExportPlugin> GetPlugins() => _plugins.AsReadOnly();
public IExportPlugin? GetPlugin(string id) => _plugins.FirstOrDefault(p => p.Id == id);
public async Task<PluginConfig?> GetConfigAsync(string pluginId)
{
var json = await _settings.GetPluginConfigAsync(pluginId);
if (string.IsNullOrEmpty(json)) return null;
return System.Text.Json.JsonSerializer.Deserialize<PluginConfig>(json);
}
public async Task SaveConfigAsync(PluginConfig config)
{
var json = System.Text.Json.JsonSerializer.Serialize(config);
await _settings.SavePluginConfigAsync(config.PluginId, json);
}
}

View file

@ -1,4 +1,5 @@
using AuthorBuddy.Web.Data; using AuthorBuddy.Web.Data;
using AuthorBuddy.Web.Models;
using LiteDB; using LiteDB;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -9,11 +10,13 @@ public class RagService : IRagService
{ {
private readonly ILiteDatabase _db; private readonly ILiteDatabase _db;
private readonly IOllamaService _ollama; private readonly IOllamaService _ollama;
private readonly OllamaSettings _settings;
public RagService(ILiteDatabase db, IOllamaService ollama) public RagService(ILiteDatabase db, IOllamaService ollama, OllamaSettings settings)
{ {
_db = db; _db = db;
_ollama = ollama; _ollama = ollama;
_settings = settings;
} }
public async Task IndexProjectAsync(int projectId, string rootPath, IProgress<string> progress) public async Task IndexProjectAsync(int projectId, string rootPath, IProgress<string> progress)
@ -43,7 +46,7 @@ public class RagService : IRagService
} }
} }
var embedding = await _ollama.GetEmbeddingAsync(content); var embedding = await _ollama.GetEmbeddingAsync(content, _settings.RagBackend);
if (embedding.Length == 0) if (embedding.Length == 0)
{ {
progress?.Report($" ↳ Fehler beim Embedding, übersprungen"); progress?.Report($" ↳ Fehler beim Embedding, übersprungen");
@ -97,7 +100,7 @@ public class RagService : IRagService
public async Task<List<RagSearchResult>> SearchAsync(int projectId, string query, int topN = 5) public async Task<List<RagSearchResult>> SearchAsync(int projectId, string query, int topN = 5)
{ {
var queryVec = await _ollama.GetEmbeddingAsync(query); var queryVec = await _ollama.GetEmbeddingAsync(query, _settings.RagBackend);
if (queryVec.Length == 0) if (queryVec.Length == 0)
return new List<RagSearchResult>(); return new List<RagSearchResult>();

View file

@ -7,11 +7,16 @@ namespace AuthorBuddy.Web.Services;
public class SettingsService : ISettingsService public class SettingsService : ISettingsService
{ {
private const string KeyOllamaUrl = "ollama_url"; private const string KeyOllamaUrl = "ollama_url";
private const string KeyOllamaCloudUrl = "ollama_cloud_url";
private const string KeyOllamaCloudApiKey = "ollama_cloud_api_key";
private const string KeyLlamaCppUrl = "llama_cpp_url"; private const string KeyLlamaCppUrl = "llama_cpp_url";
private const string KeyBackend = "llm_backend"; private const string KeyBackend = "llm_backend";
private const string KeyRagModel = "ollama_rag_model"; private const string KeyRagModel = "ollama_rag_model";
private const string KeyRagBackend = "ollama_rag_backend";
private const string KeyStyleModel = "ollama_style_model"; private const string KeyStyleModel = "ollama_style_model";
private const string KeyStyleBackend = "ollama_style_backend";
private const string KeySpellingModel = "ollama_spelling_model"; private const string KeySpellingModel = "ollama_spelling_model";
private const string KeySpellingBackend = "ollama_spelling_backend";
private const string KeyTheme = "theme"; private const string KeyTheme = "theme";
private const string KeyLektorPrompt = "ollama_lektor_prompt"; private const string KeyLektorPrompt = "ollama_lektor_prompt";
private const string KeyFactCheckTemplate = "ollama_fact_check"; private const string KeyFactCheckTemplate = "ollama_fact_check";
@ -45,6 +50,9 @@ public class SettingsService : ISettingsService
private const string KeyQuickMarkdownTemp = "quick_md_temp"; private const string KeyQuickMarkdownTemp = "quick_md_temp";
private const string KeyChatPrompt = "chat_prompt"; private const string KeyChatPrompt = "chat_prompt";
private const string KeyChatTemp = "chat_temp"; private const string KeyChatTemp = "chat_temp";
private const string KeyChatFactCheck = "chat_fact_check";
private const string KeyFactSearchPrompt = "fact_search_prompt";
private const string KeyFactSearchTemp = "fact_search_temp";
private const string KeyLanguage = "language"; private const string KeyLanguage = "language";
private const string KeyLastProjectId = "last_project_id"; private const string KeyLastProjectId = "last_project_id";
@ -169,12 +177,16 @@ public class SettingsService : ISettingsService
public Task<string?> GetOllamaUrlAsync() => Task.FromResult(GetGlobal(KeyOllamaUrl)); public Task<string?> GetOllamaUrlAsync() => Task.FromResult(GetGlobal(KeyOllamaUrl));
public Task<string?> GetLlamaCppUrlAsync() => Task.FromResult(GetGlobal(KeyLlamaCppUrl)); public Task<string?> GetLlamaCppUrlAsync() => Task.FromResult(GetGlobal(KeyLlamaCppUrl));
public Task<string?> GetBackendAsync() => Task.FromResult(GetGlobal(KeyBackend)); public Task<string?> GetBackendAsync() => Task.FromResult(GetGlobal(KeyBackend));
public Task<string?> GetRagModelAsync() => Task.FromResult(GetGlobal(KeyRagModel)); public Task<string?> GetRagModelAsync() => Task.FromResult(GetGlobal(KeyRagModel));
public Task<string?> GetStyleModelAsync() => Task.FromResult(GetGlobal(KeyStyleModel)); public Task<string?> GetStyleModelAsync() => Task.FromResult(GetGlobal(KeyStyleModel));
public Task<string?> GetLektorSystemPromptAsync() => Task.FromResult(Get(KeyLektorPrompt)); public Task<string?> GetLektorSystemPromptAsync() => Task.FromResult(Get(KeyLektorPrompt));
public Task<string?> GetFactCheckTemplateAsync() => Task.FromResult(Get(KeyFactCheckTemplate)); public Task<string?> GetFactCheckTemplateAsync() => Task.FromResult(Get(KeyFactCheckTemplate));
public Task<string?> GetSpellingPromptAsync() => Task.FromResult(Get(KeySpellingPrompt)); public Task<string?> GetSpellingPromptAsync() => Task.FromResult(Get(KeySpellingPrompt));
public Task<string?> GetSpellingModelAsync() => Task.FromResult(GetGlobal(KeySpellingModel)); public Task<string?> GetSpellingModelAsync() => Task.FromResult(GetGlobal(KeySpellingModel));
public Task<string?> GetRagBackendAsync() => Task.FromResult(GetGlobal(KeyRagBackend));
public Task<string?> GetStyleBackendAsync() => Task.FromResult(GetGlobal(KeyStyleBackend));
public Task<string?> GetSpellingBackendAsync() => Task.FromResult(GetGlobal(KeySpellingBackend));
public Task<string?> GetAdvocatusDiaboliPromptAsync() => Task.FromResult(Get(KeyAdvocatusDiaboli)); public Task<string?> GetAdvocatusDiaboliPromptAsync() => Task.FromResult(Get(KeyAdvocatusDiaboli));
public Task<string?> GetHookFinderPromptAsync() => Task.FromResult(Get(KeyHookFinder)); public Task<string?> GetHookFinderPromptAsync() => Task.FromResult(Get(KeyHookFinder));
public Task<string?> GetEli5PromptAsync() => Task.FromResult(Get(KeyEli5)); public Task<string?> GetEli5PromptAsync() => Task.FromResult(Get(KeyEli5));
@ -217,6 +229,24 @@ public class SettingsService : ISettingsService
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task<string?> GetOllamaCloudUrlAsync() => Task.FromResult(GetGlobal(KeyOllamaCloudUrl));
public Task SaveOllamaCloudUrlAsync(string url)
{
SetGlobal(KeyOllamaCloudUrl, url);
_ollamaConfig.OllamaCloudUrl = url;
return Task.CompletedTask;
}
public Task<string?> GetOllamaCloudApiKeyAsync() => Task.FromResult(GetGlobal(KeyOllamaCloudApiKey));
public Task SaveOllamaCloudApiKeyAsync(string apiKey)
{
SetGlobal(KeyOllamaCloudApiKey, apiKey);
_ollamaConfig.OllamaCloudApiKey = apiKey;
return Task.CompletedTask;
}
public Task SaveLlamaCppUrlAsync(string url) public Task SaveLlamaCppUrlAsync(string url)
{ {
SetGlobal(KeyLlamaCppUrl, url); SetGlobal(KeyLlamaCppUrl, url);
@ -224,6 +254,11 @@ public class SettingsService : ISettingsService
return Task.CompletedTask; return Task.CompletedTask;
} }
public void SetBackend(string backend)
{
_ollamaConfig.Backend = backend;
}
public Task SaveBackendAsync(string backend) public Task SaveBackendAsync(string backend)
{ {
SetGlobal(KeyBackend, backend); SetGlobal(KeyBackend, backend);
@ -245,6 +280,27 @@ public class SettingsService : ISettingsService
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task SaveRagBackendAsync(string backend)
{
SetGlobal(KeyRagBackend, backend);
_ollamaConfig.RagBackend = backend;
return Task.CompletedTask;
}
public Task SaveStyleBackendAsync(string backend)
{
SetGlobal(KeyStyleBackend, backend);
_ollamaConfig.StyleBackend = backend;
return Task.CompletedTask;
}
public Task SaveSpellingBackendAsync(string backend)
{
SetGlobal(KeySpellingBackend, backend);
_ollamaConfig.SpellingBackend = backend;
return Task.CompletedTask;
}
public Task SaveLektorSystemPromptAsync(string prompt) public Task SaveLektorSystemPromptAsync(string prompt)
{ {
Set(KeyLektorPrompt, prompt); Set(KeyLektorPrompt, prompt);
@ -386,6 +442,34 @@ public class SettingsService : ISettingsService
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task<bool> GetChatFactCheckEnabledAsync() =>
Task.FromResult(bool.TryParse(Get(KeyChatFactCheck), out var v) ? v : _ollamaConfig.ChatFactCheckEnabled);
public Task SaveChatFactCheckEnabledAsync(bool enabled)
{
Set(KeyChatFactCheck, enabled.ToString());
_ollamaConfig.ChatFactCheckEnabled = enabled;
return Task.CompletedTask;
}
public Task<string?> GetFactSearchPromptAsync() => Task.FromResult(Get(KeyFactSearchPrompt));
public Task SaveFactSearchPromptAsync(string prompt)
{
Set(KeyFactSearchPrompt, prompt);
_ollamaConfig.FactSearchPrompt = prompt;
return Task.CompletedTask;
}
public Task<double> GetFactSearchTempAsync() => Task.FromResult(double.TryParse(Get(KeyFactSearchTemp), out var v) ? v : _ollamaConfig.FactSearchTemp);
public Task SaveFactSearchTempAsync(double temp)
{
Set(KeyFactSearchTemp, temp.ToString("F1"));
_ollamaConfig.FactSearchTemp = temp;
return Task.CompletedTask;
}
public Task SaveTechLastenheftPromptAsync(string prompt) public Task SaveTechLastenheftPromptAsync(string prompt)
{ {
Set(KeyTechLastenheft, prompt); Set(KeyTechLastenheft, prompt);
@ -604,6 +688,35 @@ public class SettingsService : ISettingsService
currentVersion = 2; currentVersion = 2;
} }
if (currentVersion < 3)
{
currentVersion = 3;
}
if (currentVersion < 4)
{
var backends = new[] { "Ollama", "Ollama Cloud", "llama.cpp" };
var modelTypes = new[] { KeyRagModel, KeyStyleModel, KeySpellingModel };
foreach (var modelKey in modelTypes)
{
if (settings.FindOne(s => s.Key == modelKey && s.UserId == null && s.ProjectId == null) != null)
continue;
foreach (var backend in backends)
{
var perBackendKey = $"{modelKey}_{backend}";
var existing = settings.FindOne(s => s.Key == perBackendKey && s.UserId == null && s.ProjectId == null);
if (existing != null)
{
settings.Insert(new SettingEntity { Key = modelKey, Value = existing.Value, UserId = null, ProjectId = null });
break;
}
}
}
currentVersion = 4;
}
if (schemaSetting != null) if (schemaSetting != null)
{ {
schemaSetting.Value = currentVersion.ToString(); schemaSetting.Value = currentVersion.ToString();
@ -617,7 +730,12 @@ public class SettingsService : ISettingsService
public Task LoadProjectSettingsAsync() public Task LoadProjectSettingsAsync()
{ {
var globalKeys = new[] { KeyOllamaUrl, KeyLlamaCppUrl, KeyBackend, KeyRagModel, KeyStyleModel, KeySpellingModel }; var backend = GetGlobal(KeyBackend);
if (backend != null) _ollamaConfig.Backend = backend;
var globalKeys = new[] { KeyOllamaUrl, KeyLlamaCppUrl, KeyBackend,
KeyRagModel, KeyRagBackend, KeyStyleModel, KeyStyleBackend, KeySpellingModel, KeySpellingBackend,
KeyOllamaCloudUrl, KeyOllamaCloudApiKey };
foreach (var key in globalKeys) foreach (var key in globalKeys)
{ {
@ -629,8 +747,13 @@ public class SettingsService : ISettingsService
case var k when k == KeyLlamaCppUrl: _ollamaConfig.LlamaCppUrl = val; break; case var k when k == KeyLlamaCppUrl: _ollamaConfig.LlamaCppUrl = val; break;
case var k when k == KeyBackend: _ollamaConfig.Backend = val; break; case var k when k == KeyBackend: _ollamaConfig.Backend = val; break;
case var k when k == KeyRagModel: _ollamaConfig.EmbedModel = val; break; case var k when k == KeyRagModel: _ollamaConfig.EmbedModel = val; break;
case var k when k == KeyRagBackend: _ollamaConfig.RagBackend = val; break;
case var k when k == KeyStyleModel: _ollamaConfig.StyleModel = val; break; case var k when k == KeyStyleModel: _ollamaConfig.StyleModel = val; break;
case var k when k == KeyStyleBackend: _ollamaConfig.StyleBackend = val; break;
case var k when k == KeySpellingModel: _ollamaConfig.SpellingModel = val; break; case var k when k == KeySpellingModel: _ollamaConfig.SpellingModel = val; break;
case var k when k == KeySpellingBackend: _ollamaConfig.SpellingBackend = val; break;
case var k when k == KeyOllamaCloudUrl: _ollamaConfig.OllamaCloudUrl = val; break;
case var k when k == KeyOllamaCloudApiKey: _ollamaConfig.OllamaCloudApiKey = val; break;
} }
} }
@ -691,6 +814,7 @@ public class SettingsService : ISettingsService
public Task InitializeAsync() public Task InitializeAsync()
{ {
MigrateSchema(); MigrateSchema();
LoadProjectSettingsAsync();
var settings = _db.GetCollection<SettingEntity>("settings"); var settings = _db.GetCollection<SettingEntity>("settings");
@ -700,8 +824,13 @@ public class SettingsService : ISettingsService
(KeyLlamaCppUrl, _ollamaConfig.LlamaCppUrl), (KeyLlamaCppUrl, _ollamaConfig.LlamaCppUrl),
(KeyBackend, _ollamaConfig.Backend), (KeyBackend, _ollamaConfig.Backend),
(KeyRagModel, _ollamaConfig.EmbedModel), (KeyRagModel, _ollamaConfig.EmbedModel),
(KeyRagBackend, _ollamaConfig.RagBackend),
(KeyStyleModel, _ollamaConfig.StyleModel), (KeyStyleModel, _ollamaConfig.StyleModel),
(KeyStyleBackend, _ollamaConfig.StyleBackend),
(KeySpellingModel, _ollamaConfig.SpellingModel), (KeySpellingModel, _ollamaConfig.SpellingModel),
(KeySpellingBackend, _ollamaConfig.SpellingBackend),
(KeyOllamaCloudUrl, _ollamaConfig.OllamaCloudUrl),
(KeyOllamaCloudApiKey, _ollamaConfig.OllamaCloudApiKey),
}; };
foreach (var entry in globalEntries) foreach (var entry in globalEntries)
@ -709,17 +838,7 @@ public class SettingsService : ISettingsService
var existing = settings.FindOne(s => var existing = settings.FindOne(s =>
s.Key == entry.Key && s.UserId == null && s.ProjectId == null); s.Key == entry.Key && s.UserId == null && s.ProjectId == null);
if (existing != null) if (existing == null)
{
var val = existing.Value;
if (entry.Key == KeyOllamaUrl) _ollamaConfig.OllamaUrl = val;
else if (entry.Key == KeyLlamaCppUrl) _ollamaConfig.LlamaCppUrl = val;
else if (entry.Key == KeyBackend) _ollamaConfig.Backend = val;
else if (entry.Key == KeyRagModel) _ollamaConfig.EmbedModel = val;
else if (entry.Key == KeyStyleModel) _ollamaConfig.StyleModel = val;
else if (entry.Key == KeySpellingModel) _ollamaConfig.SpellingModel = val;
}
else
{ {
settings.Insert(new SettingEntity settings.Insert(new SettingEntity
{ {
@ -827,4 +946,16 @@ public class SettingsService : ISettingsService
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task<string?> GetPluginConfigAsync(string pluginId)
{
var val = GetGlobal($"plugin_{pluginId}");
return Task.FromResult(val);
}
public Task SavePluginConfigAsync(string pluginId, string json)
{
SetGlobal($"plugin_{pluginId}", json);
return Task.CompletedTask;
}
} }

View file

@ -797,6 +797,22 @@ button.primary {
resize: vertical; resize: vertical;
} }
.settings-model-row {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.settings-model-row label {
min-width: 100px;
}
.settings-model-row select {
flex: 1;
min-width: 120px;
}
.settings-actions { .settings-actions {
display: flex; display: flex;
gap: 8px; gap: 8px;
@ -1237,6 +1253,37 @@ button.primary {
border-color: var(--accent-color); border-color: var(--accent-color);
} }
/* Hamburger menu */
.hamburger {
display: none;
align-self: flex-start;
flex-shrink: 0;
z-index: 1100;
font-size: 24px;
width: 40px;
height: 40px;
border: none;
background: var(--button-bg);
color: var(--text-main);
cursor: pointer;
border-radius: 4px;
line-height: 40px;
text-align: center;
margin: 6px;
}
.hamburger:hover {
background: var(--button-hover);
}
.mobile-sidebar-backdrop {
display: none;
}
.mobile-sidebar-close {
display: none;
}
/* Editor modified badge */ /* Editor modified badge */
.editor-modified-badge { .editor-modified-badge {
color: #e74c3c; color: #e74c3c;
@ -1712,6 +1759,116 @@ button.primary {
min-width: 40px; min-width: 40px;
} }
.factcheck-section {
margin-top: 10px;
padding-top: 8px;
border-top: 1px dashed var(--border-color);
}
.factcheck-title {
font-size: 12px;
font-weight: bold;
text-transform: uppercase;
color: var(--muted-color, #888);
margin-bottom: 6px;
}
.factcheck-item {
margin-bottom: 8px;
padding: 8px 10px;
background: var(--bg-color, #f8f8f8);
border-radius: 4px;
border-left: 3px solid var(--border-color);
}
.factcheck-header {
margin-bottom: 4px;
}
.factcheck-verdict {
font-size: 13px;
font-weight: bold;
}
.verdict-confirmed {
color: #2e7d32;
}
.verdict-contradicted {
color: #c62828;
}
.verdict-partial {
color: #b26a00;
}
.verdict-none {
color: #757575;
}
.factcheck-claim {
font-size: 13px;
font-style: italic;
color: var(--text-color);
margin-bottom: 4px;
}
.factcheck-reasoning {
font-size: 13px;
color: var(--text-color);
margin-bottom: 4px;
}
.factcheck-sources {
font-size: 12px;
}
.factcheck-source {
color: var(--muted-color, #888);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.factcheck-toggle {
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
cursor: pointer;
}
.thinking-block {
margin-bottom: 8px;
font-size: 13px;
}
.thinking-block summary {
cursor: pointer;
font-size: 12px;
color: var(--text-color);
opacity: 0.6;
padding: 4px 0;
user-select: none;
}
.thinking-block summary:hover {
opacity: 1;
}
.thinking-content {
background: var(--border-color);
color: var(--text-color);
padding: 8px 10px;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
line-height: 1.4;
margin-top: 4px;
white-space: pre-wrap;
word-break: break-word;
}
.chat-input { .chat-input {
display: flex; display: flex;
gap: 8px; gap: 8px;
@ -1765,10 +1922,59 @@ button.primary {
overflow: auto; overflow: auto;
} }
.hamburger {
display: inline-flex;
align-items: center;
justify-content: center;
}
.mobile-sidebar-backdrop {
display: block;
}
.sidebar { .sidebar {
display: none; display: none;
} }
.mobile-sidebar-close {
display: block;
position: absolute;
top: 6px;
right: 6px;
z-index: 1060;
font-size: 20px;
width: 32px;
height: 32px;
border: none;
background: transparent;
color: var(--text-main);
cursor: pointer;
border-radius: 4px;
line-height: 32px;
text-align: center;
}
.mobile-sidebar-close:hover {
background: var(--button-hover);
}
.sidebar.mobile-visible {
display: flex;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
bottom: 0;
z-index: 1050;
width: 280px;
max-width: 85vw;
min-width: 200px;
border-right: 2px solid var(--border-color);
background: var(--window-bg);
padding: 4px;
overflow-y: auto;
}
.main-content { .main-content {
flex-direction: column; flex-direction: column;
} }
@ -1892,3 +2098,192 @@ button.primary {
flex-wrap: wrap; flex-wrap: wrap;
} }
} }
.llm-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.llm-modal {
width: 92%;
max-width: 560px;
background: var(--window-bg);
border: 2px solid var(--border-color);
box-shadow: 4px 4px 8px rgba(0,0,0,0.3);
}
.llm-title {
display: flex;
align-items: center;
gap: 10px;
background: var(--accent-color);
color: white;
padding: 6px 12px;
font-weight: bold;
font-size: 14px;
}
.llm-title-spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(255,255,255,0.4);
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
flex-shrink: 0;
}
.llm-body {
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.llm-status {
font-size: 13px;
font-weight: bold;
color: var(--text-color);
}
.llm-thoughts {
font-size: 13px;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
max-height: 40vh;
overflow-y: auto;
padding: 10px 12px;
border: 1px solid var(--border-color);
background: var(--editor-bg);
color: var(--text-color);
}
.llm-thoughts-empty {
font-style: italic;
color: var(--text-muted, #888);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.qm-fact-drawer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: 420px;
max-width: 94vw;
background: var(--window-bg);
border-left: 2px solid var(--border-color);
box-shadow: -4px 0 12px rgba(0,0,0,0.25);
z-index: 9000;
display: flex;
flex-direction: column;
}
.qm-fact-drawer-header {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--accent-color);
color: white;
padding: 8px 12px;
font-weight: bold;
font-size: 14px;
}
.qm-fact-drawer-body {
padding: 16px;
overflow-y: auto;
flex: 1;
}
.qm-fact-claim {
padding: 8px 10px;
background: var(--editor-bg);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-color);
font-size: 13px;
line-height: 1.5;
}
.qm-fact-query-row {
display: flex;
gap: 6px;
margin-bottom: 6px;
}
.qm-fact-query-row input {
flex: 1;
padding: 6px 8px;
border: 1px solid var(--border-color);
background: var(--editor-bg);
color: var(--text-color);
font-size: 13px;
}
.qm-fact-loading {
padding: 12px;
text-align: center;
color: var(--text-muted, #888);
font-style: italic;
font-size: 13px;
}
.qm-fact-error {
padding: 8px 10px;
background: #fdecea;
border: 1px solid #e0b4b4;
color: #a94442;
font-size: 13px;
margin-bottom: 8px;
}
.qm-fact-results {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 4px;
}
.qm-fact-result {
padding: 10px 12px;
border: 1px solid var(--border-color);
background: var(--card-bg);
}
.qm-fact-result a {
font-weight: bold;
font-size: 13px;
word-break: break-word;
}
.qm-fact-snippet {
font-size: 12px;
color: var(--text-color);
margin: 4px 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.qm-fact-result-actions {
text-align: right;
margin-top: 6px;
}
.qm-fact-hint {
color: var(--text-muted, #888);
font-size: 12px;
font-style: italic;
padding: 8px 0;
}