feat(web): 添加响应式布局和 PWA 支持
- 实现移动端响应式适配:抽屉式侧边栏、触摸优化、Safe Area 支持 - 添加 PWA 支持:vite-plugin-pwa、manifest、Service Worker 缓存 - 优化移动端输入体验:防缩放、最小触摸目标、键盘适配
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* ConfigPanel Component
|
||||
*
|
||||
* 配置面板组件
|
||||
* 配置面板:移动端全屏显示,桌面端居中弹窗
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
@@ -96,14 +96,17 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-gray-800 rounded-lg w-full max-w-lg mx-4 max-h-[90vh] overflow-auto">
|
||||
<div className="fixed inset-0 bg-black/50 flex items-end md:items-center justify-center z-50">
|
||||
{/* 移动端:从底部滑出的全屏面板;桌面端:居中弹窗 */}
|
||||
<div className="bg-gray-800 w-full md:w-full md:max-w-lg md:mx-4 max-h-full md:max-h-[90vh] overflow-auto rounded-t-2xl md:rounded-lg">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-700">
|
||||
<h2 className="text-lg font-semibold">Configuration</h2>
|
||||
<div className="sticky top-0 flex items-center justify-between px-4 md:px-6 py-4 border-b border-gray-700 bg-gray-800 z-10">
|
||||
{/* 移动端拖动指示器 */}
|
||||
<div className="absolute top-2 left-1/2 -translate-x-1/2 w-10 h-1 bg-gray-600 rounded-full md:hidden" />
|
||||
<h2 className="text-lg font-semibold mt-2 md:mt-0">Configuration</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-1 hover:bg-gray-700 rounded transition-colors"
|
||||
className="p-2 hover:bg-gray-700 active:bg-gray-600 rounded-lg transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -112,7 +115,7 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 space-y-6">
|
||||
<div className="p-4 md:p-6 space-y-6">
|
||||
{/* Error message */}
|
||||
{error && (
|
||||
<div className="p-3 bg-red-900/50 border border-red-700 rounded-lg text-red-300 text-sm">
|
||||
@@ -135,7 +138,7 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
<select
|
||||
value={formData.model}
|
||||
onChange={(e) => setFormData({ ...formData, model: e.target.value })}
|
||||
className="w-full px-3 py-2 bg-gray-900 border border-gray-700 rounded-lg text-white focus:outline-none focus:border-blue-500"
|
||||
className="w-full px-3 py-3 md:py-2 bg-gray-900 border border-gray-700 rounded-lg text-white text-base md:text-sm focus:outline-none focus:border-blue-500"
|
||||
>
|
||||
{AVAILABLE_MODELS.map((model) => (
|
||||
<option key={model.id} value={model.id}>
|
||||
@@ -151,7 +154,7 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
{/* Max Tokens */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Max Tokens: {formData.maxTokens}
|
||||
Max Tokens: {formData.maxTokens.toLocaleString()}
|
||||
</label>
|
||||
<input
|
||||
type="range"
|
||||
@@ -160,7 +163,7 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
step="1024"
|
||||
value={formData.maxTokens}
|
||||
onChange={(e) => setFormData({ ...formData, maxTokens: parseInt(e.target.value) })}
|
||||
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||||
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer touch-pan-x"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
||||
<span>1K</span>
|
||||
@@ -185,12 +188,12 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
step="0.05"
|
||||
value={formData.temperature}
|
||||
onChange={(e) => setFormData({ ...formData, temperature: parseFloat(e.target.value) })}
|
||||
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer"
|
||||
className="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer touch-pan-x"
|
||||
/>
|
||||
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
||||
<span>Precise (0)</span>
|
||||
<span>Balanced (0.5)</span>
|
||||
<span>Creative (1)</span>
|
||||
<span>Precise</span>
|
||||
<span>Balanced</span>
|
||||
<span>Creative</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Controls randomness in responses
|
||||
@@ -206,7 +209,7 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
type="text"
|
||||
value={formData.workdir}
|
||||
onChange={(e) => setFormData({ ...formData, workdir: e.target.value })}
|
||||
className="w-full px-3 py-2 bg-gray-900 border border-gray-700 rounded-lg text-white focus:outline-none focus:border-blue-500 font-mono text-sm"
|
||||
className="w-full px-3 py-3 md:py-2 bg-gray-900 border border-gray-700 rounded-lg text-white text-base md:text-sm focus:outline-none focus:border-blue-500 font-mono"
|
||||
placeholder="/path/to/project"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
@@ -218,16 +221,16 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
{config && (
|
||||
<div className="pt-4 border-t border-gray-700">
|
||||
<h3 className="text-sm font-medium text-gray-400 mb-3">Server Information</h3>
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex justify-between md:block">
|
||||
<span className="text-gray-500">Allowed Paths:</span>
|
||||
<span className="ml-2 text-gray-300">
|
||||
<span className="md:ml-2 text-gray-300">
|
||||
{config.allowedPaths.length || 'All'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div className="flex justify-between md:block">
|
||||
<span className="text-gray-500">Denied Paths:</span>
|
||||
<span className="ml-2 text-gray-300">
|
||||
<span className="md:ml-2 text-gray-300">
|
||||
{config.deniedPaths.length || 'None'}
|
||||
</span>
|
||||
</div>
|
||||
@@ -236,24 +239,24 @@ export function ConfigPanel({ onClose }: ConfigPanelProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-700 bg-gray-800/50">
|
||||
{/* Footer - 移动端固定在底部 */}
|
||||
<div className="sticky bottom-0 flex flex-col-reverse md:flex-row items-stretch md:items-center justify-end gap-2 md:gap-3 p-4 md:px-6 md:py-4 border-t border-gray-700 bg-gray-800 safe-area-pb">
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className="px-4 py-2 text-sm text-gray-300 hover:text-white transition-colors"
|
||||
className="px-4 py-3 md:py-2 text-sm text-gray-300 hover:text-white active:bg-gray-700 rounded-lg transition-colors"
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 text-sm bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors"
|
||||
className="px-4 py-3 md:py-2 text-sm bg-gray-700 hover:bg-gray-600 active:bg-gray-500 rounded-lg transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-4 py-2 text-sm bg-blue-600 hover:bg-blue-500 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
className="px-4 py-3 md:py-2 text-sm bg-blue-600 hover:bg-blue-500 active:bg-blue-700 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed font-medium"
|
||||
>
|
||||
{saving ? 'Saving...' : 'Save Changes'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user