feat: 完成教学设计模块(除了课程图谱),添加了炫酷的思考中动画
This commit is contained in:
parent
20471bfbe3
commit
49b9e97ee8
@ -44,6 +44,6 @@ defineProps({
|
||||
|
||||
<style>
|
||||
think {
|
||||
@apply block my-4 p-3 bg-[#f0f8ff] border-l-4 border-[#88f] rounded italic text-xs;
|
||||
@apply block my-4 p-3 bg-blue-500/5 border-l-4 border-primary/30 rounded italic text-xs;
|
||||
}
|
||||
</style>
|
||||
|
@ -10,7 +10,7 @@ const props = defineProps<{
|
||||
form: FormContext<any>
|
||||
formFieldConfig?: {
|
||||
[key: string]: {
|
||||
component: string
|
||||
component?: string
|
||||
props?: Record<string, any>
|
||||
[key: string]: any
|
||||
}
|
||||
@ -20,6 +20,15 @@ const props = defineProps<{
|
||||
disableUserInput?: boolean
|
||||
}>()
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const currentConversation = computed(() => {
|
||||
const { conversations, activeConversationId } = props
|
||||
if (conversations && activeConversationId) {
|
||||
return conversations.find((m) => m.id === activeConversationId)
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const messages = computed(() => {
|
||||
const { conversations, activeConversationId } = props
|
||||
if (conversations && activeConversationId) {
|
||||
@ -115,7 +124,9 @@ const onDeleteConversation = (conversationId: string) => {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground/60 font-medium text-center pt-2">
|
||||
<p
|
||||
class="text-xs text-muted-foreground/60 font-medium text-center pt-2"
|
||||
>
|
||||
到底了
|
||||
</p>
|
||||
</ScrollArea>
|
||||
@ -150,22 +161,33 @@ const onDeleteConversation = (conversationId: string) => {
|
||||
:class="`${message.role == 'user' ? 'justify-end' : 'justify-start'}`"
|
||||
>
|
||||
<div
|
||||
class="w-fit px-4 py-3 rounded-lg bg-white dark:bg-gray-800 shadow max-w-prose border"
|
||||
:class="`${message.role == 'user' ? 'rounded-br-none' : 'rounded-bl-none'}`"
|
||||
class="gradient-border"
|
||||
:class="
|
||||
message.role === 'assistant' && !currentConversation?.finished_at && message.content
|
||||
? ''
|
||||
: 'inactive'
|
||||
"
|
||||
>
|
||||
<MarkdownRenderer
|
||||
v-if="!!message.content"
|
||||
:source="message.content"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center gap-2 text-foreground/60 text-sm font-medium"
|
||||
class="w-fit px-4 py-3 rounded-lg max-w-prose shadow bg-white dark:bg-gray-800 border"
|
||||
:class="[
|
||||
message.role == 'user' ? 'rounded-br-none' : 'rounded-bl-none',
|
||||
]"
|
||||
>
|
||||
<Icon
|
||||
name="svg-spinners:270-ring-with-bg"
|
||||
class="text-lg"
|
||||
<MarkdownRenderer
|
||||
v-if="!!message.content"
|
||||
:source="message.content"
|
||||
/>
|
||||
思考中
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center gap-2 text-foreground/60 text-sm font-medium"
|
||||
>
|
||||
<Icon
|
||||
name="svg-spinners:270-ring-with-bg"
|
||||
class="text-lg"
|
||||
/>
|
||||
思考中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -202,7 +224,7 @@ const onDeleteConversation = (conversationId: string) => {
|
||||
:form="form"
|
||||
:field-config="formFieldConfig"
|
||||
class="space-y-2"
|
||||
@submit="(values) => $emit('submit', values)"
|
||||
@submit="(values: any) => $emit('submit', values)"
|
||||
>
|
||||
<div class="w-full flex justify-center gap-2 pt-4">
|
||||
<Button
|
||||
@ -219,4 +241,41 @@ const onDeleteConversation = (conversationId: string) => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
<style scoped>
|
||||
@property --angle {
|
||||
syntax: '<angle>';
|
||||
inherits: false;
|
||||
initial-value: 0deg;
|
||||
}
|
||||
|
||||
.gradient-border {
|
||||
position: relative;
|
||||
border-radius: 0.5rem;
|
||||
padding: 2px;
|
||||
overflow: hidden;
|
||||
background: conic-gradient(
|
||||
from var(--angle),
|
||||
hsla(188, 86%, 53%, 0.8),
|
||||
hsla(142, 69%, 58%, 0.8),
|
||||
hsla(48, 96%, 53%, 0.8),
|
||||
hsla(329, 86%, 70%, 0.8),
|
||||
hsla(188, 86%, 53%, 0.8)
|
||||
);
|
||||
animation: rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
.gradient-border.inactive {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.gradient-border > * {
|
||||
border-radius: calc(0.5rem - 2px);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
to {
|
||||
--angle: 360deg;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
126
components/fn/teach/CourseChapter.vue
Normal file
126
components/fn/teach/CourseChapter.vue
Normal file
@ -0,0 +1,126 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const historyStore = useLlmHistories('course-chapter-gen')
|
||||
const { conversations } = storeToRefs(historyStore)
|
||||
const { updateConversation, appendChunkToLast, isConversationExist } =
|
||||
historyStore
|
||||
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
|
||||
watch(activeConversationId, (val) => {
|
||||
if (val) {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
conversationId: val,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.conversationId) {
|
||||
if (isConversationExist(route.query.conversationId as string)) {
|
||||
activeConversationId.value = route.query.conversationId as string
|
||||
} else {
|
||||
activeConversationId.value = null
|
||||
toast.error('会话不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
target: z.string({ required_error: '请输入授课对象' }).describe('授课对象'),
|
||||
topic: z.string({ required_error: '请输入课程主题' }).describe('课程主题'),
|
||||
goal: z.string({ required_error: '请输入课程目标' }).describe('课程目标'),
|
||||
requirement: z.string().describe('其他要求').optional(),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream<z.infer<typeof schema>>('/ai/course-chapter/stream', values, {
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
updateConversation(id, {
|
||||
id,
|
||||
created_at,
|
||||
title: values.target,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `生成课程章节大纲\n授课对象:${values.target}\n课程主题:${values.topic}\n课程目标:${values.goal}\n其他要求:${values.requirement ?? '无'}`,
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onTextChunk: (chunk) => {
|
||||
appendChunkToLast(activeConversationId.value!, chunk)
|
||||
},
|
||||
onComplete: (id, finished_at) => {
|
||||
updateConversation(id!, {
|
||||
finished_at,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
target: {
|
||||
inputProps: {
|
||||
placeholder: '请输入授课对象,如:本科生、研究生',
|
||||
},
|
||||
},
|
||||
topic: {
|
||||
inputProps: {
|
||||
placeholder: '请输入课程主题,如:数据结构与算法',
|
||||
},
|
||||
},
|
||||
goal: {
|
||||
inputProps: {
|
||||
placeholder: '请输入课程目标,如:掌握数据结构与算法的基本概念',
|
||||
},
|
||||
},
|
||||
requirement: {
|
||||
component: 'textarea',
|
||||
inputProps: {
|
||||
placeholder: '请输入其他要求,如:教学重点',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
@submit="onSubmit"
|
||||
@update:conversation-id="activeConversationId = $event"
|
||||
@delete-conversation="historyStore.removeConversation($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
133
components/fn/teach/CourseOutline.vue
Normal file
133
components/fn/teach/CourseOutline.vue
Normal file
@ -0,0 +1,133 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const historyStore = useLlmHistories('course-course-outline')
|
||||
const { conversations } = storeToRefs(historyStore)
|
||||
const { updateConversation, appendChunkToLast, isConversationExist } =
|
||||
historyStore
|
||||
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
|
||||
watch(activeConversationId, (val) => {
|
||||
if (val) {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
conversationId: val,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.conversationId) {
|
||||
if (isConversationExist(route.query.conversationId as string)) {
|
||||
activeConversationId.value = route.query.conversationId as string
|
||||
} else {
|
||||
activeConversationId.value = null
|
||||
toast.error('会话不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
courseName: z
|
||||
.string({ required_error: '请输入课程名称' })
|
||||
.describe('课程名称'),
|
||||
targetAudience: z
|
||||
.string({ required_error: '请输入授课对象' })
|
||||
.describe('授课对象'),
|
||||
courseObjective: z
|
||||
.string({ required_error: '请输入课程目标' })
|
||||
.describe('课程目标'),
|
||||
otherRequirement: z.string().describe('其他要求').optional(),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream<z.infer<typeof schema>>('/ai/curriculum-outline/stream', values, {
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
updateConversation(id, {
|
||||
id,
|
||||
created_at,
|
||||
title: values.courseName,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `*生成一份 ${values.courseName} 的课程大纲*`,
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onTextChunk: (chunk) => {
|
||||
appendChunkToLast(activeConversationId.value!, chunk)
|
||||
},
|
||||
onComplete: (id, finished_at) => {
|
||||
updateConversation(id!, {
|
||||
finished_at,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
courseName: {
|
||||
inputProps: {
|
||||
placeholder: '请输入课程名称,如:数据结构与算法',
|
||||
},
|
||||
},
|
||||
targetAudience: {
|
||||
inputProps: {
|
||||
placeholder: '请输入授课对象,如:大一物联网专业学生',
|
||||
},
|
||||
},
|
||||
courseObjective: {
|
||||
component: 'textarea',
|
||||
inputProps: {
|
||||
placeholder:
|
||||
'例如:知识目标需掌握和理解的理论知识、概念、原理、理论框架等;技能目标需掌握学科中所需的基本操作技能,如实验操作、计算机操作、语言表达等',
|
||||
},
|
||||
},
|
||||
otherRequirement: {
|
||||
inputProps: {
|
||||
placeholder: '请输入其他要求,如:需要包含案例分析',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
@submit="onSubmit"
|
||||
@update:conversation-id="activeConversationId = $event"
|
||||
@delete-conversation="historyStore.removeConversation($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -1,16 +1,93 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const historyStore = useLlmHistories('course-std-design')
|
||||
const { conversations } = storeToRefs(historyStore)
|
||||
const {
|
||||
updateConversation,
|
||||
appendChunkToLast,
|
||||
isConversationExist
|
||||
} = historyStore
|
||||
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
|
||||
watch(activeConversationId, (val) => {
|
||||
if (val) {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
conversationId: val,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.conversationId) {
|
||||
if (isConversationExist(route.query.conversationId as string)) {
|
||||
activeConversationId.value = route.query.conversationId as string
|
||||
} else {
|
||||
activeConversationId.value = null
|
||||
toast.error('会话不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
foo: z.string().describe('测试Label').optional(),
|
||||
bar: z.number(),
|
||||
file: z.string({ required_error: '请选择课件文件' }).describe('课件文件'),
|
||||
requirement: z.string().describe('其他要求').optional(),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream<z.infer<typeof schema>>(
|
||||
'/ai/course-standard/stream',
|
||||
values,
|
||||
{
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
updateConversation(id, {
|
||||
id,
|
||||
created_at,
|
||||
title: `知识图谱`,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `*根据上传的课件生成一份知识图谱*`,
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onTextChunk: (chunk) => {
|
||||
appendChunkToLast(activeConversationId.value!, chunk)
|
||||
},
|
||||
onComplete: (id, finished_at) => {
|
||||
updateConversation(id!, {
|
||||
finished_at,
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -18,6 +95,25 @@ const form = useForm({
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
file: {
|
||||
component: 'file',
|
||||
inputProps: {
|
||||
accept: 'application/pdf,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
},
|
||||
},
|
||||
requirement: {
|
||||
inputProps: {
|
||||
placeholder: '请输入其他要求',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
@submit="onSubmit"
|
||||
@update:conversation-id="activeConversationId = $event"
|
||||
@delete-conversation="historyStore.removeConversation($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
131
components/fn/teach/Plan.vue
Normal file
131
components/fn/teach/Plan.vue
Normal file
@ -0,0 +1,131 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const historyStore = useLlmHistories('course-teaching-plan')
|
||||
const { conversations } = storeToRefs(historyStore)
|
||||
const { updateConversation, appendChunkToLast, isConversationExist } =
|
||||
historyStore
|
||||
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
|
||||
watch(activeConversationId, (val) => {
|
||||
if (val) {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
conversationId: val,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.conversationId) {
|
||||
if (isConversationExist(route.query.conversationId as string)) {
|
||||
activeConversationId.value = route.query.conversationId as string
|
||||
} else {
|
||||
activeConversationId.value = null
|
||||
toast.error('会话不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
identity: z.string({ required_error: '请输入你的身份' }).describe('身份'),
|
||||
mainPlan: z.string({ required_error: '请输入主要计划' }).describe('主要计划'),
|
||||
planTemplate: z
|
||||
.string({ required_error: '请输入教学计划模板' })
|
||||
.describe('教学计划模板'),
|
||||
otherRequirement: z.string().describe('其他要求').optional(),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream<z.infer<typeof schema>>('/ai/teaching-plan/stream', values, {
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
updateConversation(id, {
|
||||
id,
|
||||
created_at,
|
||||
title: values.mainPlan,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `*生成一份教学计划*`,
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onTextChunk: (chunk) => {
|
||||
appendChunkToLast(activeConversationId.value!, chunk)
|
||||
},
|
||||
onComplete: (id, finished_at) => {
|
||||
updateConversation(id!, {
|
||||
finished_at,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
identity: {
|
||||
inputProps: {
|
||||
placeholder: '请输入你的身份,如:高中数学教师、大学物理教师',
|
||||
},
|
||||
},
|
||||
mainPlan: {
|
||||
component: 'textarea',
|
||||
inputProps: {
|
||||
placeholder:
|
||||
'请输入主要计划,如:课程目标、教学内容安排、教学方法设计',
|
||||
},
|
||||
},
|
||||
planTemplate: {
|
||||
component: 'textarea',
|
||||
inputProps: {
|
||||
placeholder:
|
||||
'请输入教学计划模板,如:教学目标、教学进度、考核评估方式',
|
||||
},
|
||||
},
|
||||
otherRequirement: {
|
||||
inputProps: {
|
||||
placeholder: '请输入其他要求',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
@submit="onSubmit"
|
||||
@update:conversation-id="activeConversationId = $event"
|
||||
@delete-conversation="historyStore.removeConversation($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
107
components/fn/teach/PoliticalCase.vue
Normal file
107
components/fn/teach/PoliticalCase.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const historyStore = useLlmHistories('course-political-case')
|
||||
const { conversations } = storeToRefs(historyStore)
|
||||
const { updateConversation, appendChunkToLast, isConversationExist } =
|
||||
historyStore
|
||||
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
|
||||
watch(activeConversationId, (val) => {
|
||||
if (val) {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
conversationId: val,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.conversationId) {
|
||||
if (isConversationExist(route.query.conversationId as string)) {
|
||||
activeConversationId.value = route.query.conversationId as string
|
||||
} else {
|
||||
activeConversationId.value = null
|
||||
toast.error('会话不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
query: z.string({ required_error: '请输入教学需求' }).describe('教学需求'),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream<z.infer<typeof schema>>('/ai/ideological-case/stream', values, {
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
updateConversation(id, {
|
||||
id,
|
||||
created_at,
|
||||
title: values.query,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `*生成一份关于 ${values.query} 的思政案例*`,
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onTextChunk: (chunk) => {
|
||||
appendChunkToLast(activeConversationId.value!, chunk)
|
||||
},
|
||||
onComplete: (id, finished_at) => {
|
||||
updateConversation(id!, {
|
||||
finished_at,
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
query: {
|
||||
inputProps: {
|
||||
placeholder: '请输入教学需求和其他要求',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
@submit="onSubmit"
|
||||
@update:conversation-id="activeConversationId = $event"
|
||||
@delete-conversation="historyStore.removeConversation($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
137
components/fn/teach/ResearchPlan.vue
Normal file
137
components/fn/teach/ResearchPlan.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<script lang="ts" setup>
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { z } from 'zod'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const historyStore = useLlmHistories('course-research-plan')
|
||||
const { conversations } = storeToRefs(historyStore)
|
||||
const { updateConversation, appendChunkToLast, isConversationExist } =
|
||||
historyStore
|
||||
|
||||
const activeConversationId = ref<string | null>(null)
|
||||
|
||||
watch(activeConversationId, (val) => {
|
||||
if (val) {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
conversationId: val,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
router.replace({
|
||||
query: {
|
||||
fn: route.query.fn,
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.conversationId) {
|
||||
if (isConversationExist(route.query.conversationId as string)) {
|
||||
activeConversationId.value = route.query.conversationId as string
|
||||
} else {
|
||||
activeConversationId.value = null
|
||||
toast.error('会话不存在')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
subject: z.string({ required_error: '请输入学科名称' }).describe('学科'),
|
||||
backgroundAnalysis: z
|
||||
.string({ required_error: '请输入当前学科的现状和背景分析' })
|
||||
.describe('背景及现状分析'),
|
||||
workGoal: z
|
||||
.string({ required_error: '请输入教研计划的工作目标' })
|
||||
.describe('工作目标'),
|
||||
workFocusAndMeasures: z
|
||||
.string({
|
||||
required_error: '请输入教研计划的工作重点和措施',
|
||||
})
|
||||
.describe('工作重点和措施'),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
validationSchema: toTypedSchema(schema),
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream<z.infer<typeof schema>>(
|
||||
'/ai/teaching-research-plan/stream',
|
||||
values,
|
||||
{
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
updateConversation(id, {
|
||||
id,
|
||||
created_at,
|
||||
title: values.subject,
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: `生成教研计划\n学科:${values.subject}\n背景及现状分析:${values.backgroundAnalysis}\n工作目标:${values.workGoal}\n工作重点和措施:${values.workFocusAndMeasures}`,
|
||||
},
|
||||
{
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
},
|
||||
],
|
||||
})
|
||||
},
|
||||
onTextChunk: (chunk) => {
|
||||
appendChunkToLast(activeConversationId.value!, chunk)
|
||||
},
|
||||
onComplete: (id, finished_at) => {
|
||||
updateConversation(id!, {
|
||||
finished_at,
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
subject: {
|
||||
inputProps: {
|
||||
placeholder: '请输入学科名称,如:大学英语、电力电子技术',
|
||||
},
|
||||
},
|
||||
backgroundAnalysis: {
|
||||
inputProps: {
|
||||
placeholder: '请输入当前学科的现状和背景分析',
|
||||
},
|
||||
},
|
||||
workGoal: {
|
||||
inputProps: {
|
||||
placeholder: '请输入教研计划的工作目标,如:提升教学质量',
|
||||
},
|
||||
},
|
||||
workFocusAndMeasures: {
|
||||
inputProps: {
|
||||
placeholder: '请输入教研计划的工作重点和措施,如:开展教学研讨',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
@submit="onSubmit"
|
||||
@update:conversation-id="activeConversationId = $event"
|
||||
@delete-conversation="historyStore.removeConversation($event)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
@ -46,7 +46,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const schema = z.object({
|
||||
query: z.string().describe('课程名称'),
|
||||
query: z.string({ required_error: '请输入课程名称' }).describe('课程名称'),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
@ -54,11 +54,9 @@ const form = useForm({
|
||||
})
|
||||
|
||||
const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
http_stream(
|
||||
http_stream<z.infer<typeof schema>>(
|
||||
'/ai/course-standard/stream',
|
||||
{
|
||||
query: values.query,
|
||||
},
|
||||
values,
|
||||
{
|
||||
onStart(id, created_at) {
|
||||
activeConversationId.value = id
|
||||
@ -96,6 +94,13 @@ const onSubmit = (values: z.infer<typeof schema>) => {
|
||||
<AiConversation
|
||||
:form
|
||||
:form-schema="schema"
|
||||
:form-field-config="{
|
||||
query: {
|
||||
inputProps: {
|
||||
placeholder: '请输入课程名称,如:数据结构与算法',
|
||||
},
|
||||
},
|
||||
}"
|
||||
:conversations
|
||||
:active-conversation-id="activeConversationId"
|
||||
disable-user-input
|
||||
|
@ -4,6 +4,7 @@ export interface NavTertiaryItem {
|
||||
to?: string
|
||||
component?: string | Component
|
||||
props?: Record<string, unknown>
|
||||
disabled?: boolean
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -27,6 +28,7 @@ const isActiveItem = (idx: number) => {
|
||||
}
|
||||
|
||||
const onClickItem = (idx: number) => {
|
||||
if (props.navs[idx].disabled) return
|
||||
emit('update:modelValue', idx)
|
||||
}
|
||||
</script>
|
||||
@ -37,7 +39,7 @@ const onClickItem = (idx: number) => {
|
||||
v-for="(nav, i) in navs"
|
||||
:key="i"
|
||||
class="flex justify-center items-center gap-2 p-2.5 rounded-sm cursor-pointer select-none transition-colors duration-75"
|
||||
:class="`${isActiveItem(i) ? 'bg-primary text-primary-foreground' : 'bg-accent text-foreground'}`"
|
||||
:class="`${isActiveItem(i) ? 'bg-primary text-primary-foreground' : 'bg-accent text-foreground'} ${nav.disabled ? 'cursor-not-allowed text-foreground/40' : ''}`"
|
||||
@click="onClickItem(i)"
|
||||
>
|
||||
<span class="text-sm font-medium">
|
||||
|
@ -10,6 +10,7 @@ export default withNuxt(
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'@stylistic/brace-style': 'off',
|
||||
'@stylistic/arrow-parens': 'off',
|
||||
'@stylistic/operator-linebreak': 'off',
|
||||
},
|
||||
plugins: {
|
||||
prettier,
|
||||
|
@ -5,6 +5,11 @@ import {
|
||||
FnTeachCaseGen,
|
||||
FnTeachStdDesign,
|
||||
FnTeachKnowledgeDiagram,
|
||||
FnTeachCourseChapter,
|
||||
FnTeachPoliticalCase,
|
||||
FnTeachResearchPlan,
|
||||
FnTeachPlan,
|
||||
FnTeachCourseOutline,
|
||||
} from '#components'
|
||||
import type { NavTertiaryItem } from '~/components/nav/Tertiary.vue'
|
||||
|
||||
@ -25,9 +30,12 @@ const tertiaryNavs: NavTertiaryItem[] = [
|
||||
{ label: '教案设计', component: FnTeachLessonPlan },
|
||||
{ label: '案例设计', component: FnTeachCaseGen },
|
||||
{ label: '课程标准', component: FnTeachStdDesign },
|
||||
{ label: '知识图谱', component: FnTeachKnowledgeDiagram },
|
||||
{ label: '课程章节' },
|
||||
{ label: '教研计划' },
|
||||
{ label: '知识图谱', component: FnTeachKnowledgeDiagram, disabled: true },
|
||||
{ label: '课程章节', component: FnTeachCourseChapter },
|
||||
{ label: '思政案例', component: FnTeachPoliticalCase },
|
||||
{ label: '教研计划', component: FnTeachResearchPlan },
|
||||
{ label: '教学计划', component: FnTeachPlan },
|
||||
{ label: '课程大纲', component: FnTeachCourseOutline },
|
||||
]
|
||||
|
||||
const currentNav = ref(0)
|
||||
|
Loading…
Reference in New Issue
Block a user