| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606 |
- package utils
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "regexp"
- "strings"
- )
- // ExamPromptQuestionType represents the structure of question type configuration passed from frontend
- type ExamPromptQuestionType struct {
- Name string `json:"name"`
- RomanNumeral string `json:"romanNumeral"`
- QuestionCount int `json:"questionCount"`
- ScorePerQuestion int `json:"scorePerQuestion"`
- }
- // ExamPromptRequest represents the payload required to build an exam prompt
- type ExamPromptRequest struct {
- Mode string `json:"mode"`
- Client string `json:"client"`
- ProjectType string `json:"projectType"`
- ExamTitle string `json:"examTitle"`
- TotalScore int `json:"totalScore"`
- QuestionTypes []ExamPromptQuestionType `json:"questionTypes"`
- PPTContent string `json:"pptContent"`
- }
- // SingleQuestionPromptRequest represents the payload required to build a single-question prompt
- type SingleQuestionPromptRequest struct {
- Client string `json:"client"`
- SectionType string `json:"sectionType"`
- QuestionIndex int `json:"questionIndex"`
- ProjectType string `json:"projectType"`
- QuestionType string `json:"questionType"`
- ScorePerQuestion int `json:"scorePerQuestion"`
- CurrentQuestion json.RawMessage `json:"currentQuestion"`
- }
- type questionStats struct {
- Score int
- Count int
- }
- // BuildExamPrompt generates the exam prompt text based on mode (ai/ppt) and client (pc/mobile)
- func BuildExamPrompt(req ExamPromptRequest) (string, error) {
- mode := strings.ToLower(strings.TrimSpace(req.Mode))
- if mode == "" {
- mode = "ai"
- }
- client := strings.ToLower(strings.TrimSpace(req.Client))
- if client == "" {
- client = "pc"
- }
- switch client {
- case "mobile":
- if mode == "ppt" {
- return buildMobilePPTExamPrompt(req), nil
- }
- return buildMobileAIExamPrompt(req), nil
- default:
- if mode == "ppt" {
- return buildPcPPTExamPrompt(req), nil
- }
- return buildPcAIExamPrompt(req), nil
- }
- }
- // BuildSingleQuestionPrompt builds prompts for regenerating a single question
- func BuildSingleQuestionPrompt(req SingleQuestionPromptRequest) (string, error) {
- client := strings.ToLower(strings.TrimSpace(req.Client))
- if client == "" {
- client = "pc"
- }
- sectionType := normalizeSectionType(req.SectionType)
- projectType := fallbackString(req.ProjectType, "工程")
- questionType := fallbackString(req.QuestionType, questionTypeFromSection(sectionType))
- scorePerQuestion := safePositive(req.ScorePerQuestion, 1)
- questionIndex := req.QuestionIndex + 1
- currentQuestion := formatCurrentQuestionJSON(req.CurrentQuestion)
- switch client {
- case "mobile":
- return buildMobileSingleQuestionPrompt(projectType, questionType, sectionType, questionIndex, scorePerQuestion, currentQuestion), nil
- default:
- return buildPcSingleQuestionPrompt(projectType, questionType, sectionType, questionIndex, scorePerQuestion, currentQuestion), nil
- }
- }
- func buildPcAIExamPrompt(req ExamPromptRequest) string {
- projectType := fallbackString(req.ProjectType, "工程")
- examTitle := fallbackString(req.ExamTitle, "考试试卷")
- questionConfig := buildPcQuestionConfig(req.QuestionTypes)
- jsonTemplate := buildPcJSONTemplate(req, examTitle)
- return fmt.Sprintf(`请为%[1]s工程生成一份名为"%[2]s"的考试试卷:
- 试卷总分:%[3]d分
- 题型配置:
- %[4]s
- 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
- %[5]s
- 注意:
- 1. 所有题目内容必须与%[1]s工程相关
- 2. 题目难度适中,符合考试要求
- 3. 严格按照JSON格式返回,不要有多余字符
- 4. 单选题和判断题的选项要合理
- 5. 多选题至少要有2个正确答案
- 6. 简答题要提供清晰的答题要点
- 7. 必须为每道题设置正确答案:
- - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
- - 判断题:selectedAnswer字段填写"正确"或"错误"
- - 多选题:selectedAnswers数组包含所有正确答案选项
- 8. 简答题答案字数不超过500字
- `, projectType, examTitle, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
- }
- func buildPcPPTExamPrompt(req ExamPromptRequest) string {
- contentText := sanitizePPTContent(req.PPTContent)
- if contentText == "" {
- contentText = fallbackString(req.ExamTitle, "PPT内容")
- }
- examTitle := fallbackString(req.ExamTitle, "考试试卷")
- questionConfig := buildPcQuestionConfig(req.QuestionTypes)
- jsonTemplate := buildPcJSONTemplate(req, examTitle)
- return fmt.Sprintf(`请根据标题为%[1]s生成一份考试试卷:
- 试卷总分:%[2]d分
- 题型配置:
- %[3]s
- 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
- %[4]s
- 注意:
- 1. 所有题目内容必须基于提供的内容生成,与%[1]s工程相关
- 2. 题目难度适中,符合考试要求
- 3. 严格按照JSON格式返回,不要有多余字符
- 4. 单选题和判断题的选项要合理
- 5. 多选题至少要有2个正确答案
- 6. 简答题要提供清晰的答题要点
- 7. 必须为每道题设置正确答案:
- - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
- - 判断题:selectedAnswer字段填写"正确"或"错误"
- - 多选题:selectedAnswers数组包含所有正确答案选项
- 8. 简答题答案字数不超过500字
- 9. 确保题目内容与提供的内容高度相关
- `, contentText, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
- }
- func buildMobileAIExamPrompt(req ExamPromptRequest) string {
- projectType := fallbackString(req.ProjectType, "工程")
- examTitle := fallbackString(req.ExamTitle, "考试试卷")
- questionConfig := buildMobileQuestionConfig(req.QuestionTypes)
- jsonTemplate := buildMobileJSONTemplate(req, examTitle)
- return fmt.Sprintf(`请生成一份%[1]s工程施工技术考核试卷:
- 试卷要求:
- - 试卷名称:%[2]s
- - 试卷总分:%[3]d分
- - 题型配置:
- %[4]s
- 内容要求:
- 1. %[1]s工程施工技术的核心知识点
- 2. 施工工艺流程和质量控制要点
- 3. 安全操作规程和事故预防措施
- 4. 相关法规标准和验收规范
- 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
- 请在questions数组中生成具体、完整的题目内容!
- - 单选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswer正确答案
- - 判断题需要:text题目内容、selectedAnswer正确答案(正确/错误)
- - 多选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswers正确答案数组
- - 简答题需要:text题目内容、outline {keyFactors:关键要点, measures:具体措施}
- %[5]s`, projectType, examTitle, safeTotalScore(req.TotalScore), questionConfig, jsonTemplate)
- }
- func buildMobilePPTExamPrompt(req ExamPromptRequest) string {
- examTitle := fallbackString(req.ExamTitle, "考试试卷")
- pptContent := fallbackString(strings.TrimSpace(req.PPTContent), "PPT内容")
- questionConfig := buildMobileQuestionConfig(req.QuestionTypes)
- jsonTemplate := buildMobileJSONTemplate(req, examTitle+" - PPT培训考试")
- return fmt.Sprintf(`请基于以下PPT培训内容生成考试试卷:
- 培训主题:%[1]s
- PPT内容:%[2]s
- 请生成一份包含以下题型的考试试卷:
- %[3]s
- 试卷总分:%[4]d分
- 要求:
- 1. 重点考查PPT中的核心知识点
- 2. 涵盖主要概念、工艺流程、操作规程等关键内容
- 3. 题目难度适中,具有实际应用价值
- 4. 确保答案准确性和专业性
- 请严格按照以下JSON格式返回试卷内容,不要包含任何其他文字:
- 请在questions数组中生成具体、完整的题目内容!
- - 单选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswer正确答案
- - 判断题需要:text题目内容、selectedAnswer正确答案(正确/错误)
- - 多选题需要:text题目内容、options选项数组[{"key":"A","text":"选项1"},{"key":"B","text":"选项2"},{"key":"C","text":"选项3"},{"key":"D","text":"选项4"}]、selectedAnswers正确答案数组
- - 简答题需要:text题目内容、outline {keyFactors:关键要点, measures:具体措施}
- %[5]s`, examTitle, pptContent, questionConfig, safeTotalScore(req.TotalScore), jsonTemplate)
- }
- func buildPcQuestionConfig(questionTypes []ExamPromptQuestionType) string {
- if len(questionTypes) == 0 {
- return "暂无题型配置\n"
- }
- var builder strings.Builder
- for _, qt := range questionTypes {
- roman := strings.TrimSpace(qt.RomanNumeral)
- if roman == "" {
- roman = "•"
- }
- score := safePositive(qt.ScorePerQuestion, 1)
- count := safePositive(qt.QuestionCount, 1)
- builder.WriteString(fmt.Sprintf("%s、%s:%d题,每题%d分,共%d分\n", roman, qt.Name, count, score, count*score))
- }
- return builder.String()
- }
- func buildMobileQuestionConfig(questionTypes []ExamPromptQuestionType) string {
- if len(questionTypes) == 0 {
- return "暂无题型配置\n"
- }
- var builder strings.Builder
- for _, qt := range questionTypes {
- score := safePositive(qt.ScorePerQuestion, 1)
- count := safePositive(qt.QuestionCount, 1)
- builder.WriteString(fmt.Sprintf("%s:%d题,每题%d分,共%d分\n", qt.Name, count, score, count*score))
- }
- return builder.String()
- }
- func buildPcJSONTemplate(req ExamPromptRequest, title string) string {
- totalQuestions := sumQuestionCount(req.QuestionTypes)
- single := statsByName(req.QuestionTypes, "单选题", 2, 15)
- judge := statsByName(req.QuestionTypes, "判断题", 2, 10)
- multiple := statsByName(req.QuestionTypes, "多选题", 3, 10)
- short := statsByName(req.QuestionTypes, "简答题", 10, 2)
- return fmt.Sprintf(`{
- "title": "%[1]s",
- "totalScore": %[2]d,
- "totalQuestions": %[3]d,
- "singleChoice": {
- "scorePerQuestion": %[4]d,
- "totalScore": %[5]d,
- "count": %[6]d,
- "questions": [
- {
- "text": "题目内容",
- "options": [
- {"key": "A", "text": "选项A内容"},
- {"key": "B", "text": "选项B内容"},
- {"key": "C", "text": "选项C内容"},
- {"key": "D", "text": "选项D内容"}
- ],
- "selectedAnswer": "正确答案选项(A/B/C/D)"
- }
- ]
- },
- "judge": {
- "scorePerQuestion": %[7]d,
- "totalScore": %[8]d,
- "count": %[9]d,
- "questions": [
- {
- "text": "题目内容",
- "selectedAnswer": "正确答案(正确/错误)"
- }
- ]
- },
- "multiple": {
- "scorePerQuestion": %[10]d,
- "totalScore": %[11]d,
- "count": %[12]d,
- "questions": [
- {
- "text": "题目内容",
- "options": [
- {"key": "A", "text": "选项A内容"},
- {"key": "B", "text": "选项B内容"},
- {"key": "C", "text": "选项C内容"},
- {"key": "D", "text": "选项D内容"}
- ],
- "selectedAnswers": ["正确答案选项1", "正确答案选项2"]
- }
- ]
- },
- "short": {
- "scorePerQuestion": %[13]d,
- "totalScore": %[14]d,
- "count": %[15]d,
- "questions": [
- {
- "text": "题目内容",
- "outline": {
- "keyFactors": "答题要点、关键因素、示例答案"
- }
- }
- ]
- }
- }`, title, safeTotalScore(req.TotalScore), totalQuestions,
- single.Score, single.Score*single.Count, single.Count,
- judge.Score, judge.Score*judge.Count, judge.Count,
- multiple.Score, multiple.Score*multiple.Count, multiple.Count,
- short.Score, short.Score*short.Count, short.Count)
- }
- func buildMobileJSONTemplate(req ExamPromptRequest, title string) string {
- totalQuestions := sumQuestionCount(req.QuestionTypes)
- q0 := statsByIndex(req.QuestionTypes, 0, 2, 8)
- q1 := statsByIndex(req.QuestionTypes, 1, 2, 5)
- q2 := statsByIndex(req.QuestionTypes, 2, 3, 5)
- q3 := statsByIndex(req.QuestionTypes, 3, 10, 2)
- return fmt.Sprintf(`{
- "title": "%[1]s",
- "totalScore": %[2]d,
- "totalQuestions": %[3]d,
- "singleChoice": {
- "scorePerQuestion": %[4]d,
- "totalScore": %[5]d,
- "count": %[6]d,
- "questions": []
- },
- "judge": {
- "scorePerQuestion": %[7]d,
- "totalScore": %[8]d,
- "count": %[9]d,
- "questions": []
- },
- "multiple": {
- "scorePerQuestion": %[10]d,
- "totalScore": %[11]d,
- "count": %[12]d,
- "questions": []
- },
- "short": {
- "scorePerQuestion": %[13]d,
- "totalScore": %[14]d,
- "count": %[15]d,
- "questions": []
- }
- }`, title, safeTotalScore(req.TotalScore), totalQuestions,
- q0.Score, q0.Score*q0.Count, q0.Count,
- q1.Score, q1.Score*q1.Count, q1.Count,
- q2.Score, q2.Score*q2.Count, q2.Count,
- q3.Score, q3.Score*q3.Count, q3.Count)
- }
- func statsByName(questionTypes []ExamPromptQuestionType, name string, defaultScore, defaultCount int) questionStats {
- for _, qt := range questionTypes {
- if strings.TrimSpace(qt.Name) == name {
- return questionStats{
- Score: safePositive(qt.ScorePerQuestion, defaultScore),
- Count: safePositive(qt.QuestionCount, defaultCount),
- }
- }
- }
- return questionStats{Score: defaultScore, Count: defaultCount}
- }
- func statsByIndex(questionTypes []ExamPromptQuestionType, index int, defaultScore, defaultCount int) questionStats {
- if index >= 0 && index < len(questionTypes) {
- qt := questionTypes[index]
- return questionStats{
- Score: safePositive(qt.ScorePerQuestion, defaultScore),
- Count: safePositive(qt.QuestionCount, defaultCount),
- }
- }
- return questionStats{Score: defaultScore, Count: defaultCount}
- }
- func sumQuestionCount(questionTypes []ExamPromptQuestionType) int {
- total := 0
- for _, qt := range questionTypes {
- total += safePositive(qt.QuestionCount, 0)
- }
- return total
- }
- func buildPcSingleQuestionPrompt(projectType, questionType, sectionType string, questionIndex, score int, currentQuestion string) string {
- format := buildPcSingleQuestionFormat(sectionType)
- return fmt.Sprintf(`请基于以下%[1]s工程的%[2]s题目,重新生成一道相似主题的题目,要求如下:
- 当前题目参考:
- %[3]s
- 题目类型:%[2]s
- 每题分值:%[4]d分
- 题目序号:第%[5]d题
- 请严格按照以下JSON格式返回,不要包含任何其他文字:
- %[6]s
- 注意:
- 1. 新题目必须与当前题目保持相似的主题和难度
- 2. 题目内容必须与%[1]s工程相关
- 3. 题目难度适中,符合考试要求
- 4. 严格按照JSON格式返回,不要有多余字符
- 5. 单选题和判断题的选项要合理
- 6. 多选题至少要有2个正确答案
- 7. 简答题要提供清晰的答题要点
- 8. 必须为每道题设置正确答案:
- - 单选题:selectedAnswer字段填写正确的选项(A/B/C/D)
- - 判断题:selectedAnswer字段填写"正确"或"错误"
- - 多选题:selectedAnswers数组包含所有正确答案选项
- 9. 简答题答案字数不超过500字
- 10. 新题目应该是当前题目的变体,保持主题一致性但内容要有所变化`, projectType, questionType, currentQuestion, score, questionIndex, format)
- }
- func buildMobileSingleQuestionPrompt(projectType, questionType, sectionType string, questionIndex, score int, currentQuestion string) string {
- format := buildMobileSingleQuestionFormat(sectionType)
- return fmt.Sprintf(`请基于以下%[1]s工程的%[2]s题目,重新生成一道相似主题的题目:
- 当前题目参考:
- %[3]s
- 题目类型:%[2]s
- 每题分值:%[4]d分
- 题目序号:第%[5]d题
- 请严格按照以下JSON格式返回,不要包含任何其他文字:
- %[6]s`, projectType, questionType, currentQuestion, score, questionIndex, format)
- }
- func buildPcSingleQuestionFormat(sectionType string) string {
- switch sectionType {
- case "single":
- return `{
- "text": "题目内容",
- "options": [
- {"key": "A", "text": "选项A内容"},
- {"key": "B", "text": "选项B内容"},
- {"key": "C", "text": "选项C内容"},
- {"key": "D", "text": "选项D内容"}
- ],
- "selectedAnswer": "正确答案选项(A/B/C/D)"
- }`
- case "judge":
- return `{
- "text": "题目内容",
- "selectedAnswer": "正确答案(正确/错误)"
- }`
- case "multiple":
- return `{
- "text": "题目内容",
- "options": [
- {"key": "A", "text": "选项A内容"},
- {"key": "B", "text": "选项B内容"},
- {"key": "C", "text": "选项C内容"},
- {"key": "D", "text": "选项D内容"}
- ],
- "selectedAnswers": ["正确答案选项1", "正确答案选项2"]
- }`
- case "short":
- return `{
- "text": "题目内容",
- "outline": {
- "keyFactors": "答题要点、关键因素、示例答案"
- }
- }`
- default:
- return `{
- "text": "题目内容"
- }`
- }
- }
- func buildMobileSingleQuestionFormat(sectionType string) string {
- switch sectionType {
- case "single":
- return `{
- "text": "题目内容",
- "options": [
- {"key": "A", "text": "选项A"},
- {"key": "B", "text": "选项B"},
- {"key": "C", "text": "选项C"},
- {"key": "D", "text": "选项D"}
- ],
- "selectedAnswer": "A"
- }`
- case "judge":
- return `{
- "text": "题目内容",
- "selectedAnswer": "正确"
- }`
- case "multiple":
- return `{
- "text": "题目内容",
- "options": [
- {"key": "A", "text": "选项A"},
- {"key": "B", "text": "选项B"},
- {"key": "C", "text": "选项C"},
- {"key": "D", "text": "选项D"}
- ],
- "selectedAnswers": ["A", "B"]
- }`
- case "short":
- return `{
- "text": "题目内容",
- "outline": {
- "keyFactors": "关键要点内容",
- "measures": "具体措施内容"
- }
- }`
- default:
- return `{
- "text": "题目内容"
- }`
- }
- }
- func formatCurrentQuestionJSON(raw json.RawMessage) string {
- if len(raw) == 0 {
- return "无"
- }
- var buf bytes.Buffer
- if err := json.Indent(&buf, raw, "", " "); err != nil {
- return string(raw)
- }
- return buf.String()
- }
- func questionTypeFromSection(sectionType string) string {
- switch sectionType {
- case "single":
- return "单选题"
- case "judge":
- return "判断题"
- case "multiple":
- return "多选题"
- case "short":
- return "简答题"
- default:
- return "题目"
- }
- }
- func normalizeSectionType(sectionType string) string {
- switch strings.ToLower(strings.TrimSpace(sectionType)) {
- case "single", "judge", "multiple", "short":
- return strings.ToLower(strings.TrimSpace(sectionType))
- default:
- return "single"
- }
- }
- func sanitizePPTContent(content string) string {
- text := strings.TrimSpace(content)
- if text == "" {
- return ""
- }
- if strings.Contains(text, "PPT文件信息") {
- re := regexp.MustCompile(`(?s)提取的文本内容:\n(.*?)(\n\n|$)`)
- if matches := re.FindStringSubmatch(text); len(matches) > 1 {
- text = strings.TrimSpace(matches[1])
- }
- }
- return text
- }
- func fallbackString(value string, fallback string) string {
- if strings.TrimSpace(value) == "" {
- return fallback
- }
- return value
- }
- func safePositive(value int, fallback int) int {
- if value <= 0 {
- return fallback
- }
- return value
- }
- func safeTotalScore(score int) int {
- if score <= 0 {
- return 100
- }
- return score
- }
|