-
Notifications
You must be signed in to change notification settings - Fork 310
feat: add remote control page, update next dependency #3501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe changes introduce new features and enhancements to a Vue 3 application. A new "remoter" route and associated views are added, including components for chat message display and voice interaction. Dependency updates and refactoring improve session handling and reactivity. Several new components implement chat UI and speech recognition functionality. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Router
participant RemoterView
participant globalConversation
participant RobotChat
participant Sound
User->>Router: Navigate to /remoter route
Router->>RemoterView: Load Remoter component
RemoterView->>RemoterView: Decrypt session ID from URL
RemoterView->>globalConversation: Set sessionId
RemoterView->>User: Show drawer with controller options
User->>RemoterView: Select Voice or Chat controller
RemoterView->>Sound: Show Sound component (if selected)
RemoterView->>RobotChat: Show RobotChat component (if selected)
sequenceDiagram
participant User
participant Sound
participant SpeechRecognition
participant useTinyRobot
participant MessageList
User->>Sound: Tap and hold record button
Sound->>SpeechRecognition: Start listening
SpeechRecognition-->>Sound: Transcribe speech to text
Sound->>useTinyRobot: Send transcribed message
useTinyRobot-->>Sound: Receive response
Sound->>MessageList: Update with new messages
User->>Sound: Release record button or timeout
Sound->>SpeechRecognition: Stop listening
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
examples/sites/src/App.vueOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't find the plugin "eslint-plugin-vue". (The package "eslint-plugin-vue" was not found when loaded as a Node module from the directory "".) It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
The plugin "eslint-plugin-vue" was referenced from the config file in ".eslintrc.js » @antfu/eslint-config » @antfu/eslint-config-vue". If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. examples/sites/src/views/comprehensive/index.vueOops! Something went wrong! :( ESLint: 8.57.1 ESLint couldn't find the plugin "eslint-plugin-vue". (The package "eslint-plugin-vue" was not found when loaded as a Node module from the directory "".) It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:
The plugin "eslint-plugin-vue" was referenced from the config file in ".eslintrc.js » @antfu/eslint-config » @antfu/eslint-config-vue". If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (4)
examples/sites/src/composable/utils.ts (1)
14-17
: Guard the mutable global with accessors
globalConversation
is now a writable reactive singleton. Any import can mutate it directly, which makes tracking changes difficult and hampers debugging.
Consider exposing setter / getter helpers (orreadonly(globalConversation)
) and updating it only through explicit functions.examples/sites/src/views/comprehensive/index.vue (1)
92-100
: Build the remoter URL with theURL
API to avoid double “?”If
location.href
already contains a query string you’ll end up with two?
characters.
Safer construction:- sessionUrl.value = `${location.href.replace('comprehensive', 'remoter')}?id=${secretId}` + const url = new URL(location.href) + url.pathname = url.pathname.replace('comprehensive', 'remoter') + url.search = `?id=${secretId}` + sessionUrl.value = url.toString()examples/sites/src/views/remoter/index.vue (1)
21-22
: Fix inconsistent commentThe comment mentions storing to
window.sessionId
but the code stores toglobalConversation.sessionId
.- // 存储解密后的 id 到 window.sessionId + // 存储解密后的 id 到 globalConversation.sessionIdexamples/sites/src/components/MessageCard.vue (1)
30-37
: Consider making locale configurable for internationalizationThe time format is hardcoded to 'zh-CN' locale. For better internationalization support, consider making this configurable.
interface Props { role: 'user' | 'assistant' message: string timestamp: number + locale?: string } const props = defineProps<Props>() const formatTime = computed(() => { const date = new Date(props.timestamp) - return date.toLocaleTimeString('zh-CN', { + return date.toLocaleTimeString(props.locale || 'zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }) })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
examples/sites/package.json
(1 hunks)examples/sites/src/App.vue
(2 hunks)examples/sites/src/components/MessageCard.vue
(1 hunks)examples/sites/src/composable/utils.ts
(1 hunks)examples/sites/src/router.js
(2 hunks)examples/sites/src/views/comprehensive/index.vue
(1 hunks)examples/sites/src/views/remoter/index.vue
(1 hunks)examples/sites/src/views/remoter/sound.vue
(1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: build
🔇 Additional comments (7)
examples/sites/src/composable/utils.ts (1)
8-10
:✅ Verification successful
Ensure that
storage.ts
actually re-exports$session
$session
is now being re-exported and referenced all over the app, but the file./storage
is not shown here.
If that module does not export aconst $session
(or similar) you will hit a compile-time error.Run a quick grep to make sure the export exists:
🏁 Script executed:
#!/bin/bash # Check that `$session` is exported from any storage file rg --context 2 $'export.*\\$session'Length of output: 1328
Confirmed export of
$session
in storage.ts
Verified thatexamples/sites/src/composable/storage.ts
includesexport { $session, $local, $cache, useAutoStore }
. No further changes needed.examples/sites/package.json (1)
30-30
:✅ Verification successful
Verify availability & compatibility of
@opentiny/[email protected]
Jumping from an alpha tag to
0.0.1
will break CI if that version is not published or if its peer-deps changed.
🏁 Script executed:
#!/bin/bash npm view @opentiny/[email protected] version peerDependenciesLength of output: 517
Let’s check if there are any peer dependencies for version 0.0.1:
🏁 Script executed:
#!/bin/bash npm view @opentiny/[email protected] peerDependencies --jsonLength of output: 59
Let’s fetch the full package metadata to confirm there are no hidden peerDependencies or other breaking changes:
🏁 Script executed:
#!/bin/bash npm view @opentiny/[email protected] --jsonLength of output: 2466
@opentiny/[email protected] is published and defines no peerDependencies
I confirmed vianpm view @opentiny/[email protected] --json
that version 0.0.1 exists and has nopeerDependencies
, so upgrading from the alpha tag won’t break CI.examples/sites/src/App.vue (1)
20-20
: Import looks goodNothing to flag here.
examples/sites/src/router.js (1)
28-32
: Route outsideLayout
– confirm UX intent
comprehensive
is rendered insideLayout
, but the newremoter
route is not.
If that’s deliberate (e.g., full-screen mobile view) please ignore; otherwise you may lose the common header/footer.examples/sites/src/components/MessageCard.vue (1)
1-117
: Well-structured component implementationThe component is cleanly implemented with proper TypeScript types, computed properties, and role-based styling. Good use of scoped styles and semantic class names.
examples/sites/src/views/remoter/sound.vue (2)
1-283
: Comprehensive voice recording implementationThe component demonstrates good practices including:
- Proper error handling for various speech recognition errors
- Recording time limits to prevent excessive resource usage
- Cleanup on component unmount
- User-friendly error messages
- Touch event handling for mobile devices
276-281
:✅ Verification successful
Verify the correct API method for cleanup
The standard SpeechRecognition API uses
stop()
method, notabort()
. Verify ifabort()
is supported across browsers.
🏁 Script executed:
#!/bin/bash # Description: Search for SpeechRecognition API usage patterns and verify abort() method availability # Search for other SpeechRecognition usage patterns in the codebase rg -A 3 -B 3 "recognition\.(stop|abort)" --type ts --type js --type vue # Search for any documentation or comments about SpeechRecognition cleanup rg -i "speechrecognition.*(cleanup|abort|stop)" --type ts --type js --type vueLength of output: 210
🌐 Web query:
Does the Web Speech API SpeechRecognition interface support the abort() method, or should stop() be used instead?
💡 Result:
The Web Speech API SpeechRecognition interface supports both the abort() and stop() methods for stopping speech recognition, but they behave differently and are intended for distinct use cases:
abort() method: This method immediately stops the speech recognition service from listening to incoming audio and does not attempt to return a final SpeechRecognitionResult. It is used when you want to cancel speech recognition without processing or returning any results. The user agent will fire the end event once the service has stopped[1][2][3][7][8].
stop() method: This method stops the service from listening, but it will attempt to process any audio captured so far and return a result if possible (by firing the result or nomatch events before the end event)[1].
Here’s a comparison:
Method Description Returns Result? abort() Immediately stops recognition, does not return a result, cancels processing mid-stream No stop() Stops recognition, attempts to return a result with captured audio Yes (if possible) Conclusion:
You should use abort() when you need to immediately cancel recognition and discard any partial results, and stop() when you wish to end recognition but still receive recognition results for the audio processed so far. Both methods are available and supported by the SpeechRecognition interface in the Web Speech API[1][2][3][7].Citations:
- 1: https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition
- 2: https://docs3.w3cub.com/dom/speechrecognition/abort/
- 3: https://webaudio.github.io/web-speech-api/
- 4: https://udn.realityripple.com/docs/Web/API/SpeechRecognition/abort
- 5: https://assemblyai.com/blog/speech-recognition-javascript-web-speech-api
- 6: https://12daysofweb.dev/2021/speech-api/
- 7: https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/abort
- 8: https://dvcs.w3.org/hg/speech-api/raw-file/tip/webspeechapi
- 9: https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API
- 10: https://dev.to/roopalisingh/building-a-voice-assistant-3g09
No change needed:
abort()
is supported for immediate cleanupThe Web Speech API’s SpeechRecognition interface provides both
abort()
andstop()
methods.
abort()
immediately cancels recognition without returning any results (fires theend
event).stop()
ends recognition but tries to process and return any captured audio before firingend
.In this cleanup scenario—where you want to discard any partial results—using
recognition.abort()
is appropriate.
examples/sites/src/App.vue
Outdated
const { sessionId } = useNextClient({ | ||
clientInfo: { name: 'tiny-vue-website', version: '1.0.0' }, | ||
proxyOptions: { url: 'https://39.108.160.245/sse', token: '' } | ||
proxyOptions: { url: 'https://39.108.160.245/sse', token: '', sessionId: $session.sessionId } | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Persist the issued sessionId
back to $session
$session.sessionId
is passed once to useNextClient
, but when the server returns a new sessionId
you only copy it to globalConversation
, not back to storage.
On a page reload you’ll lose the session.
watch(
() => sessionId.value,
(newVal) => {
if (newVal) {
globalConversation.sessionId = newVal
+ $session.sessionId = newVal // keep storage in sync
}
}
)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const { sessionId } = useNextClient({ | |
clientInfo: { name: 'tiny-vue-website', version: '1.0.0' }, | |
proxyOptions: { url: 'https://39.108.160.245/sse', token: '' } | |
proxyOptions: { url: 'https://39.108.160.245/sse', token: '', sessionId: $session.sessionId } | |
}) | |
watch( | |
() => sessionId.value, | |
(newVal) => { | |
if (newVal) { | |
globalConversation.sessionId = newVal | |
$session.sessionId = newVal // keep storage in sync | |
} | |
} | |
) |
🤖 Prompt for AI Agents
In examples/sites/src/App.vue around lines 34 to 37, the sessionId returned from
useNextClient is assigned to a local variable but not persisted back to
$session.sessionId. To fix this, update the code to assign the new sessionId
value back to $session.sessionId whenever it changes, ensuring the session is
stored persistently and not lost on page reload.
// 加密密钥,需要和生成二维码的页面保持一致 | ||
const secretKey = 'secret-session-id' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical security issue: Hardcoded encryption key
The secret key is hardcoded in the client-side code, which is a significant security vulnerability. Anyone can view this key and decrypt the session IDs.
Consider these more secure alternatives:
- Use server-side decryption and only pass the decrypted session ID to the client
- Use environment variables or configuration management for the key
- Implement proper key rotation and management
🤖 Prompt for AI Agents
In examples/sites/src/views/remoter/index.vue around lines 10 to 11, the
secretKey is hardcoded in the client-side code, posing a critical security risk.
To fix this, remove the hardcoded key from the client code and instead retrieve
the secret key securely from the server or use environment variables injected at
build time. Implement server-side decryption of session IDs and only send
decrypted data to the client, ensuring the key is never exposed in the frontend
code. Also, consider adding key rotation and secure key management practices.
const showMoter = (type) => { | ||
state.isShow = false | ||
state[type] = true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve function naming and parameter clarity
The function name "showMoter" appears to be a typo, and the parameter name type
is too generic for its specific use case.
-const showMoter = (type) => {
+const showController = (controllerType: 'showChat' | 'showSound') => {
state.isShow = false
- state[type] = true
+ state[controllerType] = true
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const showMoter = (type) => { | |
state.isShow = false | |
state[type] = true | |
} | |
const showController = (controllerType: 'showChat' | 'showSound') => { | |
state.isShow = false | |
state[controllerType] = true | |
} |
🤖 Prompt for AI Agents
In examples/sites/src/views/remoter/index.vue around lines 24 to 27, rename the
function "showMoter" to a more descriptive and correctly spelled name that
reflects its purpose, such as "showMotor" or "displayMotor". Also, rename the
parameter "type" to a more specific name that clearly indicates what kind of
value it expects, for example "motorType" or "sectionName". Update all
references to this function and parameter accordingly.
<tiny-button @click="showMoter('showSound')" type="info" size="large">语音控制器</tiny-button> | ||
|
||
<tiny-button @click="showMoter('showChat')" type="info" size="large">综合控制器</tiny-button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update function calls to match refactored name
If you apply the suggested refactor above, update these function calls accordingly.
- <tiny-button @click="showMoter('showSound')" type="info" size="large">语音控制器</tiny-button>
+ <tiny-button @click="showController('showSound')" type="info" size="large">语音控制器</tiny-button>
- <tiny-button @click="showMoter('showChat')" type="info" size="large">综合控制器</tiny-button>
+ <tiny-button @click="showController('showChat')" type="info" size="large">综合控制器</tiny-button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<tiny-button @click="showMoter('showSound')" type="info" size="large">语音控制器</tiny-button> | |
<tiny-button @click="showMoter('showChat')" type="info" size="large">综合控制器</tiny-button> | |
<tiny-button @click="showController('showSound')" type="info" size="large">语音控制器</tiny-button> | |
<tiny-button @click="showController('showChat')" type="info" size="large">综合控制器</tiny-button> |
🤖 Prompt for AI Agents
In examples/sites/src/views/remoter/index.vue around lines 44 to 46, the
function calls to showMoter need to be updated to match the refactored function
name. Identify the new function name from the refactor and replace all instances
of showMoter with the updated function name in these button click handlers.
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Browser detection using user agent is fragile
User agent string parsing is unreliable and can be spoofed. Consider using feature detection instead.
-const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
+// Feature detection for Safari-specific behavior
+const isSafari = 'webkitSpeechRecognition' in window && !('SpeechRecognition' in window)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent) | |
// Feature detection for Safari-specific behavior | |
const isSafari = 'webkitSpeechRecognition' in window && !('SpeechRecognition' in window) |
🤖 Prompt for AI Agents
In examples/sites/src/views/remoter/sound.vue around lines 88 to 89, the code
uses user agent string parsing to detect Safari browser, which is unreliable and
can be spoofed. Replace this user agent check with feature detection by
identifying a unique Safari-specific feature or behavior, such as checking for
the existence of a Safari-only API or CSS property, to determine if the browser
is Safari more reliably.
} catch (error) { | ||
handleRecognitionError({ | ||
error: 'start_error', | ||
message: '启动语音识别失败,请重试' | ||
} as SpeechRecognitionErrorEvent) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid manually constructing error event objects
Creating a manual error object might not match the actual SpeechRecognitionErrorEvent
interface, potentially causing issues in the error handler.
} catch (error) {
- handleRecognitionError({
- error: 'start_error',
- message: '启动语音识别失败,请重试'
- } as SpeechRecognitionErrorEvent)
+ // Handle start error directly without simulating an event
+ stopRecording()
+ TinyNotify({
+ type: 'error',
+ title: '语音识别出错',
+ message: '启动语音识别失败,请重试',
+ position: 'top-right',
+ duration: 3000
+ })
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
} catch (error) { | |
handleRecognitionError({ | |
error: 'start_error', | |
message: '启动语音识别失败,请重试' | |
} as SpeechRecognitionErrorEvent) | |
} | |
} catch (error) { | |
// Handle start error directly without simulating an event | |
stopRecording() | |
TinyNotify({ | |
type: 'error', | |
title: '语音识别出错', | |
message: '启动语音识别失败,请重试', | |
position: 'top-right', | |
duration: 3000 | |
}) | |
} |
🤖 Prompt for AI Agents
In examples/sites/src/views/remoter/sound.vue around lines 222 to 227, avoid
manually constructing an object as SpeechRecognitionErrorEvent because it may
not conform to the actual interface. Instead, pass the caught error object
directly to handleRecognitionError or create a proper error event using the
SpeechRecognition API if possible, ensuring the error handler receives a valid
event object.
* feat: New MCP comprehensive example added to the official website (#3497) * feat: 官网新增mcp综合示例 * fix: 修复 tiny-robot-chat 组件中的提示项和建议项的传递逻辑,优化 Demo 组件的 QR 码展示样式 * fix: 修复引入路径问题 * chore: Update changelog and add TinyVue intelligent component access documentation (#3498) * chore: 更新changelog,并添加TinyVue智能化组件接入文档 * chore: 添加菜单优化文档 * chore: 更新mcp.md文档中的sessionId说明,简化调试提示 * chore: 更新mcp-en.md文档中的标题和内容,统一语言为英文 * feat: add remote control page, update next dependency (#3501) * feat: 添加遥控器页面,更新next依赖 * chore: 修改代理为https url * feat: 更新会话ID的处理逻辑 * fix: 更新依赖版本约束以支持更高版本 * fix: 更新版本号至3.24.4
feat: 添加遥控器页面,更新next依赖
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Enhancements
Chores