refactor: 对话输入框响应式布局重构,支持水平/垂直布局自动切换
This commit is contained in:
61
README.md
61
README.md
@@ -446,4 +446,63 @@ api.logs.filter({ user, type, status });
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2026-03-30*
|
||||
## 对话输入框布局
|
||||
|
||||
### 布局结构
|
||||
|
||||
对话输入框采用响应式水平布局,根据屏幕宽度自动调整组件排列方式。
|
||||
|
||||
**桌面端(>= 768px)**:
|
||||
- 水平布局:模型选择器 → 输入区域 → 工具按钮 → 发送按钮
|
||||
- 模型选择器:标准模式(显示完整模型名称,宽度 160px)
|
||||
- 分隔方式:右侧垂直边框
|
||||
|
||||
**平板端(480-768px)**:
|
||||
- 水平布局:模型选择器 → 输入区域 → 工具按钮 → 发送按钮
|
||||
- 模型选择器:紧凑模式(仅显示图标,宽度 100px)
|
||||
- 工具按钮:上传文件、代码块
|
||||
- 分隔方式:右侧垂直边框
|
||||
|
||||
**移动端(< 480px)**:
|
||||
- 垂直布局:模型选择器在上,输入区域在下
|
||||
- 模型选择器:标准模式(显示完整模型名称,宽度 160px)
|
||||
- 分隔方式:底部水平边框
|
||||
|
||||
### ModelSelector 组件
|
||||
|
||||
ModelSelector 组件支持 `variant` 属性,控制显示模式:
|
||||
|
||||
```jsx
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onSelectModel={setSelectedModel}
|
||||
variant="standard" // 或 "compact"
|
||||
/>
|
||||
```
|
||||
|
||||
**variant 属性值**:
|
||||
- `standard`:标准模式,显示完整模型名称
|
||||
- `compact`:紧凑模式,仅显示图标
|
||||
|
||||
### 响应式断点
|
||||
|
||||
| 断点名称 | 屏幕宽度 | 布局模式 | 模型选择器 | 宽度 |
|
||||
|---------|---------|---------|-----------|------|
|
||||
| 桌面端 | >= 768px | 水平 | 标准 | 160px |
|
||||
| 平板端 | 480-768px | 水平 | 紧凑 | 100px |
|
||||
| 移动端 | < 480px | 垂直 | 标准 | 160px |
|
||||
|
||||
### 工具按钮
|
||||
|
||||
- 工具按钮横向排列在输入区域右侧(水平布局)或下方(垂直布局)
|
||||
- 当前工具按钮:上传文件、代码块
|
||||
- 工具按钮右侧显示分隔边框(水平布局)
|
||||
|
||||
### 下拉菜单方向
|
||||
|
||||
- 默认向下展开
|
||||
- 如果下方空间不足,自动向上展开
|
||||
|
||||
---
|
||||
|
||||
*最后更新:2026-04-10*
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 融合式模型选择器触发器
|
||||
### Requirement: 独立式模型选择器触发器
|
||||
|
||||
系统 SHALL 在对话输入框的顶部集成模型选择器触发器,显示当前选中的模型名称和默认标记。
|
||||
系统 SHALL 在对话输入框的左侧集成模型选择器触发器,显示当前选中的模型名称。
|
||||
|
||||
#### Scenario: 显示模型选择器触发器
|
||||
- **WHEN** 用户访问聊天页面
|
||||
- **THEN** 系统在聊天输入框顶部显示模型选择器触发器
|
||||
- **THEN** 触发器显示模型图标、模型名称、默认标记(如果适用)和下拉箭头
|
||||
- **THEN** 触发器与输入框融合为一个整体
|
||||
- **THEN** 系统在聊天输入框左侧显示模型选择器触发器
|
||||
- **THEN** 触发器显示模型图标、模型名称和下拉箭头
|
||||
- **THEN** 触发器作为独立组件显示在输入框左侧
|
||||
|
||||
#### Scenario: 显示默认标记
|
||||
- **WHEN** 当前选中的模型是默认模型(`isDefault = true`)
|
||||
@@ -30,6 +30,16 @@
|
||||
- **THEN** 每个分组显示分组标题和该层级的模型列表
|
||||
- **THEN** 展开时有平滑的动画效果
|
||||
|
||||
#### Scenario: 默认向下展开
|
||||
- **WHEN** 用户点击模型选择器触发器且下方空间充足(> 350px)
|
||||
- **THEN** 下拉列表向下展开
|
||||
- **THEN** 下拉列表显示在触发器下方
|
||||
|
||||
#### Scenario: 向上展开
|
||||
- **WHEN** 触发器下方空间不足以显示完整下拉列表(< 350px)且上方空间充足
|
||||
- **THEN** 下拉列表向上展开
|
||||
- **THEN** 下拉列表显示在触发器上方
|
||||
|
||||
#### Scenario: 分组标题显示
|
||||
- **WHEN** 下拉列表展开
|
||||
- **THEN** 平台模型分组显示标题"📍 平台模型"
|
||||
@@ -88,17 +98,87 @@
|
||||
|
||||
### Requirement: 模型选择器样式
|
||||
|
||||
系统 SHALL 使用融合式设计,模型选择器与输入框风格一致,圆角、边框、阴影统一。
|
||||
系统 SHALL 支持标准显示和紧凑显示两种模式,在水平布局中与输入框风格协调。
|
||||
|
||||
#### Scenario: 融合式设计
|
||||
- **WHEN** 模型选择器展开
|
||||
- **THEN** 触发器和下拉列表形成一个整体
|
||||
- **THEN** 触发器使用浅灰色背景,圆角顶部 16px
|
||||
- **THEN** 下拉列表使用白色背景,圆角底部 12px
|
||||
- **THEN** 边框颜色和阴影与输入框一致
|
||||
#### Scenario: 标准显示模式
|
||||
- **WHEN** 模型选择器使用标准模式(桌面端)
|
||||
- **THEN** 触发器使用最小宽度 160px
|
||||
- **THEN** 触发器右侧显示垂直分隔边框
|
||||
- **THEN** 触发器和输入框形成水平排列
|
||||
|
||||
#### Scenario: 紧凑显示模式
|
||||
- **WHEN** 模型选择器使用紧凑模式(平板端)
|
||||
- **THEN** 触发器使用最小宽度 120px
|
||||
- **THEN** 触发器显示为图标型(仅显示模型图标,隐藏模型名称)
|
||||
- **THEN** 触发器右侧显示垂直分隔边框
|
||||
|
||||
#### Scenario: 响应式设计
|
||||
- **WHEN** 用户使用移动设备访问
|
||||
- **THEN** 模型选择器自适应屏幕宽度
|
||||
- **THEN** 触发器使用较小的内边距和字体大小
|
||||
- **THEN** 下拉列表最大高度调整为 280px
|
||||
- **WHEN** 用户使用桌面设备访问(屏幕宽度 >= 768px)
|
||||
- **THEN** 模型选择器使用标准显示模式
|
||||
- **THEN** 触发器显示模型图标和完整模型名称
|
||||
- **THEN** 触发器宽度为 160px
|
||||
|
||||
- **WHEN** 用户使用平板设备访问(屏幕宽度 480-768px)
|
||||
- **THEN** 模型选择器使用紧凑显示模式
|
||||
- **THEN** 触发器显示为图标型(隐藏模型名称)
|
||||
- **THEN** 触发器宽度为 100px
|
||||
|
||||
- **WHEN** 用户使用移动设备访问(屏幕宽度 < 480px)
|
||||
- **THEN** 模型选择器回退到垂直布局
|
||||
- **THEN** 触发器位于输入框上方
|
||||
- **THEN** 触发器显示模型图标和完整模型名称
|
||||
- **THEN** 触发器宽度为 160px
|
||||
|
||||
### Requirement: 紧凑型模型选择器
|
||||
|
||||
系统 SHALL 支持紧凑型显示模式,通过 `variant` 属性控制。
|
||||
|
||||
#### Scenario: 启用紧凑模式
|
||||
- **WHEN** 模型选择器组件接收 `variant="compact"` 属性
|
||||
- **THEN** 触发器应用紧凑型样式
|
||||
- **THEN** 触发器最小宽度为 120px
|
||||
- **THEN** 触发器显示模型图标和下拉箭头,隐藏模型名称
|
||||
|
||||
#### Scenario: 紧凑模式下的下拉展开
|
||||
- **WHEN** 用户点击紧凑型触发器
|
||||
- **THEN** 系统展开下拉列表
|
||||
- **THEN** 下拉列表使用固定定位(position: fixed)
|
||||
- **THEN** 下拉列表根据触发器位置和可用空间自动调整展开方向
|
||||
|
||||
### Requirement: 水平布局输入框
|
||||
|
||||
系统 SHALL 支持水平布局的对话输入框,模型选择器位于输入框左侧。
|
||||
|
||||
#### Scenario: 桌面端水平布局
|
||||
- **WHEN** 用户在桌面端访问(屏幕宽度 > 768px)
|
||||
- **THEN** 输入框容器使用水平 flex 布局
|
||||
- **THEN** 从左到右依次显示:模型选择器、输入区域、工具按钮组、发送按钮
|
||||
- **THEN** 模型选择器和输入框之间显示垂直分隔边框
|
||||
|
||||
#### Scenario: 平板端水平布局
|
||||
- **WHEN** 用户在平板端访问(屏幕宽度 480-768px)
|
||||
- **THEN** 输入框容器使用水平 flex 布局
|
||||
- **THEN** 从左到右依次显示:紧凑型模型选择器、输入区域、工具按钮组、发送按钮
|
||||
- **THEN** 工具按钮组限制最多显示 4 个按钮
|
||||
- **THEN** 紧凑型模型选择器和输入框之间显示垂直分隔边框
|
||||
|
||||
#### Scenario: 移动端垂直布局
|
||||
- **WHEN** 用户在移动端访问(屏幕宽度 < 480px)
|
||||
- **THEN** 输入框容器使用垂直 flex 布局
|
||||
- **THEN** 从上到下依次显示:模型选择器、输入区域、工具按钮组、发送按钮
|
||||
- **THEN** 模型选择器和输入框之间显示水平分隔边框
|
||||
|
||||
### Requirement: 工具按钮优化
|
||||
|
||||
系统 SHALL 在水平布局中优化工具按钮的显示方式。
|
||||
|
||||
#### Scenario: 工具按钮横向排列
|
||||
- **WHEN** 输入框使用水平布局(桌面端/平板端)
|
||||
- **THEN** 工具按钮横向排列在输入区域右侧
|
||||
- **THEN** 工具按钮限制最多显示 4 个
|
||||
- **THEN** 超出 4 个的按钮不显示
|
||||
|
||||
#### Scenario: 工具按钮纵向排列
|
||||
- **WHEN** 输入框使用垂直布局(移动端)
|
||||
- **THEN** 工具按钮纵向排列在输入区域下方
|
||||
- **THEN** 工具按钮横向排列,限制最多显示 4 个
|
||||
|
||||
110
openspec/specs/responsive-chat-input/spec.md
Normal file
110
openspec/specs/responsive-chat-input/spec.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# Capability: responsive-chat-input
|
||||
|
||||
## Purpose
|
||||
|
||||
系统 SHALL 支持响应式对话输入框布局,根据屏幕宽度自动切换水平/垂直布局模式,优化在不同设备上的用户体验。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 响应式布局切换
|
||||
|
||||
系统 SHALL 根据屏幕宽度自动切换对话输入框的布局模式。
|
||||
|
||||
#### Scenario: 桌面端水平布局
|
||||
- **WHEN** 用户在桌面端访问(屏幕宽度 >= 768px)
|
||||
- **THEN** 系统应用水平布局模式
|
||||
- **THEN** 输入框容器使用 `flex-direction: row`
|
||||
- **THEN** 组件从左到右依次排列:模型选择器、输入区域、工具按钮组、发送按钮
|
||||
|
||||
#### Scenario: 平板端水平布局
|
||||
- **WHEN** 用户在平板端访问(屏幕宽度 480-768px)
|
||||
- **THEN** 系统应用水平布局模式
|
||||
- **THEN** 输入框容器使用 `flex-direction: row`
|
||||
- **THEN** 模型选择器使用紧凑型显示
|
||||
- **THEN** 工具按钮组显示当前可用的工具按钮
|
||||
|
||||
#### Scenario: 移动端垂直布局
|
||||
- **WHEN** 用户在移动端访问(屏幕宽度 < 480px)
|
||||
- **THEN** 系统应用垂直布局模式
|
||||
- **THEN** 输入框容器使用 `flex-direction: column`
|
||||
- **THEN** 组件从上到下依次排列:模型选择器、输入区域、工具按钮组、发送按钮
|
||||
- **THEN** 模型选择器使用标准显示模式
|
||||
|
||||
### Requirement: 模型选择器布局适配
|
||||
|
||||
系统 SHALL 在不同布局模式下调整模型选择器的显示样式。
|
||||
|
||||
#### Scenario: 水平布局中的模型选择器
|
||||
- **WHEN** 输入框使用水平布局(桌面端/平板端)
|
||||
- **THEN** 模型选择器位于输入框左侧
|
||||
- **THEN** 模型选择器右侧显示垂直分隔边框(1px solid)
|
||||
- **THEN** 桌面端(>= 768px)使用标准显示模式(宽度 160px)
|
||||
- **THEN** 平板端(480-768px)使用紧凑显示模式(宽度 100px,图标型)
|
||||
|
||||
#### Scenario: 垂直布局中的模型选择器
|
||||
- **WHEN** 输入框使用垂直布局(移动端)
|
||||
- **THEN** 模型选择器位于输入框上方
|
||||
- **THEN** 模型选择器底部显示水平分隔边框(1px solid)
|
||||
- **THEN** 使用标准显示模式(显示完整模型名称)
|
||||
|
||||
### Requirement: 输入区域布局适配
|
||||
|
||||
系统 SHALL 在不同布局模式下调整输入区域的尺寸和样式。
|
||||
|
||||
#### Scenario: 水平布局中的输入区域
|
||||
- **WHEN** 输入框使用水平布局(桌面端/平板端)
|
||||
- **THEN** 输入区域占据剩余水平空间(`flex: 1`)
|
||||
- **THEN** 输入区域高度固定或使用自动高度
|
||||
|
||||
#### Scenario: 垂直布局中的输入区域
|
||||
- **WHEN** 输入框使用垂直布局(移动端)
|
||||
- **THEN** 输入区域占据垂直空间,高度自适应内容
|
||||
- **THEN** 输入区域宽度占满容器宽度
|
||||
|
||||
### Requirement: 工具按钮组布局适配
|
||||
|
||||
系统 SHALL 在不同布局模式下调整工具按钮组的排列方式。
|
||||
|
||||
#### Scenario: 水平布局中的工具按钮
|
||||
- **WHEN** 输入框使用水平布局(桌面端/平板端)
|
||||
- **THEN** 工具按钮组横向排列在输入区域右侧
|
||||
- **THEN** 工具按钮包括:上传文件、代码块
|
||||
- **THEN** 每个按钮使用图标型显示
|
||||
- **THEN** 工具按钮右侧使用垂直分隔边框(1px solid)
|
||||
|
||||
#### Scenario: 垂直布局中的工具按钮
|
||||
- **WHEN** 输入框使用垂直布局(移动端)
|
||||
- **THEN** 工具按钮组横向排列在输入区域下方
|
||||
- **THEN** 工具按钮包括:上传文件、代码块
|
||||
- **THEN** 每个按钮使用图标型显示
|
||||
- **THEN** 工具按钮组不显示右侧分隔边框
|
||||
|
||||
### Requirement: 发送按钮布局适配
|
||||
|
||||
系统 SHALL 在不同布局模式下调整发送按钮的位置和样式。
|
||||
|
||||
#### Scenario: 水平布局中的发送按钮
|
||||
- **WHEN** 输入框使用水平布局(桌面端/平板端)
|
||||
- **THEN** 发送按钮位于工具按钮组右侧
|
||||
- **THEN** 发送按钮与工具按钮组之间保持适当间距
|
||||
|
||||
#### Scenario: 垂直布局中的发送按钮
|
||||
- **WHEN** 输入框使用垂直布局(移动端)
|
||||
- **THEN** 发送按钮位于工具按钮组下方或与工具按钮组并排
|
||||
- **THEN** 发送按钮占据合适宽度
|
||||
|
||||
### Requirement: 视觉分隔适配
|
||||
|
||||
系统 SHALL 在不同布局模式下使用适当的视觉分隔方式。
|
||||
|
||||
#### Scenario: 水平布局中的垂直分隔
|
||||
- **WHEN** 输入框使用水平布局
|
||||
- **THEN** 模型选择器和输入框之间显示垂直分隔边框
|
||||
- **THEN** 分隔边框颜色与输入框边框一致
|
||||
- **THEN** 分隔边框宽度为 1px
|
||||
|
||||
#### Scenario: 垂直布局中的水平分隔
|
||||
- **WHEN** 输入框使用垂直布局
|
||||
- **THEN** 模型选择器和输入框之间显示水平分隔边框
|
||||
- **THEN** 分隔边框颜色与输入框边框一致
|
||||
- **THEN** 分隔边框宽度为 1px
|
||||
@@ -4,8 +4,9 @@ import { getChatScenes } from '../../data/conversations.js';
|
||||
import { FiPaperclip, FiCode, FiSend, FiChevronDown } from 'react-icons/fi';
|
||||
import { api } from '../../services/api.js';
|
||||
|
||||
function ModelSelector({ selectedModel, onSelectModel }) {
|
||||
function ModelSelector({ selectedModel, onSelectModel, variant = 'standard' }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [dropdownDirection, setDropdownDirection] = useState('down');
|
||||
const ref = useRef(null);
|
||||
|
||||
const platformModels = api.admin.modelConfigs.list().map(c => ({
|
||||
@@ -45,13 +46,49 @@ function ModelSelector({ selectedModel, onSelectModel }) {
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && ref.current) {
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const dropdownHeight = 350;
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
|
||||
if (spaceBelow < dropdownHeight && rect.top > dropdownHeight) {
|
||||
setDropdownDirection('up');
|
||||
} else {
|
||||
setDropdownDirection('down');
|
||||
}
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const handleSelect = (model) => {
|
||||
onSelectModel(model);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const getDropdownPosition = () => {
|
||||
if (!ref.current) return {};
|
||||
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const dropdownHeight = 350;
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
|
||||
if (dropdownDirection === 'down') {
|
||||
return {
|
||||
top: rect.bottom,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
bottom: window.innerHeight - rect.top,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`model-selector ${open ? 'model-selector--open' : ''}`} ref={ref}>
|
||||
<div className={`model-selector ${open ? 'model-selector--open' : ''} ${variant === 'compact' ? 'model-selector--compact' : ''}`} ref={ref}>
|
||||
<div
|
||||
className="model-selector__trigger"
|
||||
onClick={() => setOpen(!open)}
|
||||
@@ -60,9 +97,8 @@ function ModelSelector({ selectedModel, onSelectModel }) {
|
||||
<span className="model-selector__icon">
|
||||
<FiCode size={12} />
|
||||
</span>
|
||||
<span className="model-selector__name">{selectedModel?.name || '选择模型'}</span>
|
||||
{selectedModel?.isDefault && (
|
||||
<span className="model-selector__tag">默认</span>
|
||||
{variant !== 'compact' && (
|
||||
<span className="model-selector__name">{selectedModel?.name || '选择模型'}</span>
|
||||
)}
|
||||
</div>
|
||||
<span className={`model-selector__arrow ${open ? 'model-selector__arrow--open' : ''}`}>
|
||||
@@ -71,23 +107,23 @@ function ModelSelector({ selectedModel, onSelectModel }) {
|
||||
</div>
|
||||
|
||||
{open && (
|
||||
<div className="model-selector__dropdown">
|
||||
{groups.map(group => (
|
||||
<div key={group.key} className="model-selector__group">
|
||||
<div className="model-selector__group-title">{group.title}</div>
|
||||
{group.models.map(model => (
|
||||
<div
|
||||
key={model.id}
|
||||
className={`model-selector__item ${selectedModel?.id === model.id ? 'model-selector__item--selected' : ''}`}
|
||||
onClick={() => handleSelect(model)}
|
||||
>
|
||||
<span className="model-selector__item-text">{model.name}</span>
|
||||
{model.isDefault && (
|
||||
<span className="model-selector__item-tag">默认</span>
|
||||
)}
|
||||
<div
|
||||
className={`model-selector__dropdown model-selector__dropdown--${dropdownDirection}`}
|
||||
style={getDropdownPosition()}
|
||||
>
|
||||
{groups.map(group => (
|
||||
<div key={group.key} className="model-selector__group">
|
||||
<div className="model-selector__group-title">{group.title}</div>
|
||||
{group.models.map(model => (
|
||||
<div
|
||||
key={model.id}
|
||||
className={`model-selector__item ${selectedModel?.id === model.id ? 'model-selector__item--selected' : ''}`}
|
||||
onClick={() => handleSelect(model)}
|
||||
>
|
||||
<span className="model-selector__item-text">{model.name}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -101,6 +137,7 @@ function ChatPage() {
|
||||
const chatScenes = getChatScenes();
|
||||
const html = chatScenes[currentScene] || '';
|
||||
const chatMessagesRef = useRef(null);
|
||||
const [isCompact, setIsCompact] = useState(false);
|
||||
|
||||
const defaultPlatformModel = api.admin.modelConfigs.list().find(c => c.isActive);
|
||||
const defaultProjectModel = api.consoleModels.project.list().find(c => c.isActive);
|
||||
@@ -135,6 +172,16 @@ function ChatPage() {
|
||||
};
|
||||
}, [scene, html]);
|
||||
|
||||
useEffect(() => {
|
||||
const checkScreenSize = () => {
|
||||
setIsCompact(window.innerWidth >= 480 && window.innerWidth < 768);
|
||||
};
|
||||
|
||||
checkScreenSize();
|
||||
window.addEventListener('resize', checkScreenSize);
|
||||
return () => window.removeEventListener('resize', checkScreenSize);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="chat-layout">
|
||||
<div className="chat-content">
|
||||
@@ -143,10 +190,11 @@ function ChatPage() {
|
||||
</div>
|
||||
<div className="chat-input-wrapper">
|
||||
<div className="chat-input-container">
|
||||
<div className="chat-input-box">
|
||||
<div className="chat-input-box chat-input-box--horizontal">
|
||||
<ModelSelector
|
||||
selectedModel={selectedModel}
|
||||
onSelectModel={setSelectedModel}
|
||||
variant={isCompact ? 'compact' : 'standard'}
|
||||
/>
|
||||
<div className="chat-input-main">
|
||||
<textarea
|
||||
|
||||
@@ -208,11 +208,18 @@
|
||||
.chat-input-box {
|
||||
border: 1px solid var(--color-border-3);
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
background: var(--color-bg-1);
|
||||
transition: all var(--transition);
|
||||
box-shadow: 0 2px 12px rgba(15, 23, 42, 0.04);
|
||||
|
||||
&--horizontal {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
padding: 8px 12px 8px 8px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #CBD5E1;
|
||||
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.06);
|
||||
@@ -226,21 +233,27 @@
|
||||
|
||||
.chat-input-main {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px 10px 14px;
|
||||
padding: 0;
|
||||
flex: 1;
|
||||
|
||||
@include chat-mobile {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
flex: 1;
|
||||
padding: 6px 2px;
|
||||
padding: 0 2px;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 15px;
|
||||
resize: none;
|
||||
min-height: 24px;
|
||||
height: 28px;
|
||||
max-height: 200px;
|
||||
line-height: 1.6;
|
||||
line-height: 28px;
|
||||
background: transparent;
|
||||
color: var(--color-text-1);
|
||||
|
||||
@@ -254,6 +267,11 @@
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
|
||||
@include chat-mobile {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-tools {
|
||||
@@ -261,6 +279,10 @@
|
||||
gap: 2px;
|
||||
padding-right: 6px;
|
||||
border-right: 1px solid var(--color-border-2);
|
||||
|
||||
@include chat-mobile {
|
||||
border-right: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-tool {
|
||||
@@ -876,3 +898,24 @@
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// 聊天输入框响应式布局
|
||||
@include chat-mobile {
|
||||
.chat-input-box {
|
||||
&--horizontal {
|
||||
flex-direction: column;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-selector {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
|
||||
.model-selector__trigger {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--color-border-2);
|
||||
border-radius: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,28 @@
|
||||
|
||||
.model-selector {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
margin-right: 8px;
|
||||
|
||||
&--open {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
&__dropdown {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
width: 100px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:not(.model-selector--compact) {
|
||||
width: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.model-selector--open {
|
||||
@@ -21,17 +42,26 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: $spacing-2;
|
||||
padding: 6px $spacing-3;
|
||||
padding: 0 10px;
|
||||
background: var(--color-bg-2);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--color-border-2);
|
||||
border-right: 1px solid var(--color-border-2);
|
||||
border-radius: 16px;
|
||||
cursor: pointer;
|
||||
transition: background var(--transition);
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:hover {
|
||||
background: var(--color-bg-3);
|
||||
}
|
||||
|
||||
.model-selector--compact & {
|
||||
border-right: 1px solid var(--color-border-2);
|
||||
border-radius: 16px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.model-selector__content {
|
||||
@@ -40,6 +70,7 @@
|
||||
gap: $spacing-2;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.model-selector__icon {
|
||||
@@ -82,19 +113,16 @@
|
||||
}
|
||||
|
||||
.model-selector__dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
position: fixed;
|
||||
background: var(--color-bg-1);
|
||||
border: 1px solid var(--color-primary);
|
||||
border-top: none;
|
||||
border-radius: 0 0 $radius-lg $radius-lg;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12), 0 2px 8px rgba(15, 23, 42, 0.06);
|
||||
overflow: hidden;
|
||||
animation: dropdownExpand 0.2s ease-out;
|
||||
max-height: 350px;
|
||||
overflow-y: auto;
|
||||
min-width: 160px;
|
||||
padding: 4px 0;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
@@ -197,7 +225,7 @@
|
||||
|
||||
@include mobile {
|
||||
.model-selector__trigger {
|
||||
padding: 4px $spacing-2;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.model-selector__name {
|
||||
|
||||
@@ -3,3 +3,8 @@
|
||||
$breakpoint-mobile: 768px;
|
||||
$breakpoint-tablet: 1024px;
|
||||
$breakpoint-desktop: 1025px;
|
||||
|
||||
// 聊天输入框断点
|
||||
$breakpoint-chat-mobile: 480px;
|
||||
$breakpoint-chat-tablet: 768px;
|
||||
$breakpoint-chat-desktop: 769px;
|
||||
|
||||
@@ -23,6 +23,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 聊天输入框响应式断点
|
||||
@mixin chat-mobile {
|
||||
@media (max-width: #{$breakpoint-chat-mobile}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin chat-tablet {
|
||||
@media (min-width: #{$breakpoint-chat-mobile + 1}) and (max-width: #{$breakpoint-chat-tablet}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin chat-desktop {
|
||||
@media (min-width: #{$breakpoint-chat-desktop}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
// 弹性布局
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user