@quidgest/chatbot 0.4.0 → 0.5.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.
Files changed (73) hide show
  1. package/dist/components/ChatBot/ChatBot.vue.d.ts +53 -0
  2. package/dist/components/ChatBot/types.d.ts +48 -0
  3. package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +17 -0
  4. package/dist/components/ChatBotInput/index.d.ts +5 -0
  5. package/dist/components/ChatBotInput/types.d.ts +28 -0
  6. package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +41 -0
  7. package/dist/components/ChatBotMessage/ChatBotMessageButtons.vue.d.ts +21 -0
  8. package/dist/components/ChatBotMessage/index.d.ts +6 -0
  9. package/dist/components/ChatBotMessage/types.d.ts +46 -0
  10. package/dist/components/ChatToolBar/ChatToolBar.vue.d.ts +39 -0
  11. package/dist/components/ChatToolBar/index.d.ts +5 -0
  12. package/dist/components/ChatToolBar/types.d.ts +16 -0
  13. package/dist/components/FieldPreview/FieldPreview.vue.d.ts +17 -0
  14. package/dist/components/FieldPreview/index.d.ts +5 -0
  15. package/dist/components/FieldPreview/types.d.ts +7 -0
  16. package/dist/components/MarkdownRender/MarkdownRender.vue.d.ts +13 -0
  17. package/dist/components/MarkdownRender/index.d.ts +5 -0
  18. package/dist/components/MarkdownRender/types.d.ts +7 -0
  19. package/dist/composables/useChatApi.d.ts +23 -0
  20. package/dist/composables/useChatMessages.d.ts +11 -0
  21. package/dist/composables/useSSE.d.ts +10 -0
  22. package/dist/composables/useTexts.d.ts +26 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +27 -47
  25. package/dist/index.mjs +2651 -8663
  26. package/dist/style.css +1 -1
  27. package/dist/utils/helper.d.ts +1 -0
  28. package/package.json +5 -5
  29. package/src/assets/chatbot_profile.svg +1 -0
  30. package/src/assets/styles/styles.scss +10 -42
  31. package/src/components/ChatBot/ChatBot.vue +375 -0
  32. package/src/components/ChatBot/types.ts +55 -0
  33. package/src/components/ChatBotInput/ChatBotInput.vue +195 -0
  34. package/src/components/ChatBotInput/index.ts +5 -0
  35. package/src/components/ChatBotInput/types.ts +33 -0
  36. package/src/components/ChatBotMessage/ChatBotMessage.vue +139 -0
  37. package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +169 -0
  38. package/src/components/ChatBotMessage/index.ts +8 -0
  39. package/src/components/ChatBotMessage/types.ts +70 -0
  40. package/src/components/ChatToolBar/ChatToolBar.vue +82 -0
  41. package/src/components/ChatToolBar/index.ts +5 -0
  42. package/src/components/ChatToolBar/types.ts +18 -0
  43. package/src/components/FieldPreview/FieldPreview.vue +78 -0
  44. package/src/components/FieldPreview/field-preview.scss +34 -0
  45. package/src/components/FieldPreview/index.ts +5 -0
  46. package/src/components/FieldPreview/types.ts +7 -0
  47. package/src/components/MarkdownRender/MarkdownRender.vue +25 -0
  48. package/src/components/MarkdownRender/index.ts +5 -0
  49. package/src/components/MarkdownRender/markdown-render.scss +24 -0
  50. package/src/components/MarkdownRender/types.ts +7 -0
  51. package/src/components/PulseDots/PulseDots.vue +24 -0
  52. package/src/components/PulseDots/pulse-dots.scss +37 -0
  53. package/src/composables/useChatApi.ts +156 -0
  54. package/src/composables/useChatMessages.ts +58 -0
  55. package/src/composables/useSSE.ts +90 -0
  56. package/src/composables/useTexts.ts +32 -0
  57. package/src/index.ts +1 -1
  58. package/src/utils/helper.ts +12 -0
  59. package/dist/components/CBMessage.vue.d.ts +0 -95
  60. package/dist/components/ChatBot.vue.d.ts +0 -65
  61. package/dist/components/index.d.ts +0 -4
  62. package/dist/types/chatbot.type.d.ts +0 -14
  63. package/dist/types/message.type.d.ts +0 -34
  64. package/dist/types/texts.type.d.ts +0 -3
  65. package/src/assets/chatbot.png +0 -0
  66. package/src/components/CBMessage.vue +0 -276
  67. package/src/components/ChatBot.vue +0 -496
  68. package/src/components/PulseDots.vue +0 -15
  69. package/src/components/index.ts +0 -4
  70. package/src/types/chatbot.type.ts +0 -15
  71. package/src/types/message.type.ts +0 -55
  72. package/src/types/texts.type.ts +0 -3
  73. /package/dist/components/{PulseDots.vue.d.ts → PulseDots/PulseDots.vue.d.ts} +0 -0
