Forge
csharp3407750f
1using AgentForge;
2using AgentForge.Abstractions;
3using AgentForge.Plugin.Sdk;
4using AgentForge.Data;
5using AgentForge.Data.Entities;
6using AgentForge.Plugin.View.Components;
7using AgentForge.Plugin.View.Components.Code;
8using AgentForge.Plugin.View.Components.Docs;
9using AgentForge.Plugin.View.Components.Search;
10using AgentForge.Plugin.View.Pages;
11using AgentForge.Services;
12using Microsoft.AspNetCore.Mvc;
13
14namespace AgentForge.Plugin.View;
15
16public static class ForgeViewEndpoints
17{
18 public static WebApplication MapForgeViews(this WebApplication app)
19 {
20 app.MapGet("/view", RenderHomeView);
21 app.MapGet("/view/forge-slash.js", ServeForgeSlashScript);
22 app.MapGet($"/view/repos/{ForgeRepoRouting.Pattern}", RenderRepoView);
23 app.MapGet($"/view/repos/{ForgeRepoRouting.Pattern}/tree", RenderTreeView);
24 app.MapGet($"/view/repos/{ForgeRepoRouting.Pattern}/blob", RenderBlobView);
25 app.MapGet($"/view/repos/{ForgeRepoRouting.Pattern}/docs/{{*path}}", RenderDocsView);
26 app.MapGet($"/view/repos/{ForgeRepoRouting.Pattern}/docs", RenderDocsView);
27 app.MapGet($"/view/repos/{ForgeRepoRouting.Pattern}/search", RenderSearchView);
28 return app;
29 }
30
31 private static IResult RenderHomeView(
32 string? group,
33 string? groupStack,
34 string? groupStackRows,
35 ForgeRepository repository,
36 GitRepoService git,
37 ForgeRepoAccessService access,
38 HttpContext http,
39 [FromServices] IForgeOrgCatalogGrouping? grouping = null,
40 [FromServices] IForgeMainPageCatalogBody? mainPageBody = null)
41 {
42 var principal = access.Resolve(http);
43 var repos = access.FilterReadable(repository.ListRepos(), principal)
44 .Select(repo => ForgePluginResponses.ToRepoResponse(repo, git, repository))
45 .ToList();
46
47 ForgeOrgCatalogViewContext? groupingContext = null;
48 IReadOnlyList<ForgeOrgCatalogRepoItem>? catalogItems = null;
49 if (grouping is not null || mainPageBody is not null)
50 {
51 var activeGroupPath = string.IsNullOrWhiteSpace(group)
52 ? null
53 : ForgeRepoGroupPath.NormalizeRoute(group);
54 var stack = grouping?.ParseGroupStack(groupStack) ?? [];
55 var editorRowCount = grouping?.ParseEditorRowCount(groupStackRows, stack.Count) ?? 0;
56 groupingContext = new ForgeOrgCatalogViewContext("/view", activeGroupPath, stack, editorRowCount);
57 catalogItems = repos
58 .Select(repo => new ForgeOrgCatalogRepoItem(
59 repo.Name,
60 repo.Visibility,
61 repo.DriveMode,
62 ForgeRepoNames.ToUrlPath(repo.Name),
63 repo.Org,
64 repo.Group,
65 repo.CreatedAt))
66 .ToList();
67 }
68
69 var auth = http.RequestServices.GetService<ForgeAuthService>();
70 var ctx = new ForgeViewPageContext("", [], Viewer: ForgeViewViewer.Resolve(http, auth));
71 var body = ForgeHomePage.Render(repos, groupingContext, grouping, mainPageBody, catalogItems);
72 return ForgeViewPresenter.Html(ForgeViewLayout.Page("Agent Forge", body, ctx));
73 }
74
75 private static IResult RenderRepoView(
76 string name,
77 string? tab,
78 ForgeRepository repository,
79 GitRepoService git,
80 HttpContext http)
81 {
82 var repoName = ForgeRepoNames.NormalizeRoute(name);
83 var repo = ForgePluginEndpoints.RequireRepo(repository, repoName, http);
84 var response = ForgePluginResponses.ToRepoResponse(repo, git, repository);
85 var catalog = http.RequestServices.GetRequiredService<IForgeViewContributorCatalog>();
86 var tabBodies = http.RequestServices.GetRequiredService<IForgeRepoTabBodyCatalog>();
87 var activeTab = ForgeGitViewHelpers.NormalizeRepoTab(tab, catalog);
88 var body = ForgeRepoPage.Render(
89 repo,
90 response,
91 repoName,
92 activeTab,
93 git,
94 catalog,
95 tabBodies,
96 http.RequestServices);
97 return ForgeViewPresenter.RepoPage(repo.Name, repo, activeTab, repository, body, http);
98 }
99
100 private static IResult RenderSearchView(
101 string name,
102 string? q,
103 ForgeRepository repository,
104 GitRepoService git,
105 HttpContext http)
106 {
107 var repo = ForgePluginEndpoints.RequireRepo(repository, name, http);
108 var body = ForgeHtml.Fragment(
109 ForgeViewTabPanel.Render(RenderSearchPanel(repo.Name, q ?? "", repo, repository, git)),
110 ForgeViewStatusBar.Render());
111 return ForgeViewPresenter.RepoPage($"Search · {repo.Name}", repo, "code", repository, body, http, q);
112 }
113
114 private static string RenderSearchPanel(
115 string name,
116 string query,
117 ForgeRepoEntity repo,
118 ForgeRepository repository,
119 GitRepoService git) =>
120 ForgeViewSearchPage.Render(name, query, repo, repository, git);
121
122 private static IResult RenderTreeView(
123 string name,
124 string? branch,
125 string? path,
126 string? iop,
127 ForgeRepository repository,
128 GitRepoService git,
129 MergeRequestService mergeRequests,
130 HttpContext http)
131 {
132 var repo = ForgePluginEndpoints.RequireRepo(repository, name, http);
133 if (!git.BareRepoExists(repo.Name))
134 return ForgeViewPresenter.RepoPage(repo.Name, repo, "code", repository, ForgeHtml.P("error", ForgeHtml.Text("No git repository yet.")), http);
135
136 var activeBranch = ForgeGitViewHelpers.ResolveBranch(git, repo.Name, branch);
137 var iopTab = ForgeIopViewRenderer.NormalizeIopTab(iop);
138 var entries = git.ListDirectory(repo.Name, activeBranch, path);
139 var treeMain = ForgeCodeBrowsePage.RenderTree(repo.Name, activeBranch, path, git, entries);
140
141 var sidebar = new StringBuilder();
142 var branchMr = ForgeGitViewHelpers.FindOpenMrForBranch(repository, repo.Id, activeBranch);
143 ForgeCodeBrowsePage.AppendBranchIopPanel(sidebar, repo.Name, activeBranch, branchMr, repo, repository, mergeRequests, iopTab, blobPath: null);
144
145 var body = ForgeHtml.Fragment(
146 ForgeViewTabPanel.Render(ForgeViewSplitLayout.Render(treeMain, sidebar.ToString())),
147 ForgeViewStatusBar.Render());
148 return ForgeViewPresenter.RepoPage($"{repo.Name} / tree", repo, "code", repository, body, http);
149 }
150
151 private static IResult RenderBlobView(
152 string name,
153 string? branch,
154 string? path,
155 int? line,
156 int? line_end,
157 string? iop,
158 ForgeRepository repository,
159 GitRepoService git,
160 MergeRequestService mergeRequests,
161 HttpContext http)
162 {
163 var repo = ForgePluginEndpoints.RequireRepo(repository, name, http);
164 if (string.IsNullOrWhiteSpace(path))
165 return Results.Redirect($"/view/repos/{ForgeRepoNames.ToUrlPath(repo.Name)}/tree");
166
167 var activeBranch = ForgeGitViewHelpers.ResolveBranch(git, repo.Name, branch);
168 var iopTab = ForgeIopViewRenderer.NormalizeIopTab(iop);
169 var content = git.GetFileContent(repo.Name, activeBranch, path);
170 var tip = git.GetBranchTipCommit(repo.Name, activeBranch);
171
172 var fileHtml = ForgeViewFileContent.Render(content, path, line, line_end);
173 var main = ForgeCodeBrowsePage.RenderBlobMain(
174 repo.Name,
175 activeBranch,
176 path,
177 git.ListBranches(repo.Name),
178 tip,
179 fileHtml,
180 line);
181
182 var sidebar = new StringBuilder();
183 var branchMr = ForgeGitViewHelpers.FindOpenMrForBranch(repository, repo.Id, activeBranch);
184 ForgeCodeBrowsePage.AppendBranchIopPanel(sidebar, repo.Name, activeBranch, branchMr, repo, repository, mergeRequests, iopTab, path);
185
186 var body = ForgeHtml.Fragment(
187 ForgeViewTabPanel.Render(ForgeViewSplitLayout.Render(main, sidebar.ToString())),
188 ForgeViewStatusBar.Render());
189 return ForgeViewPresenter.RepoPage($"{repo.Name} / {path}", repo, "code", repository, body, http);
190 }
191
192 private static IResult RenderDocsView(
193 string name,
194 string? path,
195 ForgeRepository repository,
196 GitRepoService git,
197 HttpContext http)
198 {
199 var repo = ForgePluginEndpoints.RequireRepo(repository, name, http);
200 if (!git.BareRepoExists(repo.Name))
201 return ForgeViewPresenter.RepoPage(repo.Name, repo, "code", repository, ForgeHtml.P("error", ForgeHtml.Text("No git repository yet.")), http);
202
203 var branch = git.GetDefaultBranch(repo.Name)
204 ?? throw new ForgeApiException(404, "Repository has no branches.");
205 var docsPath = ForgeGitViewHelpers.NormalizeDocsPath(path);
206 string panel;
207
208 try
209 {
210 if (string.IsNullOrWhiteSpace(docsPath))
211 {
212 var entries = git.ListDirectory(repo.Name, branch, "docs");
213 panel = ForgeViewDocsIndex.Render(repo.Name, branch, entries);
214 }
215 else
216 {
217 var fullPath = ForgeGitViewHelpers.ToDocsRepoPath(docsPath);
218 if (fullPath.Contains('.', StringComparison.Ordinal))
219 {
220 var content = git.GetFileContent(repo.Name, branch, fullPath);
221 panel = ForgeViewDocFile.Render(repo.Name, branch, fullPath, content);
222 }
223 else
224 {
225 var entries = git.ListDirectory(repo.Name, branch, fullPath);
226 panel = ForgeViewDocsIndex.Render(repo.Name, branch, entries);
227 }
228 }
229 }
230 catch (ForgeApiException ex) when (ex.StatusCode == 404)
231 {
232 panel = ForgeHtml.Fragment(
233 ForgeHtml.P("error", ForgeHtml.Text("Documentation path not found.")),
234 ForgeHtml.P("meta", ForgeHtml.A(ForgeViewLinks.DocsHref(repo.Name), "Back to docs")));
235 }
236
237 var body = ForgeHtml.Fragment(
238 ForgeViewTabPanel.Render(panel),
239 ForgeViewStatusBar.Render());
240 return ForgeViewPresenter.RepoPage($"{repo.Name} / docs", repo, "code", repository, body, http);
241 }
242
243 private static IResult ServeForgeSlashScript() => ForgeViewSlashAssets.ServeSlashScript();
244}
245
View only · write via MCP/CIDE