ai chat wip

This commit is contained in:
Fam Zheng 2025-10-29 21:21:59 +00:00
parent afef3a9e11
commit 7d64eb480d
11 changed files with 927 additions and 61 deletions

4
doc/estor-hardening.md Normal file
View File

@ -0,0 +1,4 @@
- backup all estor data
- in new server test import all backup data
- switch to new new server
- test import

279
doc/todo.md Normal file
View File

@ -0,0 +1,279 @@
# AI Chat Implementation Action Plan
## 📋 Overview
This document outlines the remaining tasks to complete the AI chat functionality. The backend AI service, RAG system, API layer, and web interface are complete. Remaining work focuses on miniprogram integration and content management.
## 🎉 **Recent Progress Update**
**✅ COMPLETED (January 27, 2025):**
- **Web Chat Interface**: Vue component created (`web/src/views/ai-chat.vue`)
- **Router Integration**: Added `/ai-chat` route with lazy loading
- **Navigation Menu**: Added "AI 客服预览" to sidebar navigation
- **API Integration**: Full integration with `/api/v1/ai-chat/` endpoint
- **Responsive Design**: Mobile-friendly chat interface
- **Session Management**: Automatic session ID generation
- **Error Handling**: User-friendly error messages
- **Build Success**: Component compiled successfully (3.23 KiB)
**📝 Web Interface Ready:**
- **URL**: `/ai-chat` in web application
- **Features**: Real-time chat, session management, responsive design
- **Testing**: Perfect for development and stakeholder demos
## 🎯 Current Status Summary
- ✅ **Web Chat Interface**: Complete (`web/src/views/ai-chat.vue`)
- ❌ **Miniprogram Integration**: No API calls to backend
- ❌ **Content Management**: No admin interface
## 🌐 Phase 1.5: Web Chat Interface ✅ **COMPLETED**
### Task 1.5: Create Web Chat Interface for Testing ✅ **COMPLETED**
**File**: `web/src/views/ai-chat.vue`
**Estimated Time**: 2-3 hours
**Purpose**: Easy testing and verification of AI chat functionality without WeChat miniprogram
**Benefits**:
- 🧪 **Development Testing**: Test AI responses without WeChat setup
- 🔍 **Debugging**: Easy to inspect API calls and responses
- 👥 **Demo**: Show AI chat functionality to stakeholders
- 🚀 **Rapid Iteration**: Quick feedback loop for AI improvements
- 📱 **Cross-Platform**: Works on any device with a browser
**Action Items**:
- [x] Create Vue component for AI chat interface
- [x] Add message input and send functionality
- [x] Display chat messages (user vs AI)
- [x] Add session management
- [x] Handle API responses and errors
- [x] Add loading states and user feedback
- [x] Style chat interface
- [x] Add to web application routing
**API Integration**:
```javascript
// Example API call
const response = await fetch('/api/v1/ai-chat/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: userInput,
chat_type: 'platform',
session_id: sessionId
})
});
```
## 🔗 Phase 2: Miniprogram Integration (Priority: HIGH)
### Task 2.1: Update Chat Page JavaScript
**File**: `scanner/pages/chat/chat.js`
**Estimated Time**: 3-4 hours
**Action Items**:
- [ ] Add API call functions to backend
- [ ] Implement real-time message sending
- [ ] Add session management
- [ ] Handle API responses and errors
- [ ] Add loading states and user feedback
### Task 2.2: Enhance Chat UI
**File**: `scanner/pages/chat/chat.wxml`
**Estimated Time**: 2 hours
**Action Items**:
- [ ] Add input field for user messages
- [ ] Add send button
- [ ] Improve message display layout
- [ ] Add typing indicators
### Task 2.3: Add Chat Styling
**File**: `scanner/pages/chat/chat.wxss`
**Estimated Time**: 1-2 hours
**Action Items**:
- [ ] Style chat messages (user vs AI)
- [ ] Add responsive design
- [ ] Style input area
- [ ] Add animations for new messages
## 🎨 Phase 3: Content Management (Priority: MEDIUM)
### Task 3.1: Create Admin Interface
**File**: `api/products/admin.py`
**Estimated Time**: 2-3 hours
**Action Items**:
- [ ] Add ChatSession admin interface
- [ ] Add ChatMessage admin interface
- [ ] Add Article management for knowledge base
- [ ] Add search and filtering capabilities
### Task 3.2: Knowledge Base Management
**Estimated Time**: 2 hours
**Action Items**:
- [ ] Create management commands for knowledge base
- [ ] Add bulk import functionality
- [ ] Add content validation
- [ ] Add search and indexing tools
## 🔧 Phase 4: Advanced Features (Priority: LOW)
### Task 4.1: WebSocket Support
**Estimated Time**: 4-5 hours
**Action Items**:
- [ ] Install Django Channels
- [ ] Create WebSocket consumer for real-time chat
- [ ] Update miniprogram to use WebSocket
- [ ] Add connection management
### Task 4.2: Multi-modal Support
**Estimated Time**: 3-4 hours
**Action Items**:
- [ ] Add image upload support
- [ ] Add file attachment handling
- [ ] Update AI service for multi-modal responses
- [ ] Add media display in miniprogram
### Task 4.3: Brand Customization
**Estimated Time**: 2-3 hours
**Action Items**:
- [ ] Add tenant-specific theming
- [ ] Add custom AI prompts per tenant
- [ ] Add brand-specific knowledge bases
- [ ] Add custom response templates
## 📝 Implementation Checklist
### Phase 1.5: Web Chat Interface ✅ **COMPLETED**
- [x] **Day 1**: Create Vue component for AI chat interface
- [x] **Day 2**: Add API integration and message handling
- [x] **Day 3**: Style chat interface and add routing
- [x] **Day 4**: Testing and debugging
- [x] **Day 5**: Polish and optimization
### Phase 2: Miniprogram Integration (Week 2)
- [ ] **Day 1**: Update `chat.js` with API integration
- [ ] **Day 2**: Enhance chat UI and user experience
- [ ] **Day 3**: Add real-time messaging capabilities
- [ ] **Day 4**: Testing and debugging
- [ ] **Day 5**: Polish and optimization
### Phase 3: Content Management (Week 3)
- [ ] **Day 1**: Create admin interfaces
- [ ] **Day 2**: Add knowledge base management
- [ ] **Day 3**: Add bulk import/export features
- [ ] **Day 4**: Testing and validation
- [ ] **Day 5**: Documentation and training
### Phase 4: Advanced Features (Week 4)
- [ ] **Day 1**: WebSocket implementation
- [ ] **Day 2**: Multi-modal support
- [ ] **Day 3**: Brand customization
- [ ] **Day 4**: Performance optimization
- [ ] **Day 5**: Final testing and deployment
## 🧪 Testing Strategy
### Unit Tests
- [ ] Test AI chat service functions
- [ ] Test RAG knowledge base search
- [ ] Test API endpoints
- [ ] Test miniprogram API calls
### Integration Tests
- [ ] Test end-to-end chat flow
- [ ] Test session management
- [ ] Test error handling
- [ ] Test performance under load
### User Acceptance Tests
- [ ] Test chat functionality in miniprogram
- [ ] Test knowledge base responses
- [ ] Test multi-tenant isolation
- [ ] Test admin interface usability
## 📊 Success Metrics
### Technical Metrics
- [ ] API response time < 2 seconds
- [ ] Chat message delivery success rate > 99%
- [ ] Knowledge base search accuracy > 90%
- [ ] System uptime > 99.9%
### User Experience Metrics
- [ ] Chat interface loads in < 3 seconds
- [ ] Message sending feels instant
- [ ] AI responses are relevant and helpful
- [ ] Admin interface is intuitive
## 🚨 Risk Mitigation
### Technical Risks
- **API Rate Limits**: Implement caching and rate limiting
- **WebSocket Connections**: Add connection pooling and reconnection logic
- **Knowledge Base Performance**: Optimize vector search and caching
- **Miniprogram Limitations**: Test on various devices and WeChat versions
### Business Risks
- **User Adoption**: Ensure smooth user experience
- **Content Quality**: Implement content validation and moderation
- **Scalability**: Plan for increased usage and data growth
- **Security**: Implement proper authentication and data protection
## 📚 Resources and Dependencies
### Required Libraries
- [ ] Django Channels (for WebSocket)
- [ ] Celery (for background tasks)
- [ ] Redis (for caching and sessions)
- [ ] WeChat Miniprogram SDK
### External Services
- [ ] Moonshot Kimi K2 API (already configured)
- [ ] OSS storage for file uploads
- [ ] CDN for static assets
### Development Tools
- [ ] WeChat Developer Tools
- [ ] Postman for API testing
- [ ] Django Debug Toolbar
- [ ] Performance monitoring tools
## 🎯 Quick Start Guide
### For Developers
1. **Start with Web Interface**: Create Vue component for AI chat testing
2. **Test thoroughly**: Use the existing management command to verify AI service
3. **Incremental development**: Build and test each component separately
4. **Use existing code**: Leverage the complete `AIChatService` and RAG system
### For Testing
1. **Use management command**: `python manage.py chat` to test AI functionality
2. **Test RAG system**: Use `test-rag` command in chat interface
3. **Test API endpoints**: Use Postman or curl to test `/api/v1/ai-chat/` endpoint
## 📞 Support and Maintenance
### Documentation
- [ ] API documentation
- [ ] Miniprogram integration guide
- [ ] Admin user manual
- [ ] Troubleshooting guide
### Monitoring
- [ ] API performance monitoring
- [ ] Error tracking and alerting
- [ ] User usage analytics
- [ ] System health checks
---
**Next Action**: Start with Phase 2, Task 2.1 - Update `scanner/pages/chat/chat.js` with API integration