@@ -0,0 +1,5 @@
1
+ import ChatToolBar from './ChatToolBar.vue'
2
+ import type { ChatToolBarProps } from './types'
3
+
4
+ export { ChatToolBar }
5
+ export type { ChatToolBarProps }
@@ -0,0 +1,18 @@
1
+ import { AvailableAgents } from '../ChatBot/types'
2
+
3
+ export type ChatToolBarProps = {
4
+ /**
5
+ * If true, the toolbar will be disabled and not clickable.
6
+ */
7
+ disabled?: boolean
8
+
9
+ /**
10
+ * Availabe Agents to select from.
11
+ */
12
+ availableAgents?: AvailableAgents[]
13
+
14
+ /**
15
+ * The currently selected agent.
16
+ */
17
+ selectedAgentKey?: string
18
+ }
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div class="q-field-preview">
3
+ <div class="q-field-preview__toolbar">
4
+ <span>
5
+ Suggestions for field: <b>{{ props.name }}</b>
6
+ </span>
7
+ </div>
8
+ <div class="q-field-preview__content">
9
+ <component
10
+ :is="previewComponent"
11
+ v-bind="previewComponentProps" />
12
+ </div>
13
+ <div class="q-field-preview__footer">
14
+ <q-button-group borderless>
15
+ <!-- <q-button
16
+ :title="texts.regenerateResponse"
17
+ :disabled="props.disabled"
18
+ borderless
19
+ @click="console.log('Regenerate response')">
20
+ <q-icon icon="reset" />
21
+ </q-button> -->
22
+ <q-button
23
+ :label="texts.apply"
24
+ :disabled="blockApplyButton"
25
+ :readonly="blockApplyButton"
26
+ @click="emitApply">
27
+ <q-icon icon="apply" />
28
+ </q-button>
29
+ </q-button-group>
30
+ </div>
31
+ </div>
32
+ </template>
33
+
34
+ <script setup lang="ts">
35
+ import { QButton, QIcon, QButtonGroup } from '@quidgest/ui/components'
36
+ import MarkdownRender from '../MarkdownRender/MarkdownRender.vue'
37
+ import { FieldPreviewProps } from './types'
38
+ import { useTexts } from '@/composables/useTexts'
39
+ import { computed, ref } from 'vue'
40
+ import { parseFieldValue } from '@/utils/helper'
41
+
42
+ const texts = useTexts()
43
+ const props = defineProps<FieldPreviewProps>()
44
+ const blockApply = ref(props.applied)
45
+ const blockApplyButton = computed(() => {
46
+ return props.disabled || blockApply.value
47
+ })
48
+
49
+ const previewComponent = computed(() => {
50
+ if (props.type === 'text' || props.type === 'multiline_text')
51
+ return MarkdownRender
52
+
53
+ return 'div'
54
+ })
55
+
56
+ const previewComponentProps = computed(() => {
57
+ const componentProps: Record<string, unknown> = {}
58
+
59
+ if (previewComponent.value === MarkdownRender)
60
+ componentProps.source = props.text
61
+ else componentProps.innerHTML = props.text
62
+
63
+ return componentProps
64
+ })
65
+
66
+ const emits = defineEmits<{
67
+ (e: 'apply', text: unknown): void
68
+ }>()
69
+
70
+ function emitApply() {
71
+ if (blockApply.value) return
72
+ blockApply.value = true
73
+
74
+ const text = parseFieldValue(props.type, props.text)
75
+
76
+ emits('apply', text)
77
+ }
78
+ </script>
@@ -0,0 +1,34 @@
1
+ .q-field-preview {
2
+ position: relative;
3
+ display: flex;
4
+ flex-direction: column;
5
+ margin: 1rem 0.25rem;
6
+
7
+ &__toolbar {
8
+ z-index: 1;
9
+ display: flex;
10
+ flex-direction: row;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ padding: 0.1rem 0.2rem;
14
+ }
15
+
16
+ &__content {
17
+ position: relative;
18
+ background-color: #f5f5f5;
19
+ border: 1px solid #e0e0e0;
20
+ border-radius: 6px;
21
+ padding: 0.75rem 1rem;
22
+ font-size: 0.875rem;
23
+ }
24
+
25
+ &__footer {
26
+ display: flex;
27
+ flex-direction: row;
28
+ margin-top: 0.25rem;
29
+ }
30
+ }
31
+
32
+ .q-field-preview:first-child {
33
+ margin: 0 1rem 0.25rem 0.25rem;
34
+ }
@@ -0,0 +1,5 @@
1
+ import FieldPreview from './FieldPreview.vue'
2
+ import type { FieldPreviewProps } from './types'
3
+
4
+ export { FieldPreview }
5
+ export type { FieldPreviewProps }
@@ -0,0 +1,7 @@
1
+ export type FieldPreviewProps = {
2
+ name: string
3
+ text: string
4
+ type: string
5
+ disabled?: boolean
6
+ applied?: boolean
7
+ }
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <div
3
+ class="markdown-renderer"
4
+ v-html="renderedContent"></div>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import { ref, computed } from 'vue'
9
+ import type { MarkdownRenderProps } from './types'
10
+
11
+ import MarkdownIt from 'markdown-it'
12
+
13
+ const props = defineProps<MarkdownRenderProps>()
14
+
15
+ const md = ref(
16
+ new MarkdownIt({
17
+ html: true,
18
+ ...(props.options ?? {})
19
+ })
20
+ )
21
+
22
+ if (props.plugins) props.plugins.forEach((plugin) => md.value.use(plugin))
23
+
24
+ const renderedContent = computed(() => md.value.render(props.source))
25
+ </script>
@@ -0,0 +1,5 @@
1
+ import MarkdownRender from './MarkdownRender.vue'
2
+ import type { MarkdownRenderProps } from './types'
3
+
4
+ export { MarkdownRender }
5
+ export type { MarkdownRenderProps }
@@ -0,0 +1,24 @@
1
+ .markdown-renderer {
2
+ pre,
3
+ code {
4
+ white-space: pre-wrap;
5
+ word-break: break-word;
6
+ overflow-wrap: break-word;
7
+ overflow-x: auto;
8
+ }
9
+
10
+ pre {
11
+ position: relative;
12
+ background-color: #f5f5f5;
13
+ border: 1px solid #e0e0e0;
14
+ border-radius: 6px;
15
+ padding: 0.75rem 1rem;
16
+ font-size: 0.875rem;
17
+ }
18
+
19
+ code {
20
+ padding: 0.2rem 0.4rem;
21
+ border-radius: 4px;
22
+ font-size: 0.875rem;
23
+ }
24
+ }
@@ -0,0 +1,7 @@
1
+ import type { Options, PluginSimple } from 'markdown-it'
2
+
3
+ export interface MarkdownRenderProps {
4
+ source: string
5
+ options?: Options
6
+ plugins?: PluginSimple[]
7
+ }
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <div class="pulsing-dots">
3
+ <span class="generating-text">
4
+ {{ texts.generatingResponse }}
5
+ </span>
6
+ <div class="dots-container">
7
+ <span
8
+ v-for="(_, index) in dots"
9
+ :key="index"
10
+ class="dot"
11
+ :style="{ animationDelay: index * 0.2 + 's' }">
12
+ &bull;
13
+ </span>
14
+ </div>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { useTexts } from '@/composables/useTexts'
20
+
21
+ const dots = [1, 2, 3]
22
+
23
+ const texts = useTexts()
24
+ </script>
@@ -0,0 +1,37 @@
1
+ .pulsing-dots {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ flex-direction: row;
6
+ gap: 0.25rem;
7
+ }
8
+
9
+ .generating-text {
10
+ font-size: 0.9rem;
11
+ color: var(--q-theme-primary);
12
+ }
13
+
14
+ .dots-container {
15
+ display: flex;
16
+ align-items: center;
17
+ gap: 0.1rem;
18
+ }
19
+
20
+ .dot {
21
+ font-size: 16px;
22
+ line-height: 1;
23
+ animation: pulse 1s infinite;
24
+ color: var(--q-theme-primary);
25
+ }
26
+
27
+ @keyframes pulse {
28
+ 0%,
29
+ 100% {
30
+ transform: scale(0.8);
31
+ opacity: 0.6;
32
+ }
33
+ 50% {
34
+ transform: scale(1);
35
+ opacity: 1;
36
+ }
37
+ }
@@ -0,0 +1,156 @@
1
+ // Utils
2
+ import axios from 'axios'
3
+ import { ref } from 'vue'
4
+
5
+ // Types
6
+ import type { AxiosRequestConfig, AxiosResponse } from 'axios'
7
+ import { ChatBotMessageContent } from '@/components/ChatBot/types'
8
+ import { useSSE } from './useSSE'
9
+
10
+ type RequestResponse<T> = {
11
+ data: T | null
12
+ error: Error | null
13
+ }
14
+
15
+ export function useChatApi(apiEndpoint: string) {
16
+ const isLoading = ref(false)
17
+ const lastError = ref<Error | null>(null)
18
+
19
+ async function baseRequest<T>(
20
+ config: AxiosRequestConfig
21
+ ): Promise<RequestResponse<T>> {
22
+ isLoading.value = true
23
+ lastError.value = null
24
+
25
+ try {
26
+ const response: AxiosResponse<T> = await axios({
27
+ ...config,
28
+ baseURL: apiEndpoint
29
+ })
30
+ return { data: response.data, error: null }
31
+ } catch (error: any) {
32
+ lastError.value = error
33
+ console.error('Error in API request:', error)
34
+ return { data: null, error }
35
+ } finally {
36
+ isLoading.value = false
37
+ }
38
+ }
39
+
40
+ async function getChatData(
41
+ username: string,
42
+ project: string,
43
+ agentID: string,
44
+ formId: string
45
+ ) {
46
+ return await baseRequest<{
47
+ success: boolean
48
+ history: ChatBotMessageContent[]
49
+ }>({
50
+ method: 'POST',
51
+ url: `/prompt/load`,
52
+ data: {
53
+ username,
54
+ project,
55
+ agentID,
56
+ formId
57
+ }
58
+ })
59
+ }
60
+
61
+ async function clearChatData(
62
+ username: string,
63
+ project: string,
64
+ agentID: string,
65
+ formId: string
66
+ ) {
67
+ return await baseRequest<{ success: boolean }>({
68
+ method: 'POST',
69
+ url: `/prompt/clear`,
70
+ data: {
71
+ username,
72
+ project,
73
+ agentID,
74
+ formId
75
+ }
76
+ })
77
+ }
78
+
79
+ async function sendPrompt(
80
+ formData: FormData,
81
+ onChunk: (chunk: string) => void,
82
+ onError?: (error: Error) => void
83
+ ) {
84
+ isLoading.value = true
85
+ return await useSSE(
86
+ {
87
+ method: 'POST',
88
+ url: `${apiEndpoint}/prompt/submit`,
89
+ data: formData
90
+ },
91
+ {
92
+ onMessage: (data) => {
93
+ onChunk(data)
94
+ },
95
+ onError: (error) => {
96
+ lastError.value = error
97
+ onError?.(error)
98
+ console.error('Error in sendPrompt:', error)
99
+ },
100
+ onDone: () => (isLoading.value = false)
101
+ }
102
+ )
103
+ }
104
+
105
+ async function getFieldSuggestionData(
106
+ jobId: string,
107
+ onChunk: (chunk: string) => void,
108
+ onMetaData: (metadata: Record<string, unknown>) => void
109
+ ) {
110
+ isLoading.value = true
111
+ return await useSSE(
112
+ {
113
+ method: 'POST',
114
+ url: `${apiEndpoint}/get-job-result`,
115
+ data: {
116
+ jobId
117
+ }
118
+ },
119
+ {
120
+ onMessage: (data) => onChunk(data),
121
+ onFieldMetadata: (metadata) => onMetaData(metadata),
122
+ onError: (error) => {
123
+ lastError.value = error
124
+ console.error('Error in getFieldSuggestionData:', error)
125
+ },
126
+ onDone: () => (isLoading.value = false)
127
+ }
128
+ )
129
+ }
130
+
131
+ async function handleFeedback(
132
+ feedback: number,
133
+ comment: string,
134
+ sessionID: string
135
+ ) {
136
+ return await baseRequest<{ success: boolean }>({
137
+ method: 'POST',
138
+ url: `/prompt/feedback`,
139
+ data: {
140
+ messageSessionID: sessionID,
141
+ feedbackValue: feedback,
142
+ feedbackComment: comment
143
+ }
144
+ })
145
+ }
146
+
147
+ return {
148
+ isLoading,
149
+ lastError,
150
+ getChatData,
151
+ clearChatData,
152
+ getFieldSuggestionData,
153
+ sendPrompt,
154
+ handleFeedback
155
+ }
156
+ }
@@ -0,0 +1,58 @@
1
+ import { ref } from 'vue'
2
+ import { v4 as uuidv4 } from 'uuid'
3
+
4
+ import type { Ref } from 'vue'
5
+ import type {
6
+ ChatMessage,
7
+ ChatBotMessageSender
8
+ } from '@/components/ChatBot/types'
9
+
10
+ const messages: Ref<ChatMessage[]> = ref([])
11
+ const nextMessageId = ref(1)
12
+
13
+ export function useChatMessages() {
14
+ function addChatMessage(
15
+ message: string,
16
+ sender?: ChatBotMessageSender,
17
+ imagePreviewUrl?: string | null,
18
+ sessionID?: string,
19
+ isWelcomeMessage?: boolean
20
+ ) {
21
+ const newMessage: ChatMessage = {
22
+ id: nextMessageId.value++,
23
+ message,
24
+ date: new Date(),
25
+ sender: sender || 'bot',
26
+ sessionID: sessionID || uuidv4(),
27
+ imagePreviewUrl: imagePreviewUrl || undefined,
28
+ isWelcomeMessage,
29
+ fields: []
30
+ }
31
+
32
+ messages.value.push(newMessage)
33
+ }
34
+
35
+ function getLastMessage() {
36
+ return messages.value.find(
37
+ (m: ChatMessage) => m.id === nextMessageId.value - 1
38
+ )
39
+ }
40
+
41
+ function clearMessages() {
42
+ messages.value = []
43
+ nextMessageId.value = 1
44
+ }
45
+
46
+ function getMessages() {
47
+ return messages.value
48
+ }
49
+
50
+ return {
51
+ messages,
52
+ nextMessageId,
53
+ addChatMessage,
54
+ getLastMessage,
55
+ clearMessages,
56
+ getMessages
57
+ }
58
+ }
@@ -0,0 +1,90 @@
1
+ import axios, { AxiosRequestConfig } from 'axios'
2
+
3
+ type SSEvents = {
4
+ onMessage?: (message: string) => void
5
+ onError?: (error: Error) => void
6
+ onFieldMetadata?: (metadata: Record<string, unknown>) => void
7
+ onDone?: () => void
8
+ }
9
+
10
+ export async function useSSE(config: AxiosRequestConfig, handlers: SSEvents) {
11
+ const controller = new AbortController()
12
+ const signal = controller.signal
13
+
14
+ const response = await axios({
15
+ ...config,
16
+ headers: {
17
+ Accept: 'text/event-stream'
18
+ },
19
+ responseType: 'stream',
20
+ signal,
21
+ adapter: 'fetch'
22
+ })
23
+
24
+ const stream = response.data
25
+
26
+ if (!stream || !(stream instanceof ReadableStream)) {
27
+ throw new Error('Invalid stream response')
28
+ }
29
+
30
+ const reader = stream.getReader()
31
+ const decoder = new TextDecoder()
32
+
33
+ while (true) {
34
+ const { done, value } = await reader.read()
35
+ if (done) {
36
+ handlers.onDone?.()
37
+ break
38
+ }
39
+
40
+ const chunk = decoder.decode(value, { stream: true })
41
+ const events = chunk.split(/\n\n+/)
42
+
43
+ for (const eventBlock of events) {
44
+ const lines = eventBlock.trim().split('\n')
45
+ let name = ''
46
+ let data = ''
47
+
48
+ for (const line of lines) {
49
+ if (line.startsWith('event:')) {
50
+ name = line.replace('event:', '').trim()
51
+ } else if (line.startsWith('data:')) {
52
+ data = line.replace('data:', '').trim()
53
+ }
54
+ }
55
+
56
+ if (!name || !data) continue
57
+
58
+ try {
59
+ const parsedData = JSON.parse(data)
60
+
61
+ switch (name) {
62
+ case 'message':
63
+ handlers.onMessage?.(parsedData.value)
64
+ break
65
+ case 'error':
66
+ handlers.onError?.(new Error(parsedData.value))
67
+ break
68
+ case 'field_metadata':
69
+ handlers.onFieldMetadata?.(parsedData)
70
+ break
71
+ case 'done':
72
+ handlers.onDone?.()
73
+ break
74
+ default:
75
+ console.warn(`Unknown event type: ${name}`)
76
+ }
77
+ } catch (error) {
78
+ handlers.onError?.(error as Error)
79
+ console.error('Error processing event:', error)
80
+ continue
81
+ } finally {
82
+ // Ensure we always clean up the reader
83
+ if (done) {
84
+ reader.releaseLock()
85
+ controller.abort()
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
@@ -0,0 +1,32 @@
1
+ export function useTexts() {
2
+ return {
3
+ copy: 'Copy',
4
+ apply: 'Apply',
5
+ applyAll: 'Apply all',
6
+ chatbotTitle: 'ChatBot',
7
+ sendMessage: 'Send message',
8
+ clearChat: 'Clear chat',
9
+ inputLabel: 'What can I help with?',
10
+ imageUpload: 'Upload Image',
11
+ imageUploadQButton: 'Upload Image',
12
+ goodResponse: 'Good response',
13
+ badResponse: 'Bad response',
14
+ initialMessage:
15
+ "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
16
+ initialAgentMessage:
17
+ 'Just a temporary message while we are working on the agent mode',
18
+ loginError:
19
+ 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
20
+ botIsSick:
21
+ '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!',
22
+ commentDialogTitle: 'Would you like to add a comment?',
23
+ commentPlaceholder: 'Type your comment here (optional)...',
24
+ copyResponse: 'Copy response',
25
+ submitButton: 'Submit',
26
+ cancelButton: 'Cancel',
27
+ senderImage: 'Sender Image',
28
+ imagePreview: 'Image preview',
29
+ regenerateResponse: 'Regenerate response',
30
+ generatingResponse: 'Generating'
31
+ }
32
+ }
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import ChatBot from '@/components/ChatBot.vue'
1
+ import ChatBot from '@/components/ChatBot/ChatBot.vue'
2
2
  import '@/assets/styles/styles.scss'
3
3
 
4
4
  export default ChatBot
@@ -0,0 +1,12 @@
1
+ export function parseFieldValue(type: string, text: string) {
2
+ switch (type) {
3
+ case 'date':
4
+ return new Date(text).toLocaleDateString()
5
+ case 'number':
6
+ return parseFloat(text)
7
+ case 'boolean':
8
+ return text.toLowerCase() === 'true'
9
+ default:
10
+ return text.toString()
11
+ }
12
+ }