Chat.vue 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. <script setup lang="ts">
  2. import AppLayout from "@/Layouts/AppLayout.vue"
  3. import {ArrowUpIcon, Loader2} from "lucide-vue-next"
  4. import {
  5. InputGroup,
  6. InputGroupAddon,
  7. InputGroupButton,
  8. InputGroupTextarea
  9. } from "@/Packages/Shadcn/Components/ui/input-group"
  10. import {computed, nextTick, watch} from "vue";
  11. import {useForm} from "@inertiajs/vue3";
  12. import { parse } from 'marked'
  13. const { chat } = defineProps(['chats', 'chat'])
  14. const form = useForm({
  15. uuid: chat?.id ?? null,
  16. message: null
  17. })
  18. const submit = function () {
  19. if (!form.message || form.processing) return
  20. form.post(route('chats.message'), {
  21. onSuccess: () => form.reset()
  22. })
  23. }
  24. const handleEnter = function (e) {
  25. if (e.metaKey || e.ctrlKey || e.shiftKey) {
  26. const {selectionStart, selectionEnd, value} = e.target
  27. form.message = value.slice(0, selectionStart) + '\n' + value.slice(selectionEnd)
  28. nextTick(() => {
  29. const pos = selectionStart + 1
  30. e.target.setSelectionRange(pos, pos)
  31. })
  32. } else submit()
  33. }
  34. const messages = computed(() => chat?.messages ?? [])
  35. watch(() => chat?.id, () => form.defaults('uuid', chat?.id ?? null))
  36. </script>
  37. <template>
  38. <AppLayout page-class="!py-0" :chats="chats">
  39. <div class="max-w-4xl mx-auto flex flex-col h-full">
  40. <div class="overflow-y-auto duration-500 space-y-4" :class="{'grow mt-2': messages.length !== 0}">
  41. <div v-for="message in messages">
  42. <div class="flex" v-if="message.from === 'user'">
  43. <div class="ml-auto border bg-card text-card-foreground p-2 rounded-lg max-w-[75%]"
  44. v-html="message.text.replaceAll('\n', '<br />')"></div>
  45. </div>
  46. <div class="prose dark:prose-invert" v-else v-html="parse(message.text)"></div>
  47. </div>
  48. <!-- <div class=""></div>-->
  49. </div>
  50. <div class="my-auto pb-2 pt-5" :class="{'sticky bottom-0': messages.length !== 0}">
  51. <InputGroup class="!bg-card">
  52. <InputGroupTextarea placeholder="Ask, Search or Chat..." v-model="form.message"
  53. @keydown.enter.prevent="handleEnter"/>
  54. <InputGroupAddon align="block-end">
  55. <InputGroupButton class="ml-auto cursor-pointer" variant="default"
  56. :disabled="!form.message || form.processing" :loading="true"
  57. @click="() => submit()">
  58. <template v-if="!form.processing">
  59. <ArrowUpIcon class="size-5"/>
  60. <span>Отправить</span>
  61. </template>
  62. <template v-else>
  63. <Loader2 class="w-4 h-4 animate-spin"/>
  64. Отправка
  65. </template>
  66. </InputGroupButton>
  67. </InputGroupAddon>
  68. </InputGroup>
  69. </div>
  70. </div>
  71. </AppLayout>
  72. </template>
  73. <style scoped>
  74. </style>