update ui and fix some problem (#3)

* update code lint and update ui
This commit is contained in:
bingochaos
2025-08-15 17:19:29 +08:00
committed by GitHub
parent 3fddf021be
commit 519fcb64a2
18 changed files with 162219 additions and 2816 deletions

View File

@@ -1,12 +0,0 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 100
quote_type = single
trim_trailing_whitespace = true
semi = false

View File

@@ -1,11 +0,0 @@
build
coverage
dist
es
lib
node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
*.min.js
*.min.css

View File

@@ -1,10 +1,7 @@
{
"printWidth": 80,
"singleQuote": true,
"trailingComma": "all",
"proseWrap": "never",
"tabWidth": 2,
"semi": false,
"arrowParens": "always",
"overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }]
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"htmlWhitespaceSensitivity": "ignore"
}

View File

@@ -1,10 +0,0 @@
build
coverage
dist
es
lib
node_modules
package-lock.json
pnpm-lock.yaml
yarn.lock
*.min.css

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

70731
dist/assets/index.js vendored

File diff suppressed because one or more lines are too long

21
eslint.config.js Normal file
View File

@@ -0,0 +1,21 @@
import pluginVue from 'eslint-plugin-vue'
import globals from 'globals'
export default [
// add more generic rulesets here, such as:
// js.configs.recommended,
...pluginVue.configs['flat/recommended'],
// ...pluginVue.configs['flat/vue2-recommended'], // Use this if you are using Vue.js 2.x.
{
rules: {
// override/add rules settings here, such as:
// 'vue/no-unused-vars': 'error'
},
languageOptions: {
sourceType: 'module',
globals: {
...globals.browser,
},
},
},
]

View File

@@ -1,37 +0,0 @@
import { base as aliBase } from 'eslint-config-ali';
import prettier from 'eslint-plugin-prettier/recommended';
import { defineConfig, globalIgnores } from 'eslint/config';
import globals from 'globals';
// import tslintPlugin from 'typescript-eslint';
export default defineConfig([
// ...tslintPlugin.configs.recommended,
...aliBase,
prettier,
{
ignores: ['dist/**/*', 'node_modules/**/*'],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
rules: {
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@/semi': [1, 'never'],
'prettier/prettier': [
'error',
{
printWidth: 80,
singleQuote: true,
trailingComma: 'all',
proseWrap: 'never',
tabWidth: 2,
semi: false,
arrowParens: 'always',
},
],
},
},
globalIgnores(['**/dist/**', '**/node_modules/**']),
]);

View File

@@ -5,28 +5,14 @@
"type": "module",
"scripts": {
"build": "vue-tsc && vite build",
"ci:eslint": "eslint -f json src -o ./.ci/eslint.json",
"ci:test": "vitest -c ./vitest.config.ts --coverage",
"dev": "vite",
"eslint": "eslint --fix --ext .js,.ts,.vue src",
"format": "prettier --write --cache --parser typescript \"**/*.[tj]s?(x)\"",
"lint": "eslint . && stylelint --allow-empty-input \"**/*.{css,less,scss}\"",
"lint-staged": "lint-staged",
"lint:fix": "prettier --write . && eslint --fix . && stylelint --allow-empty-input --fix \"**/*.{css,less,scss}\"",
"local": "sudo vite",
"prepare": "husky",
"preview": "vite preview",
"test": "vitest"
"lint": "eslint . --ext .js,.ts,.vue --fix",
"prepare": "husky install"
},
"lint-staged": {
"*.{cjs,cts,js,jsx,mjs,mts,ts,tsx,vue}": "eslint --fix",
"*.{cjs,css,cts,html,js,json,jsx,less,md,mjs,mts,scss,ts,tsx,vue,yaml,yml}": "prettier --write"
},
"prettier": "prettier-config-ali",
"stylelint": {
"extends": [
"stylelint-config-ali",
"stylelint-prettier/recommended"
"*.{js,ts,vue}": [
"eslint --fix",
"prettier --write"
]
},
"dependencies": {
@@ -48,36 +34,24 @@
"vue-i18n": "^11.1.9"
},
"devDependencies": {
"@commitlint/config-conventional": "^19.8.1",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.33.0",
"@types/node": "^20.17.6",
"@types/python-struct": "^1.0.4",
"@typescript-eslint/parser": "^8.39.1",
"@vitejs/plugin-legacy": "^7.0.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-prettier": "^10.2.0",
"eslint": "^9.31.0",
"eslint-config-ali": "^16.3.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-vue": "^10.3.0",
"eslint": "^9.33.0",
"eslint-plugin-vue": "^10.4.0",
"globals": "^16.3.0",
"husky": "^9.1.7",
"less": "^4.2.0",
"lint-staged": "^16.1.2",
"lint-staged": "^16.1.5",
"prettier": "^3.6.2",
"prettier-config-ali": "^1.3.4",
"simple-git-hooks": "^2.13.0",
"stylelint": "^16.22.0",
"stylelint-config-ali": "^2.1.2",
"stylelint-config-standard": "^38.0.0",
"stylelint-prettier": "^5.0.3",
"terser": "^5.36.0",
"typescript": "5.8.3",
"typescript-eslint": "^8.38.0",
"vite": "^7.0.1",
"vite-plugin-eslint2": "^5.0.4",
"vite-plugin-mkcert": "^1.17.6",
"vite-plugin-stylelint": "^6.0.2",
"vue-eslint-parser": "^10.2.0",
"vue-tsc": "^3.0.1"
},
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39"

2584
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
<script setup lang="ts">
import WebcamPermission from '@/components/WebcamPermission.vue';
import { antdLocale, locale } from '@/langs';
import VideoChat from '@/views/VideoChat/index.vue';
import { ConfigProvider } from 'ant-design-vue';
import { useVideoChatStore } from './store';
const videoChatState = useVideoChatStore();
videoChatState.init();
import WebcamPermission from '@/components/WebcamPermission.vue'
import { antdLocale, locale } from '@/langs'
import VideoChat from '@/views/VideoChat/index.vue'
import { ConfigProvider } from 'ant-design-vue'
import { useVideoChatStore } from './store'
const videoChatState = useVideoChatStore()
videoChatState.init()
// import dayjs from 'dayjs';
// import 'dayjs/locale/zh-cn';
// dayjs.locale('zh-cn');

View File

@@ -1,19 +1,19 @@
<template>
<div class="action-group">
<div v-if="hasCamera">
<div class="action" @click="handleCameraOff" v-click-outside="() => (cameraListShow = false)">
<div v-click-outside="() => (cameraListShow = false)" class="action" @click="handleCameraOff">
<Iconfont :icon="cameraOff ? CameraOff : CameraOn" />
<div
v-if="streamState === 'closed'"
class="corner"
@click.stop.prevent="() => (cameraListShow = !cameraListShow)"
>
<div class="corner-inner"></div>
<div class="corner-inner" />
</div>
<div
v-show="cameraListShow && streamState === 'closed'"
class="selectors"
:class="{ left: isLandscape }"
v-show="cameraListShow && streamState === 'closed'"
>
<div
v-for="device in availableVideoDevices"
@@ -21,8 +21,8 @@
class="selector"
@click.stop="
() => {
handleDeviceChange(device.deviceId);
cameraListShow = false;
handleDeviceChange(device.deviceId)
cameraListShow = false
}
"
>
@@ -38,19 +38,19 @@
</div>
</div>
<div v-if="hasMic">
<div class="action" @click="handleMicMuted" v-click-outside="() => (micListShow = false)">
<div v-click-outside="() => (micListShow = false)" class="action" @click="handleMicMuted">
<Iconfont :icon="micMuted ? MicOff : MicOn" />
<div
v-if="streamState === 'closed'"
class="corner"
@click.stop.prevent="() => (micListShow = !micListShow)"
>
<div class="corner-inner"></div>
<div class="corner-inner" />
</div>
<div
v-show="micListShow && streamState === 'closed'"
class="selectors"
:class="{ left: isLandscape }"
v-show="micListShow && streamState === 'closed'"
>
<div
v-for="device in availableAudioDevices"
@@ -58,8 +58,8 @@
class="selector"
@click.stop="
(e) => {
handleDeviceChange(device.deviceId);
micListShow = false;
handleDeviceChange(device.deviceId)
micListShow = false
}
"
>
@@ -86,10 +86,10 @@
</div>
</template>
<script setup lang="ts">
import { useVideoChatStore } from '@/store';
import { useVisionStore } from '@/store/vision';
import { storeToRefs } from 'pinia';
import { ref } from 'vue';
import { useVideoChatStore } from '@/store'
import { useVisionStore } from '@/store/vision'
import { storeToRefs } from 'pinia'
import { ref } from 'vue'
import Iconfont, {
CameraOff,
CameraOn,
@@ -100,9 +100,9 @@ import Iconfont, {
SubtitleOn,
VolumeOff,
VolumeOn,
} from './Iconfont';
const videoChatStore = useVideoChatStore();
const visionStore = useVisionStore();
} from './Iconfont'
const videoChatStore = useVideoChatStore()
const visionStore = useVisionStore()
const {
hasCamera,
hasMic,
@@ -116,18 +116,18 @@ const {
selectedVideoDevice,
availableAudioDevices,
availableVideoDevices,
} = storeToRefs(videoChatStore);
} = storeToRefs(videoChatStore)
const {
handleCameraOff,
handleMicMuted,
handleVolumeMute,
handleDeviceChange,
handleSubtitleToggle,
} = videoChatStore;
} = videoChatStore
const { wrapperRect, isLandscape } = storeToRefs(visionStore);
const micListShow = ref(false);
const cameraListShow = ref(false);
const { wrapperRect, isLandscape } = storeToRefs(visionStore)
const micListShow = ref(false)
const cameraListShow = ref(false)
</script>
<style lang="less" scoped>

View File

@@ -4,7 +4,7 @@
:class="[
'chat-btn',
streamState === StreamState.closed && 'start-chat',
streamState === StreamState.open && 'stop-chat'
streamState === StreamState.open && 'stop-chat',
]"
@click="onStartChat"
>
@@ -14,8 +14,7 @@
<template v-else-if="streamState === StreamState.waiting">
<div class="waiting-icon-text">
<div class="icon" title="spinner">
<!-- <Spin wrapperClassName="spin-icon"></Spin> -->
<!-- TODO: spinner 替换 -->
<Spin wrapperClassName="spin-icon"></Spin>
</div>
<span>等待中</span>
</div>
@@ -37,23 +36,23 @@
</template>
<script setup lang="ts">
import {Spin} from 'ant-design-vue'
import { StreamState } from '@/interface/voiceChat'
import AudioWave from '@/components/AudioWave.vue'
import { Spin } from 'ant-design-vue';
import { StreamState } from '@/interface/voiceChat';
import AudioWave from '@/components/AudioWave.vue';
const props = withDefaults(
defineProps<{
streamState: StreamState
onStartChat: any
audioSourceCallback: () => MediaStream | null
waveColor: string
streamState: StreamState;
onStartChat: any;
audioSourceCallback: () => MediaStream | null;
waveColor: string;
}>(),
{
streamState: StreamState.closed
}
)
streamState: StreamState.closed,
},
);
const emit = defineEmits([])
const emit = defineEmits([]);
</script>
<style scoped lang="less"></style>
@@ -106,6 +105,12 @@ const emit = defineEmits([])
stroke: #ffffff;
color: #ffffff;
}
.spin-icon {
color: #fff;
}
:global(.ant-spin-dot-item) {
background-color: #fff !important;
}
}
.stop-chat {

View File

@@ -110,9 +110,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
this.cameraOff = false
this.volumeMuted = false
if (!navigator.mediaDevices) {
message.error(
'无法获取媒体设备请确保用localhost访问或https协议访问',
)
message.error('无法获取媒体设备请确保用localhost访问或https协议访问')
return
}
await navigator.mediaDevices
@@ -136,16 +134,12 @@ export const useVideoChatStore = defineStore('videoChatStore', {
console.log('🚀 ~ access_webcam ~ devices:', devices)
const videoDeviceId =
this.selectedVideoDevice &&
devices.some(
(device) => device.deviceId === this.selectedVideoDevice?.deviceId,
)
devices.some((device) => device.deviceId === this.selectedVideoDevice?.deviceId)
? this.selectedVideoDevice.deviceId
: ''
const audioDeviceId =
this.selectedAudioDevice &&
devices.some(
(device) => device.deviceId === this.selectedAudioDevice?.deviceId,
)
devices.some((device) => device.deviceId === this.selectedAudioDevice?.deviceId)
? this.selectedAudioDevice.deviceId
: ''
console.log(videoDeviceId, audioDeviceId, ' access web device')
@@ -153,7 +147,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
this.webcamAccessed = true
} catch (err: any) {
console.log(err)
message.error(err.message)
message.error(err)
}
},
async init() {
@@ -163,6 +157,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
if (config.rtc_configuration) {
this.rtcConfig = config.rtc_configuration
}
console.log(config)
if (config.avatar_config) {
this.avatarType = config.avatar_config.avatar_type
@@ -174,9 +169,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
}
})
.catch(() => {
message.error(
'服务端链接失败,请检查是否能正确访问到 OpenAvatarChat 服务端',
)
message.error('服务端链接失败,请检查是否能正确访问到 OpenAvatarChat 服务端')
})
},
handleCameraOff() {
@@ -204,29 +197,19 @@ export const useVideoChatStore = defineStore('videoChatStore', {
console.log('🚀 ~ handle_device_change ~ devices:', devices)
let videoDeviceId =
this.selectedVideoDevice &&
devices.some(
(device) => device.deviceId === this.selectedVideoDevice?.deviceId,
)
devices.some((device) => device.deviceId === this.selectedVideoDevice?.deviceId)
? this.selectedVideoDevice.deviceId
: ''
let audioDeviceId =
this.selectedAudioDevice &&
devices.some(
(device) => device.deviceId === this.selectedAudioDevice?.deviceId,
)
devices.some((device) => device.deviceId === this.selectedAudioDevice?.deviceId)
? this.selectedAudioDevice.deviceId
: ''
if (
this.availableVideoDevices.find(
(video_device) => video_device.deviceId === device_id,
)
) {
if (this.availableVideoDevices.find((video_device) => video_device.deviceId === device_id)) {
videoDeviceId = device_id
this.cameraOff = false
} else if (
this.availableAudioDevices.find(
(audio_device) => audio_device.deviceId === device_id,
)
this.availableAudioDevices.find((audio_device) => audio_device.deviceId === device_id)
) {
audioDeviceId = device_id
this.micMuted = false
@@ -235,6 +218,14 @@ export const useVideoChatStore = defineStore('videoChatStore', {
},
handleSubtitleToggle() {
this.showChatRecords = !this.showChatRecords
const visionState = useVisionStore()
const { wrapperRef, wrapperRect } = visionState
console.log(wrapperRect, wrapperRef)
if (!wrapperRef || !wrapperRect) return
wrapperRef.getBoundingClientRect()
wrapperRect.width = wrapperRef!.clientWidth
wrapperRect.height = wrapperRef!.clientHeight
visionState.isLandscape = wrapperRect.width > wrapperRect.height
},
async updateAvailableDevices() {
const devices = await getDevices()
@@ -250,9 +241,8 @@ export const useVideoChatStore = defineStore('videoChatStore', {
return device.kind === 'audioinput' && device.deviceId
}) && this.hasMicPermission
this.hasCamera =
devices.some(
(device) => device.kind === 'videoinput' && device.deviceId,
) && this.hasCameraPermission
devices.some((device) => device.kind === 'videoinput' && device.deviceId) &&
this.hasCameraPermission
await getStream(
audioDeviceId && audioDeviceId !== 'default'
? { deviceId: { exact: audioDeviceId } }
@@ -260,7 +250,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
videoDeviceId && videoDeviceId !== 'default'
? { deviceId: { exact: videoDeviceId } }
: this.hasCamera,
this.trackConstraints,
this.trackConstraints
)
.then(async (local_stream) => {
console.log('local_stream', local_stream)
@@ -269,20 +259,17 @@ export const useVideoChatStore = defineStore('videoChatStore', {
})
.then(() => {
const used_devices = this.stream!.getTracks().map(
(track) => track.getSettings()?.deviceId,
(track) => track.getSettings()?.deviceId
)
used_devices.forEach((device_id) => {
const used_device = devices.find(
(device) => device.deviceId === device_id,
)
const used_device = devices.find((device) => device.deviceId === device_id)
if (used_device && used_device?.kind.includes('video')) {
this.selectedVideoDevice = used_device
} else if (used_device && used_device?.kind.includes('audio')) {
this.selectedAudioDevice = used_device
}
})
!this.selectedVideoDevice &&
(this.selectedVideoDevice = this.availableVideoDevices[0])
!this.selectedVideoDevice && (this.selectedVideoDevice = this.availableVideoDevices[0])
})
.catch((e) => {
console.error('image.no_webcam_support', e)
@@ -315,29 +302,22 @@ export const useVideoChatStore = defineStore('videoChatStore', {
if (this.streamState === 'closed') {
this.chatRecords = []
this.peerConnection = new RTCPeerConnection() // TODO RTC_configuration
this.peerConnection.addEventListener(
'connectionstatechange',
async (event) => {
switch (this.peerConnection!.connectionState) {
case 'connected':
this.streamState = StreamState.open
break
case 'disconnected':
this.streamState = StreamState.closed
stop(this.peerConnection!)
// await access_webcam() //TODO 重置状态
break
default:
break
}
},
)
this.peerConnection.addEventListener('connectionstatechange', async (event) => {
switch (this.peerConnection!.connectionState) {
case 'connected':
this.streamState = StreamState.open
break
case 'disconnected':
this.streamState = StreamState.closed
stop(this.peerConnection!)
// await access_webcam() //TODO 重置状态
break
default:
break
}
})
this.streamState = StreamState.waiting
await setupWebRTC(
this.stream!,
this.peerConnection!,
visionState.remoteVideoRef!,
)
await setupWebRTC(this.stream!, this.peerConnection!, visionState.remoteVideoRef!)
.then(([dataChannel, webRTCId]) => {
this.streamState = StreamState.open
this.webRTCId = webRTCId as string
@@ -355,6 +335,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
console.info('catching', e)
this.streamState = StreamState.closed
message.error(e)
message.error('请检查是否超过数字人并发上限')
})
} else if (this.streamState === 'waiting') {
// waiting 中不允许操作
@@ -373,7 +354,7 @@ export const useVideoChatStore = defineStore('videoChatStore', {
},
initWebsocket(ws_route: string, webRTCId: string) {
const ws = new WS(
`${window.location.protocol.includes('https') ? 'wss' : 'ws'}://${window.location.host}${ws_route}/${webRTCId}`,
`${window.location.protocol.includes('https') ? 'wss' : 'ws'}://${window.location.host}${ws_route}/${webRTCId}`
)
ws.on(WsEventTypes.WS_OPEN, () => {
console.log('socket opened')

View File

@@ -85,6 +85,7 @@
position: absolute;
bottom: 0;
right: 0;
width: 50%;
padding: 10px;
}
}

View File

@@ -1,30 +0,0 @@
export default {
extends: ['stylelint-config-standard'],
// stylelint不识别:global, 添加selector-pseudo-class-no-unknown忽略:global
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
// 对:global处理有问题, 所以关掉该规则
'no-descending-specificity': null,
// 要求css的选择器名称是kebab-case, 历史代码很多是驼峰的, 所以关掉该规则
'selector-class-pattern': null,
// 该规则不允许供应商前缀值; 而最多显示几行时需要display: -webkit-box; 所以忽略'box'
'value-no-vendor-prefix': [true, { ignoreValues: ['box'] }],
'custom-property-pattern': '^([a-zA-Z0-9]|-|_)*$',
'rule-empty-line-before': null,
'declaration-empty-line-before': null,
'allow-empty-input': true,
// 采用系统默认字体
'font-family-no-missing-generic-family-keyword': null,
},
overrides: [
{
files: ['**/*.less'],
customSyntax: 'postcss-less',
},
],
};

View File

@@ -1,10 +1,11 @@
import legacyPlugin from '@vitejs/plugin-legacy'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
// import mkcert from 'vite-plugin-mkcert'
import mkcert from 'vite-plugin-mkcert'
import { join } from 'path'
// server of your OpenAvatarChat
// if you are not use localhost, you need to start https
const serverIP = '127.0.0.1'
const serverPort = '8282'
@@ -13,10 +14,6 @@ export default defineConfig({
base: './',
build: {
rollupOptions: {
// input: {
// index: resolve(__dirname, 'index.html'),
// cropImage: resolve(__dirname, 'cropImage.html')
// },
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,