FeedbackDropdown.utils.ts136 lines · main
1import { createSupportStorageClient } from '@/components/interfaces/Support/support-storage-client'
2import { generateAttachmentURLs } from '@/data/support/generate-attachment-urls-mutation'
3import { uuidv4 } from '@/lib/helpers'
4
5export const convertB64toBlob = (image: string) => {
6 const contentType = 'image/png'
7 const byteCharacters = atob(image.substr(`data:${contentType};base64,`.length))
8 const byteArrays = []
9
10 for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
11 const slice = byteCharacters.slice(offset, offset + 1024)
12
13 const byteNumbers = new Array(slice.length)
14 for (let i = 0; i < slice.length; i++) {
15 byteNumbers[i] = slice.charCodeAt(i)
16 }
17
18 const byteArray = new Uint8Array(byteNumbers)
19
20 byteArrays.push(byteArray)
21 }
22 const blob = new Blob(byteArrays, { type: contentType })
23 return blob
24}
25
26type UploadAttachmentArgs = {
27 image: string
28 userId?: string
29}
30
31export const uploadAttachment = async ({ image, userId }: UploadAttachmentArgs) => {
32 if (!userId) {
33 console.error(
34 '[FeedbackWidget > uploadAttachment] Unable to upload screenshot, missing user ID'
35 )
36 return undefined
37 }
38
39 const brivenClient = createSupportStorageClient()
40
41 const blob = convertB64toBlob(image)
42 const filename = `${userId}/${uuidv4()}.png`
43 const options = { cacheControl: '3600' }
44
45 const { data: file, error: uploadError } = await brivenClient.storage
46 .from('feedback-attachments')
47 .upload(filename, blob, options)
48
49 if (uploadError || !file) {
50 console.error('Failed to upload screenshot attachment:', uploadError)
51 return undefined
52 }
53
54 const signedUrls = await generateAttachmentURLs({
55 bucket: 'feedback-attachments',
56 filenames: [file.path],
57 })
58 return signedUrls[0]
59}
60
61/**
62 * Client-side heuristic to quickly detect obvious support requests
63 * This provides immediate feedback before the AI classification completes
64 */
65export function isLikelySupportRequest(text: string): boolean {
66 if (!text || text.trim().length === 0) return false
67
68 const lowerText = text.toLowerCase()
69
70 // Common support request patterns
71 const supportPatterns = [
72 // Help requests
73 /need help/i,
74 /having trouble/i,
75 /having issues/i,
76 /having problems/i,
77 /can you help/i,
78 /could you help/i,
79 /please help/i,
80 /need help/i,
81 /how do/i,
82 /how can/i,
83 /how to/i,
84 /why isn't/i,
85 /why doesn't/i,
86 /why won't/i,
87 /why can't/i,
88
89 // Problem indicators
90 /it's not working/i,
91 /it doesn't work/i,
92 /it won't work/i,
93 /it can't work/i,
94 /isn't working/i,
95 /doesn't work/i,
96 /won't work/i,
97 /can't work/i,
98 /not work/i,
99 /i can't/i,
100 /i cannot/i,
101 /unable to/i,
102 /wrong/i,
103 /bad/i,
104
105 // Bug/error indicators
106 /\bbug\b/i,
107 /\bbroken\b/i,
108 /\berror\b/i,
109 /\berrors\b/i,
110 /\bfailed\b/i,
111 /\bfailure\b/i,
112 /\bfailing\b/i,
113 /\bcrash\b/i,
114 /\bcrashed\b/i,
115 /\bcrashing\b/i,
116
117 // Issue/problem keywords
118 /\bissue\b/i,
119 /\bissues\b/i,
120 /\bproblem\b/i,
121 /\bproblems\b/i,
122 /\btrouble\b/i,
123 /\bdifficulty\b/i,
124 /\bdifficulties\b/i,
125
126 // Support-specific phrases
127 /support ticket/i,
128 /contact support/i,
129 /get help/i,
130 /need assistance/i,
131 /require assistance/i,
132 /technical support/i,
133 ]
134
135 return supportPatterns.some((pattern) => pattern.test(lowerText))
136}