|
1 | | -<script setup></script> |
| 1 | +<script setup> |
| 2 | +import { ref, onMounted, onUnmounted } from 'vue' |
| 3 | +import ProgressBar from './components/ProgressBar.vue' |
| 4 | +
|
| 5 | +const PLACEHOLDER_TEXT = |
| 6 | + 'The tower is 324 metres (1,063 ft) tall, about the same height as an 81-storey building, and the tallest structure in Paris. Its base is square, measuring 125 metres (410 ft) on each side. During its construction, the Eiffel Tower surpassed the Washington Monument to become the tallest man-made structure in the world, a title it held for 41 years until the Chrysler Building in New York City was finished in 1930.' |
| 7 | +
|
| 8 | +const LOADING_MESSAGE = 'Loading models... (only run once)' |
| 9 | +const INPUT_ROWS = 6 |
| 10 | +const OUTPUT_ROWS = 3 |
| 11 | +
|
| 12 | +const ready = ref(null) |
| 13 | +const disabled = ref(false) |
| 14 | +const progressItems = ref([]) |
| 15 | +const input = ref(PLACEHOLDER_TEXT) |
| 16 | +const output = ref('') |
| 17 | +const worker = ref(null) |
| 18 | +
|
| 19 | +onMounted(() => { |
| 20 | + worker.value ??= new Worker(new URL('./worker.js', import.meta.url), { |
| 21 | + type: 'module', |
| 22 | + }) |
| 23 | +
|
| 24 | + /** |
| 25 | + * Handles messages received from the Web Worker |
| 26 | + * @param {MessageEvent} event - The message event from the worker |
| 27 | + */ |
| 28 | + const onMessageReceived = (event) => { |
| 29 | + switch (event.data.status) { |
| 30 | + case 'initiate': |
| 31 | + ready.value = false |
| 32 | + progressItems.value = [...progressItems.value, event.data] |
| 33 | + break |
| 34 | +
|
| 35 | + case 'progress': |
| 36 | + progressItems.value = progressItems.value.map((item) => { |
| 37 | + if (item.file === event.data.file) { |
| 38 | + return { ...item, progress: event.data.progress } |
| 39 | + } |
| 40 | + return item |
| 41 | + }) |
| 42 | + break |
| 43 | +
|
| 44 | + case 'done': |
| 45 | + progressItems.value = progressItems.value.filter((item) => item.file !== event.data.file) |
| 46 | + break |
| 47 | +
|
| 48 | + case 'ready': |
| 49 | + ready.value = true |
| 50 | + break |
| 51 | +
|
| 52 | + case 'update': |
| 53 | + output.value += event.data.output |
| 54 | + break |
| 55 | +
|
| 56 | + case 'complete': |
| 57 | + disabled.value = false |
| 58 | + break |
| 59 | + } |
| 60 | + } |
| 61 | +
|
| 62 | + worker.value.addEventListener('message', onMessageReceived) |
| 63 | +
|
| 64 | + onUnmounted(() => worker.value?.removeEventListener('message', onMessageReceived)) |
| 65 | +}) |
| 66 | +
|
| 67 | +const summarize = () => { |
| 68 | + disabled.value = true |
| 69 | + output.value = '' |
| 70 | + worker.value.postMessage({ |
| 71 | + text: input.value, |
| 72 | + }) |
| 73 | +} |
| 74 | +</script> |
2 | 75 |
|
3 | 76 | <template> |
4 | | - <div> |
5 | | - <h1>You did it!</h1> |
6 | | - <p> |
7 | | - Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the |
8 | | - documentation |
9 | | - </p> |
| 77 | + <div class="app"> |
| 78 | + <h1>Transformers.js</h1> |
| 79 | + <h2>ML-powered text summarization in Vue!</h2> |
| 80 | +
|
| 81 | + <div class="container"> |
| 82 | + <div class="textbox-container"> |
| 83 | + <textarea |
| 84 | + v-model="input" |
| 85 | + :rows="INPUT_ROWS" |
| 86 | + placeholder="Enter text to summarize..." |
| 87 | + ></textarea> |
| 88 | + <textarea |
| 89 | + :value="output" |
| 90 | + :rows="OUTPUT_ROWS" |
| 91 | + readonly |
| 92 | + placeholder="Summary will appear here..." |
| 93 | + ></textarea> |
| 94 | + </div> |
| 95 | + </div> |
| 96 | +
|
| 97 | + <button :disabled="disabled" @click="summarize">Summarize</button> |
| 98 | +
|
| 99 | + <div class="progress-bars-container"> |
| 100 | + <label v-if="ready === false">{{ LOADING_MESSAGE }}</label> |
| 101 | + <div v-for="data in progressItems" :key="data.file"> |
| 102 | + <ProgressBar :text="data.file" :percentage="data.progress" /> |
| 103 | + </div> |
| 104 | + </div> |
10 | 105 | </div> |
11 | 106 | </template> |
12 | 107 |
|
13 | | -<style scoped></style> |
| 108 | +<style scoped> |
| 109 | +.app { |
| 110 | + font-family: Arial, Helvetica, sans-serif; |
| 111 | + max-width: 800px; |
| 112 | + margin: 40px auto; |
| 113 | + padding: 0 20px; |
| 114 | + text-align: center; |
| 115 | +} |
| 116 | +
|
| 117 | +.container { |
| 118 | + margin: 20px 0; |
| 119 | +} |
| 120 | +
|
| 121 | +.textbox-container { |
| 122 | + display: flex; |
| 123 | + flex-direction: column; |
| 124 | + gap: 20px; |
| 125 | + margin-bottom: 20px; |
| 126 | +} |
| 127 | +
|
| 128 | +textarea { |
| 129 | + width: 100%; |
| 130 | + padding: 12px; |
| 131 | + border: 2px solid #ccc; |
| 132 | + border-radius: 8px; |
| 133 | + font-size: 16px; |
| 134 | + font-family: inherit; |
| 135 | + resize: vertical; |
| 136 | + box-sizing: border-box; |
| 137 | +} |
| 138 | +
|
| 139 | +textarea:focus { |
| 140 | + outline: none; |
| 141 | + border-color: #4caf50; |
| 142 | +} |
| 143 | +
|
| 144 | +button { |
| 145 | + background-color: #4caf50; |
| 146 | + color: white; |
| 147 | + border: none; |
| 148 | + padding: 12px 24px; |
| 149 | + font-size: 16px; |
| 150 | + border-radius: 8px; |
| 151 | + cursor: pointer; |
| 152 | + transition: background-color 0.3s; |
| 153 | +} |
| 154 | +
|
| 155 | +button:hover:not(:disabled) { |
| 156 | + background-color: #45a049; |
| 157 | +} |
| 158 | +
|
| 159 | +button:disabled { |
| 160 | + background-color: #cccccc; |
| 161 | + cursor: not-allowed; |
| 162 | +} |
| 163 | +
|
| 164 | +.progress-bars-container { |
| 165 | + margin-top: 20px; |
| 166 | +} |
| 167 | +
|
| 168 | +.progress-bars-container label { |
| 169 | + display: block; |
| 170 | + margin-bottom: 10px; |
| 171 | + font-weight: bold; |
| 172 | +} |
| 173 | +</style> |
0 commit comments