PromptCommands.svelte 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. <script lang="ts">
  2. import { prompts } from '$lib/stores';
  3. import { findWordIndices } from '$lib/utils';
  4. import { tick, getContext } from 'svelte';
  5. import { toast } from 'svelte-sonner';
  6. const i18n = getContext('i18n');
  7. export let files;
  8. export let prompt = '';
  9. let selectedCommandIdx = 0;
  10. let filteredPromptCommands = [];
  11. $: filteredPromptCommands = $prompts
  12. .filter((p) => p.command.includes(prompt))
  13. .sort((a, b) => a.title.localeCompare(b.title));
  14. $: if (prompt) {
  15. selectedCommandIdx = 0;
  16. }
  17. export const selectUp = () => {
  18. selectedCommandIdx = Math.max(0, selectedCommandIdx - 1);
  19. };
  20. export const selectDown = () => {
  21. selectedCommandIdx = Math.min(selectedCommandIdx + 1, filteredPromptCommands.length - 1);
  22. };
  23. const confirmCommand = async (command) => {
  24. let text = command.content;
  25. if (command.content.includes('{{CLIPBOARD}}')) {
  26. const clipboardText = await navigator.clipboard.readText().catch((err) => {
  27. toast.error($i18n.t('Failed to read clipboard contents'));
  28. return '{{CLIPBOARD}}';
  29. });
  30. console.log(clipboardText);
  31. const clipboardItems = await navigator.clipboard.read();
  32. let imageUrl = null;
  33. for (const item of clipboardItems) {
  34. // Check for known image types
  35. for (const type of item.types) {
  36. if (type.startsWith('image/')) {
  37. const blob = await item.getType(type);
  38. imageUrl = URL.createObjectURL(blob);
  39. console.log(`Image URL (${type}): ${imageUrl}`);
  40. }
  41. }
  42. }
  43. if (imageUrl) {
  44. files = [
  45. ...files,
  46. {
  47. type: 'image',
  48. url: imageUrl
  49. }
  50. ];
  51. }
  52. text = command.content.replaceAll('{{CLIPBOARD}}', clipboardText);
  53. }
  54. prompt = text;
  55. const chatInputElement = document.getElementById('chat-textarea');
  56. await tick();
  57. chatInputElement.style.height = '';
  58. chatInputElement.style.height = Math.min(chatInputElement.scrollHeight, 200) + 'px';
  59. chatInputElement?.focus();
  60. await tick();
  61. const words = findWordIndices(prompt);
  62. if (words.length > 0) {
  63. const word = words.at(0);
  64. chatInputElement.setSelectionRange(word?.startIndex, word.endIndex + 1);
  65. }
  66. };
  67. </script>
  68. {#if filteredPromptCommands.length > 0}
  69. <div class="md:px-2 mb-3 text-left w-full absolute bottom-0 left-0 right-0">
  70. <div class="flex w-full px-2">
  71. <div class=" bg-gray-100 dark:bg-gray-700 w-10 rounded-l-xl text-center">
  72. <div class=" text-lg font-semibold mt-2">/</div>
  73. </div>
  74. <div class="max-h-60 flex flex-col w-full rounded-r-xl bg-white">
  75. <div class="m-1 overflow-y-auto p-1 rounded-r-xl space-y-0.5">
  76. {#each filteredPromptCommands as command, commandIdx}
  77. <button
  78. class=" px-3 py-1.5 rounded-xl w-full text-left {commandIdx === selectedCommandIdx
  79. ? ' bg-gray-100 selected-command-option-button'
  80. : ''}"
  81. type="button"
  82. on:click={() => {
  83. confirmCommand(command);
  84. }}
  85. on:mousemove={() => {
  86. selectedCommandIdx = commandIdx;
  87. }}
  88. on:focus={() => {}}
  89. >
  90. <div class=" font-medium text-black">
  91. {command.command}
  92. </div>
  93. <div class=" text-xs text-gray-600">
  94. {command.title}
  95. </div>
  96. </button>
  97. {/each}
  98. </div>
  99. <div
  100. class=" px-2 pb-1 text-xs text-gray-600 bg-white rounded-br-xl flex items-center space-x-1"
  101. >
  102. <div>
  103. <svg
  104. xmlns="http://www.w3.org/2000/svg"
  105. fill="none"
  106. viewBox="0 0 24 24"
  107. stroke-width="1.5"
  108. stroke="currentColor"
  109. class="w-3 h-3"
  110. >
  111. <path
  112. stroke-linecap="round"
  113. stroke-linejoin="round"
  114. d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z"
  115. />
  116. </svg>
  117. </div>
  118. <div class="line-clamp-1">
  119. {$i18n.t(
  120. 'Tip: Update multiple variable slots consecutively by pressing the tab key in the chat input after each replacement.'
  121. )}
  122. </div>
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. {/if}