feat(screenshot): add screenshots of rounded corners and write input box

update readme
update release.yml and remove pr-chatbot-review.yml

closed #323
This commit is contained in:
Dawn
2025-08-27 00:02:39 +08:00
parent 300860cda4
commit 0ccedafdb6
21 changed files with 108 additions and 187 deletions

View File

@@ -1,145 +0,0 @@
name: PR Review Bot
on:
pull_request:
types: [opened, synchronize]
permissions:
contents: read
pull-requests: write
jobs:
review:
# 跳过 Renovate PR
if: |
github.actor != 'renovate[bot]' &&
github.actor != 'renovate-preview[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get PR diff
id: diff
run: |
git fetch origin ${{ github.event.pull_request.base.sha }}
# 排除配置文件,只分析源代码文件
DIFF=$(git diff ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} -- \
'src/**/*.vue' \
'src/**/*.ts' \
'src/**/*.tsx' \
'src-tauri/**/*.rs' \
':!:**/*.json' \
':!:**/*.yaml' \
':!:**/*.yml' \
':!:**/*.config.*' \
':!:**/*.lock' \
':!:**/*.toml' \
':!:.env*' \
':!:.eslintrc*' \
':!:.prettierrc*')
# 如果没有相关文件变更,设置一个提示信息
if [ -z "$DIFF" ]; then
echo "NO_CHANGES=true" >> $GITHUB_ENV
echo "DIFF=没有检测到相关文件的变更。" >> $GITHUB_ENV
else
echo "NO_CHANGES=false" >> $GITHUB_ENV
echo "DIFF<<EOF" >> $GITHUB_ENV
echo "$DIFF" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
fi
# 首先安装 pnpm
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9
run_install: false
# 然后设置 Node.js
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'
- name: Install dependencies
run: |
pnpm config set registry https://registry.npmmirror.com/
pnpm install
pnpm add openai
- name: Analyze PR
id: analyze
if: env.NO_CHANGES != 'true'
uses: actions/github-script@v7
env:
DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }}
with:
script: |
const OpenAI = require('openai');
const openai = new OpenAI({
apiKey: process.env.DASHSCOPE_API_KEY,
baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
});
const diff = process.env.DIFF;
try {
const completion = await openai.chat.completions.create({
model: "qwen-plus",
messages: [{
role: "system",
content: "你是一个代码审查助手。请用中文分析以下代码变更,重点关注:\n" +
"1. 代码逻辑的改动\n" +
"2. 潜在的问题或优化空间\n" +
"3. TypeScript 类型定义的准确性\n" +
"4. Vue 组件的性能影响\n" +
"5. Rust 代码的安全性和性能\n" +
"请用中文简明扼要地总结。"
}, {
role: "user",
content: `请分析以下代码变更并总结主要改动:\n\n${diff}`
}],
temperature: 0.7,
max_tokens: 1000
});
const analysis = completion.choices[0].message.content;
core.setOutput('analysis', analysis);
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `
PR 代码分析
${analysis}
*这是由通义千问 AI 自动生成的 PR 分析,仅供参考。*`
});
} catch (error) {
core.setFailed(`分析失败: ${error.message}`);
}
- name: Skip Analysis Comment
if: env.NO_CHANGES == 'true'
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## PR 代码分析
本次变更不包含需要分析的代码文件src 目录下的 .vue/.ts/.tsx 文件或 src-tauri 目录下的 .rs 文件)。
---
*这是自动生成的通知。*`
});

View File

@@ -69,9 +69,6 @@ jobs:
- name: Install dependencies
run: pnpm install
- name: Build Vite + Tauri
run: pnpm build
# 安装 Rust
- name: install Rust stable
uses: dtolnay/rust-toolchain@stable # Set this to dtolnay/rust-toolchain@nightly
@@ -98,4 +95,4 @@ jobs:
releaseBody: 'See the assets to download and install this version.'
releaseDraft: true
prerelease: false
args: ${{ matrix.args }}
args: ${{ matrix.args }}

View File

@@ -237,7 +237,7 @@ HuLa is an instant messaging system built with Tauri, Vite 7, Vue 3, and TypeScr
| Feature | Description | Status |
|---------|-------------|--------|
| 💻 | Windows/macOS/Linux | ![Completed](https://img.shields.io/badge/✅-Completed-008080?style=flat&labelColor=e6f7f7&color=008080) |
| 📱 | iOS/Android Adaptation | ![In Progress](https://img.shields.io/badge/🐣-Accomplish60%-ee9f20?style=flat&labelColor=fef7e6&color=ee9f20) |
| 📱 | iOS/Android Adaptation | ![In Progress](https://img.shields.io/badge/🐣-In_Progress-ee9f20?style=flat&labelColor=fef7e6&color=ee9f20) |
### 🤖 AI Integration
| Feature | Description | Status |
@@ -344,6 +344,7 @@ Execute **pnpm run commit** to invoke _git commit_ interaction, complete informa
### 🏆 Gold Sponsors ($15+)
| 💝 Date | 👤 Sponsor | 💰 Amount | 🏷️ Platform |
|---------|----------|--------|---------|
| 2025-08-26 | **唐勇** | `¥200` | ![WeChat](https://img.shields.io/badge/WeChat-07C160?style=flat&logo=wechat&logoColor=white) |
| 2025-04-25 | **上官俊斌** | `¥200` | ![WeChat](https://img.shields.io/badge/WeChat-07C160?style=flat&logo=wechat&logoColor=white) |
| 2025-05-27 | **临安居士** | `¥188` | ![WeChat](https://img.shields.io/badge/WeChat-07C160?style=flat&logo=wechat&logoColor=white) |
| 2025-04-20 | **姜兴(Simon)** | `¥188` | ![WeChat](https://img.shields.io/badge/WeChat-07C160?style=flat&logo=wechat&logoColor=white) |

View File

@@ -238,7 +238,7 @@ HuLa 是一款基于 Tauri、Vite 7、Vue 3 和 TypeScript 构建的即时通讯
| 功能 | 描述 | 状态 |
|------|------|------|
| 💻 | Windows/macOS/Linux | ![完成](https://img.shields.io/badge/✅-完成-008080?style=flat&labelColor=e6f7f7&color=008080) |
| 📱 | iOS/Android 适配 | ![进行中](https://img.shields.io/badge/🐣-完成60%-ee9f20?style=flat&labelColor=fef7e6&color=ee9f20) |
| 📱 | iOS/Android 适配 | ![进行中](https://img.shields.io/badge/🐣-进行中-ee9f20?style=flat&labelColor=fef7e6&color=ee9f20) |
### 🤖 AI 集成
| 功能 | 描述 | 状态 |
@@ -345,6 +345,7 @@ sudo xattr -r -d com.apple.quarantine /Applications/应用名称.app
### 🏆 金牌赞助者 (¥100+)
| 💝 日期 | 👤 赞助者 | 💰 金额 | 🏷️ 平台 |
|---------|----------|--------|---------|
| 2025-08-26 | **唐勇** | `¥200` | ![微信赞赏](https://img.shields.io/badge/微信赞赏-07C160?style=flat&logo=wechat&logoColor=white) |
| 2025-04-25 | **上官俊斌** | `¥200` | ![微信赞赏](https://img.shields.io/badge/微信赞赏-07C160?style=flat&logo=wechat&logoColor=white) |
| 2025-05-27 | **临安居士** | `¥188` | ![微信赞赏](https://img.shields.io/badge/微信赞赏-07C160?style=flat&logo=wechat&logoColor=white) |
| 2025-04-20 | **姜兴(Simon)** | `¥188` | ![微信赞赏](https://img.shields.io/badge/微信赞赏-07C160?style=flat&logo=wechat&logoColor=white) |

View File

@@ -137,7 +137,7 @@ onMounted(async () => {
closeWindow?.close()
})
addListener(
await addListener(
listen('refresh_token_event', (event) => {
console.log('🔄 收到 refresh_token 事件')

View File

@@ -84,6 +84,7 @@
</template>
<script setup lang="ts">
import { emitTo } from '@tauri-apps/api/event'
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
import { writeImage } from '@tauri-apps/plugin-clipboard-manager'
import type { Ref } from 'vue'
@@ -1180,6 +1181,33 @@ const confirmSelection = async () => {
height // 绘制到临时 canvas 的区域
)
// 如果设置了圆角,则将裁剪结果应用圆角蒙版,导出带透明圆角的 PNG
if (borderRadius.value > 0) {
const scale = screenConfig.value.scaleX || 1
const r = Math.min(borderRadius.value * scale, width / 2, height / 2)
if (r > 0) {
offscreenCtx.save()
// 仅保留圆角矩形内的内容
offscreenCtx.globalCompositeOperation = 'destination-in'
offscreenCtx.beginPath()
// 在 (0,0,width,height) 上构建圆角矩形路径
offscreenCtx.moveTo(r, 0)
offscreenCtx.lineTo(width - r, 0)
offscreenCtx.quadraticCurveTo(width, 0, width, r)
offscreenCtx.lineTo(width, height - r)
offscreenCtx.quadraticCurveTo(width, height, width - r, height)
offscreenCtx.lineTo(r, height)
offscreenCtx.quadraticCurveTo(0, height, 0, height - r)
offscreenCtx.lineTo(0, r)
offscreenCtx.quadraticCurveTo(0, 0, r, 0)
offscreenCtx.closePath()
offscreenCtx.fill()
offscreenCtx.restore()
}
}
// 测试检查canvas数据是否有效
try {
offscreenCtx.getImageData(0, 0, Math.min(10, width), Math.min(10, height))
@@ -1187,13 +1215,24 @@ const confirmSelection = async () => {
console.error('获取ImageData失败,可能是安全限制:', error)
}
// 将裁剪后的图像转换为 Blob 并复制到剪贴板
offscreenCanvas.toBlob(async (blob) => {
if (blob && blob.size > 0) {
try {
// 将 Blob 转换为 ArrayBuffer 以便通过 Tauri 事件传递
const arrayBuffer = await blob.arrayBuffer()
const buffer = new Uint8Array(arrayBuffer)
try {
await emitTo('home', 'screenshot', {
type: 'image',
buffer: Array.from(buffer),
mimeType: 'image/png'
})
} catch (e) {
console.warn('发送截图到主窗口失败:', e)
}
// 复制到剪贴板
await writeImage(buffer)
await resetScreenshot()
} catch (error) {
@@ -1304,7 +1343,7 @@ const handleScreenshot = () => {
}
onMounted(async () => {
addListener(
await addListener(
appWindow.listen('capture', () => {
resetDrawTools()
initCanvas()
@@ -1314,7 +1353,7 @@ onMounted(async () => {
)
// 监听窗口隐藏时的重置事件
addListener(
await addListener(
appWindow.listen('capture-reset', () => {
resetDrawTools()
resetScreenshot()

View File

@@ -433,12 +433,38 @@ onMounted(async () => {
}
})
/** 这里使用的是窗口之间的通信来监听信息对话的变化 */
addListener(
await addListener(
appWindow.listen('aloneData', (event: any) => {
activeItem.value = { ...event.payload.item }
}),
'aloneData'
)
await addListener(
appWindow.listen('screenshot', async (e: any) => {
// 确保输入框获得焦点
if (messageInputDom.value) {
messageInputDom.value.focus()
try {
// 从 ArrayBuffer 数组重建 Blob 对象
const buffer = new Uint8Array(e.payload.buffer)
const blob = new Blob([buffer], { type: e.payload.mimeType })
const file = new File([blob], 'screenshot.png', { type: e.payload.mimeType })
// 创建一个模拟的粘贴事件包含File对象
const mockPasteEvent = {
preventDefault: () => {},
clipboardData: {
files: [file]
}
}
handlePaste(mockPasteEvent, messageInputDom.value, showFileModalCallback)
} catch (error) {
console.error('处理截图失败:', error)
}
}
}),
'screenshot'
)
window.addEventListener('click', closeMenu, true)
window.addEventListener('keydown', disableSelectAll)
})

View File

@@ -472,7 +472,7 @@ onMounted(async () => {
handlePopoverUpdate(event.uid)
})
addListener(
await addListener(
appWindow.listen('announcementClear', async () => {
announNum.value = 0
}),

View File

@@ -248,7 +248,7 @@ onMounted(async () => {
// info('ActionBar 组件已挂载')
window.addEventListener('resize', handleResize)
addListener(
await addListener(
appWindow.listen(EventEnum.EXIT, async () => {
await exit(0)
}),

View File

@@ -55,7 +55,7 @@ export const useGlobalShortcut = () => {
if (captureWindow) {
// 设置关闭拦截 - 将关闭转为隐藏
addListener(
await addListener(
captureWindow.onCloseRequested(async (event) => {
event.preventDefault()
await captureWindow.hide()
@@ -330,7 +330,7 @@ export const useGlobalShortcut = () => {
}
// 监听全局快捷键开关变化
addListener(
await addListener(
listen('global-shortcut-enabled-changed', (event) => {
const enabled = (event.payload as any)?.enabled
if (typeof enabled === 'boolean') {
@@ -344,7 +344,7 @@ export const useGlobalShortcut = () => {
// 监听每个快捷键的更新事件
for (const config of shortcutConfigs) {
addListener(
await addListener(
listen(config.updateEventName, (event) => {
const newShortcut = (event.payload as any)?.shortcut
if (newShortcut) {

View File

@@ -327,7 +327,7 @@ export const useMessage = () => {
onMounted(async () => {
const appWindow = WebviewWindow.getCurrent()
addListener(
await addListener(
appWindow.listen(EventEnum.ALONE, () => {
emit(EventEnum.ALONE + itemRef.value?.roomId, itemRef.value)
if (aloneWin.value.has(EventEnum.ALONE + itemRef.value?.roomId)) return

View File

@@ -32,7 +32,7 @@ export const useTauriListener = () => {
* @param listener Promise<UnlistenFn>
*/
const addListener = async (listener: Promise<UnlistenFn>, id?: string) => {
const listenerId = id || `listener_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
const listenerId = id || `listener_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
if (listenerIdMap.has(listenerId)) {
try {
const unlisten = await listener

View File

@@ -352,7 +352,7 @@ onMounted(async () => {
startResize()
// 监听自定义事件,处理设置中菜单显示模式切换和添加插件后,导致高度变化,需重新调整插件菜单显示
addListener(
await addListener(
appWindow.listen('startResize', () => {
startResize()
}),

View File

@@ -216,8 +216,8 @@ const openEditInfo = () => {
})
}
onMounted(() => {
addListener(
onMounted(async () => {
await addListener(
appWindow.listen('open_edit_info', async () => {
openEditInfo()
}),

View File

@@ -205,7 +205,7 @@ export const leftHook = () => {
useMitt.on(MittEnum.TO_SEND_MSG, (event: any) => {
activeUrl.value = event.url
})
addListener(
await addListener(
appWindow.listen(EventEnum.WIN_SHOW, (e) => {
// 如果已经存在就不添加
if (openWindowsList.value.has(e.payload)) return
@@ -213,7 +213,7 @@ export const leftHook = () => {
}),
EventEnum.WIN_SHOW
)
addListener(
await addListener(
appWindow.listen(EventEnum.WIN_CLOSE, (e) => {
openWindowsList.value.delete(e.payload)
}),

View File

@@ -1156,7 +1156,7 @@ onMounted(async () => {
})
// 监听公告更新事件
addListener(
await addListener(
appWindow.listen('announcementUpdated', async (event: any) => {
if (event.payload) {
const { hasAnnouncements, topAnnouncement: newTopAnnouncement } = event.payload
@@ -1178,14 +1178,14 @@ onMounted(async () => {
)
// 监听公告清空事件
addListener(
await addListener(
appWindow.listen('announcementClear', () => {
topAnnouncement.value = null
}),
'announcementClear'
)
addListener(
await addListener(
appWindow.listen(EventEnum.SHARE_SCREEN, async () => {
await createWebviewWindow('共享屏幕', 'sharedScreen', 840, 840)
}),

View File

@@ -19,24 +19,26 @@ const { addListener } = useTauriListener()
const video = ref<HTMLVideoElement>()
const peerConnection = new RTCPeerConnection()
addListener(
appWindow.listen('offer', async (event) => {
console.log(event.payload)
await peerConnection.setRemoteDescription(new RTCSessionDescription(event.payload as RTCSessionDescriptionInit))
const answer = await peerConnection.createAnswer()
await peerConnection.setLocalDescription(answer)
// 在这里,你需要将应答发送给发送方
// 你可以使用信令服务器来发送应答,或者将应答复制粘贴到发送方页面
console.log(JSON.stringify(answer))
})
)
peerConnection.ontrack = (event) => {
if (video.value) {
video.value.srcObject = event.streams[0]
}
}
onBeforeUnmount(async () => {
await addListener(
appWindow.listen('offer', async (event) => {
console.log(event.payload)
await peerConnection.setRemoteDescription(new RTCSessionDescription(event.payload as RTCSessionDescriptionInit))
const answer = await peerConnection.createAnswer()
await peerConnection.setLocalDescription(answer)
// 在这里,你需要将应答发送给发送方
// 你可以使用信令服务器来发送应答,或者将应答复制粘贴到发送方页面
console.log(JSON.stringify(answer))
})
)
})
onMounted(async () => {
await getCurrentWebviewWindow().show()
await emit('SharedScreenWin')

View File

@@ -245,7 +245,7 @@ onMounted(async () => {
// 监听其他窗口发来的WebSocket发送请求
// TODO频繁切换会话会导致频繁请求切换的时候也会有点卡顿
if (appWindow.label === 'home') {
addListener(
await addListener(
appWindow.listen('search_to_msg', (event: { payload: { uid: string; roomType: number } }) => {
openMsgSession(event.payload.uid, event.payload.roomType)
})

View File

@@ -367,7 +367,7 @@ onMounted(async () => {
// 显示窗口
await getCurrentWebviewWindow().show()
addListener(
await addListener(
appWindow.listen('update-image', (event: any) => {
const { list, index } = event.payload
imageList.value = list

View File

@@ -138,7 +138,7 @@ onMounted(async () => {
const webviewWindow = getCurrentWebviewWindow()
const label = webviewWindow.label
addListener(
await addListener(
listen(`${label}:update`, (event: any) => {
const payload: PayloadData = event.payload.payload
console.log('payload更新', payload)

View File

@@ -368,7 +368,7 @@ onMounted(async () => {
await getCurrentWebviewWindow().show()
// 修改事件名称与发送端保持一致
addListener(
await addListener(
appWindow.listen('video-updated', (event: any) => {
const { list, index } = event.payload
videoList.value = list