RateComment.svelte 6.6 KB

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