Compare commits
4 commits
e64a38ccdf
...
96f0e04381
| Author | SHA1 | Date | |
|---|---|---|---|
| 96f0e04381 | |||
| 4af00fd978 | |||
| 5eba92e83a | |||
| 890b5bbf0b |
42 changed files with 3403 additions and 711 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -2,3 +2,4 @@
|
|||
/obj/*
|
||||
/.vs/*
|
||||
/.vs/*
|
||||
/.vs/*
|
||||
|
|
|
|||
|
|
@ -3,16 +3,28 @@
|
|||
"WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\",
|
||||
"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}",
|
||||
"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\\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\\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\\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\\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\\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\\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}",
|
||||
|
|
@ -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}"
|
||||
},
|
||||
{
|
||||
"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\\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\\components\\layout\\mainlayout.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\\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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}"
|
||||
},
|
||||
|
|
@ -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}",
|
||||
"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}",
|
||||
"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",
|
||||
"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",
|
||||
"DocumentIndex": 1,
|
||||
"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",
|
||||
"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": "AgIAAAAAAAAAAAAAAAAAAAwAAAARAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-09-21T08:09:49.503Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "Editor.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Editor.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeToolTip": "Components\\Pages\\Editor.razor",
|
||||
"ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
|
||||
"WhenOpened": "2026-05-16T13:01:59.743Z"
|
||||
"DocumentIndex": 0,
|
||||
"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": "AgIAAAADAAAAAAAAAAAMwBEDAAAlAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-09-21T06:22:02.245Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IOllamaService.cs",
|
||||
|
|
@ -253,7 +300,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"DocumentIndex": 19,
|
||||
"Title": "Chat.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Chat.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Chat.razor",
|
||||
|
|
@ -265,32 +312,98 @@
|
|||
},
|
||||
{
|
||||
"$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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
|
||||
"RelativeToolTip": "Components\\Layout\\MainLayout.razor",
|
||||
"ViewState": "AgIAADIAAAAAAAAAAAApwEUAAAByAAAAAAAAAA==",
|
||||
"ViewState": "AgIAABoCAAAAAAAAAAApwC0CAAAfAAAAAAAAAA==",
|
||||
"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": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"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": "AgIAAEcAAAAAAAAAAAAQwFoAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-20T04:07:21.54Z"
|
||||
"Title": "AuthorBuddy.Web.csproj",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeDocumentMoniker": "AuthorBuddy.Web.csproj",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeToolTip": "AuthorBuddy.Web.csproj",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
|
||||
"WhenOpened": "2026-05-14T10:14:30.128Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaBackend.cs",
|
||||
"RelativeDocumentMoniker": "Services\\OllamaBackend.cs",
|
||||
|
|
@ -302,68 +415,44 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"DocumentIndex": 11,
|
||||
"Title": "Settings.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Settings.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
|
||||
"RelativeToolTip": "Components\\Pages\\Settings.razor",
|
||||
"ViewState": "AgIAABIAAAAAAAAAAAAAADMAAAAfAAAAAAAAAA==",
|
||||
"ViewState": "AgIAAIsCAAAAAAAAAAArwKACAAAQAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
|
||||
"WhenOpened": "2026-05-15T16:43:26.6Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 23,
|
||||
"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": "AgIAABUAAAAAAAAAAAAowAAAAAAAAAAAAAAAAA==",
|
||||
"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==",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "Editor.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Editor.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeToolTip": "Components\\Pages\\Editor.razor",
|
||||
"ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
|
||||
"WhenOpened": "2026-07-01T16:53:16.035Z"
|
||||
"WhenOpened": "2026-05-16T13:01:59.743Z"
|
||||
},
|
||||
{
|
||||
"$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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Login.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Login.razor",
|
||||
|
|
@ -375,7 +464,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 20,
|
||||
"DocumentIndex": 24,
|
||||
"Title": "FirstRunSetup.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\FirstRunSetup.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\FirstRunSetup.razor",
|
||||
|
|
@ -387,19 +476,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"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,
|
||||
"DocumentIndex": 16,
|
||||
"Title": "ChatService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ChatService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ChatService.cs",
|
||||
|
|
@ -411,7 +488,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"DocumentIndex": 18,
|
||||
"Title": "de.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\de.json",
|
||||
"RelativeDocumentMoniker": "Resources\\de.json",
|
||||
|
|
@ -423,7 +500,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"DocumentIndex": 17,
|
||||
"Title": "en.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\en.json",
|
||||
"RelativeDocumentMoniker": "Resources\\en.json",
|
||||
|
|
@ -435,7 +512,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"DocumentIndex": 21,
|
||||
"Title": "NavMenu.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\NavMenu.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Layout\\NavMenu.razor",
|
||||
|
|
@ -447,7 +524,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"DocumentIndex": 20,
|
||||
"Title": "MainLayout.razor.css",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor.css",
|
||||
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor.css",
|
||||
|
|
@ -459,7 +536,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 22,
|
||||
"DocumentIndex": 26,
|
||||
"Title": "DbAdmin.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\DbAdmin.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\DbAdmin.razor",
|
||||
|
|
@ -471,7 +548,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"DocumentIndex": 22,
|
||||
"Title": "ILLMBackend.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ILLMBackend.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ILLMBackend.cs",
|
||||
|
|
@ -483,19 +560,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"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,
|
||||
"DocumentIndex": 25,
|
||||
"Title": "OllamaAssistant.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\OllamaAssistant.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\OllamaAssistant.razor",
|
||||
|
|
@ -507,7 +572,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 27,
|
||||
"DocumentIndex": 30,
|
||||
"Title": "ProjectService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ProjectService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ProjectService.cs",
|
||||
|
|
@ -519,7 +584,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 28,
|
||||
"DocumentIndex": 31,
|
||||
"Title": "IOllamaSetupService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaSetupService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IOllamaSetupService.cs",
|
||||
|
|
@ -531,7 +596,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 24,
|
||||
"DocumentIndex": 27,
|
||||
"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",
|
||||
"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",
|
||||
"DocumentIndex": 25,
|
||||
"DocumentIndex": 28,
|
||||
"Title": "A7405018.jpg",
|
||||
"DocumentMoniker": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
|
||||
"ToolTip": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
|
||||
|
|
@ -551,7 +616,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 29,
|
||||
"DocumentIndex": 32,
|
||||
"Title": "INSTALL.md",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\INSTALL.md",
|
||||
"RelativeDocumentMoniker": "INSTALL.md",
|
||||
|
|
@ -563,7 +628,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 30,
|
||||
"DocumentIndex": 33,
|
||||
"Title": "IThemeService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IThemeService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IThemeService.cs",
|
||||
|
|
@ -575,7 +640,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 31,
|
||||
"DocumentIndex": 34,
|
||||
"Title": "RedirectToLogin.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\RedirectToLogin.razor",
|
||||
"RelativeDocumentMoniker": "Components\\RedirectToLogin.razor",
|
||||
|
|
@ -587,7 +652,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 32,
|
||||
"DocumentIndex": 35,
|
||||
"Title": "ISettingsService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ISettingsService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ISettingsService.cs",
|
||||
|
|
@ -595,11 +660,12 @@
|
|||
"RelativeToolTip": "Services\\ISettingsService.cs",
|
||||
"ViewState": "AgIAABwAAAAAAAAAAAA9wC4AAAAZAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-21T16:35:36.655Z"
|
||||
"WhenOpened": "2026-05-21T16:35:36.655Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 33,
|
||||
"DocumentIndex": 36,
|
||||
"Title": "IRagService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IRagService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IRagService.cs",
|
||||
|
|
@ -611,7 +677,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 34,
|
||||
"DocumentIndex": 37,
|
||||
"Title": "ThemeService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ThemeService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ThemeService.cs",
|
||||
|
|
@ -623,7 +689,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 35,
|
||||
"DocumentIndex": 38,
|
||||
"Title": "logo.jpg",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Assets\\logo.jpg",
|
||||
"RelativeDocumentMoniker": "Assets\\logo.jpg",
|
||||
|
|
@ -634,7 +700,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 36,
|
||||
"DocumentIndex": 39,
|
||||
"Title": "OllamaSettings.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\OllamaSettings.cs",
|
||||
"RelativeDocumentMoniker": "Models\\OllamaSettings.cs",
|
||||
|
|
@ -646,7 +712,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 26,
|
||||
"DocumentIndex": 29,
|
||||
"Title": "AppDbContext.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\AppDbContext.cs",
|
||||
"RelativeDocumentMoniker": "Data\\AppDbContext.cs",
|
||||
|
|
@ -657,7 +723,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 37,
|
||||
"DocumentIndex": 40,
|
||||
"Title": "FileScannerService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\FileScannerService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\FileScannerService.cs",
|
||||
|
|
@ -669,7 +735,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 38,
|
||||
"DocumentIndex": 41,
|
||||
"Title": "TotpHelper.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\TotpHelper.cs",
|
||||
"RelativeDocumentMoniker": "Services\\TotpHelper.cs",
|
||||
|
|
@ -681,7 +747,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 39,
|
||||
"DocumentIndex": 42,
|
||||
"Title": "Project.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\Project.cs",
|
||||
"RelativeDocumentMoniker": "Models\\Project.cs",
|
||||
|
|
@ -693,7 +759,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 40,
|
||||
"DocumentIndex": 43,
|
||||
"Title": "RagService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\RagService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\RagService.cs",
|
||||
|
|
@ -705,7 +771,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 41,
|
||||
"DocumentIndex": 44,
|
||||
"Title": "App.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\App.razor",
|
||||
"RelativeDocumentMoniker": "Components\\App.razor",
|
||||
|
|
@ -717,7 +783,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 42,
|
||||
"DocumentIndex": 45,
|
||||
"Title": "Models.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Models.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Models.razor",
|
||||
|
|
@ -729,7 +795,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 43,
|
||||
"DocumentIndex": 46,
|
||||
"Title": "LLM_Model.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\LLM_Model.cs",
|
||||
"RelativeDocumentMoniker": "Models\\LLM_Model.cs",
|
||||
|
|
@ -741,7 +807,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 44,
|
||||
"DocumentIndex": 47,
|
||||
"Title": "ModelStatus.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\ModelStatus.cs",
|
||||
"RelativeDocumentMoniker": "Models\\ModelStatus.cs",
|
||||
|
|
@ -753,7 +819,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 45,
|
||||
"DocumentIndex": 48,
|
||||
"Title": "IFileScannerService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IFileScannerService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IFileScannerService.cs",
|
||||
|
|
@ -765,7 +831,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 46,
|
||||
"DocumentIndex": 49,
|
||||
"Title": "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",
|
||||
|
|
@ -777,19 +843,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 47,
|
||||
"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,
|
||||
"DocumentIndex": 50,
|
||||
"Title": "IProjectService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IProjectService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IProjectService.cs",
|
||||
|
|
@ -801,7 +855,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 49,
|
||||
"DocumentIndex": 51,
|
||||
"Title": "appsettings.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
|
||||
"RelativeDocumentMoniker": "appsettings.json",
|
||||
|
|
|
|||
|
|
@ -3,16 +3,28 @@
|
|||
"WorkspaceRootPath": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\",
|
||||
"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}",
|
||||
"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\\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\\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\\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\\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\\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\\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}",
|
||||
|
|
@ -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}"
|
||||
},
|
||||
{
|
||||
"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\\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\\components\\layout\\mainlayout.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\\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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}",
|
||||
"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}"
|
||||
},
|
||||
|
|
@ -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}",
|
||||
"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}",
|
||||
"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",
|
||||
"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",
|
||||
"DocumentIndex": 1,
|
||||
"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",
|
||||
"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": "AgIAAAAAAAAAAAAAAAAAAAwAAAARAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-09-21T08:09:49.503Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 5,
|
||||
"Title": "Editor.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Editor.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeToolTip": "Components\\Pages\\Editor.razor",
|
||||
"ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
|
||||
"WhenOpened": "2026-05-16T13:01:59.743Z"
|
||||
"DocumentIndex": 0,
|
||||
"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": "AgIAAPoCAAAAAAAAAAAIwBUDAAAmAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-09-21T06:22:02.245Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IOllamaService.cs",
|
||||
|
|
@ -253,7 +300,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 14,
|
||||
"DocumentIndex": 19,
|
||||
"Title": "Chat.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Chat.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Chat.razor",
|
||||
|
|
@ -265,32 +312,98 @@
|
|||
},
|
||||
{
|
||||
"$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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor",
|
||||
"RelativeToolTip": "Components\\Layout\\MainLayout.razor",
|
||||
"ViewState": "AgIAADIAAAAAAAAAAAApwEUAAAByAAAAAAAAAA==",
|
||||
"ViewState": "AgIAABoCAAAAAAAAAAApwC0CAAAfAAAAAAAAAA==",
|
||||
"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": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 6,
|
||||
"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": "AgIAAEcAAAAAAAAAAAAQwFoAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-20T04:07:21.54Z"
|
||||
"Title": "AuthorBuddy.Web.csproj",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj",
|
||||
"RelativeDocumentMoniker": "AuthorBuddy.Web.csproj",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\AuthorBuddy.Web.csproj*",
|
||||
"RelativeToolTip": "AuthorBuddy.Web.csproj*",
|
||||
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|",
|
||||
"WhenOpened": "2026-05-14T10:14:30.128Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\OllamaBackend.cs",
|
||||
"RelativeDocumentMoniker": "Services\\OllamaBackend.cs",
|
||||
|
|
@ -302,68 +415,44 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 2,
|
||||
"DocumentIndex": 11,
|
||||
"Title": "Settings.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Settings.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Settings.razor",
|
||||
"RelativeToolTip": "Components\\Pages\\Settings.razor",
|
||||
"ViewState": "AgIAABIAAAAAAAAAAAAAADMAAAAfAAAAAAAAAA==",
|
||||
"ViewState": "AgIAAIsCAAAAAAAAAAArwKACAAAQAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
|
||||
"WhenOpened": "2026-05-15T16:43:26.6Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 23,
|
||||
"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": "AgIAABUAAAAAAAAAAAAowAAAAAAAAAAAAAAAAA==",
|
||||
"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==",
|
||||
"DocumentIndex": 13,
|
||||
"Title": "Editor.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Editor.razor",
|
||||
"ToolTip": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Editor.razor",
|
||||
"RelativeToolTip": "Components\\Pages\\Editor.razor",
|
||||
"ViewState": "AgIAAC0AAAAAAAAAAAAAABIAAAAKAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
|
||||
"WhenOpened": "2026-07-01T16:53:16.035Z"
|
||||
"WhenOpened": "2026-05-16T13:01:59.743Z"
|
||||
},
|
||||
{
|
||||
"$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",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Login.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Login.razor",
|
||||
|
|
@ -375,7 +464,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 20,
|
||||
"DocumentIndex": 24,
|
||||
"Title": "FirstRunSetup.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\FirstRunSetup.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\FirstRunSetup.razor",
|
||||
|
|
@ -387,19 +476,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 13,
|
||||
"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,
|
||||
"DocumentIndex": 16,
|
||||
"Title": "ChatService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ChatService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ChatService.cs",
|
||||
|
|
@ -411,7 +488,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 12,
|
||||
"DocumentIndex": 18,
|
||||
"Title": "de.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\de.json",
|
||||
"RelativeDocumentMoniker": "Resources\\de.json",
|
||||
|
|
@ -423,7 +500,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 11,
|
||||
"DocumentIndex": 17,
|
||||
"Title": "en.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Resources\\en.json",
|
||||
"RelativeDocumentMoniker": "Resources\\en.json",
|
||||
|
|
@ -435,7 +512,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 16,
|
||||
"DocumentIndex": 21,
|
||||
"Title": "NavMenu.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\NavMenu.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Layout\\NavMenu.razor",
|
||||
|
|
@ -447,7 +524,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 15,
|
||||
"DocumentIndex": 20,
|
||||
"Title": "MainLayout.razor.css",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Layout\\MainLayout.razor.css",
|
||||
"RelativeDocumentMoniker": "Components\\Layout\\MainLayout.razor.css",
|
||||
|
|
@ -459,7 +536,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 22,
|
||||
"DocumentIndex": 26,
|
||||
"Title": "DbAdmin.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\DbAdmin.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\DbAdmin.razor",
|
||||
|
|
@ -471,7 +548,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 17,
|
||||
"DocumentIndex": 22,
|
||||
"Title": "ILLMBackend.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ILLMBackend.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ILLMBackend.cs",
|
||||
|
|
@ -483,19 +560,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 18,
|
||||
"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,
|
||||
"DocumentIndex": 25,
|
||||
"Title": "OllamaAssistant.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\OllamaAssistant.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\OllamaAssistant.razor",
|
||||
|
|
@ -507,7 +572,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 27,
|
||||
"DocumentIndex": 30,
|
||||
"Title": "ProjectService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ProjectService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ProjectService.cs",
|
||||
|
|
@ -519,7 +584,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 28,
|
||||
"DocumentIndex": 31,
|
||||
"Title": "IOllamaSetupService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IOllamaSetupService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IOllamaSetupService.cs",
|
||||
|
|
@ -531,7 +596,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 24,
|
||||
"DocumentIndex": 27,
|
||||
"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",
|
||||
"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",
|
||||
"DocumentIndex": 25,
|
||||
"DocumentIndex": 28,
|
||||
"Title": "A7405018.jpg",
|
||||
"DocumentMoniker": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
|
||||
"ToolTip": "C:\\Users\\HP\\Desktop\\Website\\A7405018.jpg",
|
||||
|
|
@ -551,7 +616,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 29,
|
||||
"DocumentIndex": 32,
|
||||
"Title": "INSTALL.md",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\INSTALL.md",
|
||||
"RelativeDocumentMoniker": "INSTALL.md",
|
||||
|
|
@ -563,7 +628,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 30,
|
||||
"DocumentIndex": 33,
|
||||
"Title": "IThemeService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IThemeService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IThemeService.cs",
|
||||
|
|
@ -575,7 +640,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 31,
|
||||
"DocumentIndex": 34,
|
||||
"Title": "RedirectToLogin.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\RedirectToLogin.razor",
|
||||
"RelativeDocumentMoniker": "Components\\RedirectToLogin.razor",
|
||||
|
|
@ -587,7 +652,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 32,
|
||||
"DocumentIndex": 35,
|
||||
"Title": "ISettingsService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ISettingsService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ISettingsService.cs",
|
||||
|
|
@ -595,11 +660,12 @@
|
|||
"RelativeToolTip": "Services\\ISettingsService.cs",
|
||||
"ViewState": "AgIAABwAAAAAAAAAAAA9wC4AAAAZAAAAAAAAAA==",
|
||||
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
|
||||
"WhenOpened": "2026-05-21T16:35:36.655Z"
|
||||
"WhenOpened": "2026-05-21T16:35:36.655Z",
|
||||
"EditorCaption": ""
|
||||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 33,
|
||||
"DocumentIndex": 36,
|
||||
"Title": "IRagService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IRagService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IRagService.cs",
|
||||
|
|
@ -611,7 +677,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 34,
|
||||
"DocumentIndex": 37,
|
||||
"Title": "ThemeService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\ThemeService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\ThemeService.cs",
|
||||
|
|
@ -623,7 +689,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 35,
|
||||
"DocumentIndex": 38,
|
||||
"Title": "logo.jpg",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Assets\\logo.jpg",
|
||||
"RelativeDocumentMoniker": "Assets\\logo.jpg",
|
||||
|
|
@ -634,7 +700,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 36,
|
||||
"DocumentIndex": 39,
|
||||
"Title": "OllamaSettings.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\OllamaSettings.cs",
|
||||
"RelativeDocumentMoniker": "Models\\OllamaSettings.cs",
|
||||
|
|
@ -646,7 +712,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 26,
|
||||
"DocumentIndex": 29,
|
||||
"Title": "AppDbContext.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Data\\AppDbContext.cs",
|
||||
"RelativeDocumentMoniker": "Data\\AppDbContext.cs",
|
||||
|
|
@ -657,7 +723,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 37,
|
||||
"DocumentIndex": 40,
|
||||
"Title": "FileScannerService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\FileScannerService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\FileScannerService.cs",
|
||||
|
|
@ -669,7 +735,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 38,
|
||||
"DocumentIndex": 41,
|
||||
"Title": "TotpHelper.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\TotpHelper.cs",
|
||||
"RelativeDocumentMoniker": "Services\\TotpHelper.cs",
|
||||
|
|
@ -681,7 +747,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 39,
|
||||
"DocumentIndex": 42,
|
||||
"Title": "Project.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\Project.cs",
|
||||
"RelativeDocumentMoniker": "Models\\Project.cs",
|
||||
|
|
@ -693,7 +759,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 40,
|
||||
"DocumentIndex": 43,
|
||||
"Title": "RagService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\RagService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\RagService.cs",
|
||||
|
|
@ -705,7 +771,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 41,
|
||||
"DocumentIndex": 44,
|
||||
"Title": "App.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\App.razor",
|
||||
"RelativeDocumentMoniker": "Components\\App.razor",
|
||||
|
|
@ -717,7 +783,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 42,
|
||||
"DocumentIndex": 45,
|
||||
"Title": "Models.razor",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Components\\Pages\\Models.razor",
|
||||
"RelativeDocumentMoniker": "Components\\Pages\\Models.razor",
|
||||
|
|
@ -729,7 +795,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 43,
|
||||
"DocumentIndex": 46,
|
||||
"Title": "LLM_Model.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\LLM_Model.cs",
|
||||
"RelativeDocumentMoniker": "Models\\LLM_Model.cs",
|
||||
|
|
@ -741,7 +807,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 44,
|
||||
"DocumentIndex": 47,
|
||||
"Title": "ModelStatus.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Models\\ModelStatus.cs",
|
||||
"RelativeDocumentMoniker": "Models\\ModelStatus.cs",
|
||||
|
|
@ -753,7 +819,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 45,
|
||||
"DocumentIndex": 48,
|
||||
"Title": "IFileScannerService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IFileScannerService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IFileScannerService.cs",
|
||||
|
|
@ -765,7 +831,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 46,
|
||||
"DocumentIndex": 49,
|
||||
"Title": "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",
|
||||
|
|
@ -777,19 +843,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 47,
|
||||
"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,
|
||||
"DocumentIndex": 50,
|
||||
"Title": "IProjectService.cs",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\Services\\IProjectService.cs",
|
||||
"RelativeDocumentMoniker": "Services\\IProjectService.cs",
|
||||
|
|
@ -801,7 +855,7 @@
|
|||
},
|
||||
{
|
||||
"$type": "Document",
|
||||
"DocumentIndex": 49,
|
||||
"DocumentIndex": 51,
|
||||
"Title": "appsettings.json",
|
||||
"DocumentMoniker": "E:\\ollama_net\\ollama\\AuthorBuddy.Web\\appsettings.json",
|
||||
"RelativeDocumentMoniker": "appsettings.json",
|
||||
|
|
|
|||
|
|
@ -26,10 +26,35 @@
|
|||
else
|
||||
{
|
||||
<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">
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
<aside class="sidebar">
|
||||
<aside class="sidebar @(_showMobileSidebar ? "mobile-visible" : "")">
|
||||
<div class="mobile-sidebar-close" @onclick="CloseMobileSidebar">✕</div>
|
||||
<div class="sidebar-logo">
|
||||
<img src="/Assets/logo.jpg" alt="Author Buddy" />
|
||||
</div>
|
||||
|
|
@ -343,6 +368,14 @@ else
|
|||
</aside>
|
||||
}
|
||||
|
||||
@if (_isAuthenticated)
|
||||
{
|
||||
<button class="hamburger" @onclick="ToggleMobileSidebar">☰</button>
|
||||
}
|
||||
@if (_showMobileSidebar)
|
||||
{
|
||||
<div class="mobile-sidebar-backdrop" @onclick="CloseMobileSidebar"></div>
|
||||
}
|
||||
<main class="detail-panel">
|
||||
@if (currentProjectId > 0)
|
||||
{
|
||||
|
|
@ -452,12 +485,15 @@ else
|
|||
private string newFileName = string.Empty;
|
||||
private FileTreeItem? newFileParentDir;
|
||||
private bool showCopyProjectDialog;
|
||||
private bool _showMobileSidebar;
|
||||
private Action? _opStateHandler;
|
||||
private string? _editingCategoryPath;
|
||||
private HashSet<string> _selectedFiles = new();
|
||||
private bool _showExportDialog;
|
||||
private string exportFileName = string.Empty;
|
||||
private string exportFormat = "Html";
|
||||
private long _lastThoughtScrollTicks;
|
||||
private string? _lastVisitedPage;
|
||||
|
||||
[Inject]
|
||||
private NavigationManager Navigation { get; set; } = default!;
|
||||
|
|
@ -513,27 +549,34 @@ else
|
|||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
var themeChanged = OllamaSettings.Theme != ThemeService.Theme;
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
// Auto-login runs AFTER circuit establishment (correct scoped services)
|
||||
await LoadCurrentUser();
|
||||
// #if DEBUG
|
||||
// if (!_isAuthenticated && Program.DebugAutoLogin)
|
||||
// {
|
||||
// var users = await SettingsService.GetUsersAsync();
|
||||
// var first = users.FirstOrDefault(u => u.Role == UserRole.Administrator);
|
||||
// if (first != null)
|
||||
// {
|
||||
// await AuthService.AdminBypassLoginAsync(first.Id);
|
||||
// AuthStateProvider.NotifyStateChanged();
|
||||
// await LoadCurrentUser();
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
// #if DEBUG
|
||||
// if (!_isAuthenticated && Program.DebugAutoLogin)
|
||||
// {
|
||||
// var users = await SettingsService.GetUsersAsync();
|
||||
// var first = users.FirstOrDefault(u => u.Role == UserRole.Administrator);
|
||||
// if (first != null)
|
||||
// {
|
||||
// await AuthService.AdminBypassLoginAsync(first.Id);
|
||||
// AuthStateProvider.NotifyStateChanged();
|
||||
// await LoadCurrentUser();
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
if (_isAuthenticated)
|
||||
{
|
||||
await LoadProjects();
|
||||
|
|
@ -544,6 +587,16 @@ else
|
|||
_isLoading = false;
|
||||
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()
|
||||
|
|
@ -560,6 +613,14 @@ else
|
|||
{
|
||||
await LoadProjects();
|
||||
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();
|
||||
}
|
||||
|
|
@ -710,9 +771,20 @@ else
|
|||
private void NavigateTo(string path)
|
||||
{
|
||||
activeSection = null;
|
||||
_showMobileSidebar = false;
|
||||
Navigation.NavigateTo(path);
|
||||
}
|
||||
|
||||
private void ToggleMobileSidebar()
|
||||
{
|
||||
_showMobileSidebar = !_showMobileSidebar;
|
||||
}
|
||||
|
||||
private void CloseMobileSidebar()
|
||||
{
|
||||
_showMobileSidebar = false;
|
||||
}
|
||||
|
||||
private void ToggleInfoOverlay()
|
||||
{
|
||||
_showInfoOverlay = !_showInfoOverlay;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
@inject IAuthService AuthService
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JS
|
||||
@inject IFactCheckService FactCheckService
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
||||
|
|
@ -38,6 +39,12 @@
|
|||
<span>kreativ (2)</span>
|
||||
</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">
|
||||
<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>
|
||||
|
|
@ -50,22 +57,36 @@
|
|||
{
|
||||
<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-avatar">U</div>
|
||||
<div class="chat-bubble">@msg.UserMessage</div>
|
||||
</div>
|
||||
<div class="chat-message chat-assistant">
|
||||
<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>
|
||||
}
|
||||
@if (_isBusy)
|
||||
{
|
||||
<div class="chat-message chat-assistant">
|
||||
<div class="chat-avatar">A</div>
|
||||
<div class="chat-bubble chat-thinking">…</div>
|
||||
<div class="chat-bubble">@RenderAssistantMessage(_streamingResponse, true)</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
@ -97,6 +118,9 @@
|
|||
private string _systemPrompt = string.Empty;
|
||||
private double _temperature = 0.7;
|
||||
private bool _needsScroll;
|
||||
private string _streamingResponse = string.Empty;
|
||||
private bool _factCheckEnabled;
|
||||
private List<List<FactCheckResult>?> _factChecks = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
|
|
@ -108,9 +132,11 @@
|
|||
}
|
||||
_userId = user.Id;
|
||||
_messages = await ChatService.GetHistoryAsync(_userId);
|
||||
_factChecks = _messages.Select(_ => (List<FactCheckResult>?)null).ToList();
|
||||
_systemPrompt = await SettingsService.GetChatPromptAsync()
|
||||
?? Settings.GetChatDefaultPrompt(Settings.Language);
|
||||
_temperature = await SettingsService.GetChatTempAsync();
|
||||
_factCheckEnabled = await SettingsService.GetChatFactCheckEnabledAsync();
|
||||
_needsScroll = true;
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +158,7 @@
|
|||
{
|
||||
await SettingsService.SaveChatPromptAsync(_systemPrompt);
|
||||
await SettingsService.SaveChatTempAsync(_temperature);
|
||||
await SettingsService.SaveChatFactCheckEnabledAsync(_factCheckEnabled);
|
||||
}
|
||||
|
||||
private async Task ResetToDefault()
|
||||
|
|
@ -157,22 +184,40 @@
|
|||
_input = string.Empty;
|
||||
_error = string.Empty;
|
||||
_isBusy = true;
|
||||
_streamingResponse = string.Empty;
|
||||
_needsScroll = true;
|
||||
|
||||
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
|
||||
{
|
||||
UserId = _userId,
|
||||
UserMessage = text,
|
||||
AssistantResponse = response,
|
||||
Model = Settings.StyleModel,
|
||||
AssistantResponse = _streamingResponse,
|
||||
Model = model,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
await ChatService.AddMessageAsync(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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -182,7 +227,6 @@
|
|||
finally
|
||||
{
|
||||
_isBusy = false;
|
||||
_input = string.Empty;
|
||||
_needsScroll = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -198,13 +242,99 @@
|
|||
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);
|
||||
var html = System.Web.HttpUtility.HtmlEncode(text);
|
||||
html = System.Text.RegularExpressions.Regex.Replace(html, @"\*\*(.+?)\*\*", "<strong>$1</strong>");
|
||||
html = System.Text.RegularExpressions.Regex.Replace(html, @"\*(.+?)\*", "<em>$1</em>");
|
||||
html = html.Replace("\n", "<br />");
|
||||
return new MarkupString(html);
|
||||
if (string.IsNullOrEmpty(text)) return ("", "");
|
||||
|
||||
var closeTag = "</thinking>";
|
||||
var closeIdx = text.IndexOf(closeTag, StringComparison.Ordinal);
|
||||
if (closeIdx >= 0)
|
||||
{
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
@using System.Threading
|
||||
@inject IOllamaService OllamaService
|
||||
@inject IProjectService ProjectService
|
||||
@inject IRagService RagService
|
||||
@inject ILlmWikiService WikiService
|
||||
@inject ISettingsService SettingsService
|
||||
@inject IWikipediaService WikipediaService
|
||||
@inject NavigationManager Navigation
|
||||
|
|
@ -62,7 +62,7 @@
|
|||
<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="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>
|
||||
|
||||
@if (showVersionDialog)
|
||||
|
|
@ -87,7 +87,7 @@
|
|||
<div class="search-section">
|
||||
<input value="@searchQuery" @oninput="HandleSearchInput" placeholder="@Loc["editor.search.placeholder"]"
|
||||
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>
|
||||
</div>
|
||||
|
||||
|
|
@ -153,18 +153,11 @@
|
|||
<button class="btn btn-secondary" @onclick="CloseVersionBrowser">@Loc["common.cancel"]</button>
|
||||
</div>
|
||||
}
|
||||
else if (ragResults != null && ragResults.Count > 0)
|
||||
else if (!string.IsNullOrEmpty(wikiResult))
|
||||
{
|
||||
<div class="results-area">
|
||||
<h4>@Loc["editor.rag.heading"]</h4>
|
||||
@foreach (var r in ragResults)
|
||||
{
|
||||
<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>
|
||||
}
|
||||
<h4>@Loc["editor.wiki.heading"]</h4>
|
||||
<div class="wiki-result">@Markup(wikiResult)</div>
|
||||
</div>
|
||||
}
|
||||
else if (wikipediaResults != null && wikipediaResults.Count > 0)
|
||||
|
|
@ -261,7 +254,7 @@
|
|||
private string originalText = string.Empty;
|
||||
private string correctedText = string.Empty;
|
||||
private bool showSpellDiff;
|
||||
private List<RagSearchResult>? ragResults;
|
||||
private string? wikiResult;
|
||||
private List<WikipediaResult>? wikipediaResults;
|
||||
|
||||
private int currentProjectId;
|
||||
|
|
@ -414,7 +407,7 @@
|
|||
showSpellDiff = false;
|
||||
showVersionBrowser = false;
|
||||
showVersionDiff = false;
|
||||
ragResults = null;
|
||||
wikiResult = null;
|
||||
wikipediaResults = null;
|
||||
analysisResult = string.Empty;
|
||||
}
|
||||
|
|
@ -464,7 +457,7 @@
|
|||
correctedText += chunk;
|
||||
OpState.AppendThought(chunk);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}, OllamaSettings.SpellingPrompt, OllamaSettings.SpellingTemp);
|
||||
}, OllamaSettings.SpellingPrompt, OllamaSettings.SpellingTemp, OllamaSettings.SpellingBackend);
|
||||
|
||||
showSpellDiff = true;
|
||||
OpState.SetStatus(Loc["common.status.ready"], false);
|
||||
|
|
@ -493,12 +486,12 @@
|
|||
statusText = Loc["editor.status.corrections_applied"];
|
||||
}
|
||||
|
||||
private async Task RagSearch()
|
||||
private async Task QueryWiki()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchQuery)) return;
|
||||
|
||||
isChecking = true;
|
||||
statusText = Loc["editor.status.searching_rag"];
|
||||
statusText = Loc["editor.status.searching_wiki"];
|
||||
ClearResults();
|
||||
analysisResult = string.Empty;
|
||||
|
||||
|
|
@ -506,20 +499,16 @@
|
|||
{
|
||||
if (currentProjectId == 0)
|
||||
{
|
||||
analysisResult = Loc["editor.rag.error.no_project"];
|
||||
analysisResult = Loc["editor.wiki.error.no_project"];
|
||||
return;
|
||||
}
|
||||
|
||||
ragResults = await RagService.SearchAsync(currentProjectId, searchQuery, 5);
|
||||
|
||||
if (ragResults.Count == 0)
|
||||
{
|
||||
analysisResult = Loc["editor.rag.error.no_facts"];
|
||||
}
|
||||
var answer = await WikiService.QueryWikiAsync(currentProjectId, searchQuery);
|
||||
wikiResult = answer;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
analysisResult = $"{Loc["editor.rag.error.generic"]} {ex.Message}";
|
||||
analysisResult = $"{Loc["editor.wiki.error.generic"]} {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -682,18 +671,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
private async Task IndexProject()
|
||||
private async Task CompileWiki()
|
||||
{
|
||||
if (currentProjectId == 0)
|
||||
{
|
||||
analysisResult = Loc["editor.index.error.no_project"];
|
||||
analysisResult = Loc["editor.wiki.error.no_project"];
|
||||
return;
|
||||
}
|
||||
|
||||
isChecking = true;
|
||||
statusText = Loc["editor.status.indexing"];
|
||||
statusText = Loc["editor.status.compiling_wiki"];
|
||||
analysisResult = string.Empty;
|
||||
ragResults = null;
|
||||
wikiResult = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
|
@ -701,7 +690,7 @@
|
|||
var project = projects.FirstOrDefault(p => p.Id == currentProjectId);
|
||||
if (project == null)
|
||||
{
|
||||
analysisResult = Loc["editor.index.error.not_found"];
|
||||
analysisResult = Loc["editor.wiki.error.not_found"];
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -711,12 +700,12 @@
|
|||
InvokeAsync(StateHasChanged);
|
||||
});
|
||||
|
||||
await RagService.IndexProjectAsync(currentProjectId, project.RootPath, p);
|
||||
analysisResult = Loc["editor.index.status.success"];
|
||||
await WikiService.CompileWikiAsync(currentProjectId, project.RootPath, p);
|
||||
analysisResult = Loc["editor.wiki.status.success"];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
analysisResult = $"{Loc["editor.index.error.generic"]} {ex.Message}";
|
||||
analysisResult = $"{Loc["editor.wiki.error.generic"]} {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -900,4 +889,6 @@
|
|||
comment
|
||||
);
|
||||
}
|
||||
|
||||
private static MarkupString Markup(string text) => new(text);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,39 +6,18 @@
|
|||
|
||||
<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">
|
||||
@if (activeTab == "rag")
|
||||
@if (styleSuggestions.Length == 0)
|
||||
{
|
||||
@if (foundFacts.Count == 0)
|
||||
{
|
||||
<p style="color: var(--border-color);">@Loc["home.rag.empty"]</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var fact in foundFacts)
|
||||
{
|
||||
<div class="fact-card">@fact</div>
|
||||
}
|
||||
}
|
||||
<p style="color: var(--border-color);">Willkommen bei Author Buddy. Öffne ein Projekt im Editor, um loszulegen.</p>
|
||||
}
|
||||
else if (activeTab == "style")
|
||||
else
|
||||
{
|
||||
<h3>@Loc["home.tab.style_analysis"]</h3>
|
||||
<textarea style="width: 100%; min-height: 300px; background: var(--editor-bg); color: var(--text-color); border: 1px solid var(--border-color); padding: 8px; font-family: Consolas;" readonly>@styleSuggestions</textarea>
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string activeTab = "rag";
|
||||
private List<string> foundFacts = new();
|
||||
private string styleSuggestions = string.Empty;
|
||||
|
||||
private string GetTabClass(string tab)
|
||||
{
|
||||
return activeTab == tab ? "active" : "";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,85 +7,95 @@
|
|||
|
||||
<PageTitle>@Loc["page.title.models"]</PageTitle>
|
||||
|
||||
<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)
|
||||
@if (_isCloudBackend)
|
||||
{
|
||||
<p>@Loc["models.status.loading"]</p>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
<div style="color: red; margin-bottom: 12px;">@errorMessage</div>
|
||||
<div style="padding: 24px; text-align: center; color: var(--text-muted);">
|
||||
<p>@Loc["models.cloud.not_available"]</p>
|
||||
<p style="font-size: 12px; margin-top: 8px;">@Loc["models.cloud.hint"]</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="model-list">
|
||||
@foreach (var model in localModels)
|
||||
{
|
||||
<div class="model-card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||||
<div>
|
||||
<div class="model-name">@model.Name</div>
|
||||
<div class="model-details">
|
||||
@Loc["models.label.size"] @FormatSize(model.Size) |
|
||||
@Loc["models.label.modified"] @model.ModifiedAt?.ToString("g")
|
||||
<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">
|
||||
@foreach (var model in localModels)
|
||||
{
|
||||
<div class="model-card">
|
||||
<div style="display:flex;justify-content:space-between;align-items:flex-start;">
|
||||
<div>
|
||||
<div class="model-name">@model.Name</div>
|
||||
<div class="model-details">
|
||||
@Loc["models.label.size"] @FormatSize(model.Size) |
|
||||
@Loc["models.label.modified"] @model.ModifiedAt?.ToString("g")
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-sm" @onclick="() => DeleteModel(model.Name)" style="color:#c0392b;flex-shrink:0;">
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-sm" @onclick="() => DeleteModel(model.Name)" style="color:#c0392b;flex-shrink:0;">
|
||||
Löschen
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<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>
|
||||
<div style="display: flex; gap: 8px; align-items: flex-end;">
|
||||
<div class="form-group" style="flex: 1; margin-bottom: 0;">
|
||||
<label for="pullModelName">@Loc["models.pull.label.name"]</label>
|
||||
<input id="pullModelName" @bind="modelToPull" @bind:event="oninput" placeholder="@Loc["models.pull.placeholder"]" />
|
||||
</div>
|
||||
<button @onclick="PullModel" disabled="@_pullDisabled">
|
||||
@Loc["models.pull.button.download"]
|
||||
</button>
|
||||
</div>
|
||||
@if (isPulling)
|
||||
{
|
||||
<div style="margin-top: 8px;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: @pullPercentDisplay"></div>
|
||||
</div>
|
||||
<div style="font-size: 12px; margin-top: 4px;">@pullStatus (@pullPercentDisplay)</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<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>
|
||||
<div style="display: flex; gap: 8px; align-items: flex-end;">
|
||||
<div class="form-group" style="flex: 1; margin-bottom: 0;">
|
||||
<label for="pullModelName">@Loc["models.pull.label.name"]</label>
|
||||
<input id="pullModelName" @bind="modelToPull" @bind:event="oninput" placeholder="@Loc["models.pull.placeholder"]" />
|
||||
</div>
|
||||
<button @onclick="PullModel" disabled="@_pullDisabled">
|
||||
@Loc["models.pull.button.download"]
|
||||
</button>
|
||||
</div>
|
||||
@if (isPulling)
|
||||
@if (pullResult != null)
|
||||
{
|
||||
<div style="margin-top: 8px;">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: @pullPercentDisplay"></div>
|
||||
</div>
|
||||
<div style="font-size: 12px; margin-top: 4px;">@pullStatus (@pullPercentDisplay)</div>
|
||||
<div style="margin-top: 12px; padding: 8px; background: var(--card-bg); border: 1px solid var(--accent-color); font-size: 13px;">
|
||||
@pullResult
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (pullResult != null)
|
||||
{
|
||||
<div style="margin-top: 12px; padding: 8px; background: var(--card-bg); border: 1px solid var(--accent-color); font-size: 13px;">
|
||||
@pullResult
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (_confirmDeleteModel != null)
|
||||
{
|
||||
<div class="modal-overlay">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-title">Modell löschen</div>
|
||||
<div class="modal-body">
|
||||
<p>Soll das Modell <strong>@_confirmDeleteModel</strong> wirklich gelöscht werden?</p>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button @onclick="CancelDelete">Abbrechen</button>
|
||||
<button @onclick="ConfirmDelete" class="btn-danger">Löschen</button>
|
||||
@if (_confirmDeleteModel != null)
|
||||
{
|
||||
<div class="modal-overlay">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-title">Modell löschen</div>
|
||||
<div class="modal-body">
|
||||
<p>Soll das Modell <strong>@_confirmDeleteModel</strong> wirklich gelöscht werden?</p>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button @onclick="CancelDelete">Abbrechen</button>
|
||||
<button @onclick="ConfirmDelete" class="btn-danger">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
|
|
@ -100,10 +110,13 @@ else
|
|||
private string? pullResult;
|
||||
private string pullPercentDisplay => $"{pullProgress:F0}%";
|
||||
private string? _confirmDeleteModel;
|
||||
private bool _isCloudBackend;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshModels();
|
||||
_isCloudBackend = !OllamaService.SupportsModelManagement;
|
||||
if (!_isCloudBackend)
|
||||
await RefreshModels();
|
||||
}
|
||||
|
||||
private async Task RefreshModels()
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
@attribute [Authorize]
|
||||
@inject IOllamaService OllamaService
|
||||
@inject ISettingsService SettingsService
|
||||
@inject IRagService RagService
|
||||
@inject ILlmWikiService WikiService
|
||||
@inject IProjectService ProjectService
|
||||
@inject NavigationManager Navigation
|
||||
@inject ILocalizationService Loc
|
||||
@inject OperationState OpState
|
||||
@inject OllamaSettings OllamaConfig
|
||||
|
||||
<PageTitle>@Loc["page.title.assistant"]</PageTitle>
|
||||
|
||||
|
|
@ -18,7 +19,7 @@
|
|||
<option value="hookfinder">@Loc["assistant.mode.hookfinder"]</option>
|
||||
<option value="eli5">@Loc["assistant.mode.eli5"]</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)
|
||||
{
|
||||
<option disabled style="font-style:italic;">── Requirements ──</option>
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
}
|
||||
</div>
|
||||
|
||||
@if (showPromptEditor && selectedMode != "rag")
|
||||
@if (showPromptEditor && selectedMode != "wiki")
|
||||
{
|
||||
<div class="assistant-prompt-editor">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px;">
|
||||
|
|
@ -131,7 +132,7 @@
|
|||
|
||||
private async Task LoadPromptForMode()
|
||||
{
|
||||
if (selectedMode == "rag")
|
||||
if (selectedMode == "wiki")
|
||||
{
|
||||
promptOverride = string.Empty;
|
||||
temperatureOverride = 0.3;
|
||||
|
|
@ -186,7 +187,7 @@
|
|||
|
||||
try
|
||||
{
|
||||
if (selectedMode == "rag")
|
||||
if (selectedMode == "wiki")
|
||||
{
|
||||
var projectId = SettingsService.CurrentProjectId;
|
||||
if (projectId == null)
|
||||
|
|
@ -197,18 +198,10 @@
|
|||
|
||||
OpState.SetStatus(Loc["assistant.status.searching"], true);
|
||||
progress = 30;
|
||||
var results = await RagService.SearchAsync(projectId.Value, inputText, 5);
|
||||
if (results.Count == 0)
|
||||
{
|
||||
errorMessage = Loc["assistant.error.no_facts"];
|
||||
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);
|
||||
var answer = await WikiService.QueryWikiAsync(projectId.Value, inputText);
|
||||
progress = 100;
|
||||
outputText = answer;
|
||||
OpState.SetThought(answer);
|
||||
statusText = Loc["assistant.status.done"];
|
||||
OpState.SetStatus(Loc["assistant.status.done"], false);
|
||||
}
|
||||
|
|
@ -254,7 +247,7 @@
|
|||
outputText += chunk;
|
||||
OpState.AppendThought(chunk);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}, systemPrompt, temperatureOverride);
|
||||
}, systemPrompt, temperatureOverride, OllamaConfig.StyleBackend);
|
||||
|
||||
statusText = Loc["assistant.status.done"];
|
||||
OpState.SetStatus(Loc["assistant.status.done"], false);
|
||||
|
|
|
|||
|
|
@ -6,12 +6,55 @@
|
|||
@inject IAuthService AuthService
|
||||
@inject ISettingsService SettingsService
|
||||
@inject IQuickMarkdownService QuickMarkdownService
|
||||
@inject IPluginManager PluginManager
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@rendermode InteractiveServer
|
||||
@inject IFactSearchToolService FactSearchToolService
|
||||
|
||||
<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">
|
||||
<h2>Quick Markdown</h2>
|
||||
<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>
|
||||
}
|
||||
</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="DeleteCurrentPrompt" disabled="@(_selectedPromptId == 0)">Löschen</button>
|
||||
</div>
|
||||
|
|
@ -44,12 +88,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<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 class="form-group">
|
||||
<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">
|
||||
<span>präzise (0)</span>
|
||||
<span>kreativ (2)</span>
|
||||
|
|
@ -58,7 +102,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<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 class="qm-generate-row">
|
||||
|
|
@ -75,6 +119,9 @@
|
|||
<button class="btn btn-sm" @onclick="SaveEntry" disabled="@_saveEntryDisabled">
|
||||
Eingabe & Ergebnis speichern
|
||||
</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>
|
||||
|
||||
|
|
@ -84,7 +131,24 @@
|
|||
<div class="qm-output-toolbar">
|
||||
<button class="btn btn-small" @onclick="CopyOutput" disabled="@string.IsNullOrEmpty(_output)">📋 Kopieren</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>
|
||||
@if (!string.IsNullOrEmpty(_exportResult))
|
||||
{
|
||||
<div class="qm-notice" style="margin-top:4px;font-size:12px;">@_exportResult</div>
|
||||
}
|
||||
</div>
|
||||
<textarea class="qm-textarea qm-output" @bind="_output" rows="30" readonly></textarea>
|
||||
</div>
|
||||
|
|
@ -116,9 +180,83 @@
|
|||
{
|
||||
<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>
|
||||
|
||||
@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 _inputText = string.Empty;
|
||||
private string _output = string.Empty;
|
||||
|
|
@ -134,6 +272,20 @@
|
|||
private int _selectedPromptId;
|
||||
private bool _showPromptNameInput;
|
||||
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()
|
||||
{
|
||||
|
|
@ -150,10 +302,77 @@
|
|||
?? Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
|
||||
_temperature = await SettingsService.GetQuickMarkdownTempAsync();
|
||||
_savedEntries = await QuickMarkdownService.GetAllAsync(_userId);
|
||||
_availablePlugins = PluginManager.GetPlugins().ToList();
|
||||
|
||||
Navigation.LocationChanged += OnLocationChanged;
|
||||
|
||||
_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)
|
||||
{
|
||||
var id = int.Parse(e.Value?.ToString() ?? "0");
|
||||
|
|
@ -172,6 +391,8 @@
|
|||
_temperature = prompt.Temperature;
|
||||
}
|
||||
}
|
||||
|
||||
await PersistPromptDraft();
|
||||
}
|
||||
|
||||
private void ShowSavePromptDialog()
|
||||
|
|
@ -185,33 +406,50 @@
|
|||
if (string.IsNullOrWhiteSpace(_newPromptName))
|
||||
return;
|
||||
|
||||
var existing = _savedPrompts.FirstOrDefault(p =>
|
||||
p.Name == _newPromptName && p.Id != _selectedPromptId);
|
||||
if (existing != null)
|
||||
if (_fullscreenMode && _selectedPromptId > 0)
|
||||
{
|
||||
existing.PromptText = _systemPrompt;
|
||||
existing.Temperature = _temperature;
|
||||
existing.Timestamp = DateTime.UtcNow;
|
||||
await QuickMarkdownService.SavePromptAsync(existing);
|
||||
_selectedPromptId = existing.Id;
|
||||
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 prompt = new QuickMarkdownPrompt
|
||||
var existing = _savedPrompts.FirstOrDefault(p =>
|
||||
p.Name == _newPromptName && p.Id != _selectedPromptId);
|
||||
if (existing != null)
|
||||
{
|
||||
UserId = _userId,
|
||||
Name = _newPromptName,
|
||||
PromptText = _systemPrompt,
|
||||
Temperature = _temperature,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
await QuickMarkdownService.SavePromptAsync(prompt);
|
||||
_selectedPromptId = prompt.Id;
|
||||
existing.PromptText = _systemPrompt;
|
||||
existing.Temperature = _temperature;
|
||||
existing.Timestamp = DateTime.UtcNow;
|
||||
await QuickMarkdownService.SavePromptAsync(existing);
|
||||
_selectedPromptId = existing.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
var prompt = new QuickMarkdownPrompt
|
||||
{
|
||||
UserId = _userId,
|
||||
Name = _newPromptName,
|
||||
PromptText = _systemPrompt,
|
||||
Temperature = _temperature,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
await QuickMarkdownService.SavePromptAsync(prompt);
|
||||
_selectedPromptId = prompt.Id;
|
||||
}
|
||||
}
|
||||
|
||||
_savedPrompts = await QuickMarkdownService.GetPromptsAsync(_userId);
|
||||
_showPromptNameInput = false;
|
||||
_newPromptName = string.Empty;
|
||||
|
||||
await PersistPromptDraft();
|
||||
}
|
||||
|
||||
private void CancelSavePrompt()
|
||||
|
|
@ -220,6 +458,11 @@
|
|||
_newPromptName = string.Empty;
|
||||
}
|
||||
|
||||
private void ToggleFullscreen()
|
||||
{
|
||||
_fullscreenMode = !_fullscreenMode;
|
||||
}
|
||||
|
||||
private async Task DeleteCurrentPrompt()
|
||||
{
|
||||
if (_selectedPromptId == 0)
|
||||
|
|
@ -230,6 +473,8 @@
|
|||
_selectedPromptId = 0;
|
||||
_systemPrompt = Settings.GetQuickMarkdownDefaultPrompt(Settings.Language);
|
||||
_temperature = Settings.QuickMarkdownTemp;
|
||||
|
||||
await PersistPromptDraft();
|
||||
}
|
||||
|
||||
private async Task SaveEntry()
|
||||
|
|
@ -263,6 +508,8 @@
|
|||
_systemPrompt = entry.SystemPrompt;
|
||||
_temperature = entry.Temperature;
|
||||
}
|
||||
|
||||
await PersistDraftAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteEntry(int id)
|
||||
|
|
@ -275,13 +522,15 @@
|
|||
{
|
||||
if (string.IsNullOrWhiteSpace(_inputText)) return;
|
||||
|
||||
await PersistDraftAsync();
|
||||
|
||||
_isBusy = true;
|
||||
_error = string.Empty;
|
||||
_output = string.Empty;
|
||||
|
||||
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;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -305,4 +554,181 @@
|
|||
_output = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
@inject ISettingsService SettingsService
|
||||
@inject IThemeService ThemeService
|
||||
@inject IOllamaService OllamaService
|
||||
@inject OllamaSettings OllamaConfig
|
||||
@inject NavigationManager Navigation
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject IProjectService ProjectService
|
||||
@inject ILocalizationService Loc
|
||||
@inject IAuthService AuthService
|
||||
@inject IPluginManager PluginManager
|
||||
|
||||
<PageTitle>@Loc["page.title.settings"] - Author Buddy</PageTitle>
|
||||
|
||||
|
|
@ -73,40 +75,55 @@
|
|||
|
||||
<div class="settings-section">
|
||||
<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>
|
||||
<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">
|
||||
@if (availableModels.Count == 0)
|
||||
@if (availableRagModels.Count == 0)
|
||||
{
|
||||
<option value="">@Loc["settings.models.empty"]</option>
|
||||
}
|
||||
@foreach (var m in availableModels)
|
||||
@foreach (var m in availableRagModels)
|
||||
{
|
||||
<option value="@m.Name">@m.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group settings-model-row">
|
||||
<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">
|
||||
@if (availableModels.Count == 0)
|
||||
@if (availableStyleModels.Count == 0)
|
||||
{
|
||||
<option value="">@Loc["settings.models.empty"]</option>
|
||||
}
|
||||
@foreach (var m in availableModels)
|
||||
@foreach (var m in availableStyleModels)
|
||||
{
|
||||
<option value="@m.Name">@m.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-group settings-model-row">
|
||||
<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">
|
||||
@if (availableModels.Count == 0)
|
||||
@if (availableSpellingModels.Count == 0)
|
||||
{
|
||||
<option value="">@Loc["settings.models.empty"]</option>
|
||||
}
|
||||
@foreach (var m in availableModels)
|
||||
@foreach (var m in availableSpellingModels)
|
||||
{
|
||||
<option value="@m.Name">@m.Name</option>
|
||||
}
|
||||
|
|
@ -116,13 +133,6 @@
|
|||
|
||||
<div class="settings-section">
|
||||
<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">
|
||||
<label for="ollamaUrl">@Loc["settings.ollama.label.url"] (Ollama)</label>
|
||||
<div class="input-with-button">
|
||||
|
|
@ -144,6 +154,16 @@
|
|||
<input id="llamaCppUrl" @bind="llamaCppUrl" placeholder="http://localhost:8080" />
|
||||
<div class="form-hint">Nur aktiv wenn Backend auf "llama.cpp" gesetzt ist</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>
|
||||
|
|
@ -163,6 +183,30 @@
|
|||
</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">
|
||||
<h3 class="settings-section-title">@Loc["settings.section.appearance"]</h3>
|
||||
<div class="form-group">
|
||||
|
|
@ -409,12 +453,14 @@
|
|||
private void SwitchToTechnicalTab() => _activePromptTab = "technical";
|
||||
private void SwitchToAdminTab() => _activePromptTab = "admin";
|
||||
private string ragModel = "llama3.2";
|
||||
private string ragBackend = "Ollama";
|
||||
private string styleModel = "llama3.2";
|
||||
private string styleBackend = "Ollama";
|
||||
private string spellingModel = "phi3:mini";
|
||||
private string spellingBackend = "Ollama";
|
||||
private string selectedTheme = "light";
|
||||
private string ollamaUrl = "http://localhost:11434";
|
||||
private string llamaCppUrl = "http://localhost:8080";
|
||||
private string selectedBackend = "Ollama";
|
||||
private string lektorPrompt = string.Empty;
|
||||
private string factCheckTemplate = string.Empty;
|
||||
private string spellingPrompt = string.Empty;
|
||||
|
|
@ -447,7 +493,9 @@
|
|||
private string newProjectName = string.Empty;
|
||||
private string newUserName = 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 int selectedUserId;
|
||||
private string? currentProjectName;
|
||||
|
|
@ -459,6 +507,13 @@
|
|||
private bool _isTestingConnection;
|
||||
private List<string>? _diagnosticsResults;
|
||||
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()
|
||||
{
|
||||
|
|
@ -481,27 +536,24 @@
|
|||
|
||||
if (_isAdmin)
|
||||
{
|
||||
try
|
||||
{
|
||||
var models = await OllamaService.GetLocalModelsAsync();
|
||||
availableModels = models.ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
availableModels = new();
|
||||
}
|
||||
await RefreshModelsForAllBackends();
|
||||
|
||||
_availablePlugins = PluginManager.GetPlugins().ToList();
|
||||
await LoadPluginConfig();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadUserSettings()
|
||||
{
|
||||
ragModel = await SettingsService.GetRagModelAsync() ?? "llama3.2";
|
||||
ragBackend = await SettingsService.GetRagBackendAsync() ?? "Ollama";
|
||||
styleModel = await SettingsService.GetStyleModelAsync() ?? "llama3.2";
|
||||
styleBackend = await SettingsService.GetStyleBackendAsync() ?? "Ollama";
|
||||
spellingModel = await SettingsService.GetSpellingModelAsync() ?? "phi3:mini";
|
||||
spellingBackend = await SettingsService.GetSpellingBackendAsync() ?? "Ollama";
|
||||
selectedTheme = await SettingsService.GetThemeAsync() ?? "light";
|
||||
ollamaUrl = await SettingsService.GetOllamaUrlAsync() ?? "http://localhost:11434";
|
||||
llamaCppUrl = await SettingsService.GetLlamaCppUrlAsync() ?? "http://localhost:8080";
|
||||
selectedBackend = await SettingsService.GetBackendAsync() ?? "Ollama";
|
||||
lektorPrompt = await SettingsService.GetLektorSystemPromptAsync() ?? string.Empty;
|
||||
factCheckTemplate = await SettingsService.GetFactCheckTemplateAsync() ?? string.Empty;
|
||||
spellingPrompt = await SettingsService.GetSpellingPromptAsync() ?? string.Empty;
|
||||
|
|
@ -531,6 +583,8 @@
|
|||
techQualitaetTemp = await SettingsService.GetTechQualitaetTempAsync();
|
||||
techRisikoTemp = await SettingsService.GetTechRisikoTempAsync();
|
||||
selectedLanguage = await SettingsService.GetLanguageAsync() ?? "de";
|
||||
_ollamaCloudUrl = await SettingsService.GetOllamaCloudUrlAsync() ?? OllamaConfig.OllamaCloudUrl;
|
||||
_ollamaCloudApiKey = await SettingsService.GetOllamaCloudApiKeyAsync() ?? OllamaConfig.OllamaCloudApiKey;
|
||||
}
|
||||
|
||||
private async Task SwitchUser(ChangeEventArgs e)
|
||||
|
|
@ -552,29 +606,84 @@
|
|||
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()
|
||||
{
|
||||
if (_isAdmin)
|
||||
{
|
||||
await SettingsService.SaveOllamaUrlAsync(ollamaUrl);
|
||||
await SettingsService.SaveLlamaCppUrlAsync(llamaCppUrl);
|
||||
await SettingsService.SaveBackendAsync(selectedBackend);
|
||||
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))
|
||||
{
|
||||
var ok = await OllamaService.TestEmbeddingAsync(ragModel);
|
||||
var ok = await OllamaService.TestEmbeddingAsync(ragModel, ragBackend);
|
||||
if (!ok)
|
||||
{
|
||||
statusMessage = selectedLanguage == "en"
|
||||
? $"Model '{ragModel}' does not support embeddings. RAG model not saved."
|
||||
: $"Modell '{ragModel}' unterstützt keine Embeddings. RAG-Modell nicht gespeichert.";
|
||||
return;
|
||||
//return;
|
||||
}
|
||||
}
|
||||
|
||||
await SettingsService.SaveRagModelAsync(ragModel);
|
||||
await SettingsService.SaveStyleModelAsync(styleModel);
|
||||
await SettingsService.SaveSpellingModelAsync(spellingModel);
|
||||
|
||||
}
|
||||
await SettingsService.SaveThemeAsync(selectedTheme);
|
||||
await SettingsService.SaveLektorSystemPromptAsync(lektorPrompt);
|
||||
|
|
@ -605,7 +714,7 @@
|
|||
await SettingsService.SaveTechGlossarTempAsync(techGlossarTemp);
|
||||
await SettingsService.SaveTechQualitaetTempAsync(techQualitaetTemp);
|
||||
await SettingsService.SaveTechRisikoTempAsync(techRisikoTemp);
|
||||
await SettingsService.SaveLanguageAsync(selectedLanguage);
|
||||
|
||||
|
||||
if (selectedLanguage != Loc.CurrentLanguage)
|
||||
await Loc.SetLanguageAsync(selectedLanguage);
|
||||
|
|
@ -614,6 +723,33 @@
|
|||
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)
|
||||
{
|
||||
if (temp <= 0.2) return Loc["settings.temperature.category.precise"];
|
||||
|
|
@ -675,33 +811,33 @@
|
|||
}
|
||||
|
||||
var models = new[] {
|
||||
(Loc["settings.models.label.style"] ?? "Style", styleModel),
|
||||
(Loc["settings.models.label.rag"] ?? "RAG", ragModel),
|
||||
(Loc["settings.models.label.spellcheck"] ?? "Spelling", spellingModel)
|
||||
(Label: Loc["settings.models.label.style"] ?? "Style", Model: styleModel, Backend: styleBackend),
|
||||
(Label: Loc["settings.models.label.rag"] ?? "RAG", Model: ragModel, Backend: ragBackend),
|
||||
(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;
|
||||
}
|
||||
|
||||
var exists = await OllamaService.ModelExistsAsync(model);
|
||||
var exists = await OllamaService.ModelExistsAsync(entry.Model, entry.Backend);
|
||||
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;
|
||||
}
|
||||
|
||||
var latency = await OllamaService.MeasureLatencyAsync(model);
|
||||
var latency = await OllamaService.MeasureLatencyAsync(entry.Model, entry.Backend);
|
||||
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}" : "?";
|
||||
|
||||
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;
|
||||
|
|
|
|||
11
Models/DuckDuckGoResult.cs
Normal file
11
Models/DuckDuckGoResult.cs
Normal 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();
|
||||
}
|
||||
22
Models/ExportPluginContext.cs
Normal file
22
Models/ExportPluginContext.cs
Normal 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
17
Models/FactCheckResult.cs
Normal 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
|
||||
}
|
||||
8
Models/FactSearchResult.cs
Normal file
8
Models/FactSearchResult.cs
Normal 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;
|
||||
}
|
||||
8
Models/McpToolDefinition.cs
Normal file
8
Models/McpToolDefinition.cs
Normal 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
9
Models/McpToolResult.cs
Normal 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; }
|
||||
}
|
||||
|
|
@ -8,8 +8,13 @@ public class OllamaSettings
|
|||
public string OllamaUrl { get; set; } = "http://localhost:11434";
|
||||
public string LlamaCppUrl { get; set; } = "http://localhost:8080";
|
||||
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 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. " +
|
||||
"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 ChatPromptEn { get; } = "You are a helpful assistant. Answer friendly and precisely in English.";
|
||||
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 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)
|
||||
{
|
||||
return currentLanguage == "en" ? enPrompt : dePrompt;
|
||||
|
|
|
|||
94
Plugins/WordPressPlugin.cs
Normal file
94
Plugins/WordPressPlugin.cs
Normal 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] + "…";
|
||||
}
|
||||
11
Program.cs
11
Program.cs
|
|
@ -1,6 +1,7 @@
|
|||
using AuthorBuddy.Web.Components;
|
||||
using AuthorBuddy.Web.Data;
|
||||
using AuthorBuddy.Web.Models;
|
||||
using AuthorBuddy.Web.Plugins;
|
||||
using AuthorBuddy.Web.Services;
|
||||
using LiteDB;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
|
@ -29,14 +30,18 @@ builder.Services.AddSingleton<OllamaSettings>();
|
|||
builder.Services.AddSingleton<IOllamaService, OllamaService>();
|
||||
builder.Services.AddSingleton<ILLMBackend, OllamaBackend>();
|
||||
builder.Services.AddSingleton<ILLMBackend, LlamaCppBackend>();
|
||||
builder.Services.AddSingleton<ILLMBackend, OllamaCloudBackend>();
|
||||
builder.Services.AddSingleton<IOllamaSetupService, OllamaSetupService>();
|
||||
builder.Services.AddSingleton<IFileScannerService, FileScannerService>();
|
||||
builder.Services.AddScoped<ISettingsService, SettingsService>();
|
||||
builder.Services.AddScoped<IThemeService, ThemeService>();
|
||||
builder.Services.AddSingleton<IProjectService, ProjectService>();
|
||||
builder.Services.AddSingleton<IExportService, ExportService>();
|
||||
builder.Services.AddSingleton<IRagService, RagService>();
|
||||
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<IDbAdminService, DbAdminService>();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
|
|
@ -44,6 +49,10 @@ builder.Services.AddSingleton<IEmailService, EmailService>();
|
|||
builder.Services.AddSingleton<IChatService, ChatService>();
|
||||
builder.Services.AddSingleton<IQuickMarkdownService, QuickMarkdownService>();
|
||||
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddScoped<IExportPlugin, WordPressPlugin>();
|
||||
builder.Services.AddScoped<IPluginManager, PluginManager>();
|
||||
|
||||
var smtpSettings = new SmtpSettings();
|
||||
builder.Configuration.GetSection("Smtp").Bind(smtpSettings);
|
||||
builder.Services.AddSingleton(smtpSettings);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<_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 />
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
"common.error": "Fehler:",
|
||||
"common.copy": "Kopieren",
|
||||
"common.status.error": "Fehler",
|
||||
"llm.overlay.title": "KI-Kommunikation",
|
||||
"llm.overlay.placeholder": "Die KI denkt nach…",
|
||||
"page.title.login": "Anmelden - Author Buddy",
|
||||
"page.title.register": "Registrieren - 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.status.processing": "Verarbeite...",
|
||||
"assistant.mode.coauthor": "Co-Author Brainstorming",
|
||||
"assistant.mode.rag": "RAG-Faktenabfrage",
|
||||
"assistant.mode.wiki": "Wiki-Wissen",
|
||||
"assistant.mode.tech_lastenheft": "📋 Lastenheft",
|
||||
"assistant.mode.tech_pflichtenheft": "⚙️ Pflichtenheft",
|
||||
"assistant.mode.tech_usecase": "🔄 Use Cases",
|
||||
|
|
@ -197,17 +199,16 @@
|
|||
"editor.button.save": "Speichern",
|
||||
"editor.button.version": "Version",
|
||||
"editor.button.assistant": "Assistent",
|
||||
"editor.button.index_project": "Projekt indexieren",
|
||||
"editor.button.compile_wiki": "Wiki kompilieren",
|
||||
"editor.version.title": "Version speichern",
|
||||
"editor.version.label.comment": "Kommentar (optional)",
|
||||
"editor.version.placeholder.comment": "Was hat sich geändert?",
|
||||
"editor.version.button.save": "Version speichern",
|
||||
"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.spellcheck.heading": "Rechtschreibprüfung - Änderungen:",
|
||||
"editor.rag.heading": "Gefundene Fakten (RAG):",
|
||||
"editor.rag.label.similarity": "Ähnlichkeit:",
|
||||
"editor.wiki.heading": "Wiki-Antwort:",
|
||||
"editor.wikipedia.heading": "Wikipedia-Recherche:",
|
||||
"editor.wikipedia.status.loaded": "✓ Geladen",
|
||||
"editor.wikipedia.button.load": "Vollständigen Artikel laden",
|
||||
|
|
@ -233,18 +234,19 @@
|
|||
"settings.temperature.desc.chaos": "Unbrauchbar für produktives Schreiben",
|
||||
"editor.status.analyzing_style": "Ollama analysiert Stil...",
|
||||
"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.loading_article": "Lade Artikel:",
|
||||
"editor.status.saving_fact": "Speichere als Fakt...",
|
||||
"editor.status.creating_project": "Erstelle Projekt...",
|
||||
"editor.status.indexing": "Indexiere Projekt...",
|
||||
"editor.status.compiling_wiki": "Komprimiere Wiki...",
|
||||
"editor.status.saved": "Gespeichert:",
|
||||
"editor.status.version_saved": "Version gespeichert:",
|
||||
"editor.status.saved_as": "Gespeichert als:",
|
||||
"editor.rag.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.rag.error.generic": "RAG-Fehler:",
|
||||
"editor.wiki.error.no_project": "Kein Projekt geladen. Öffne zuerst ein Projekt über die Sidebar.",
|
||||
"editor.wiki.error.not_found": "Projekt nicht gefunden.",
|
||||
"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.generic": "Wikipedia-Fehler:",
|
||||
"editor.wikipedia.error.load_failed": "Konnte Artikel nicht laden.",
|
||||
|
|
@ -252,9 +254,6 @@
|
|||
"editor.error.save_failed": "Fehler beim Speichern:",
|
||||
"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.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_suffix": "geladen.",
|
||||
"home.tab.rag_facts": "Gefundene Fakten (RAG)",
|
||||
|
|
@ -282,6 +281,8 @@
|
|||
"usermanagement.status.deleted": "Benutzer gelöscht.",
|
||||
"common.back": "Zurück",
|
||||
"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.label.filename": "Dateiname",
|
||||
"export.placeholder.filename": "z.B. MeinExport",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
"common.error": "Error:",
|
||||
"common.copy": "Copy",
|
||||
"common.status.error": "Error",
|
||||
"llm.overlay.title": "AI Communication",
|
||||
"llm.overlay.placeholder": "The AI is thinking…",
|
||||
"page.title.login": "Login - Author Buddy",
|
||||
"page.title.register": "Register - Author Buddy",
|
||||
"page.title.setup2fa": "2FA setup - Author Buddy",
|
||||
|
|
@ -156,7 +158,7 @@
|
|||
"assistant.hint.empty_result": "Result appears here after execution.",
|
||||
"assistant.status.processing": "Processing...",
|
||||
"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_pflichtenheft": "⚙️ Functional Spec",
|
||||
"assistant.mode.tech_usecase": "🔄 Use Cases",
|
||||
|
|
@ -197,17 +199,16 @@
|
|||
"editor.button.save": "Save",
|
||||
"editor.button.version": "Version",
|
||||
"editor.button.assistant": "Assistant",
|
||||
"editor.button.index_project": "Index project",
|
||||
"editor.button.compile_wiki": "Compile wiki",
|
||||
"editor.version.title": "Save version",
|
||||
"editor.version.label.comment": "Comment (optional)",
|
||||
"editor.version.placeholder.comment": "What changed?",
|
||||
"editor.version.button.save": "Save version",
|
||||
"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.spellcheck.heading": "Spell check - Changes:",
|
||||
"editor.rag.heading": "Found facts (RAG):",
|
||||
"editor.rag.label.similarity": "Similarity:",
|
||||
"editor.wiki.heading": "Wiki answer:",
|
||||
"editor.wikipedia.heading": "Wikipedia research:",
|
||||
"editor.wikipedia.status.loaded": "\u2713 Loaded",
|
||||
"editor.wikipedia.button.load": "Load full article",
|
||||
|
|
@ -233,18 +234,19 @@
|
|||
"settings.temperature.desc.chaos": "Unusable for productive writing",
|
||||
"editor.status.analyzing_style": "Ollama analyzing style...",
|
||||
"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.loading_article": "Loading article:",
|
||||
"editor.status.saving_fact": "Saving as fact...",
|
||||
"editor.status.creating_project": "Creating project...",
|
||||
"editor.status.indexing": "Indexing project...",
|
||||
"editor.status.compiling_wiki": "Compiling wiki...",
|
||||
"editor.status.saved": "Saved:",
|
||||
"editor.status.version_saved": "Version saved:",
|
||||
"editor.status.saved_as": "Saved as:",
|
||||
"editor.rag.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.rag.error.generic": "RAG error:",
|
||||
"editor.wiki.error.no_project": "No project loaded. Open a project via the sidebar first.",
|
||||
"editor.wiki.error.not_found": "Project not found.",
|
||||
"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.generic": "Wikipedia error:",
|
||||
"editor.wikipedia.error.load_failed": "Could not load article.",
|
||||
|
|
@ -252,9 +254,6 @@
|
|||
"editor.error.save_failed": "Error saving:",
|
||||
"editor.error.create_failed": "Error creating:",
|
||||
"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_suffix": "loaded.",
|
||||
"home.tab.rag_facts": "Found facts (RAG)",
|
||||
|
|
@ -282,6 +281,8 @@
|
|||
"usermanagement.status.deleted": "User deleted.",
|
||||
"common.back": "Back",
|
||||
"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.label.filename": "Filename",
|
||||
"export.placeholder.filename": "e.g. MyExport",
|
||||
|
|
|
|||
166
Services/DuckDuckGoService.cs
Normal file
166
Services/DuckDuckGoService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
146
Services/FactCheckService.cs
Normal file
146
Services/FactCheckService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
209
Services/FactSearchToolService.cs
Normal file
209
Services/FactSearchToolService.cs
Normal 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 }) };
|
||||
}
|
||||
9
Services/IDuckDuckGoService.cs
Normal file
9
Services/IDuckDuckGoService.cs
Normal 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
11
Services/IExportPlugin.cs
Normal 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);
|
||||
}
|
||||
8
Services/IFactCheckService.cs
Normal file
8
Services/IFactCheckService.cs
Normal 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);
|
||||
}
|
||||
12
Services/IFactSearchToolService.cs
Normal file
12
Services/IFactSearchToolService.cs
Normal 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);
|
||||
}
|
||||
8
Services/ILlmWikiService.cs
Normal file
8
Services/ILlmWikiService.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -6,18 +6,20 @@ namespace AuthorBuddy.Web.Services;
|
|||
|
||||
public interface IOllamaService
|
||||
{
|
||||
bool SupportsModelManagement { get; }
|
||||
Task<IEnumerable<LLM_Model>> GetLocalModelsAsync();
|
||||
Task<IEnumerable<LLM_Model>> GetModelsForBackendAsync(string backendName);
|
||||
Task<bool> PullModelAsync(string modelName, IProgress<string> progress);
|
||||
Task<string> QuickSpellCheckAsync(string text);
|
||||
Task<float[]> GetEmbeddingAsync(string text);
|
||||
Task<bool> TestEmbeddingAsync(string model);
|
||||
Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null);
|
||||
Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null);
|
||||
Task<float[]> GetEmbeddingAsync(string text, string? backendName = null);
|
||||
Task<bool> TestEmbeddingAsync(string model, string? backendName = 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, string? backendName = null);
|
||||
Task<string> AnswerBasedOnFactsAsync(string question, string facts);
|
||||
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 Success, int ContextWindow)> GetModelContextWindowAsync(string model);
|
||||
Task<long> MeasureLatencyAsync(string model);
|
||||
Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model, string? backendName = null);
|
||||
Task<long> MeasureLatencyAsync(string model, string? backendName = null);
|
||||
void Reconfigure(string ollamaUrl);
|
||||
}
|
||||
|
|
|
|||
11
Services/IPluginManager.cs
Normal file
11
Services/IPluginManager.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -14,9 +14,16 @@ public interface ISettingsService
|
|||
Task SaveOllamaUrlAsync(string url);
|
||||
Task SaveLlamaCppUrlAsync(string url);
|
||||
Task SaveBackendAsync(string backend);
|
||||
void SetBackend(string backend);
|
||||
Task SaveRagModelAsync(string model);
|
||||
Task SaveStyleModelAsync(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<string?> GetLektorSystemPromptAsync();
|
||||
|
|
@ -86,10 +93,24 @@ public interface ISettingsService
|
|||
Task SaveChatPromptAsync(string prompt);
|
||||
Task<double> GetChatTempAsync();
|
||||
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 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<UserEntity> GetCurrentUserAsync();
|
||||
void SetCurrentUser(int userId);
|
||||
|
|
|
|||
137
Services/LlmWikiService.cs
Normal file
137
Services/LlmWikiService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
225
Services/OllamaCloudBackend.cs
Normal file
225
Services/OllamaCloudBackend.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -18,18 +18,19 @@ public class OllamaService : IOllamaService
|
|||
_opState = opState;
|
||||
}
|
||||
|
||||
private ILLMBackend ActiveBackend =>
|
||||
_backends.FirstOrDefault(b => b.Name == _settings.Backend) ?? _backends.First();
|
||||
|
||||
private readonly object _warmLock = new();
|
||||
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;
|
||||
|
||||
var reachable = await ActiveBackend.TestConnectionAsync();
|
||||
var reachable = await backend.TestConnectionAsync();
|
||||
if (reachable)
|
||||
{
|
||||
await WarmupModelAsync();
|
||||
|
|
@ -62,7 +63,7 @@ public class OllamaService : IOllamaService
|
|||
for (int i = 0; i < 30; i++)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
reachable = await ActiveBackend.TestConnectionAsync();
|
||||
reachable = await backend.TestConnectionAsync();
|
||||
if (reachable)
|
||||
{
|
||||
_opState.AppendThought(" Ollama ist bereit");
|
||||
|
|
@ -83,21 +84,28 @@ public class OllamaService : IOllamaService
|
|||
_warmupAttempted = true;
|
||||
}
|
||||
|
||||
var modelsToWarm = new[] { _settings.StyleModel, _settings.EmbedModel }
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m))
|
||||
.Distinct()
|
||||
var modelsToWarm = new[]
|
||||
{
|
||||
(Model: _settings.StyleModel, Backend: _settings.StyleBackend),
|
||||
(Model: _settings.EmbedModel, Backend: _settings.RagBackend)
|
||||
}
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m.Model))
|
||||
.DistinctBy(m => m.Model)
|
||||
.ToList();
|
||||
|
||||
if (modelsToWarm.Count == 0) return;
|
||||
|
||||
_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
|
||||
{
|
||||
var warmCts = new CancellationTokenSource(TimeSpan.FromMinutes(3));
|
||||
var dummy = await ActiveBackend.ChatAsync(model, " ", null, 0.0);
|
||||
var dummy = await backend.ChatAsync(model, " ", null, 0.0);
|
||||
_opState.AppendThought(" ✓");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -115,112 +123,218 @@ public class OllamaService : IOllamaService
|
|||
|
||||
public async Task<(bool Success, long LatencyMs)> TestConnectionAsync()
|
||||
{
|
||||
var backend = GetBackend(null);
|
||||
await EnsureRunningAsync();
|
||||
_opState.SetThought($"Teste Verbindung zu {ActiveBackend.Name}...");
|
||||
_opState.SetThought($"Teste Verbindung zu {backend.Name}...");
|
||||
var sw = Stopwatch.StartNew();
|
||||
var result = await ActiveBackend.TestConnectionAsync();
|
||||
var result = await backend.TestConnectionAsync();
|
||||
sw.Stop();
|
||||
_opState.AppendThought(result ? " OK" : " Fehlgeschlagen");
|
||||
return (result, result ? sw.ElapsedMilliseconds : 0);
|
||||
}
|
||||
|
||||
public bool SupportsModelManagement => GetBackend(null).SupportsModelManagement;
|
||||
|
||||
public async Task<IEnumerable<LLM_Model>> GetLocalModelsAsync()
|
||||
{
|
||||
var backend = GetBackend(null);
|
||||
await EnsureRunningAsync();
|
||||
_opState.SetThought($"Lade Modelle von {ActiveBackend.Name}...");
|
||||
var models = await ActiveBackend.GetModelsAsync();
|
||||
_opState.SetThought($"Lade Modelle von {backend.Name}...");
|
||||
var models = await backend.GetModelsAsync();
|
||||
_opState.AppendThought($" {models.Count()} gefunden");
|
||||
return models;
|
||||
}
|
||||
|
||||
public async Task<bool> TestEmbeddingAsync(string model)
|
||||
public async Task<IEnumerable<LLM_Model>> GetModelsForBackendAsync(string backendName)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
return ActiveBackend.TestEmbedding(model);
|
||||
var backend = GetBackend(backendName);
|
||||
if (backend.Name == "Ollama")
|
||||
await EnsureRunningAsync();
|
||||
_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();
|
||||
return await ActiveBackend.GetEmbeddingAsync(text, _settings.EmbedModel);
|
||||
var backend = GetBackend(backendName);
|
||||
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)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
return await Chat(_settings.SpellingModel, text, _settings.SpellingPrompt, _settings.SpellingTemp);
|
||||
_opState.SetBusy(true);
|
||||
_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)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
return await Chat(_settings.StyleModel, text, _settings.LektorSystemPrompt, _settings.LektorTemp);
|
||||
_opState.SetBusy(true);
|
||||
_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)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
var prompt = _settings.GetFactCheckPrompt(question, facts);
|
||||
return await Chat(_settings.EmbedModel, prompt, temperature: _settings.FactCheckTemp);
|
||||
}
|
||||
|
||||
public async Task<bool> ModelExistsAsync(string model)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
return await ActiveBackend.ModelExistsAsync(model);
|
||||
}
|
||||
|
||||
public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
return await ActiveBackend.GetModelContextWindowAsync(model);
|
||||
}
|
||||
|
||||
public async Task<long> MeasureLatencyAsync(string model)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
return await ActiveBackend.MeasureLatencyAsync(model);
|
||||
}
|
||||
|
||||
public async Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
_opState.SetThought($"{ActiveBackend.Name}: Sende Anfrage an {model}...");
|
||||
var result = await ActiveBackend.ChatAsync(model, userMessage, systemPrompt, temperature);
|
||||
_opState.AppendThought(" Fertig");
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task ChatStream(string model, string userMessage, Func<string, Task> onToken, string? systemPrompt = null, double? temperature = null)
|
||||
{
|
||||
await EnsureRunningAsync();
|
||||
_opState.SetThought($"{ActiveBackend.Name}: Verbinde mit Modell {model}...");
|
||||
|
||||
int tokenCount = 0;
|
||||
await ActiveBackend.ChatStreamAsync(model, userMessage, async chunk =>
|
||||
_opState.SetBusy(true);
|
||||
_opState.SetThought("Faktencheck läuft...");
|
||||
try
|
||||
{
|
||||
tokenCount++;
|
||||
_opState.AppendThought(chunk);
|
||||
await onToken(chunk);
|
||||
}, systemPrompt, temperature);
|
||||
await EnsureRunningAsync(_settings.RagBackend);
|
||||
var prompt = _settings.GetFactCheckPrompt(question, facts);
|
||||
return await Chat(_settings.EmbedModel, prompt, temperature: _settings.FactCheckTemp, backendName: _settings.RagBackend);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_opState.SetThought("Fehler bei Faktencheck");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_opState.SetBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
_opState.AppendThought($" Fertig ({tokenCount} Token)");
|
||||
public async Task<bool> ModelExistsAsync(string model, string? backendName = null)
|
||||
{
|
||||
var backend = GetBackend(backendName);
|
||||
await EnsureRunningAsync(backendName);
|
||||
return await backend.ModelExistsAsync(model);
|
||||
}
|
||||
|
||||
public async Task<(bool Success, int ContextWindow)> GetModelContextWindowAsync(string model, string? backendName = null)
|
||||
{
|
||||
var backend = GetBackend(backendName);
|
||||
await EnsureRunningAsync(backendName);
|
||||
return await backend.GetModelContextWindowAsync(model);
|
||||
}
|
||||
|
||||
public async Task<long> MeasureLatencyAsync(string model, string? backendName = null)
|
||||
{
|
||||
var backend = GetBackend(backendName);
|
||||
await EnsureRunningAsync(backendName);
|
||||
return await backend.MeasureLatencyAsync(model);
|
||||
}
|
||||
|
||||
public async Task<string> Chat(string model, string userMessage, string? systemPrompt = null, double? temperature = null, string? backendName = null)
|
||||
{
|
||||
var backend = GetBackend(backendName);
|
||||
_opState.SetBusy(true);
|
||||
_opState.SetThought($"{backend.Name}: Sende Anfrage an {model}...");
|
||||
try
|
||||
{
|
||||
await EnsureRunningAsync(backendName);
|
||||
var result = await backend.ChatAsync(model, userMessage, systemPrompt, temperature);
|
||||
_opState.AppendThought(" Fertig");
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_opState.SetThought($"Fehler bei {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;
|
||||
await backend.ChatStreamAsync(model, userMessage, async chunk =>
|
||||
{
|
||||
tokenCount++;
|
||||
_opState.AppendThought(chunk);
|
||||
await onToken(chunk);
|
||||
}, systemPrompt, temperature);
|
||||
|
||||
_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)
|
||||
{
|
||||
var backend = GetBackend(null);
|
||||
await EnsureRunningAsync();
|
||||
_opState.SetThought($"Lade Modell {modelName}...");
|
||||
var result = await ActiveBackend.PullModelAsync(modelName, progress);
|
||||
var result = await backend.PullModelAsync(modelName, progress);
|
||||
_opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen");
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteModelAsync(string modelName)
|
||||
{
|
||||
var backend = GetBackend(null);
|
||||
await EnsureRunningAsync();
|
||||
_opState.SetThought($"Lösche Modell {modelName}...");
|
||||
var result = await ActiveBackend.DeleteModelAsync(modelName);
|
||||
var result = await backend.DeleteModelAsync(modelName);
|
||||
_opState.AppendThought(result ? " Erfolgreich" : " Fehlgeschlagen");
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ public class OperationState
|
|||
Notify();
|
||||
}
|
||||
|
||||
public void SetBusy(bool busy)
|
||||
{
|
||||
IsBusy = busy;
|
||||
Notify();
|
||||
}
|
||||
|
||||
public void SetThought(string text)
|
||||
{
|
||||
ThoughtText = text;
|
||||
|
|
|
|||
38
Services/PluginManager.cs
Normal file
38
Services/PluginManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using AuthorBuddy.Web.Data;
|
||||
using AuthorBuddy.Web.Models;
|
||||
using LiteDB;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
|
@ -9,11 +10,13 @@ public class RagService : IRagService
|
|||
{
|
||||
private readonly ILiteDatabase _db;
|
||||
private readonly IOllamaService _ollama;
|
||||
private readonly OllamaSettings _settings;
|
||||
|
||||
public RagService(ILiteDatabase db, IOllamaService ollama)
|
||||
public RagService(ILiteDatabase db, IOllamaService ollama, OllamaSettings settings)
|
||||
{
|
||||
_db = db;
|
||||
_ollama = ollama;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var queryVec = await _ollama.GetEmbeddingAsync(query);
|
||||
var queryVec = await _ollama.GetEmbeddingAsync(query, _settings.RagBackend);
|
||||
if (queryVec.Length == 0)
|
||||
return new List<RagSearchResult>();
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,16 @@ namespace AuthorBuddy.Web.Services;
|
|||
public class SettingsService : ISettingsService
|
||||
{
|
||||
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 KeyBackend = "llm_backend";
|
||||
private const string KeyRagModel = "ollama_rag_model";
|
||||
private const string KeyRagBackend = "ollama_rag_backend";
|
||||
private const string KeyStyleModel = "ollama_style_model";
|
||||
private const string KeyStyleBackend = "ollama_style_backend";
|
||||
private const string KeySpellingModel = "ollama_spelling_model";
|
||||
private const string KeySpellingBackend = "ollama_spelling_backend";
|
||||
private const string KeyTheme = "theme";
|
||||
private const string KeyLektorPrompt = "ollama_lektor_prompt";
|
||||
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 KeyChatPrompt = "chat_prompt";
|
||||
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 KeyLastProjectId = "last_project_id";
|
||||
|
|
@ -169,12 +177,16 @@ public class SettingsService : ISettingsService
|
|||
public Task<string?> GetOllamaUrlAsync() => Task.FromResult(GetGlobal(KeyOllamaUrl));
|
||||
public Task<string?> GetLlamaCppUrlAsync() => Task.FromResult(GetGlobal(KeyLlamaCppUrl));
|
||||
public Task<string?> GetBackendAsync() => Task.FromResult(GetGlobal(KeyBackend));
|
||||
|
||||
public Task<string?> GetRagModelAsync() => Task.FromResult(GetGlobal(KeyRagModel));
|
||||
public Task<string?> GetStyleModelAsync() => Task.FromResult(GetGlobal(KeyStyleModel));
|
||||
public Task<string?> GetLektorSystemPromptAsync() => Task.FromResult(Get(KeyLektorPrompt));
|
||||
public Task<string?> GetFactCheckTemplateAsync() => Task.FromResult(Get(KeyFactCheckTemplate));
|
||||
public Task<string?> GetSpellingPromptAsync() => Task.FromResult(Get(KeySpellingPrompt));
|
||||
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?> GetHookFinderPromptAsync() => Task.FromResult(Get(KeyHookFinder));
|
||||
public Task<string?> GetEli5PromptAsync() => Task.FromResult(Get(KeyEli5));
|
||||
|
|
@ -217,6 +229,24 @@ public class SettingsService : ISettingsService
|
|||
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)
|
||||
{
|
||||
SetGlobal(KeyLlamaCppUrl, url);
|
||||
|
|
@ -224,6 +254,11 @@ public class SettingsService : ISettingsService
|
|||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void SetBackend(string backend)
|
||||
{
|
||||
_ollamaConfig.Backend = backend;
|
||||
}
|
||||
|
||||
public Task SaveBackendAsync(string backend)
|
||||
{
|
||||
SetGlobal(KeyBackend, backend);
|
||||
|
|
@ -245,6 +280,27 @@ public class SettingsService : ISettingsService
|
|||
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)
|
||||
{
|
||||
Set(KeyLektorPrompt, prompt);
|
||||
|
|
@ -386,6 +442,34 @@ public class SettingsService : ISettingsService
|
|||
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)
|
||||
{
|
||||
Set(KeyTechLastenheft, prompt);
|
||||
|
|
@ -604,6 +688,35 @@ public class SettingsService : ISettingsService
|
|||
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)
|
||||
{
|
||||
schemaSetting.Value = currentVersion.ToString();
|
||||
|
|
@ -617,7 +730,12 @@ public class SettingsService : ISettingsService
|
|||
|
||||
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)
|
||||
{
|
||||
|
|
@ -629,8 +747,13 @@ public class SettingsService : ISettingsService
|
|||
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 == 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 == KeyStyleBackend: _ollamaConfig.StyleBackend = 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()
|
||||
{
|
||||
MigrateSchema();
|
||||
LoadProjectSettingsAsync();
|
||||
|
||||
var settings = _db.GetCollection<SettingEntity>("settings");
|
||||
|
||||
|
|
@ -700,8 +824,13 @@ public class SettingsService : ISettingsService
|
|||
(KeyLlamaCppUrl, _ollamaConfig.LlamaCppUrl),
|
||||
(KeyBackend, _ollamaConfig.Backend),
|
||||
(KeyRagModel, _ollamaConfig.EmbedModel),
|
||||
(KeyRagBackend, _ollamaConfig.RagBackend),
|
||||
(KeyStyleModel, _ollamaConfig.StyleModel),
|
||||
(KeyStyleBackend, _ollamaConfig.StyleBackend),
|
||||
(KeySpellingModel, _ollamaConfig.SpellingModel),
|
||||
(KeySpellingBackend, _ollamaConfig.SpellingBackend),
|
||||
(KeyOllamaCloudUrl, _ollamaConfig.OllamaCloudUrl),
|
||||
(KeyOllamaCloudApiKey, _ollamaConfig.OllamaCloudApiKey),
|
||||
};
|
||||
|
||||
foreach (var entry in globalEntries)
|
||||
|
|
@ -709,17 +838,7 @@ public class SettingsService : ISettingsService
|
|||
var existing = settings.FindOne(s =>
|
||||
s.Key == entry.Key && s.UserId == null && s.ProjectId == 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
|
||||
if (existing == null)
|
||||
{
|
||||
settings.Insert(new SettingEntity
|
||||
{
|
||||
|
|
@ -827,4 +946,16 @@ public class SettingsService : ISettingsService
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
395
wwwroot/app.css
395
wwwroot/app.css
|
|
@ -797,6 +797,22 @@ button.primary {
|
|||
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 {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
|
@ -1237,6 +1253,37 @@ button.primary {
|
|||
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 {
|
||||
color: #e74c3c;
|
||||
|
|
@ -1712,6 +1759,116 @@ button.primary {
|
|||
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 {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
|
@ -1765,10 +1922,59 @@ button.primary {
|
|||
overflow: auto;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.mobile-sidebar-backdrop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
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 {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
@ -1892,3 +2098,192 @@ button.primary {
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue