RateComment.svelte 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. <script lang="ts">
  2. import { toast } from 'svelte-sonner';
  3. import { createEventDispatcher, onMount, getContext } from 'svelte';
  4. import { config, models } from '$lib/stores';
  5. import Tags from '$lib/components/common/Tags.svelte';
  6. const i18n = getContext('i18n');
  7. const dispatch = createEventDispatcher();
  8. export let message;
  9. export let show = false;
  10. let LIKE_REASONS = [
  11. 'accurate_information',
  12. 'followed_instructions_perfectly',
  13. 'showcased_creativity',
  14. 'positive_attitude',
  15. 'attention_to_detail',
  16. 'thorough_explanation',
  17. 'other'
  18. ];
  19. let DISLIKE_REASONS = [
  20. 'dont_like_the_style',
  21. 'too_verbose',
  22. 'not_helpful',
  23. 'not_factually_correct',
  24. 'didnt_fully_follow_instructions',
  25. 'refused_when_it_shouldnt_have',
  26. 'being_lazy',
  27. 'other'
  28. ];
  29. let tags = [];
  30. let reasons = [];
  31. let selectedReason = null;
  32. let comment = '';
  33. let detailedRating = null;
  34. let selectedModel = null;
  35. $: if (message?.annotation?.rating === 1) {
  36. reasons = LIKE_REASONS;
  37. } else if (message?.annotation?.rating === -1) {
  38. reasons = DISLIKE_REASONS;
  39. }
  40. $: if (message) {
  41. init();
  42. }
  43. const init = () => {
  44. selectedReason = message?.annotation?.reason ?? '';
  45. comment = message?.annotation?.comment ?? '';
  46. tags = (message?.annotation?.tags ?? []).map((tag) => ({
  47. name: tag
  48. }));
  49. detailedRating = message?.annotation?.details?.rating ?? null;
  50. };
  51. onMount(() => {
  52. if (message?.arena) {
  53. selectedModel = $models.find((m) => m.id === message.selectedModelId);
  54. toast.success(
  55. $i18n.t('This response was generated by "{{model}}"', {
  56. model: selectedModel ? (selectedModel?.name ?? selectedModel.id) : message.selectedModelId
  57. })
  58. );
  59. }
  60. });
  61. const saveHandler = () => {
  62. console.log('saveHandler');
  63. // if (!selectedReason) {
  64. // toast.error($i18n.t('Please select a reason'));
  65. // return;
  66. // }
  67. dispatch('save', {
  68. reason: selectedReason,
  69. comment: comment,
  70. tags: tags.map((tag) => tag.name),
  71. details: {
  72. rating: detailedRating
  73. }
  74. });
  75. toast.success($i18n.t('Thanks for your feedback!'));
  76. show = false;
  77. };
  78. </script>
  79. {#if message?.arena}
  80. <div class="text-xs font-medium pt-1.5 -mb-0.5">
  81. {$i18n.t('This response was generated by "{{model}}"', {
  82. model: selectedModel ? (selectedModel?.name ?? selectedModel.id) : message.selectedModelId
  83. })}
  84. </div>
  85. {/if}
  86. <div
  87. class=" my-2.5 rounded-xl px-4 py-3 border border-gray-50 dark:border-gray-850"
  88. id="message-feedback-{message.id}"
  89. >
  90. <div class="flex justify-between items-center">
  91. <div class="text-sm font-medium">{$i18n.t('How would you rate this response?')}</div>
  92. <!-- <div class=" text-sm">{$i18n.t('Tell us more:')}</div> -->
  93. <button
  94. on:click={() => {
  95. show = false;
  96. }}
  97. >
  98. <svg
  99. xmlns="http://www.w3.org/2000/svg"
  100. fill="none"
  101. viewBox="0 0 24 24"
  102. stroke-width="1.5"
  103. stroke="currentColor"
  104. class="size-4"
  105. >
  106. <path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
  107. </svg>
  108. </button>
  109. </div>
  110. <div class="w-full flex justify-center">
  111. <div class=" relative w-fit">
  112. <div class="mt-1.5 w-fit flex gap-1 pb-5">
  113. <!-- 1-10 scale -->
  114. {#each Array.from({ length: 10 }).map((_, i) => i + 1) as rating}
  115. <button
  116. class="size-7 text-sm border border-gray-50 dark:border-gray-850 hover:bg-gray-50 dark:hover:bg-gray-850 {detailedRating ===
  117. rating
  118. ? 'bg-gray-100 dark:bg-gray-800'
  119. : ''} transition rounded-full disabled:cursor-not-allowed disabled:text-gray-500 disabled:bg-white disabled:dark:bg-gray-900"
  120. on:click={() => {
  121. detailedRating = rating;
  122. }}
  123. disabled={message?.annotation?.rating === -1 ? rating > 5 : rating < 6}
  124. >
  125. {rating}
  126. </button>
  127. {/each}
  128. </div>
  129. <div class="absolute bottom-0 left-0 right-0 flex justify-between text-xs">
  130. <div>
  131. 1 - {$i18n.t('Awful')}
  132. </div>
  133. <div>
  134. 10 - {$i18n.t('Amazing')}
  135. </div>
  136. </div>
  137. </div>
  138. </div>
  139. <div>
  140. {#if reasons.length > 0}
  141. <div class="text-sm mt-1.5 font-medium">{$i18n.t('Why?')}</div>
  142. <div class="flex flex-wrap gap-1.5 text-sm mt-1.5">
  143. {#each reasons as reason}
  144. <button
  145. class="px-3 py-0.5 border border-gray-50 dark:border-gray-850 hover:bg-gray-50 dark:hover:bg-gray-850 {selectedReason ===
  146. reason
  147. ? 'bg-gray-100 dark:bg-gray-800'
  148. : ''} transition rounded-xl"
  149. on:click={() => {
  150. selectedReason = reason;
  151. }}
  152. >
  153. {#if reason === 'accurate_information'}
  154. {$i18n.t('Accurate information')}
  155. {:else if reason === 'followed_instructions_perfectly'}
  156. {$i18n.t('Followed instructions perfectly')}
  157. {:else if reason === 'showcased_creativity'}
  158. {$i18n.t('Showcased creativity')}
  159. {:else if reason === 'positive_attitude'}
  160. {$i18n.t('Positive attitude')}
  161. {:else if reason === 'attention_to_detail'}
  162. {$i18n.t('Attention to detail')}
  163. {:else if reason === 'thorough_explanation'}
  164. {$i18n.t('Thorough explanation')}
  165. {:else if reason === 'dont_like_the_style'}
  166. {$i18n.t("Don't like the style")}
  167. {:else if reason === 'too_verbose'}
  168. {$i18n.t('Too verbose')}
  169. {:else if reason === 'not_helpful'}
  170. {$i18n.t('Not helpful')}
  171. {:else if reason === 'not_factually_correct'}
  172. {$i18n.t('Not factually correct')}
  173. {:else if reason === 'didnt_fully_follow_instructions'}
  174. {$i18n.t("Didn't fully follow instructions")}
  175. {:else if reason === 'refused_when_it_shouldnt_have'}
  176. {$i18n.t("Refused when it shouldn't have")}
  177. {:else if reason === 'being_lazy'}
  178. {$i18n.t('Being lazy')}
  179. {:else if reason === 'other'}
  180. {$i18n.t('Other')}
  181. {:else}
  182. {reason}
  183. {/if}
  184. </button>
  185. {/each}
  186. </div>
  187. {/if}
  188. </div>
  189. <div class="mt-2">
  190. <textarea
  191. bind:value={comment}
  192. class="w-full text-sm px-1 py-2 bg-transparent outline-none resize-none rounded-xl"
  193. placeholder={$i18n.t('Feel free to add specific details')}
  194. rows="3"
  195. />
  196. </div>
  197. <div class="mt-2 gap-1.5 flex justify-between">
  198. <div class="flex items-end group">
  199. <Tags
  200. {tags}
  201. on:delete={(e) => {
  202. tags = tags.filter(
  203. (tag) =>
  204. tag.name.replaceAll(' ', '_').toLowerCase() !==
  205. e.detail.replaceAll(' ', '_').toLowerCase()
  206. );
  207. }}
  208. on:add={(e) => {
  209. tags = [...tags, { name: e.detail }];
  210. }}
  211. />
  212. </div>
  213. <button
  214. class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
  215. on:click={() => {
  216. saveHandler();
  217. }}
  218. >
  219. {$i18n.t('Save')}
  220. </button>
  221. </div>
  222. </div>