Compare commits

...

4 Commits

Author SHA1 Message Date
b20b657008 feat: trim llm response 2025-08-07 12:25:39 +02:00
0c57663f5f feat: add tool disclaimer for issue generation 2025-03-10 10:06:08 +01:00
48cf3bb342 chore: rename file 2025-03-07 20:31:18 +01:00
eda0f87fe3 fix(gitea): project listing 2025-03-07 20:31:07 +01:00
6 changed files with 39 additions and 43 deletions

View File

@ -270,18 +270,18 @@ func (f *Forge) ListProjects(ctx context.Context) ([]*model.Project, error) {
return nil, errors.WithStack(err) return nil, errors.WithStack(err)
} }
page = res.NextPage
if res.NextPage == 0 {
break
}
for _, r := range repositories { for _, r := range repositories {
projects = append(projects, &model.Project{ projects = append(projects, &model.Project{
ID: strconv.FormatInt(r.ID, 10), ID: strconv.FormatInt(r.ID, 10),
Name: r.FullName, Name: r.FullName,
}) })
} }
if res.NextPage == 0 {
break
}
page = res.NextPage
} }
return projects, nil return projects, nil

View File

@ -173,8 +173,12 @@ func (m *ForgeManager) GeneratePullRequest(ctx context.Context, user *model.User
body := res.Message().Content() body := res.Message().Content()
body = strings.TrimSpace(body)
body = strings.TrimPrefix(body, "```markdown")
body = strings.TrimSuffix(body, "```")
messages = append(messages, res.Message()) messages = append(messages, res.Message())
messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this issue. Keep it descriptive, simple and short. Do not write anything else.")) messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this pull request. Keep it descriptive, simple and short. Do not write anything else. Use the same language."))
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...)) res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
if err != nil { if err != nil {
@ -184,7 +188,7 @@ func (m *ForgeManager) GeneratePullRequest(ctx context.Context, user *model.User
title := toTitle(res.Message().Content()) title := toTitle(res.Message().Content())
messages = append(messages, res.Message()) messages = append(messages, res.Message())
messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings.")) messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings. Use the same language."))
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...)) res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
if err != nil { if err != nil {
@ -300,8 +304,12 @@ func (m *ForgeManager) GenerateIssue(ctx context.Context, user *model.User, proj
body := res.Message().Content() body := res.Message().Content()
body = strings.TrimSpace(body)
body = strings.TrimPrefix(body, "```markdown")
body = strings.TrimSuffix(body, "```")
messages = append(messages, res.Message()) messages = append(messages, res.Message())
messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this issue. Keep it descriptive, simple and short. Do not write anything else.")) messages = append(messages, llm.NewMessage(llm.RoleUser, "Generate a title for this issue. Keep it descriptive, simple and short. Do not write anything else. Use the same language."))
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...)) res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
if err != nil { if err != nil {
@ -311,7 +319,7 @@ func (m *ForgeManager) GenerateIssue(ctx context.Context, user *model.User, proj
title := toTitle(res.Message().Content()) title := toTitle(res.Message().Content())
messages = append(messages, res.Message()) messages = append(messages, res.Message())
messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings.")) messages = append(messages, llm.NewMessage(llm.RoleUser, "Give me a list of questions as Markdown that could help clarify the request. Only write the list without additional headings. Use the same language."))
res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...)) res, err = m.llmClient.ChatCompletion(ctx, llm.WithMessages(messages...))
if err != nil { if err != nil {

View File

@ -2,10 +2,6 @@
La description du besoin, écrite de manière claire et concise. La description du besoin, écrite de manière claire et concise.
## Détails d'implémentation
Liste des actions envisagées pour réaliser une évolution ou un correctif pour remplir le besoin.
## Tests d'acceptation ## Tests d'acceptation
Liste des critères d'évaluation pour la validation finale des développements réalisés dans le cadre de la demande. Liste des critères d'évaluation pour la validation finale des développements réalisés dans le cadre de la demande.

View File

@ -133,9 +133,6 @@ templ IssuePage(vmodel IssuePageVModel) {
<summary class="is-clickable">Paramètres avancés</summary> <summary class="is-clickable">Paramètres avancés</summary>
@common.FormTextarea( @common.FormTextarea(
vmodel.SummaryForm, "issue-template", "template", "Surcharger le modèle de demande", vmodel.SummaryForm, "issue-template", "template", "Surcharger le modèle de demande",
common.WithTextareaAttrs(
"hx-on:change", "onIssueTemplateChange(event)",
),
) )
</details> </details>
<div class="buttons is-right"> <div class="buttons is-right">
@ -153,6 +150,17 @@ templ IssuePage(vmodel IssuePageVModel) {
<form action={ common.CurrentURL(ctx) } method="post" hx-disabled-elt="textarea, input, select, button" hx-indicator="#generation-progress"> <form action={ common.CurrentURL(ctx) } method="post" hx-disabled-elt="textarea, input, select, button" hx-indicator="#generation-progress">
@common.FormField(vmodel.IssueForm, "issue-title", "title", "Titre") @common.FormField(vmodel.IssueForm, "issue-title", "title", "Titre")
@common.FormTextarea(vmodel.IssueForm, "issue-body", "body", "Corps") @common.FormTextarea(vmodel.IssueForm, "issue-body", "body", "Corps")
if value, ok := vmodel.IssueForm.Field("body").Get("value"); ok && value != nil {
<article class="message is-warning mt-5">
<div class="message-header">
<p><span class="icon"><i class="fa fa-exclamation-triangle"></i></span>Avertissement</p>
</div>
<div class="message-body content">
<p><b>ClearCase n'est qu'un outil.</b> Il est peu probable que le ticket généré soit parfait dès le premier essai, et il nécessitera sans doute des ajustements de votre part.</p>
<p>Assurez-vous de bien relire votre ticket et de supprimer les éléments superflus ou hors sujet avant de le créer.</p>
</div>
</article>
}
<div class="buttons is-right"> <div class="buttons is-right">
<button type="submit" class="button is-info is-large"> <button type="submit" class="button is-info is-large">
<span class="icon"> <span class="icon">
@ -193,12 +201,6 @@ templ IssuePage(vmodel IssuePageVModel) {
sessionStorage.setItem(`summary-${projectId}`, summary); sessionStorage.setItem(`summary-${projectId}`, summary);
} }
function onIssueTemplateChange(evt) {
const issueTemplate = evt.currentTarget.value;
const projectId = document.getElementById("issue-project").value;
localStorage.setItem(`issue-template-${projectId}`, issueTemplate);
}
function savePreferredProject() { function savePreferredProject() {
const projectId = document.getElementById("issue-project").value; const projectId = document.getElementById("issue-project").value;
localStorage.setItem(`preferred-project`, projectId); localStorage.setItem(`preferred-project`, projectId);
@ -226,22 +228,9 @@ templ IssuePage(vmodel IssuePageVModel) {
summaryTextarea.value = savedSummary; summaryTextarea.value = savedSummary;
} }
function restoreIssueTemplate() {
const issueTemplateTextarea = document.getElementById("issue-template");
if (!issueTemplateTextarea) return;
const issueTemplate = issueTemplateTextarea.value;
if (issueTemplate !== "") return;
const projectId = document.getElementById("issue-project").value;
if (!projectId) return;
const savedIssueTemplate = localStorage.getItem(`issue-template-${projectId}`);
if (!savedIssueTemplate) return;
issueTemplateTextarea.value = savedIssueTemplate;
}
htmx.onLoad(function(){ htmx.onLoad(function(){
restoreLastSummary(); restoreLastSummary();
restorePreferredProject(); restorePreferredProject();
restoreIssueTemplate();
}) })
</script> </script>
} }

View File

@ -205,9 +205,6 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
} }
templ_7745c5c3_Err = common.FormTextarea( templ_7745c5c3_Err = common.FormTextarea(
vmodel.SummaryForm, "issue-template", "template", "Surcharger le modèle de demande", vmodel.SummaryForm, "issue-template", "template", "Surcharger le modèle de demande",
common.WithTextareaAttrs(
"hx-on:change", "onIssueTemplateChange(event)",
),
).Render(ctx, templ_7745c5c3_Buffer) ).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
@ -233,14 +230,20 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-rocket\"></i></span> <span>Créer</span></button></div></form></div></div><progress id=\"generation-progress\" class=\"htmx-indicator progress\"></progress> ") if value, ok := vmodel.IssueForm.Field("body").Get("value"); ok && value != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<article class=\"message is-warning mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-exclamation-triangle\"></i></span>Avertissement</p></div><div class=\"message-body content\"><p><b>ClearCase n'est qu'un outil.</b> Il est peu probable que le ticket généré soit parfait dès le premier essai, et il nécessitera sans doute des ajustements de votre part.</p><p>Assurez-vous de bien relire votre ticket et de supprimer les éléments superflus ou hors sujet avant de le créer.</p></div></article>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<div class=\"buttons is-right\"><button type=\"submit\" class=\"button is-info is-large\"><span class=\"icon\"><i class=\"fa fa-rocket\"></i></span> <span>Créer</span></button></div></form></div></div><progress id=\"generation-progress\" class=\"htmx-indicator progress\"></progress> ")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if vmodel.IssueTips != "" { if vmodel.IssueTips != "" {
html := markdownToHTML(ctx, vmodel.IssueTips) html := markdownToHTML(ctx, vmodel.IssueTips)
if html != "" { if html != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "<article class=\"message is-info mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-lightbulb\"></i></span>Questionnements</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\"><div class=\"content\"><p>Utilisez ces quelques questions pour réfléchir aux éléments d'informations nécessaire à la bonne rédaction de votre demande:</p>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<article class=\"message is-info mt-5\"><div class=\"message-header\"><p><span class=\"icon\"><i class=\"fa fa-lightbulb\"></i></span>Questionnements</p><button class=\"delete\" aria-label=\"delete\" hx-on:click=\"onCloseMessage(this)\"></button></div><div class=\"message-body\"><div class=\"content\"><p>Utilisez ces quelques questions pour réfléchir aux éléments d'informations nécessaire à la bonne rédaction de votre demande:</p>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -248,13 +251,13 @@ func IssuePage(vmodel IssuePageVModel) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></article>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></div></article>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, " <script type=\"text/javascript\">\n\t\tfunction onCloseMessage(closeElement) {\n\t\t\tcloseElement.closest('.message').style.display = 'none';\n\t\t}\n\n\t\tfunction onSummaryChange(evt) {\n\t\t\tconst summary = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tsessionStorage.setItem(`summary-${projectId}`, summary);\n\t\t}\n\n\t\tfunction onIssueTemplateChange(evt) {\n\t\t\tconst issueTemplate = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tlocalStorage.setItem(`issue-template-${projectId}`, issueTemplate);\n\t\t}\n\n\t\tfunction savePreferredProject() {\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tlocalStorage.setItem(`preferred-project`, projectId);\n\t\t}\n\n\t\tfunction restorePreferredProject() {\n\t\t\tconst preferredProject = localStorage.getItem(`preferred-project`);\n\t\t\tif (!preferredProject) return;\n\t\t\tconst projectElement = document.getElementById(\"issue-project\");\n\t\t\tif (!projectElement) return;\n\t\t\tif (preferredProject === projectElement.value) return;\n\t\t\tprojectElement.value = preferredProject;\n\t\t\tprojectElement.dispatchEvent(new Event('change'));\n\t\t}\n\n\t\tfunction restoreLastSummary() {\n\t\t\tconst summaryTextarea = document.getElementById(\"issue-summary\");\n\t\t\tif (!summaryTextarea) return;\n const summary = summaryTextarea.value;\n\t\t\tif (summary !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedSummary = sessionStorage.getItem(`summary-${projectId}`);\n\t\t\tif (!savedSummary) return;\n\t\t\tsummaryTextarea.value = savedSummary;\n\t\t}\n\n\t\tfunction restoreIssueTemplate() {\n\t\t\tconst issueTemplateTextarea = document.getElementById(\"issue-template\");\n\t\t\tif (!issueTemplateTextarea) return;\n const issueTemplate = issueTemplateTextarea.value;\n\t\t\tif (issueTemplate !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedIssueTemplate = localStorage.getItem(`issue-template-${projectId}`);\n\t\t\tif (!savedIssueTemplate) return;\n\t\t\tissueTemplateTextarea.value = savedIssueTemplate;\n\t\t}\n\n\t\thtmx.onLoad(function(){\n\t\t\trestoreLastSummary();\n\t\t\trestorePreferredProject();\n\t\t\trestoreIssueTemplate();\n })\n\t\t</script>") templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " <script type=\"text/javascript\">\n\t\tfunction onCloseMessage(closeElement) {\n\t\t\tcloseElement.closest('.message').style.display = 'none';\n\t\t}\n\n\t\tfunction onSummaryChange(evt) {\n\t\t\tconst summary = evt.currentTarget.value;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tsessionStorage.setItem(`summary-${projectId}`, summary);\n\t\t}\n\n\t\tfunction savePreferredProject() {\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tlocalStorage.setItem(`preferred-project`, projectId);\n\t\t}\n\n\t\tfunction restorePreferredProject() {\n\t\t\tconst preferredProject = localStorage.getItem(`preferred-project`);\n\t\t\tif (!preferredProject) return;\n\t\t\tconst projectElement = document.getElementById(\"issue-project\");\n\t\t\tif (!projectElement) return;\n\t\t\tif (preferredProject === projectElement.value) return;\n\t\t\tprojectElement.value = preferredProject;\n\t\t\tprojectElement.dispatchEvent(new Event('change'));\n\t\t}\n\n\t\tfunction restoreLastSummary() {\n\t\t\tconst summaryTextarea = document.getElementById(\"issue-summary\");\n\t\t\tif (!summaryTextarea) return;\n const summary = summaryTextarea.value;\n\t\t\tif (summary !== \"\") return;\n\t\t\tconst projectId = document.getElementById(\"issue-project\").value;\n\t\t\tif (!projectId) return;\n\t\t\tconst savedSummary = sessionStorage.getItem(`summary-${projectId}`);\n\t\t\tif (!savedSummary) return;\n\t\t\tsummaryTextarea.value = savedSummary;\n\t\t}\n\n\t\thtmx.onLoad(function(){\n\t\t\trestoreLastSummary();\n\t\t\trestorePreferredProject();\n })\n\t\t</script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }