diff --git a/gobot/handlers/pull_request_create.go b/gobot/handlers/pull_request_create.go index a518701..baad31d 100644 --- a/gobot/handlers/pull_request_create.go +++ b/gobot/handlers/pull_request_create.go @@ -7,10 +7,8 @@ import ( "net/http" "os" "path" - "strings" "time" - "github.com/Pallinder/go-randomdata" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" @@ -73,6 +71,7 @@ const ( KnowledgeStr = "knowledge" YamlFileName = "qna.yaml" AttributionFileName = "attribution.txt" + branchNamePrefix = "bot-pr" ) func (prc *PullRequestCreateHandler) SkillPRHandler(w http.ResponseWriter, r *http.Request) { @@ -90,7 +89,7 @@ func (prc *PullRequestCreateHandler) SkillPRHandler(w http.ResponseWriter, r *ht PullRequestCreateHandler: *prc, } - prTask.branchName = prc.generateBranchName() + prTask.branchName = prc.generateBranchName(SkillStr) // Clone the taxonomy repo repo, err := prTask.cloneTaxonomyRepo() @@ -160,7 +159,7 @@ func (prc *PullRequestCreateHandler) KnowledgePRHandler(w http.ResponseWriter, r PullRequestCreateHandler: *prc, } - prTask.branchName = prc.generateBranchName() + prTask.branchName = prc.generateBranchName(KnowledgeStr) // Clone the taxonomy repo repo, err := prTask.cloneTaxonomyRepo() @@ -476,8 +475,7 @@ func (prt *PullRequestTask) createPullRequest(prType string, prTitle string, prD } // TODO: We need better way to generate branch name -func (prc *PullRequestCreateHandler) generateBranchName() string { - str := randomdata.City() - str = strings.ReplaceAll(str, " ", "_") - return str +func (prc *PullRequestCreateHandler) generateBranchName(prType string) string { + return fmt.Sprintf("%s-%s-%s", branchNamePrefix, prType, time.Now().Format("20060102150405")) + } diff --git a/gobot/handlers/yaml_util.go b/gobot/handlers/yaml_util.go index 67dc22a..a85286c 100644 --- a/gobot/handlers/yaml_util.go +++ b/gobot/handlers/yaml_util.go @@ -8,6 +8,10 @@ import ( "gopkg.in/yaml.v3" ) +const ( + MaxLen = 110 +) + type SkillYaml struct { Task_description string `yaml:"task_description"` Created_by string `yaml:"created_by"` @@ -35,7 +39,7 @@ type KnowledgeYaml struct { func (prc *PullRequestCreateHandler) generateKnowledgeYaml(requestData KnowledgePRRequest) (string, error) { knowledgeYaml := KnowledgeYaml{ - Task_description: strings.TrimSpace(requestData.Task_description), + Task_description: splitLongLines(strings.TrimSpace(requestData.Task_description), MaxLen), Created_by: strings.TrimSpace(requestData.Name), Domain: strings.TrimSpace(requestData.Domain), Seed_examples: []struct { @@ -61,12 +65,12 @@ func (prc *PullRequestCreateHandler) generateKnowledgeYaml(requestData Knowledge yaml.Node{ Kind: yaml.ScalarNode, Style: yaml.FoldedStyle, - Value: strings.TrimSpace(question), + Value: splitLongLines(strings.TrimSpace(question), MaxLen), }, yaml.Node{ Kind: yaml.ScalarNode, Style: yaml.FoldedStyle, - Value: strings.TrimSpace(requestData.Answers[i]), + Value: splitLongLines(strings.TrimSpace(requestData.Answers[i]), MaxLen), }, }) } @@ -89,7 +93,7 @@ func (prc *PullRequestCreateHandler) generateKnowledgeAttributionData(requestDat func (prc *PullRequestCreateHandler) generateSkillYaml(requestData SkillPRRequest) (string, error) { skillYaml := SkillYaml{ - Task_description: strings.TrimSpace(requestData.Task_description), + Task_description: splitLongLines(strings.TrimSpace(requestData.Task_description), MaxLen), Created_by: strings.TrimSpace(requestData.Name), Seed_examples: []struct { Question yaml.Node @@ -107,21 +111,20 @@ func (prc *PullRequestCreateHandler) generateSkillYaml(requestData SkillPRReques yaml.Node{ Kind: yaml.ScalarNode, Style: yaml.FoldedStyle, - Value: strings.TrimSpace(question), + Value: splitLongLines(strings.TrimSpace(question), MaxLen), }, yaml.Node{ Kind: yaml.ScalarNode, Style: yaml.FoldedStyle, - Value: strings.TrimSpace(requestData.Contexts[i]), + Value: splitLongLines(strings.TrimSpace(requestData.Contexts[i]), MaxLen), }, yaml.Node{ Kind: yaml.ScalarNode, Style: yaml.FoldedStyle, - Value: strings.TrimSpace(requestData.Answers[i]), + Value: splitLongLines(strings.TrimSpace(requestData.Answers[i]), MaxLen), }, }) } - // Generate the yaml file using new yaml encoder var buf bytes.Buffer yamlEncoder := yaml.NewEncoder(&buf) @@ -137,3 +140,25 @@ func (prc *PullRequestCreateHandler) generateSkillAttributionData(requestData Sk strings.TrimSpace(requestData.Title_work), strings.TrimSpace(requestData.Link_work), strings.TrimSpace(requestData.License_work), strings.TrimSpace(requestData.Creators)) } + +func splitLongLines(input string, maxLen int) string { + var result strings.Builder + lines := strings.Split(input, "\n") + + if len(lines) > 1 || len(lines[0]) > maxLen { + for _, line := range lines { + for len(line) > maxLen { + splitIndex := strings.LastIndexAny(line[:maxLen], " \t") + if splitIndex == -1 { + splitIndex = maxLen + } + result.WriteString(line[:splitIndex] + "\n") + line = line[splitIndex:] + line = strings.TrimLeft(line, " \t") + } + result.WriteString(line + "\n") + } + return result.String() + } + return input +} diff --git a/ui/apiserver/apiserver.go b/ui/apiserver/apiserver.go index 2ea8d06..aa78196 100644 --- a/ui/apiserver/apiserver.go +++ b/ui/apiserver/apiserver.go @@ -155,7 +155,7 @@ func (api *ApiServer) skillPRHandler(c *gin.Context) { } api.logger.Infof("Skill pull request response: %v", responseBody.String()) - c.JSON(http.StatusOK, gin.H{"msg": responseBody.String()}) + c.JSON(http.StatusCreated, gin.H{"msg": responseBody.String()}) } func (api *ApiServer) knowledgePRHandler(c *gin.Context) { @@ -199,7 +199,7 @@ func (api *ApiServer) knowledgePRHandler(c *gin.Context) { api.logger.Infof("Knowledge pull request response: %v", responseBody.String()) - c.JSON(http.StatusOK, gin.H{"msg": responseBody.String()}) + c.JSON(http.StatusCreated, gin.H{"msg": responseBody.String()}) } // Sent http post request using custom client with zero timeout @@ -305,8 +305,8 @@ func (api *ApiServer) setupRoutes(apiUser, apiPass string) { authorized.Use(AuthRequired(apiUser, apiPass)) authorized.GET("/jobs", api.getAllJobs) authorized.POST("/chat", api.chatHandler) - authorized.POST("/pr/skill", api.skillPRHandler) - authorized.POST("/pr/knowledge", api.knowledgePRHandler) + authorized.POST("pr/skill", api.skillPRHandler) + authorized.POST("pr/knowledge", api.knowledgePRHandler) api.router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "IL Redis Queue") diff --git a/ui/package-lock.json b/ui/package-lock.json index 3cdf0e5..180efda 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,13 @@ { - "name": "react-nextjs-patternfly4", + "name": "instructlab-bot-ui", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-nextjs-patternfly4", + "name": "instructlab-bot-ui", "version": "0.1.0", + "license": "Apache-2.0", "dependencies": { "@patternfly/react-charts": "^7.3.0", "@patternfly/react-core": "^5.3.3", diff --git a/ui/package.json b/ui/package.json index ed56750..ca6dde8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -15,6 +15,7 @@ "pretty": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,md}'" }, "dependencies": { + "@next/env": "^14.2.3", "@patternfly/react-charts": "^7.3.0", "@patternfly/react-core": "^5.3.3", "@patternfly/react-icons": "^5.3.1", diff --git a/ui/src/app/api/auth/[...nextauth]/route.ts b/ui/src/app/api/auth/[...nextauth]/route.ts index 6d9d9ee..291cffe 100644 --- a/ui/src/app/api/auth/[...nextauth]/route.ts +++ b/ui/src/app/api/auth/[...nextauth]/route.ts @@ -18,8 +18,8 @@ export const authOptions: NextAuthOptions = { }, authorize: async (credentials) => { if ( - credentials?.username === (process.env.NEXT_PUBLIC_IL_UI_ADMIN_USERNAME || 'admin') && - credentials?.password === (process.env.NEXT_PUBLIC_IL_UI_ADMIN_PASSWORD || 'password') + credentials?.username === (process.env.IL_UI_ADMIN_USERNAME || 'admin') && + credentials?.password === (process.env.IL_UI_ADMIN_PASSWORD || 'password') ) { return { id: '1', name: 'Admin', email: 'admin@example.com' }; } diff --git a/ui/src/app/api/pr/knowledge/route.ts b/ui/src/app/api/pr/knowledge/route.ts index d125bdc..580bf53 100644 --- a/ui/src/app/api/pr/knowledge/route.ts +++ b/ui/src/app/api/pr/knowledge/route.ts @@ -1,38 +1,40 @@ -// pages/api/pr/knowledge.ts -import type { NextApiRequest, NextApiResponse } from 'next'; +// src/app/api/pr/knowledge/route.ts + +import { NextResponse } from 'next/server'; const API_SERVER_URL = process.env.IL_UI_API_SERVER_URL || 'http://localhost:3000'; const USERNAME = process.env.IL_UI_API_SERVER_USERNAME || 'kitteh'; const PASSWORD = process.env.IL_UI_API_SERVER_PASSWORD || 'floofykittens'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === 'POST') { - const auth = Buffer.from(`${USERNAME}:${PASSWORD}`).toString('base64'); - const headers = { - 'Content-Type': 'application/json', - Authorization: 'Basic ' + auth, - }; - - try { - const apiRes = await fetch(`${API_SERVER_URL}pr/knowledge`, { - method: 'POST', - headers, - body: JSON.stringify(req.body), - }); - - if (!apiRes.ok) { - const errorResult = await apiRes.json(); - throw new Error(`HTTP error! status: ${apiRes.status} - ${errorResult.error}`); - } - - const result = await apiRes.json(); - res.status(201).json(result); - } catch (error) { - console.error('Failed to post Knowledge PR:', error); - res.status(500).json({ error: 'Failed to post Knowledge PR' }); +export async function POST(req: Request) { + console.log(`Received request: ${req.method} ${req.url} ${req.body}}`); + + const auth = Buffer.from(`${USERNAME}:${PASSWORD}`).toString('base64'); + const headers = { + 'Content-Type': 'application/json', + Authorization: 'Basic ' + auth, + }; + + try { + const body = await req.json(); + const response = await fetch(`${API_SERVER_URL}/pr/knowledge`, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + + if (response.status !== 201) { + throw new Error(`HTTP error! status: ${response.status}`); } - } else { - res.setHeader('Allow', ['POST']); - res.status(405).end(`Method ${req.method} Not Allowed`); + + const result = await response.json(); + return NextResponse.json(result, { status: 201 }); + } catch (error) { + console.error('Failed to post knowledge data:', error); + return NextResponse.json({ error: 'Failed to post knowledge data' }, { status: 500 }); } } + +export const methods = { + POST, +}; diff --git a/ui/src/app/api/pr/skill/route.ts b/ui/src/app/api/pr/skill/route.ts index 0f991f1..2fb8cc4 100644 --- a/ui/src/app/api/pr/skill/route.ts +++ b/ui/src/app/api/pr/skill/route.ts @@ -1,38 +1,40 @@ -// pages/api/pr/skill.ts -import type { NextApiRequest, NextApiResponse } from 'next'; +// src/app/api/pr/skill/route.ts + +import { NextResponse } from 'next/server'; const API_SERVER_URL = process.env.IL_UI_API_SERVER_URL || 'http://localhost:3000'; const USERNAME = process.env.IL_UI_API_SERVER_USERNAME || 'kitteh'; const PASSWORD = process.env.IL_UI_API_SERVER_PASSWORD || 'floofykittens'; -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === 'POST') { - const auth = Buffer.from(`${USERNAME}:${PASSWORD}`).toString('base64'); - const headers = { - 'Content-Type': 'application/json', - Authorization: 'Basic ' + auth, - }; - - try { - const apiRes = await fetch(`${API_SERVER_URL}pr/skill`, { - method: 'POST', - headers, - body: JSON.stringify(req.body), - }); - - if (!apiRes.ok) { - const errorResult = await apiRes.json(); - throw new Error(`HTTP error! status: ${apiRes.status} - ${errorResult.error}`); - } - - const result = await apiRes.json(); - res.status(201).json(result); - } catch (error) { - console.error('Failed to post Skill PR:', error); - res.status(500).json({ error: 'Failed to post Skill PR' }); +export async function POST(req: Request) { + console.log(`Received request: ${req.method} ${req.url} ${req.body}}`); + + const auth = Buffer.from(`${USERNAME}:${PASSWORD}`).toString('base64'); + const headers = { + 'Content-Type': 'application/json', + Authorization: 'Basic ' + auth, + }; + + try { + const body = await req.json(); + const response = await fetch(`${API_SERVER_URL}/pr/skill`, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + + if (response.status !== 201) { + throw new Error(`HTTP error! status: ${response.status}`); } - } else { - res.setHeader('Allow', ['POST']); - res.status(405).end(`Method ${req.method} Not Allowed`); + + const result = await response.json(); + return NextResponse.json(result, { status: 201 }); + } catch (error) { + console.error('Failed to post skill data:', error); + return NextResponse.json({ error: 'Failed to post skill data' }, { status: 500 }); } } + +export const methods = { + POST, +}; diff --git a/ui/src/components/Contribute/Knowledge/index.tsx b/ui/src/components/Contribute/Knowledge/index.tsx index 9695b13..610d166 100644 --- a/ui/src/components/Contribute/Knowledge/index.tsx +++ b/ui/src/components/Contribute/Knowledge/index.tsx @@ -1,11 +1,11 @@ // src/components/Contribute/Knowledge/index.tsx 'use client'; import React, { useState } from 'react'; -import './knowledge.module.css'; +import './knowledge.css'; import { usePostKnowledgePR } from '../../../common/HooksPostKnowledgePR'; import { Alert } from '@patternfly/react-core/dist/dynamic/components/Alert'; import { AlertActionCloseButton } from '@patternfly/react-core/dist/dynamic/components/Alert'; -import { ActionGroup } from '@patternfly/react-core/dist/dynamic/components/Form'; +import { ActionGroup, FormFieldGroupExpandable, FormFieldGroupHeader } from '@patternfly/react-core/dist/dynamic/components/Form'; import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; import { Text } from '@patternfly/react-core/dist/dynamic/components/Text'; import { TextInput } from '@patternfly/react-core/dist/dynamic/components/TextInput'; @@ -194,7 +194,191 @@ export const KnowledgeForm: React.FunctionComponent = () => { }; return ( -
+
+ + } + > + + setEmail(value)} + /> + setName(value)} + /> + + + + } + > + + setTaskDescription(value)} + /> + setDomain(value)} + /> +