perf: 🍻 Update base copilot

This commit is contained in:
viarotel 2023-11-21 16:37:02 +08:00
parent 3dab0cc833
commit 5ac5ee6e97
17 changed files with 444 additions and 29 deletions

63
copilot/App.vue Normal file
View File

@ -0,0 +1,63 @@
<template>
<div class="flex flex-col absolute inset-fix-0 h-full overflow-hidden">
<div class="py-4 px-4 flex items-center flex-none">
<a class="block" :href="escrcpyURL" target="_blank">
<img src="@electron/resources/build/logo.png" class="h-9" alt="" />
</a>
<div class="pl-2 text-sm">
Escrcpy Copilot
</div>
</div>
<div class="flex-1 h-0 overflow-hidden bg-gray-100">
<el-tabs v-model="tabValue" class="el-tabs-flex" @tab-click="onTabClick">
<el-tab-pane
v-for="(item, index) of tabModel"
:key="index"
:label="item.label"
:name="item.value"
lazy
class=""
>
<component :is="item.value" />
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script>
import Transmission from './components/Transmission/index.vue'
export default {
components: {
Transmission,
},
data() {
return {
escrcpyURL: 'https://github.com/viarotel-org/escrcpy',
tabValue: 'Transmission',
tabModel: [
{
label: '传输助手',
value: 'Transmission',
},
],
}
},
methods: {
onTabClick() {},
},
}
</script>
<style lang="postcss" scoped>
:deep() {
.el-tabs__header {
@apply bg-white px-4;
}
.el-tabs__nav-wrap::after {
@apply bg-transparent;
}
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<Message v-bind="messageProps">
<slot></slot>
</Message>
</template>
<script>
import Message from './index.vue'
export default {
components: {
Message,
},
props: {
type: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
},
computed: {
messageProps() {
let value = {}
switch (this.type) {
case 'server':
value = {
name: 'PC',
content: this.content,
avatar: 'computer',
}
break
case 'client':
value = {
name: this.$attrs.name || '我的手机',
content: this.content,
avatar: 'mobile',
reversed: true,
}
break
default:
value = this.$attrs
break
}
return value
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,81 @@
<template>
<div :class="reversed ? 'flex-row-reverse' : ''" class="flex items-start">
<img :src="avatarURL" alt="" class="w-12 h-12 flex-none" />
<div
class="flex flex-col"
:class="reversed ? 'mr-4 pl-16 items-end' : 'ml-4 pr-16'"
>
<div v-if="!reversed" class="mt-1 text-base">
{{ name }}
</div>
<div
class="mt-2 shadow-el-light px-4 py-2 rounded-lg break-all overflow-hidden relative"
>
<slot>
<span class="pr-1">
{{ content }}
</span>
<el-icon v-if="loading" class="is-loading relative top-[2px]">
<Loading />
</el-icon>
</slot>
</div>
</div>
</div>
</template>
<script>
import logoURL from '@electron/resources/build/logo.png'
import userURL from '@/assets/user.png'
import mobileURL from '@/assets/mobile.png'
import computerURL from '@/assets/computer.png'
export default {
props: {
reversed: {
type: Boolean,
default: false,
},
avatar: {
type: String,
default: 'logo',
},
name: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
loading: {
type: Boolean,
default: false,
},
},
computed: {
avatarURL() {
let value = ''
switch (this.avatar) {
case 'logo':
value = logoURL
break
case 'user':
value = userURL
break
case 'mobile':
value = mobileURL
break
case 'computer':
value = computerURL
break
}
return value
},
},
}
</script>
<style></style>

View File

@ -0,0 +1,177 @@
<template>
<div class="h-full flex flex-col">
<div
ref="chats"
class="flex-1 h-0 space-y-4 pb-4 px-4 overflow-auto scroll-smooth"
>
<Message
v-for="(item, index) of messageList"
v-bind="{
type: item.type,
content: item.content,
}"
:key="index"
:loading="item.$loading"
>
</Message>
</div>
<div class="flex-none px-4 py-2 bg-white">
<el-input
v-model="inputValue"
placeholder="请输入想要发送的消息"
@keyup.enter="handleSubmit"
>
<template #append>
<el-button
icon="Promotion"
:loading="loading"
@click="handleSubmit"
/>
</template>
</el-input>
</div>
</div>
</template>
<script>
import Message from './Message/Preset.vue'
export default {
components: {
Message,
},
data() {
return {
messageList: [],
inputValue: '',
loading: false,
}
},
async created() {
await this.getMessageData()
await this.$nextTick()
this.handleScroll()
},
methods: {
handleScroll() {
const chatsEl = this.$refs.chats
chatsEl.scrollTop = chatsEl.scrollHeight
},
async handleSubmit() {
if (!this.inputValue) {
return false
}
this.loading = true
const newMessage = {
type: 'client',
content: this.inputValue,
$loading: true,
}
this.messageList.push(newMessage)
const params = {}
const res = await this.$mockAPI(params)
this.loading = false
if (res.success) {
this.inputValue = ''
newMessage.$loading = false
await this.$nextTick()
this.handleScroll()
}
},
async getMessageData() {
const params = {
imitate: [
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
{
type: 'server',
content: '你好啊',
},
{
type: 'client',
content: '你也好啊',
},
],
}
const res = await this.$mockAPI(params)
if (res.success) {
this.messageList = res.data.map(item => ({
...item,
$loading: false,
}))
}
},
},
}
</script>
<style></style>

View File

@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<html lang="en" class="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/logo.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Escrcpy-Server</title>
<title>Escrcpy Copilot</title>
</head>
<body class="">
<div id="app"></div>
<script type="module" src="main.js"></script>
</body>
</html>
</html>

37
copilot/main.js Normal file
View File

@ -0,0 +1,37 @@
import { createApp, toRaw } from 'vue'
import App from './App.vue'
import { i18n, t } from '@/locales/index.js'
import plugins from '@/plugins/index.js'
import icons from '@/icons/index.js'
import { replaceIP, restoreIP } from '@/utils/index.js'
import 'virtual:uno.css'
import '@/styles/index.js'
const app = createApp(App)
app.use(plugins)
app.use(icons)
app.use(i18n)
window.t = t
app.config.globalProperties.$replaceIP = replaceIP
app.config.globalProperties.$restoreIP = restoreIP
app.config.globalProperties.$toRaw = toRaw
app.config.globalProperties.$mockAPI = ({ imitate = {}, delay = 500 } = {}) =>
new Promise((resolve) => {
setTimeout(() => {
resolve({
code: '0000',
data: imitate,
success: true,
})
}, delay)
})
app.mount('#app')

View File

@ -7,14 +7,14 @@ export default async (mainWindow) => {
const app = new Hono()
app.notFound((c) => {
return c.text('Escrcpy server 404', 404)
return c.text('Escrcpy copilot 404', 404)
})
const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL
if (VITE_DEV_SERVER_URL) {
app.get('/', ctx =>
ctx.redirect(`${VITE_DEV_SERVER_URL}server/index.html`),
ctx.redirect(`${VITE_DEV_SERVER_URL}copilot/index.html`),
)
}
else {
@ -23,7 +23,7 @@ export default async (mainWindow) => {
serveStatic({
root: relative('./', process.env.DIST),
rewriteRequestPath: (path) => {
return path.replace(/^\//, '/server')
return path.replace(/^\//, '/copilot')
},
}),
)

View File

@ -14,7 +14,7 @@ import { icnsLogoPath, icoLogoPath, logoPath } from './configs/index.js'
import events from './events/index.js'
import server from './server/index.js'
import copilot from './copilot/index.js'
log.initialize({ preload: true })
@ -104,7 +104,7 @@ function createWindow() {
events(mainWindow)
server(mainWindow)
copilot(mainWindow)
}
app.whenReady().then(() => {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en" class="dark">
<html lang="en" class="">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/logo.ico" />
@ -10,4 +10,4 @@
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
</html>

View File

@ -3,11 +3,12 @@
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@copilot/*": ["copilot/*"],
"@root/*": ["*"],
"@electron/*": ["electron/*"],
"@renderer/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist", "dist-electron", "dist-release"],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.vue", "electron"]
"include": ["src", "electron", "copilot"]
}

View File

@ -1,3 +0,0 @@
import { createApp } from 'vue'
console.log('createApp', createApp)

BIN
src/assets/computer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
src/assets/mobile.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
src/assets/user.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -2,7 +2,7 @@ import { createI18n } from 'vue-i18n'
import messages from '@intlify/unplugin-vue-i18n/messages'
const locale
= window.appStore.get('common.language')
= window.appStore?.get('common.language')
|| window.electron?.process?.env?.LOCALE
// const locale = 'en_US'

View File

@ -3,20 +3,23 @@ html {
}
/* 自定义滚动条的外观 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background-color: theme("colors.gray.100");
@apply bg-gray-100 dark:bg-gray-800;
}
@screen sm {
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-thumb {
border-radius: 9999px;
@apply bg-gray-300 dark:bg-gray-600;
}
::-webkit-scrollbar-track {
background-color: theme('colors.gray.100');
@apply bg-gray-100 dark:bg-gray-800;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-gray-500 dark:bg-gray-300;
::-webkit-scrollbar-thumb {
border-radius: 9999px;
@apply bg-gray-300 dark:bg-gray-600;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-gray-500 dark:bg-gray-300;
}
}

View File

@ -35,13 +35,14 @@ export default params =>
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
server: resolve(__dirname, 'server/index.html'),
copilot: resolve(__dirname, 'copilot/index.html'),
},
},
},
resolve: {
alias: {
'@': resolve('src'),
'@copilot': resolve('copilot'),
'@electron': resolve('electron'),
},
},