View File

@ -54,7 +54,6 @@ Component({
} }
}, },
update_visible_content() { update_visible_content() {
console.log('update_visible_content', this.data.next_index, this.properties.content.length);
if (this.data.next_index >= this.properties.content.length) { if (this.data.next_index >= this.properties.content.length) {
clearInterval(this.update_interval); clearInterval(this.update_interval);
return; return;

View File

@ -3,6 +3,8 @@ import {
goto_camera, goto_camera,
} from '../../utils.js' } from '../../utils.js'
const app = getApp();
Page({ Page({
/** /**
@ -12,25 +14,18 @@ Page({
show_modal: '', show_modal: '',
prompt: [], prompt: [],
response: [], response: [],
messages: [], // Store chat history
currentMessage: '', // Current input
isLoading: false, // Loading state
sessionId: null, // Session ID for chat
welcome_response: [ welcome_response: [
{ {
type: 'text', type: 'text',
content: '欢迎使用徵象AI,让每一次验证都成为与品牌的深度对话。' content: '欢迎使用徵象AI客服!我是您的智能防伪验证助手,可以帮您解答关于产品验证、防伪技术等问题。'
}, },
{ {
type: 'text', type: 'text',
content: '为保障您的权益,本系统采用多模态AI深度鉴真引擎,通过多重校验机制实现智能二维码防伪核验。' content: '您可以向我提问任何关于产品验证、防伪技术的问题或者点击下方按钮开启AI验证。'
},
{
type: 'text',
content: '请将手机摄像头对准产品智能二维码,'
},
{
type: 'scanguidecss',
},
{
type: 'text',
content: '保持画面完整覆盖定位点(图示区域), 系统将自动触发高精度图像分割算法完成验证。 '
}, },
], ],
prompt_message_verify_result: [ prompt_message_verify_result: [
@ -48,7 +43,12 @@ Page({
this.setData({ this.setData({
serial_code: options.serial_code, serial_code: options.serial_code,
verify_time: options.time || new Date().toLocaleString(), verify_time: options.time || new Date().toLocaleString(),
sessionId: this.generateSessionId(),
}); });
// Add welcome message to chat history
this.addMessageToHistory('assistant', '欢迎使用徵象AI客服我是您的智能防伪验证助手可以帮您解答关于产品验证、防伪技术等问题。请随时向我提问');
if (options.serial_code) { if (options.serial_code) {
this.show_verify_result(true); this.show_verify_result(true);
} else { } else {
@ -408,5 +408,128 @@ Page({
if (x) { if (x) {
x.restart_content_output(); x.restart_content_output();
} }
} },
// New methods for AI chat integration
generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substring(2, 11);
},
addMessageToHistory(role, content) {
const messages = this.data.messages;
messages.push({
role: role,
content: content,
timestamp: new Date(),
});
this.setData({
messages: messages,
});
},
onInputChange(e) {
this.setData({
currentMessage: e.detail.value,
});
},
async sendMessage() {
const message = this.data.currentMessage.trim();
if (!message || this.data.isLoading) {
return;
}
// Clear input
this.setData({
currentMessage: '',
isLoading: true,
});
// Add user message to history
this.addMessageToHistory('user', message);
// Show user message in UI
this.set_content({
prompt: [{
type: 'text',
content: message,
}],
response: [{
type: 'text',
content: '正在思考中...',
}],
});
try {
// Call AI chat API
const response = await this.callAIChatAPI(message);
// Add AI response to history
this.addMessageToHistory('assistant', response);
// Show AI response in UI
this.set_content({
prompt: [{
type: 'text',
content: message,
}],
response: [{
type: 'text',
content: response,
}],
});
} catch (error) {
console.error('AI Chat Error:', error);
const errorMessage = '抱歉,我遇到了一些技术问题。请稍后再试,或联系技术支持。';
this.addMessageToHistory('assistant', errorMessage);
this.set_content({
prompt: [{
type: 'text',
content: message,
}],
response: [{
type: 'text',
content: errorMessage,
}],
});
} finally {
this.setData({
isLoading: false,
});
}
},
callAIChatAPI(message) {
return new Promise((resolve, reject) => {
wx.request({
url: app.globalData.server_url + '/api/v1/ai-chat/',
method: 'POST',
data: {
message: message,
session_id: this.data.sessionId,
chat_type: 'platform',
},
header: {
'content-type': 'application/json',
},
success: (res) => {
if (res.statusCode === 200 && res.data.response) {
// Update session ID if provided
if (res.data.session_id) {
this.setData({
sessionId: res.data.session_id,
});
}
resolve(res.data.response);
} else {
reject(new Error(res.data.error || 'Unknown error'));
}
},
fail: (error) => {
reject(error);
}
});
});
},
}) })

View File

@ -13,11 +13,26 @@
人工客服 人工客服
</view> </view>
<servicemodal show_hide_button="1" bindhide="hide_modal" wx:if="{{ show_modal == 'service' }}" /> <servicemodal show_hide_button="1" bindhide="hide_modal" wx:if="{{ show_modal == 'service' }}" />
<view bindtap="goto_camera" class="quick-action">
AI验证
</view>
</view> </view>
<view class="inputbox" bindtap="goto_camera"> <view class="input-container">
<view class="placeholder">开启AI验证</view> <input
<view class="icon"> class="input-field"
<image src="/static/up-button.png" class="up"></image> placeholder="输入您的问题..."
value="{{ currentMessage }}"
bindinput="onInputChange"
confirm-type="send"
bindconfirm="sendMessage"
disabled="{{ isLoading }}"
/>
<view
class="send-button {{ currentMessage.length > 0 && !isLoading ? 'active' : '' }}"
bindtap="sendMessage"
>
<text wx:if="{{ !isLoading }}">发送</text>
<text wx:else>...</text>
</view> </view>
</view> </view>
</view> </view>

View File

@ -9,57 +9,64 @@
position: fixed; position: fixed;
bottom: 0; bottom: 0;
width: 750rpx; width: 750rpx;
height: 300rpx; background: white;
} padding-bottom: 20rpx;
.bottom .inputbox {
display: flex;
margin: 30rpx;
height: 100rpx;
border-radius: 60rpx;
background: #eee;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 20rpx;
}
.bottom .placeholder {
font-size: 30rpx;
flex: 1;
display: flex;
align-items: center;
padding-left: 30rpx;
}
.bottom .icon {
display: flex;
align-items: center;
justify-content: center;
}
.bottom .icon .up {
width: 60rpx;
height: 60rpx;
} }
.bottom .quick-actions { .bottom .quick-actions {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 0 40rpx; padding: 20rpx 40rpx 10rpx;
flex-wrap: wrap;
} }
.bottom .quick-action { .bottom .quick-action {
height: 68rpx; height: 60rpx;
font-size: 28rpx; font-size: 26rpx;
color: #ef4823; color: #ef4823;
background: #eee; background: #f5f5f5;
border-radius: 60rpx; border-radius: 40rpx;
padding: 0 28rpx; padding: 0 24rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-right: 20rpx; margin-right: 15rpx;
margin-bottom: 10rpx;
}
.bottom .input-container {
display: flex;
margin: 10rpx 30rpx 20rpx;
align-items: center;
background: #f5f5f5;
border-radius: 50rpx;
padding: 10rpx 20rpx;
}
.bottom .input-field {
flex: 1;
font-size: 28rpx;
padding: 10rpx 15rpx;
background: transparent;
}
.bottom .send-button {
min-width: 100rpx;
height: 60rpx;
background: #ddd;
color: #999;
border-radius: 40rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
margin-left: 15rpx;
transition: all 0.2s;
}
.bottom .send-button.active {
background: #4a90e2;
color: white;
} }
.hidden { .hidden {

View File

@ -2,12 +2,19 @@
"condition": { "condition": {
"miniprogram": { "miniprogram": {
"list": [ "list": [
{
"name": "pages/chat/chat",
"pathName": "pages/chat/chat",
"query": "",
"scene": null,
"launchMode": "default"
},
{ {
"name": "emblemscanner force camera", "name": "emblemscanner force camera",
"pathName": "pages/emblemscanner/emblemscanner", "pathName": "pages/emblemscanner/emblemscanner",
"query": "debug=1&no_web_view=1&return_page=/pages/test_result_page/test_result_page", "query": "debug=1&no_web_view=1&return_page=/pages/test_result_page/test_result_page",
"scene": null, "launchMode": "default",
"launchMode": "default" "scene": null
}, },
{ {
"name": "emblemscanner", "name": "emblemscanner",

View File

@ -25,6 +25,13 @@ export default [
to: '/articles', to: '/articles',
icon: 'folder-open', icon: 'folder-open',
}, },
{
component: 'CNavItem',
name: 'AI 客服预览',
to: '/ai-chat',
icon: 'comments',
target: '_blank',
},
{ {
component: 'CNavItem', component: 'CNavItem',
name: '文件管理', name: '文件管理',

View File

@ -333,6 +333,12 @@ const routes = [
}, },
], ],
}, },
{
path: '/ai-chat',
name: 'AIChat',
component: () =>
import(/* webpackChunkName: "ai-chat" */ '@/views/ai-chat.vue'),
},
] ]
const router = createRouter({ const router = createRouter({

419
web/src/views/ai-chat.vue Normal file
View File

@ -0,0 +1,419 @@
<template>
<div class="ai-chat-container">
<div class="chat-header">
<div class="header-content">
<div>
<h4>徵象 AI 客服</h4>
<p>智能防伪验证助手</p>
</div>
<div class="mode-selector">
<select v-model="selectedMode" @change="onModeChange" class="form-control">
<option value="platform">平台客服</option>
<option
v-for="product in products"
:key="`product-${product.id}`"
:value="`product-${product.id}`"
>
产品客服: {{ product.name }}
</option>
</select>
</div>
</div>
</div>
<div class="chat-messages" ref="chatMessages">
<div
v-for="message in messages"
:key="message.id"
:class="['message', message.role]"
>
<div class="message-content">
<div class="message-text">{{ message.content }}</div>
<div class="message-time">{{ formatTime(message.timestamp) }}</div>
</div>
</div>
<div v-if="isLoading" class="message assistant">
<div class="message-content">
<div class="message-text typing">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<div class="chat-input">
<div class="input-group">
<input
v-model="currentMessage"
@keyup.enter="sendMessage"
:disabled="isLoading"
placeholder="输入您的问题..."
class="form-control"
/>
<button
@click="sendMessage"
:disabled="!currentMessage.trim() || isLoading"
class="btn btn-primary send-btn"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</button>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'AIChat',
data() {
return {
messages: [],
currentMessage: '',
isLoading: false,
sessionId: null,
messageId: 0,
selectedMode: 'platform',
products: []
}
},
mounted() {
this.initializeChat()
this.fetchProducts()
},
methods: {
initializeChat() {
// Generate a new session ID
this.sessionId = this.generateSessionId()
// Add welcome message
this.addMessage('assistant', '欢迎使用徵象AI客服我是您的智能防伪验证助手可以帮您解答关于产品验证、防伪技术等问题。请随时向我提问')
},
generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9)
},
async sendMessage() {
if (!this.currentMessage.trim() || this.isLoading) return
const userMessage = this.currentMessage.trim()
this.currentMessage = ''
// Add user message
this.addMessage('user', userMessage)
// Show loading state
this.isLoading = true
try {
const requestData = {
message: userMessage,
session_id: this.sessionId,
chat_type: this.isProductMode ? 'product' : 'platform'
}
if (this.isProductMode) {
requestData.product_id = this.currentProductId
}
const response = await axios.post('/api/v1/ai-chat/', requestData)
// Add AI response
this.addMessage('assistant', response.data.response)
// Update session ID if provided
if (response.data.session_id) {
this.sessionId = response.data.session_id
}
} catch (error) {
console.error('AI Chat Error:', error)
this.addMessage('assistant', '抱歉,我遇到了一些技术问题。请稍后再试,或联系技术支持。')
} finally {
this.isLoading = false
this.scrollToBottom()
}
},
addMessage(role, content) {
this.messages.push({
id: ++this.messageId,
role: role,
content: content,
timestamp: new Date()
})
this.scrollToBottom()
},
scrollToBottom() {
this.$nextTick(() => {
const chatMessages = this.$refs.chatMessages
if (chatMessages) {
chatMessages.scrollTop = chatMessages.scrollHeight
}
})
},
formatTime(timestamp) {
return timestamp.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
})
},
async fetchProducts() {
try {
const response = await axios.get('/api/v1/products/')
this.products = response.data.items || []
} catch (error) {
console.error('Failed to fetch products:', error)
// Fallback to mock data for testing
this.products = [
{ id: 1, name: '测试产品 A' },
{ id: 2, name: '测试产品 B' },
{ id: 3, name: '测试产品 C' }
]
}
},
onModeChange() {
// Reset session when switching modes
this.sessionId = this.generateSessionId()
this.messages = []
if (this.selectedMode === 'platform') {
this.addMessage('assistant', '欢迎使用徵象AI客服我是您的智能防伪验证助手可以帮您解答关于产品验证、防伪技术等问题。请随时向我提问')
} else {
const selectedProduct = this.products.find(p => p.id === this.currentProductId)
if (selectedProduct) {
this.addMessage('assistant', `欢迎使用${selectedProduct.name}的专属客服!我可以帮您解答关于此产品的问题。请随时向我提问!`)
}
}
}
},
computed: {
isProductMode() {
return this.selectedMode !== 'platform'
},
currentProductId() {
if (!this.isProductMode) return null
const parts = this.selectedMode.split('-')
return parts.length > 1 ? parseInt(parts[1]) : null
}
}
}
</script>
<style scoped>
.ai-chat-container {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
background: #fff;
}
.chat-header {
padding: 15px 20px;
border-bottom: 1px solid #e9ecef;
background: linear-gradient(135deg, #4a90e2 0%, #357abd 100%);
color: white;
flex-shrink: 0;
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
}
.chat-header h4 {
margin: 0 0 5px 0;
font-weight: 600;
}
.chat-header p {
margin: 0;
color: #ffffff;
}
.mode-selector .form-control {
min-width: 200px;
border-radius: 8px;
padding: 6px 12px;
font-size: 14px;
border: none;
background: rgba(255, 255, 255, 0.95);
color: #333;
}
.chat-messages {
flex: 1;
padding: 20px;
overflow-y: auto;
background: #f8f9fa;
}
.message {
margin-bottom: 15px;
display: flex;
}
.message.user {
justify-content: flex-end;
}
.message.assistant {
justify-content: flex-start;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
position: relative;
}
.message.user .message-content {
background: #007bff;
color: white;
border-bottom-right-radius: 4px;
}
.message.assistant .message-content {
background: white;
color: #333;
border: 1px solid #e9ecef;
border-bottom-left-radius: 4px;
}
.message-text {
margin-bottom: 4px;
line-height: 1.4;
word-wrap: break-word;
}
.message-time {
font-size: 0.75rem;
opacity: 0.7;
}
.message.user .message-time {
text-align: right;
}
.typing {
display: flex;
align-items: center;
gap: 4px;
}
.typing span {
width: 8px;
height: 8px;
border-radius: 50%;
background: #999;
animation: typing 1.4s infinite ease-in-out;
}
.typing span:nth-child(1) { animation-delay: -0.32s; }
.typing span:nth-child(2) { animation-delay: -0.16s; }
.typing span:nth-child(3) { animation-delay: 0s; }
@keyframes typing {
0%, 80%, 100% { transform: scale(0.8); opacity: 0.5; }
40% { transform: scale(1); opacity: 1; }
}
.chat-input {
padding: 15px 20px;
border-top: 1px solid #e9ecef;
background: white;
flex-shrink: 0;
}
.input-group {
display: flex;
gap: 10px;
}
.input-group .form-control {
flex: 1;
border: 1px solid #ced4da;
border-radius: 25px;
padding: 12px 20px;
font-size: 14px;
}
.input-group .form-control:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.input-group .btn {
border-radius: 25px;
min-width: 80px;
height: 45px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: #4a90e2;
color: white;
transition: all 0.2s;
padding: 0 20px;
}
.input-group .btn svg {
display: block;
}
.input-group .btn:hover:not(:disabled) {
background: #357abd;
transform: scale(1.05);
}
.input-group .btn:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
/* Responsive design */
@media (max-width: 768px) {
.message-content {
max-width: 85%;
}
.chat-header {
padding: 12px 15px;
}
.header-content {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.mode-selector .form-control {
width: 100%;
}
.chat-messages {
padding: 15px;
}
.chat-input {
padding: 12px 15px;
}
}
</style>

View File

@ -3,7 +3,7 @@
:uri="resource_uri" :uri="resource_uri"
object_name="内容页" object_name="内容页"
:visible_fields="['title']" :visible_fields="['title']"
:editable_fields="['title']" :editable_fields="['title', 'is_platform_knowledge_base']"
:actions="actions" :actions="actions"
no_details=1 no_details=1
show_search=1 show_search=1