-
Notifications
You must be signed in to change notification settings - Fork 206
feat: add Telegram channel support #319
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
acb7d5c
ac7d086
cebd05b
b509ee2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -31,6 +31,7 @@ import ( | |||||||||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/inbox" | ||||||||||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/inbox/channel/email" | ||||||||||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/inbox/channel/livechat" | ||||||||||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/inbox/channel/telegram" | ||||||||||||||||||||||||||||||||||||||
| imodels "github.com/abhinavxd/libredesk/internal/inbox/models" | ||||||||||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/macro" | ||||||||||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/media" | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -740,6 +741,34 @@ func initLiveChatInbox(inboxRecord imodels.Inbox, msgStore inbox.MessageStore, u | |||||||||||||||||||||||||||||||||||||
| return inbox, nil | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // initTelegramInbox initializes the Telegram bot inbox. | ||||||||||||||||||||||||||||||||||||||
| func initTelegramInbox(inboxRecord imodels.Inbox, msgStore inbox.MessageStore, usrStore inbox.UserStore) (inbox.Inbox, error) { | ||||||||||||||||||||||||||||||||||||||
| var config telegram.Config | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // Load JSON data into Koanf. | ||||||||||||||||||||||||||||||||||||||
| if err := ko.Load(rawbytes.Provider([]byte(inboxRecord.Config)), kjson.Parser()); err != nil { | ||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("loading config: %w", err) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if err := ko.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil { | ||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("unmarshalling `%s` %s config: %w", inboxRecord.Channel, inboxRecord.Name, err) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's see the actual code at lines 748-755 and surrounding context
fd cmd/init.go | head -1 | xargs -I {} bash -c 'wc -l {} && cat -n {} | sed -n "740,760p"'Repository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Let's also search for the function definition and related code
rg -n "initTelegramInbox" cmd/init.go -A 20 -B 2Repository: abhinavxd/libredesk Length of output: 2083 🏁 Script executed: # Check where the global `ko` is declared
rg -n "^var.*ko|:= koanf\.|ko\s*=" cmd/init.go | head -20Repository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Look for other inbox initialization functions to see if they have similar patterns
rg -n "func init.*Inbox" cmd/init.goRepository: abhinavxd/libredesk Length of output: 576 🏁 Script executed: # Look for ko variable declaration at package level in cmd/init.go
rg -n "^var.*ko|^ko\s*:=" cmd/init.go | head -10Repository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Check initEmailInbox implementation
rg -n "func initEmailInbox" cmd/init.go -A 40Repository: abhinavxd/libredesk Length of output: 1871 🏁 Script executed: # Check initLiveChatInbox implementation
rg -n "func initLiveChatInbox" cmd/init.go -A 30Repository: abhinavxd/libredesk Length of output: 1407 🏁 Script executed: # Search for all usages of 'ko' in the file to understand the full pattern
rg -n "\bko\." cmd/init.goRepository: abhinavxd/libredesk Length of output: 4584 🏁 Script executed: # Check for package-level variable declarations at the beginning of the file
head -100 cmd/init.go | cat -n | grep -E "var|const"Repository: abhinavxd/libredesk Length of output: 391 🏁 Script executed: # Search for ko declaration - look at the very beginning of the file
head -200 cmd/init.go | cat -nRepository: abhinavxd/libredesk Length of output: 8974 🏁 Script executed: # Look for ko declaration more broadly - might be before line 74
rg -n "^var\s+ko\b|^ko\s*:=\s*koanf" cmd/init.goRepository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Check imports to understand Koanf source
head -50 cmd/init.go | grep -i koanfRepository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Look for global ko variable - check around main function and beyond line 200
tail -n +200 cmd/init.go | head -100 | cat -nRepository: abhinavxd/libredesk Length of output: 3454 🏁 Script executed: # Better approach - search for "var" at the very file level (no indentation)
rg "^var\s" cmd/init.goRepository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Check the main function to see how ko is created/used
rg -n "func main" cmd/init.go -A 50Repository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: # Look for ko usage without seeing declaration - it might be in a different file
# Search for "var ko" across all Go files
fd "\.go$" | xargs rg "^var\s+ko\b" 2>/dev/nullRepository: abhinavxd/libredesk Length of output: 47 🏁 Script executed: # Or check all files in cmd directory
fd "\.go$" cmd/ | xargs rg "var\s+ko\s*\*koanf" 2>/dev/nullRepository: abhinavxd/libredesk Length of output: 47 🏁 Script executed: # Let's search the entire codebase for where ko is initialized
rg "ko\s*=\s*koanf\.New" --type goRepository: abhinavxd/libredesk Length of output: 105 🏁 Script executed: # Alternative: look at the main function location
find . -name "*.go" -exec rg -l "func main" {} \;Repository: abhinavxd/libredesk Length of output: 77 🏁 Script executed: # Found it! ko is in cmd/main.go. Let's see the declaration and context
rg -n "ko\s*=" cmd/main.go -B 5 -A 5 | head -30Repository: abhinavxd/libredesk Length of output: 331 🏁 Script executed: # Now let's understand Koanf's Load() behavior by checking if there are any tests or comments
# in the repository that show how Load() works with multiple calls
rg -n "ko\.Load" cmd/init.go | head -20Repository: abhinavxd/libredesk Length of output: 666 🌐 Web query:
💡 Result: The Load method in Koanf is designed to accumulate configuration state by merging new data into the existing configuration [1][2]. It does not replace the entire state [1][2]. When you call Load, Koanf performs a recursive merge of the new configuration map into the current internal state [1][2]. By default, this process merges nested maps (map[string]any) while overwriting static values such as strings, integers, or slices [1][2]. Key behaviors of this accumulation process include: 1. Incremental Merging: Every successive call to Load or Merge merges new configuration data into the existing set [1][2]. This allows you to layer configurations from multiple sources (e.g., loading a base YAML file followed by environment variable overrides) in any order [1][2]. 2. Recursive Merging: For nested structures, Koanf traverses the maps and merges them recursively rather than simply replacing the top-level key [1][2]. 3. Strict Merge Option: If the default merging behavior is not desired, you can enable StrictMerge in the Koanf configuration [1][2]. When enabled, Load will return an error if it detects conflicting keys or types during the merge process [1][2]. 4. Customization: You can further control how data is merged by providing a custom merge function using the WithMergeFunc option [1][2]. If you need to clear the existing state and start fresh, you must create a new Koanf instance (e.g., k = koanf.New(".")) rather than relying on Load to reset the state [3]. Citations:
🏁 Script executed: # Final verification: confirm the sequence of calls to these functions to ensure they're truly sequential
rg -n "initEmailInbox|initLiveChatInbox|initTelegramInbox" cmd/init.goRepository: abhinavxd/libredesk Length of output: 908 🏁 Script executed: # Check how makeInboxInitializer is used to confirm multiple inboxes are initialized in sequence
rg -n "makeInboxInitializer\|InitInboxes" cmd/init.go -B 2 -A 5Repository: abhinavxd/libredesk Length of output: 45 Use an isolated Koanf instance for per-inbox config parsing to prevent cross-inbox state bleed. Loading each inbox's JSON configuration into the global Proposed fix func initTelegramInbox(inboxRecord imodels.Inbox, msgStore inbox.MessageStore, usrStore inbox.UserStore) (inbox.Inbox, error) {
var config telegram.Config
+ k := koanf.New(".")
// Load JSON data into Koanf.
- if err := ko.Load(rawbytes.Provider([]byte(inboxRecord.Config)), kjson.Parser()); err != nil {
+ if err := k.Load(rawbytes.Provider([]byte(inboxRecord.Config)), kjson.Parser()); err != nil {
return nil, fmt.Errorf("loading config: %w", err)
}
- if err := ko.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil {
+ if err := k.UnmarshalWithConf("", &config, koanf.UnmarshalConf{Tag: "json"}); err != nil {
return nil, fmt.Errorf("unmarshalling `%s` %s config: %w", inboxRecord.Channel, inboxRecord.Name, err)
}Apply the same fix to 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| inb, err := telegram.New(msgStore, usrStore, telegram.Opts{ | ||||||||||||||||||||||||||||||||||||||
| ID: inboxRecord.ID, | ||||||||||||||||||||||||||||||||||||||
| Config: config, | ||||||||||||||||||||||||||||||||||||||
| Lo: initLogger("telegram_inbox"), | ||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("initializing `%s` inbox: `%s` error : %w", inboxRecord.Channel, inboxRecord.Name, err) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| log.Printf("`%s` inbox successfully initialized", inboxRecord.Name) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return inb, nil | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // makeInboxInitializer creates an inbox initializer function. | ||||||||||||||||||||||||||||||||||||||
| func makeInboxInitializer(mgr *inbox.Manager, signAvatarURL func(*null.String)) func(imodels.Inbox, inbox.MessageStore, inbox.UserStore) (inbox.Inbox, error) { | ||||||||||||||||||||||||||||||||||||||
| return func(inboxR imodels.Inbox, msgStore inbox.MessageStore, usrStore inbox.UserStore) (inbox.Inbox, error) { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -748,6 +777,8 @@ func makeInboxInitializer(mgr *inbox.Manager, signAvatarURL func(*null.String)) | |||||||||||||||||||||||||||||||||||||
| return initEmailInbox(inboxR, msgStore, usrStore, mgr) | ||||||||||||||||||||||||||||||||||||||
| case inbox.ChannelLiveChat: | ||||||||||||||||||||||||||||||||||||||
| return initLiveChatInbox(inboxR, msgStore, usrStore, signAvatarURL) | ||||||||||||||||||||||||||||||||||||||
| case inbox.ChannelTelegram: | ||||||||||||||||||||||||||||||||||||||
| return initTelegramInbox(inboxR, msgStore, usrStore) | ||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("unknown inbox channel: %s", inboxR.Channel) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,58 @@ | ||||||||||||||||||||||||||||||
| package main | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/inbox" | ||||||||||||||||||||||||||||||
| "github.com/abhinavxd/libredesk/internal/inbox/channel/telegram" | ||||||||||||||||||||||||||||||
| "github.com/valyala/fasthttp" | ||||||||||||||||||||||||||||||
| "github.com/zerodha/fastglue" | ||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| func handleTelegramWebhook(r *fastglue.Request) error { | ||||||||||||||||||||||||||||||
| var app = r.Context.(*App) | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| inboxID, err := strconv.Atoi(r.RequestCtx.UserValue("id").(string)) | ||||||||||||||||||||||||||||||
| if err != nil || inboxID == 0 { | ||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusBadRequest) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| inb, err := app.inbox.Get(inboxID) | ||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusNotFound) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if inb.Channel() != inbox.ChannelTelegram { | ||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusBadRequest) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| tgInbox, ok := inb.(*telegram.Telegram) | ||||||||||||||||||||||||||||||
| if !ok { | ||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusInternalServerError) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| // Verify webhook authenticity using X-Telegram-Bot-Api-Secret-Token header. | ||||||||||||||||||||||||||||||
| secretToken := string(r.RequestCtx.Request.Header.Peek("X-Telegram-Bot-Api-Secret-Token")) | ||||||||||||||||||||||||||||||
| if !tgInbox.VerifyWebhook(secretToken) { | ||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusUnauthorized) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| var update telegram.Update | ||||||||||||||||||||||||||||||
| if err := json.Unmarshal(r.RequestCtx.PostBody(), &update); err != nil { | ||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusBadRequest) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| if err := tgInbox.ProcessWebhookUpdate(update); err != nil { | ||||||||||||||||||||||||||||||
| app.lo.Error("telegram webhook: error processing update", "error", err) | ||||||||||||||||||||||||||||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should return error? instead of 200? |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
|
coderabbitai[bot] marked this conversation as resolved.
|
||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||
| r.RequestCtx.SetStatusCode(fasthttp.StatusOK) | ||||||||||||||||||||||||||||||
| return nil | ||||||||||||||||||||||||||||||
|
Comment on lines
+58
to
+63
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "telegram.go" -type fRepository: abhinavxd/libredesk Length of output: 127 🏁 Script executed: head -70 cmd/telegram.go | tail -20Repository: abhinavxd/libredesk Length of output: 726 🏁 Script executed: rg "ProcessWebhookUpdate" --type go -B 3 -A 3Repository: abhinavxd/libredesk Length of output: 774 🏁 Script executed: rg "handleTelegramWebhook" --type go -B 5 -A 15Repository: abhinavxd/libredesk Length of output: 2678 🏁 Script executed: rg "func.*ProcessWebhookUpdate" --type go -A 30 internal/inbox/channel/telegram/Repository: abhinavxd/libredesk Length of output: 2234 🏁 Script executed: rg "idempoten|duplicate|retry" --type go cmd/telegram.go internal/inbox/channel/telegram/Repository: abhinavxd/libredesk Length of output: 45 🏁 Script executed: cat -n internal/inbox/channel/telegram/telegram.go | head -100Repository: abhinavxd/libredesk Length of output: 2957 🏁 Script executed: rg "func.*ProcessWebhookUpdate" --type go -A 50 internal/inbox/channel/telegram/telegram.goRepository: abhinavxd/libredesk Length of output: 1559 🏁 Script executed: rg "messageStore\.|userStore\." --type go internal/inbox/channel/telegram/telegram.go | head -20Repository: abhinavxd/libredesk Length of output: 132 🏁 Script executed: web_search
Telegram bot webhook HTTP status codes retry behaviorRepository: abhinavxd/libredesk Length of output: 161 🌐 Web query:
💡 Result: The Telegram Bot API handles webhook delivery based on the HTTP status code returned by your server [1]. If your server responds with a 2xx status code (e.g., 200 OK), Telegram considers the update successfully delivered [1][2]. If your server responds with any status code outside the 2xx range (such as 5xx or 4xx errors), Telegram considers the request unsuccessful [1]. In these cases, Telegram will automatically retry the delivery of the update [1]. The system will continue to attempt redelivery for a reasonable number of attempts before eventually giving up on that specific update [1]. To ensure reliable operation, your webhook listener should be configured to return a 2xx status code immediately upon receiving and successfully processing (or queuing for processing) an update [3][2]. If your server returns non-2xx codes, it will trigger these automatic retries, which can lead to duplicate processing if your logic is not idempotent [1]. You can monitor the status of your webhook, including the last error message and date, using the getWebhookInfo method [1]. Citations:
🏁 Script executed: rg "func.*ProcessWebhookUpdate" --type go -A 60 internal/inbox/channel/telegram/telegram.go | tail -20Repository: abhinavxd/libredesk Length of output: 671 🏁 Script executed: rg "EnqueueIncoming" --type go -B 2 -A 5Repository: abhinavxd/libredesk Length of output: 2164 Return a retriable status on processing failures. If Return a retriable status on processing failures if err := tgInbox.ProcessWebhookUpdate(update); err != nil {
app.lo.Error("telegram webhook: error processing update", "error", err)
+ r.RequestCtx.SetStatusCode(fasthttp.StatusInternalServerError)
+ return nil
}
r.RequestCtx.SetStatusCode(fasthttp.StatusOK)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| <template> | ||
| <form @submit="onSubmit" class="space-y-6 w-full max-w-lg"> | ||
| <FormField v-slot="{ componentField }" name="name"> | ||
| <FormItem> | ||
| <FormLabel>{{ $t('globals.terms.name') }}</FormLabel> | ||
| <FormControl> | ||
| <Input type="text" placeholder="My Telegram Bot" v-bind="componentField" /> | ||
| </FormControl> | ||
| <FormDescription> | ||
| {{ $t('admin.inbox.telegram.name.description') }} | ||
| </FormDescription> | ||
| <FormMessage /> | ||
| </FormItem> | ||
| </FormField> | ||
|
|
||
| <FormField v-slot="{ componentField }" name="bot_token"> | ||
| <FormItem> | ||
| <FormLabel>{{ $t('admin.inbox.telegram.botToken') }}</FormLabel> | ||
| <FormControl> | ||
| <Input type="password" placeholder="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" v-bind="componentField" /> | ||
| </FormControl> | ||
| <FormDescription> | ||
| {{ $t('admin.inbox.telegram.botToken.description') }} | ||
| </FormDescription> | ||
| <FormMessage /> | ||
| </FormItem> | ||
| </FormField> | ||
|
|
||
| <FormField v-slot="{ componentField }" name="bot_name"> | ||
| <FormItem> | ||
| <FormLabel>{{ $t('admin.inbox.telegram.botName') }}</FormLabel> | ||
| <FormControl> | ||
| <Input type="text" placeholder="@my_support_bot" v-bind="componentField" /> | ||
| </FormControl> | ||
| <FormDescription> | ||
| {{ $t('admin.inbox.telegram.botName.description') }} | ||
| </FormDescription> | ||
| <FormMessage /> | ||
| </FormItem> | ||
| </FormField> | ||
|
|
||
| <FormField v-slot="{ componentField, handleChange }" name="enabled"> | ||
| <FormItem> | ||
| <SwitchField | ||
| :title="$t('globals.terms.enabled')" | ||
| :checked="componentField.modelValue" | ||
| @update:checked="handleChange" | ||
| /> | ||
| </FormItem> | ||
| </FormField> | ||
|
|
||
| <FormField v-slot="{ componentField, handleChange }" name="csat_enabled"> | ||
| <FormItem> | ||
| <SwitchField | ||
| :title="$t('admin.inbox.csatSurveys')" | ||
| :description="$t('admin.inbox.csatSurveys.description_1')" | ||
| :checked="componentField.modelValue" | ||
| @update:checked="handleChange" | ||
| /> | ||
| </FormItem> | ||
| </FormField> | ||
|
|
||
| <FormField v-slot="{ componentField, handleChange }" name="prompt_tags_on_reply"> | ||
| <FormItem> | ||
| <SwitchField | ||
| :title="$t('admin.inbox.promptTagsOnReply')" | ||
| :description="$t('admin.inbox.promptTagsOnReply.description')" | ||
| :checked="componentField.modelValue" | ||
| @update:checked="handleChange" | ||
| /> | ||
| </FormItem> | ||
| </FormField> | ||
|
|
||
| <!-- Webhook URL info (shown only in edit mode) --> | ||
| <div v-if="!isNewForm && webhookUrl" class="rounded-md border p-4 space-y-2"> | ||
| <p class="text-sm font-medium">{{ $t('admin.inbox.telegram.webhookUrl') }}</p> | ||
| <div class="flex items-center gap-2"> | ||
| <code class="text-xs bg-muted px-2 py-1 rounded flex-1 break-all">{{ webhookUrl }}</code> | ||
| <Button type="button" variant="outline" size="sm" @click="copyWebhookUrl"> | ||
| {{ $t('globals.terms.copy') }} | ||
| </Button> | ||
| </div> | ||
| <p class="text-xs text-muted-foreground"> | ||
| {{ $t('admin.inbox.telegram.webhookUrl.description') }} | ||
| </p> | ||
| </div> | ||
|
|
||
| <Button type="submit" :disabled="isLoading"> | ||
| <Loader2 v-if="isLoading" class="w-4 h-4 mr-2 animate-spin" /> | ||
| {{ isNewForm ? $t('globals.messages.create') : $t('globals.messages.save') }} | ||
| </Button> | ||
| </form> | ||
| </template> | ||
|
|
||
| <script setup> | ||
| import { computed } from 'vue' | ||
| import { useForm } from 'vee-validate' | ||
| import { toTypedSchema } from '@vee-validate/zod' | ||
| import * as z from 'zod' | ||
| import { Button } from '@shared-ui/components/ui/button/index.js' | ||
| import { Input } from '@shared-ui/components/ui/input/index.js' | ||
| import { | ||
| FormControl, | ||
| FormDescription, | ||
| FormField, | ||
| FormItem, | ||
| FormLabel, | ||
| FormMessage | ||
| } from '@shared-ui/components/ui/form/index.js' | ||
| import SwitchField from '@shared-ui/components/SwitchField.vue' | ||
| import { Loader2 } from 'lucide-vue-next' | ||
| import { useI18n } from 'vue-i18n' | ||
| import { EMITTER_EVENTS } from '@/constants/emitterEvents.js' | ||
| import { useEmitter } from '@/composables/useEmitter' | ||
|
|
||
| const { t } = useI18n() | ||
| const emitter = useEmitter() | ||
|
|
||
| const props = defineProps({ | ||
| initialValues: { | ||
| type: Object, | ||
| default: () => ({}) | ||
| }, | ||
| submitForm: { | ||
| type: Function, | ||
| required: true | ||
| }, | ||
| isLoading: { | ||
| type: Boolean, | ||
| default: false | ||
| }, | ||
| isNewForm: { | ||
| type: Boolean, | ||
| default: false | ||
| } | ||
| }) | ||
|
|
||
| const formSchema = toTypedSchema( | ||
| z.object({ | ||
| name: z.string().min(1, t('globals.messages.required')), | ||
| bot_token: z.string().min(1, t('globals.messages.required')), | ||
| bot_name: z.string().optional().default(''), | ||
| enabled: z.boolean().default(true), | ||
| csat_enabled: z.boolean().default(false), | ||
| prompt_tags_on_reply: z.boolean().default(false) | ||
| }) | ||
| ) | ||
|
|
||
| const form = useForm({ | ||
| validationSchema: formSchema, | ||
| initialValues: { | ||
| name: props.initialValues?.name || '', | ||
| bot_token: props.initialValues?.config?.bot_token || '', | ||
| bot_name: props.initialValues?.config?.bot_name || '', | ||
| enabled: props.initialValues?.enabled ?? true, | ||
| csat_enabled: props.initialValues?.csat_enabled ?? false, | ||
| prompt_tags_on_reply: props.initialValues?.prompt_tags_on_reply ?? false | ||
| } | ||
| }) | ||
|
|
||
| const webhookUrl = computed(() => { | ||
| if (props.initialValues?.id) { | ||
| const baseUrl = window.location.origin | ||
| return `${baseUrl}/api/v1/inboxes/telegram/${props.initialValues.id}/webhook` | ||
| } | ||
| return '' | ||
| }) | ||
|
|
||
| const copyWebhookUrl = async () => { | ||
| try { | ||
| await navigator.clipboard.writeText(webhookUrl.value) | ||
| emitter.emit(EMITTER_EVENTS.SHOW_TOAST, { | ||
| description: t('globals.messages.savedSuccessfully') | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } catch (error) { | ||
| console.error('Failed to copy:', error) | ||
| } | ||
| } | ||
|
|
||
| const onSubmit = form.handleSubmit(async (values) => { | ||
| await props.submitForm(values) | ||
| }) | ||
| </script> | ||
Uh oh!
There was an error while loading. Please reload this page.