@windward/integrations 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/components/Integration/AiAgentIntegration/ChatWindow.vue +916 -0
- package/components/LLM/BloomTaxonomySelector.vue +120 -0
- package/components/LLM/ContentSelector.vue +66 -0
- package/components/LLM/GenerateContent/AssessmentQuestionGenerateButton.vue +285 -0
- package/components/LLM/GenerateContent/BlockQuestionGenerateButton.vue +514 -0
- package/components/LLM/GenerateContent/FakeTextStream.vue +67 -0
- package/i18n/en-US/components/ai_agent/chat.ts +20 -0
- package/i18n/en-US/components/ai_agent/index.ts +5 -0
- package/i18n/en-US/components/index.ts +4 -0
- package/i18n/en-US/components/llm/blooms.ts +15 -0
- package/i18n/en-US/components/llm/content_selector.ts +3 -0
- package/i18n/en-US/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/en-US/components/llm/generate_content/generate_questions.ts +81 -0
- package/i18n/en-US/components/llm/generate_content/index.ts +7 -0
- package/i18n/en-US/components/llm/index.ts +10 -0
- package/i18n/en-US/shared/permission.ts +10 -0
- package/i18n/en-US/shared/settings.ts +2 -1
- package/i18n/es-ES/components/ai_agent/chat.ts +20 -0
- package/i18n/es-ES/components/ai_agent/index.ts +5 -0
- package/i18n/es-ES/components/index.ts +4 -0
- package/i18n/es-ES/components/llm/blooms.ts +15 -0
- package/i18n/es-ES/components/llm/content_selector.ts +3 -0
- package/i18n/es-ES/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/es-ES/components/llm/generate_content/generate_questions.ts +85 -0
- package/i18n/es-ES/components/llm/generate_content/index.ts +7 -0
- package/i18n/es-ES/components/llm/index.ts +10 -0
- package/i18n/es-ES/shared/permission.ts +10 -0
- package/i18n/es-ES/shared/settings.ts +2 -1
- package/i18n/sv-SE/components/ai_agent/chat.ts +19 -0
- package/i18n/sv-SE/components/ai_agent/index.ts +5 -0
- package/i18n/sv-SE/components/index.ts +4 -0
- package/i18n/sv-SE/components/llm/blooms.ts +15 -0
- package/i18n/sv-SE/components/llm/content_selector.ts +3 -0
- package/i18n/sv-SE/components/llm/generate_content/fake_text_stream.ts +62 -0
- package/i18n/sv-SE/components/llm/generate_content/generate_questions.ts +82 -0
- package/i18n/sv-SE/components/llm/generate_content/index.ts +7 -0
- package/i18n/sv-SE/components/llm/index.ts +10 -0
- package/i18n/sv-SE/shared/permission.ts +10 -0
- package/i18n/sv-SE/shared/settings.ts +1 -0
- package/models/Activity.ts +8 -0
- package/models/AgentChat.ts +12 -0
- package/models/AgentChatMessage.ts +12 -0
- package/package.json +2 -1
- package/plugin.js +34 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-select
|
|
3
|
+
v-model="selectedDifficulty"
|
|
4
|
+
:items="taxonomyLevels"
|
|
5
|
+
item-text="text"
|
|
6
|
+
outlined
|
|
7
|
+
:hide-details="hideDetails"
|
|
8
|
+
dense
|
|
9
|
+
:disabled="disabled"
|
|
10
|
+
:label="
|
|
11
|
+
$t('windward.integrations.components.llm.blooms.blooms_taxonomy')
|
|
12
|
+
"
|
|
13
|
+
@input="$emit('input', selectedDifficulty)"
|
|
14
|
+
></v-select>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script>
|
|
18
|
+
import _ from 'lodash'
|
|
19
|
+
|
|
20
|
+
export default {
|
|
21
|
+
name: 'LLMBloomTaxonomySelector',
|
|
22
|
+
props: {
|
|
23
|
+
// The assessment question
|
|
24
|
+
value: {
|
|
25
|
+
type: [String, null],
|
|
26
|
+
required: false,
|
|
27
|
+
default: 'None',
|
|
28
|
+
},
|
|
29
|
+
levels: {
|
|
30
|
+
type: Array,
|
|
31
|
+
required: false,
|
|
32
|
+
default: () => {
|
|
33
|
+
return []
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
advancedLevels: { type: Boolean, required: false, default: false },
|
|
37
|
+
hideDetails: { type: Boolean, required: false, default: true },
|
|
38
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
39
|
+
},
|
|
40
|
+
data() {
|
|
41
|
+
return {
|
|
42
|
+
selectedDifficulty: 'None',
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
computed: {
|
|
46
|
+
taxonomyLevels() {
|
|
47
|
+
let levels = []
|
|
48
|
+
|
|
49
|
+
// Basic Bloom's taxonomy levels available to all question types
|
|
50
|
+
const basicBloomTaxonomy = [
|
|
51
|
+
{
|
|
52
|
+
value: 'None',
|
|
53
|
+
text: this.$t(
|
|
54
|
+
'windward.integrations.components.llm.blooms.none'
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: 'Remember',
|
|
59
|
+
text: this.$t(
|
|
60
|
+
'windward.integrations.components.llm.blooms.remember'
|
|
61
|
+
),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
value: 'Understand',
|
|
65
|
+
text: this.$t(
|
|
66
|
+
'windward.integrations.components.llm.blooms.understand'
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
value: 'Apply',
|
|
71
|
+
text: this.$t(
|
|
72
|
+
'windward.integrations.components.llm.blooms.apply'
|
|
73
|
+
),
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
const advancedBlooms = [
|
|
78
|
+
{
|
|
79
|
+
value: 'Analyze',
|
|
80
|
+
text: this.$t(
|
|
81
|
+
'windward.integrations.components.llm.blooms.analyze'
|
|
82
|
+
),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
value: 'Evaluate',
|
|
86
|
+
text: this.$t(
|
|
87
|
+
'windward.integrations.components.llm.blooms.evaluate'
|
|
88
|
+
),
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
// Manually defined levels
|
|
93
|
+
if (this.levels.length > 0) {
|
|
94
|
+
// All blooms for filtering
|
|
95
|
+
levels = levels.concat(basicBloomTaxonomy, advancedBlooms)
|
|
96
|
+
|
|
97
|
+
levels = levels.filter((v) => {
|
|
98
|
+
return this.levels.includes(v.value)
|
|
99
|
+
})
|
|
100
|
+
} else {
|
|
101
|
+
// Show all levels and omit advanced if not enabled
|
|
102
|
+
levels = levels.concat(basicBloomTaxonomy)
|
|
103
|
+
|
|
104
|
+
if (this.advancedLevels) {
|
|
105
|
+
levels = levels.concat(advancedBlooms)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return levels
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
mounted() {
|
|
112
|
+
if (_.isEmpty(this.value)) {
|
|
113
|
+
// Emit the default of 'None' as the initial value if none is set
|
|
114
|
+
this.$emit('input', this.selectedDifficulty)
|
|
115
|
+
} else {
|
|
116
|
+
this.selectedDifficulty = _.cloneDeep(this.value)
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-select
|
|
3
|
+
v-model="selectedContent"
|
|
4
|
+
:items="flattenedContent"
|
|
5
|
+
outlined
|
|
6
|
+
hide-details
|
|
7
|
+
dense
|
|
8
|
+
:disabled="disabled"
|
|
9
|
+
:label="
|
|
10
|
+
$t(
|
|
11
|
+
'windward.integrations.components.llm.content_selector.selected_pages'
|
|
12
|
+
)
|
|
13
|
+
"
|
|
14
|
+
item-text="content.name"
|
|
15
|
+
return-object
|
|
16
|
+
@input="$emit('input', selectedContent)"
|
|
17
|
+
></v-select>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<script>
|
|
21
|
+
import _ from 'lodash'
|
|
22
|
+
import { mapGetters } from 'vuex'
|
|
23
|
+
|
|
24
|
+
export default {
|
|
25
|
+
name: 'LLMContentSelector',
|
|
26
|
+
props: {
|
|
27
|
+
value: {
|
|
28
|
+
type: [Object, null],
|
|
29
|
+
required: false,
|
|
30
|
+
default: null,
|
|
31
|
+
},
|
|
32
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
33
|
+
},
|
|
34
|
+
data() {
|
|
35
|
+
return {
|
|
36
|
+
selectedContent: null,
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
computed: {
|
|
40
|
+
...mapGetters({
|
|
41
|
+
content: 'content/get',
|
|
42
|
+
contentTree: 'content/getTree',
|
|
43
|
+
}),
|
|
44
|
+
flattenedContent() {
|
|
45
|
+
const flatTree = this.$ContentService.getFlatTree()
|
|
46
|
+
|
|
47
|
+
const homepage = this.$ContentService.getHomepage()
|
|
48
|
+
if (!_.isEmpty(homepage)) {
|
|
49
|
+
flatTree.unshift(homepage)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return flatTree
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
mounted() {
|
|
56
|
+
if (_.isEmpty(this.value)) {
|
|
57
|
+
this.selectedContent = _.cloneDeep(this.content)
|
|
58
|
+
|
|
59
|
+
// Emit the current page as selected if none was set
|
|
60
|
+
this.$emit('input', this.selectedContent)
|
|
61
|
+
} else {
|
|
62
|
+
this.selectedContent = _.cloneDeep(this.value)
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-row class="container-generate-ai mt-2">
|
|
4
|
+
<v-col cols="12">{{
|
|
5
|
+
$t('windward.integrations.components.llm.ai_assistance')
|
|
6
|
+
}}</v-col>
|
|
7
|
+
<v-col cols="12">
|
|
8
|
+
<ContentSelector
|
|
9
|
+
v-model="selectedContent"
|
|
10
|
+
:disabled="isLoading || disabled"
|
|
11
|
+
></ContentSelector>
|
|
12
|
+
</v-col>
|
|
13
|
+
<v-col cols="12">
|
|
14
|
+
<BloomTaxonomySelector
|
|
15
|
+
v-model="selectedDifficulty"
|
|
16
|
+
:advanced-levels="hasAdvancedBlooms"
|
|
17
|
+
:disabled="isLoading || disabled"
|
|
18
|
+
></BloomTaxonomySelector>
|
|
19
|
+
</v-col>
|
|
20
|
+
<v-col cols="12">
|
|
21
|
+
<v-btn
|
|
22
|
+
elevation="0"
|
|
23
|
+
color="secondary"
|
|
24
|
+
class="mb-1 w-100"
|
|
25
|
+
:loading="isLoading"
|
|
26
|
+
:disabled="isLoading || disabled"
|
|
27
|
+
@click="generateAIQuestion"
|
|
28
|
+
>
|
|
29
|
+
<v-icon v-if="!isLoading" class="pr-1">
|
|
30
|
+
mdi-magic-staff
|
|
31
|
+
</v-icon>
|
|
32
|
+
{{
|
|
33
|
+
$t(
|
|
34
|
+
'windward.integrations.components.llm.generate_content.generate_questions.button_label'
|
|
35
|
+
)
|
|
36
|
+
}}
|
|
37
|
+
<template #loader>
|
|
38
|
+
<v-progress-circular
|
|
39
|
+
v-if="!showText || !showLoadingText"
|
|
40
|
+
indeterminate
|
|
41
|
+
size="23"
|
|
42
|
+
></v-progress-circular>
|
|
43
|
+
<FakeTextStream
|
|
44
|
+
v-if="showText && showLoadingText"
|
|
45
|
+
></FakeTextStream>
|
|
46
|
+
</template>
|
|
47
|
+
</v-btn>
|
|
48
|
+
</v-col>
|
|
49
|
+
</v-row>
|
|
50
|
+
</div>
|
|
51
|
+
</template>
|
|
52
|
+
|
|
53
|
+
<script>
|
|
54
|
+
import _ from 'lodash'
|
|
55
|
+
import ContentSelector from '../ContentSelector.vue'
|
|
56
|
+
import BloomTaxonomySelector from '../BloomTaxonomySelector.vue'
|
|
57
|
+
import FakeTextStream from './FakeTextStream'
|
|
58
|
+
import AssessmentQuestion from '~/models/AssessmentQuestion'
|
|
59
|
+
import Course from '~/models/Course'
|
|
60
|
+
import Assessment from '~/models/Assessment'
|
|
61
|
+
import Content from '~/models/Content'
|
|
62
|
+
import ContentBlock from '~/models/ContentBlock'
|
|
63
|
+
|
|
64
|
+
export default {
|
|
65
|
+
name: 'AssessmentQuestionGenerateButton',
|
|
66
|
+
components: {
|
|
67
|
+
ContentSelector,
|
|
68
|
+
BloomTaxonomySelector,
|
|
69
|
+
FakeTextStream,
|
|
70
|
+
},
|
|
71
|
+
props: {
|
|
72
|
+
// The assessment question
|
|
73
|
+
value: { type: [AssessmentQuestion, Object], required: true },
|
|
74
|
+
course: { type: [Course, Object], required: true },
|
|
75
|
+
content: { type: [Content, Object], required: true },
|
|
76
|
+
block: { type: [ContentBlock, Object], required: true },
|
|
77
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
78
|
+
showText: { type: Boolean, required: false, default: true },
|
|
79
|
+
},
|
|
80
|
+
data() {
|
|
81
|
+
return {
|
|
82
|
+
isLoading: false,
|
|
83
|
+
loadingTimeout: null,
|
|
84
|
+
showLoadingText: false,
|
|
85
|
+
selectedContent: null,
|
|
86
|
+
selectedDifficulty: null,
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
computed: {
|
|
90
|
+
questionType() {
|
|
91
|
+
let type = _.get(this.value, 'question_type', '')
|
|
92
|
+
|
|
93
|
+
// Type wasn't readily available on the this.value. Determine based off the question type id
|
|
94
|
+
if (
|
|
95
|
+
type === '' &&
|
|
96
|
+
_.get(this.value, 'assessment_question_type_id', null) !== null
|
|
97
|
+
) {
|
|
98
|
+
type = _.get(
|
|
99
|
+
this.$Assessment.getQuestionType(
|
|
100
|
+
_.get(this.value, 'assessment_question_type_id')
|
|
101
|
+
),
|
|
102
|
+
'name',
|
|
103
|
+
''
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
return type
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
hasAdvancedBlooms() {
|
|
110
|
+
// Only add higher-level Bloom's taxonomy for supported question types
|
|
111
|
+
return [
|
|
112
|
+
'multi_choice_single_answer',
|
|
113
|
+
'multi_choice_multi_answer',
|
|
114
|
+
'ordering',
|
|
115
|
+
].includes(this.questionType)
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
methods: {
|
|
119
|
+
async generateAIQuestion() {
|
|
120
|
+
this.isLoading = true
|
|
121
|
+
|
|
122
|
+
// Wait 5 seconds before kicking on the text
|
|
123
|
+
this.loadingTimeout = setTimeout(() => {
|
|
124
|
+
this.showLoadingText = true
|
|
125
|
+
}, 5000)
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const course = new Course(this.course)
|
|
129
|
+
const content = new Content(
|
|
130
|
+
this.selectedContent || this.content
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// ASSESSMENT QUESTION GENERATION
|
|
134
|
+
const assessment = new Assessment({ id: this.block.id })
|
|
135
|
+
|
|
136
|
+
const responseBuilder = AssessmentQuestion.custom(
|
|
137
|
+
course,
|
|
138
|
+
content,
|
|
139
|
+
assessment,
|
|
140
|
+
`suggest-questions`
|
|
141
|
+
).where('question_type', this.questionType)
|
|
142
|
+
|
|
143
|
+
// Apply blooms level filter if it's not 'None'
|
|
144
|
+
if (this.selectedDifficulty !== 'None') {
|
|
145
|
+
responseBuilder.where(
|
|
146
|
+
'blooms_level',
|
|
147
|
+
this.selectedDifficulty
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const response = await responseBuilder.get()
|
|
152
|
+
|
|
153
|
+
if (response && response.length > 0) {
|
|
154
|
+
const generatedQuestion = response[0]
|
|
155
|
+
|
|
156
|
+
this.$emit('input', generatedQuestion)
|
|
157
|
+
} else {
|
|
158
|
+
throw new Error('Invalid response from question generation')
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
const errorMessage =
|
|
162
|
+
error.response?.data?.error?.message ||
|
|
163
|
+
error.message ||
|
|
164
|
+
'assessment.error.technical'
|
|
165
|
+
const errorType = errorMessage.split('.').pop()
|
|
166
|
+
const basePath =
|
|
167
|
+
'windward.integrations.components.llm.generate_content.generate_questions.errors'
|
|
168
|
+
|
|
169
|
+
let errorText = ''
|
|
170
|
+
|
|
171
|
+
// Check for character limit error by structured data first
|
|
172
|
+
const errorSubtype =
|
|
173
|
+
error.response?.data?.error?.details?.error_subtype
|
|
174
|
+
const errorDetails = error.response?.data?.error?.details
|
|
175
|
+
|
|
176
|
+
if (errorSubtype === 'CHARACTER_LIMIT_EXCEEDED') {
|
|
177
|
+
// Use structured data if available
|
|
178
|
+
const field = errorDetails?.field || 'content'
|
|
179
|
+
const limit = errorDetails?.limit
|
|
180
|
+
const actual = errorDetails?.actual
|
|
181
|
+
|
|
182
|
+
// Use parameterized message if we have the data
|
|
183
|
+
if (limit && actual) {
|
|
184
|
+
errorText = this.$t(
|
|
185
|
+
`${basePath}.character_limit_detailed`,
|
|
186
|
+
{ field, limit, actual }
|
|
187
|
+
)
|
|
188
|
+
} else {
|
|
189
|
+
// Fallback to generic message
|
|
190
|
+
errorText = this.$t(`${basePath}.character_limit`)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Keep backward compatibility - check for old message pattern
|
|
194
|
+
else if (
|
|
195
|
+
errorDetails?.message ===
|
|
196
|
+
'An error occurred while generating content. Try generating again.'
|
|
197
|
+
) {
|
|
198
|
+
errorText = this.$t(`${basePath}.character_limit`)
|
|
199
|
+
} else if (
|
|
200
|
+
errorType === 'insufficient_content' &&
|
|
201
|
+
error.response?.data?.error?.details
|
|
202
|
+
) {
|
|
203
|
+
// Handle dynamic Bloom's taxonomy insufficient content errors
|
|
204
|
+
const details = error.response.data.error.details
|
|
205
|
+
const bloomsLevel = details.blooms_level
|
|
206
|
+
const minimumRequired = details.minimum_required
|
|
207
|
+
|
|
208
|
+
if (bloomsLevel && bloomsLevel !== 'None') {
|
|
209
|
+
// Message with Bloom's level
|
|
210
|
+
errorText =
|
|
211
|
+
this.$t(`${basePath}.insufficient_content_blooms`, {
|
|
212
|
+
bloomsLevel,
|
|
213
|
+
}) +
|
|
214
|
+
'\n\n' +
|
|
215
|
+
this.$t(
|
|
216
|
+
`${basePath}.insufficient_content_blooms_support`,
|
|
217
|
+
{ minimumRequired }
|
|
218
|
+
)
|
|
219
|
+
} else if (minimumRequired) {
|
|
220
|
+
// Message without Bloom's level but with word count
|
|
221
|
+
errorText =
|
|
222
|
+
this.$t(
|
|
223
|
+
`${basePath}.insufficient_content_dynamic`
|
|
224
|
+
) +
|
|
225
|
+
'\n\n' +
|
|
226
|
+
this.$t(
|
|
227
|
+
`${basePath}.insufficient_content_dynamic_support`,
|
|
228
|
+
{ minimumRequired }
|
|
229
|
+
)
|
|
230
|
+
} else {
|
|
231
|
+
// Fallback to static translation
|
|
232
|
+
errorText =
|
|
233
|
+
this.$t(`${basePath}.${errorType}`) +
|
|
234
|
+
'\n\n' +
|
|
235
|
+
this.$t(`${basePath}.${errorType}_support`)
|
|
236
|
+
}
|
|
237
|
+
} else {
|
|
238
|
+
// Check if the error type is a valid i18n key
|
|
239
|
+
const errorKey = `${basePath}.${errorType}`
|
|
240
|
+
const supportKey = `${basePath}.${errorType}_support`
|
|
241
|
+
|
|
242
|
+
// Try to get the translation, fall back to default error if not found
|
|
243
|
+
const hasTranslation = this.$te(errorKey)
|
|
244
|
+
|
|
245
|
+
if (hasTranslation) {
|
|
246
|
+
errorText =
|
|
247
|
+
this.$t(errorKey) + '\n\n' + this.$t(supportKey)
|
|
248
|
+
} else {
|
|
249
|
+
// Fall back to default error message
|
|
250
|
+
errorText =
|
|
251
|
+
this.$t(`${basePath}.default`) +
|
|
252
|
+
'\n\n' +
|
|
253
|
+
this.$t(`${basePath}.default_support`)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (errorType === 'technical') {
|
|
257
|
+
const errorCode =
|
|
258
|
+
error.response?.data?.error?.details?.error_type ||
|
|
259
|
+
'UNKNOWN'
|
|
260
|
+
errorText = errorText.replace('[ERROR_CODE]', errorCode)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
this.$dialog.error(errorText, {
|
|
265
|
+
duration: 5000,
|
|
266
|
+
keepOnHover: true,
|
|
267
|
+
singleton: true,
|
|
268
|
+
type: 'error',
|
|
269
|
+
})
|
|
270
|
+
} finally {
|
|
271
|
+
this.isLoading = false
|
|
272
|
+
this.showLoadingText = false
|
|
273
|
+
clearTimeout(this.loadingTimeout)
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
</script>
|
|
279
|
+
|
|
280
|
+
<style lang="scss" scoped>
|
|
281
|
+
.container-generate-ai {
|
|
282
|
+
outline: 1px solid var(--v-secondary-base);
|
|
283
|
+
border-radius: $border-radius-root;
|
|
284
|
+
}
|
|
285
|
+
</style>
|