poetry-camera / html /index.html
RangerUFO's picture
Rename the style control argument
46ca356
<!DOCTYPE html>
<html>
<head>
<title>Poetry Camera</title>
<meta charset="UTF-8">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/index.css">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div id="app" class="client-area">
<div :style="appContainer">
<div>
<div><b>Poetry Camera</b></div>
<div class="follow">
<a class="github-button" href="https://github.com/ufownl" aria-label="Follow @ufownl on GitHub">Follow @ufownl</a>
</div>
</div>
<div v-if="vlm">
<el-upload class="image-uploader" :file-list="selectedFiles" :limit="1" :show-file-list="false" :auto-upload="false" accept="image/*" :on-change="selectImage" :disabled="working">
<img v-if="image" class="image" :src="imageUrl" />
<img v-else class="image-uploader-icon" src="img/logo.jpg" />
</el-upload>
<el-form class="params" :model="form" inline>
<el-form-item class="params-item">
<el-select class="lang-sel" v-model="form.lang" :disabled="working">
<el-option label="English" value="en"></el-option>
<el-option label="Chinese" value="zh"></el-option>
</el-select>
</el-form-item>
<el-form-item class="params-item">
<el-input v-model="form.style" placeholder="Style" maxlength="32" :style="styleInput" :disabled="working" show-word-limit />
</el-form-item>
<el-form-item class="params-end">
<el-button class="req-btn" type="primary" @click="request" :disabled="!image || !form.style.trim() || working">{{ reqBtnText }}</el-button>
</el-form-item>
</el-form>
<el-steps :active="statusId" finish-status="success" align-center>
<el-step title="Viewing Picture" icon="View"></el-step>
<el-step title="Thinking Hard" icon="Cpu"></el-step>
<el-step title="Writing Poem" icon="EditPen"></el-step>
</el-steps>
<el-card v-if="poem" class="poem-container" shadow="never">
<pre :style="poemStyle">{{ poem }}</pre>
<div v-if="statusId === 3" class="copy-container">
<el-link id="copy" type="primary" :data-clipboard-text="poem">Click Here to Copy!</el-link>
</div>
</el-card>
<div v-else>
<el-empty v-if="statusId < 0" :description="statusDesc"></el-empty>
<div v-else class="status-container">
<el-skeleton v-loading="true" element-loading-background="rgba(255, 255, 255, 0.4)" :element-loading-text="statusDesc" :rows="5" animated />
</div>
</div>
</div>
<div v-else>
<el-empty description="Loading model and initializing..." />
</div>
</div>
</div>
</body>
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/[email protected]/dist/index.full.min.js"></script>
<script src="https://unpkg.com/@element-plus/[email protected]/dist/index.iife.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/clipboard.min.js"></script>
<script async defer src="https://buttons.github.io/buttons.js"></script>
<script>
const enPoemStyle = { margin: '0px', fontSize: '14px' }
const zhPoemStyle = { margin: '18px', fontSize: '18px' }
const statusText = [
'Take a picture and start creation!',
'AI poet is viewing your picture...',
'AI poet is thinking hard about the poem...',
'AI poet is writing the poem...'
]
const wsUrl = window.location.origin.replace('http', 'ws') + '/cgemma/session'
const app = Vue.createApp({
data() {
return {
clientHeight: 0,
clientWidth: 0,
cb: undefined,
ws: undefined,
vlm: undefined,
selectedFiles: [],
image: undefined,
form: {
lang: 'en',
style: 'Emily Dickinson'
},
lastForm: {},
statusId: -1,
working: false,
poem: '',
poemStyle: enPoemStyle
}
},
computed: {
appContainer() {
const margin = this.clientWidth > 640 ? (this.clientWidth - 640) / 2 : 0
return {
marginLeft: margin + 'px',
marginRight: margin + 'px',
padding: '8px'
}
},
imageUrl() {
return URL.createObjectURL(this.image)
},
styleInput() {
const appWidth = this.clientWidth > 640 ? 640 - 16 : this.clientWidth - 16
return {
width: appWidth - 180 + 'px'
}
},
reqBtnText() {
return this.statusId < 0 ? 'Create' : 'Retry'
},
statusDesc() {
return statusText[this.statusId + 1]
}
},
watch: {
'form.lang': function(value) {
if (value === 'en') {
this.form.style = 'Emily Dickinson'
} else if (value === 'zh') {
this.form.style = '海子'
}
}
},
mounted() {
this.clientHeight = this.$el.clientHeight
this.clientWidth = this.$el.clientWidth
window.onresize = () => {
this.clientHeight = this.$el.clientHeight
this.clientWidth = this.$el.clientWidth
}
this.cb = new ClipboardJS('#copy')
this.cb.on('success', () => {
this.$message.success('Copied! Now you can share it with your friends!')
})
const ws = new WebSocket(wsUrl)
ws.onerror = evt => {
this.$message.error('WebSocket Connection Error')
this.ws = undefined
}
ws.onclose = evt => {
this.$message.error('WebSocket Connection Closed')
this.ws = undefined
}
ws.onopen = evt => {
ws.onmessage = evt => {
this.handleMessage(JSON.parse(evt.data))
}
this.ws = ws
this.keepalive()
}
},
methods: {
selectImage(file) {
this.selectedFiles = []
this.image = undefined
if (file.size > this.vlm.max_file_size) {
this.$message.error('Image size cannot exceed ' + this.vlm.max_file_size / (1024 * 1024) + 'MB!')
return
}
const reader = new FileReader()
reader.onload = (e) => {
this.image = new Blob([e.target.result], { type: file.raw.type })
this.statusId = -1
}
reader.readAsArrayBuffer(file.raw)
},
keepalive() {
setTimeout(() => {
if (this.ws) {
this.ws.send(JSON.stringify({ op: 'keepalive' }))
this.keepalive()
}
}, 30000)
},
handleMessage(msg) {
if (msg.op === 'init') {
this.vlm = msg.vlm
} else if (msg.op === 'status') {
this.statusId = msg.id
} else if (msg.op === 'stream') {
if (msg.token) {
this.poem += msg.token
} else {
this.statusId += 1
this.working = false
}
}
},
request() {
if (!this.ws) {
this.$message.error('Not connected! Please refresh the page to reconnect!')
return
}
this.working = true
this.poem = ''
this.poemStyle = this.form.lang === 'zh' ? zhPoemStyle : enPoemStyle
if (this.statusId < 0) {
this.image.arrayBuffer().then(buf => {
const arr = new Uint8Array(buf)
let bin = ''
arr.forEach(v => {
bin += String.fromCharCode(v)
})
this.ws.send(JSON.stringify({ op: 'create', image: btoa(bin), ...this.form }))
})
} else {
if (Object.entries(this.form).every(x => x[1] === this.lastForm[x[0]])) {
this.ws.send(JSON.stringify({ op: 'retry' }))
} else {
this.ws.send(JSON.stringify({ op: 'retry', ...this.form }))
}
}
this.lastForm = { ...this.form }
}
}
})
app.use(ElementPlus)
for (const [name, comp] of Object.entries(ElementPlusIconsVue)) {
app.component(name, comp)
}
app.mount('#app')
</script>