feat: AI自动添加功能,智能识别文本类型并整理数据

This commit is contained in:
2026-04-13 16:56:43 +08:00
parent 8f9a8d2ad5
commit 033b7509d7

View File

@@ -164,6 +164,82 @@ def get_stats():
return jsonify({'success': True, 'data': stats}) return jsonify({'success': True, 'data': stats})
@app.route('/api/ai-process', methods=['POST'])
def ai_process():
"""AI处理文本"""
import requests
data = request.get_json()
text = data.get('text', '').strip()
if not text:
return jsonify({'success': False, 'error': '请输入文本内容'}), 400
# 大模型配置
llm_url = "http://192.168.2.17:19007/v1/chat/completions"
llm_key = "xxxx"
prompt = f"""请分析以下文本内容,识别其类型并提取关键信息。
文本内容:
{text}
请按以下JSON格式返回结果只返回JSON不要其他内容
{
"type": "text/link/column/todo",
"title": "提取的标题(简短概括)",
"content": "主要内容(如果是文本类型)",
"url": "如果是链接或专栏提取URL",
"source": "如果是专栏,提取来源",
"tags": ["相关标签1", "标签2"],
"note": "补充说明或备注",
"status": "如果是待办默认pending",
"priority": "如果是待办默认medium"
}
类型判断规则:
- link: 包含http/https链接且不是专栏订阅地址
- column: 专栏订阅地址或RSS链接
- todo: 包含任务、待办、提醒等关键词
- text: 其他文本内容"""
try:
response = requests.post(
llm_url,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {llm_key}"
},
json={
"model": "auto",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.3
},
timeout=30
)
if response.status_code != 200:
return jsonify({'success': False, 'error': f'模型调用失败: {response.status_code}'}), 500
result = response.json()
content = result['choices'][0]['message']['content']
# 解析JSON
import json
import re
# 提取JSON部分
json_match = re.search(r'\{.*\}', content, re.DOTALL)
if json_match:
parsed = json.loads(json_match.group())
return jsonify({'success': True, 'data': parsed})
else:
return jsonify({'success': False, 'error': '无法解析模型返回'}), 500
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/search', methods=['GET']) @app.route('/api/search', methods=['GET'])
def search_items(): def search_items():
"""搜索条目""" """搜索条目"""
@@ -263,6 +339,9 @@ INDEX_TEMPLATE = '''
<option value="todo">待办</option> <option value="todo">待办</option>
</select> </select>
</div> </div>
<button class="btn btn-outline-info me-2" onclick="showAIAddModal()" title="AI自动添加">
<i class="bi bi-robot"></i> AI添加
</button>
<button class="btn btn-outline-success me-2" onclick="exportData()" title="导出JSON"> <button class="btn btn-outline-success me-2" onclick="exportData()" title="导出JSON">
<i class="bi bi-download"></i> 导出 <i class="bi bi-download"></i> 导出
</button> </button>
@@ -533,6 +612,44 @@ INDEX_TEMPLATE = '''
</div> </div>
</div> </div>
<!-- AI自动添加模态框 -->
<div class="modal fade" id="aiAddModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="bi bi-robot"></i> AI自动添加</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">输入文本内容</label>
<textarea id="aiInputText" class="form-control" rows="6" placeholder="粘贴文本、链接、笔记等AI会自动识别并整理..."></textarea>
</div>
<div id="aiResult" style="display:none;">
<hr>
<h6>识别结果:</h6>
<div id="aiResultContent" class="border rounded p-3 bg-light"></div>
</div>
<div id="aiLoading" style="display:none;">
<div class="text-center py-3">
<div class="spinner-border text-primary" role="status"></div>
<div class="mt-2">AI正在分析...</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="aiProcessBtn" onclick="processAIInput()">
<i class="bi bi-magic"></i> 分析并添加
</button>
<button type="button" class="btn btn-success" id="aiConfirmBtn" style="display:none;" onclick="confirmAIAdd()">
<i class="bi bi-check"></i> 确认添加
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
const API_BASE = '/api'; const API_BASE = '/api';
@@ -1132,6 +1249,95 @@ async function exportData() {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
} }
// AI自动添加
let aiParsedData = null;
function showAIAddModal() {
document.getElementById('aiInputText').value = '';
document.getElementById('aiResult').style.display = 'none';
document.getElementById('aiLoading').style.display = 'none';
document.getElementById('aiProcessBtn').style.display = 'inline-block';
document.getElementById('aiConfirmBtn').style.display = 'none';
aiParsedData = null;
new bootstrap.Modal(document.getElementById('aiAddModal')).show();
}
async function processAIInput() {
const text = document.getElementById('aiInputText').value.trim();
if (!text) {
alert('请输入文本内容');
return;
}
// 显示加载
document.getElementById('aiLoading').style.display = 'block';
document.getElementById('aiProcessBtn').disabled = true;
try {
const res = await fetch(`${API_BASE}/ai-process`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
const data = await res.json();
document.getElementById('aiLoading').style.display = 'none';
document.getElementById('aiProcessBtn').disabled = false;
if (!data.success) {
alert('AI分析失败: ' + data.error);
return;
}
aiParsedData = data.data;
// 显示结果
const typeLabels = { text: '📝 文本', link: '🔗 链接', column: '📰 专栏', todo: '✅ 待办' };
let html = `<div><strong>类型:</strong> ${typeLabels[aiParsedData.type] || aiParsedData.type}</div>`;
if (aiParsedData.title) html += `<div><strong>标题:</strong> ${aiParsedData.title}</div>`;
if (aiParsedData.url) html += `<div><strong>URL:</strong> ${aiParsedData.url}</div>`;
if (aiParsedData.content) html += `<div><strong>内容:</strong> ${aiParsedData.content.substring(0, 200)}${aiParsedData.content.length > 200 ? '...' : ''}</div>`;
if (aiParsedData.tags && aiParsedData.tags.length) html += `<div><strong>标签:</strong> ${aiParsedData.tags.join(', ')}</div>`;
if (aiParsedData.note) html += `<div><strong>备注:</strong> ${aiParsedData.note.substring(0, 100)}${aiParsedData.note.length > 100 ? '...' : ''}</div>`;
if (aiParsedData.type === 'todo') {
const statusLabels = { pending: '⏳ 待处理', in_progress: '🔄 进行中', completed: '✅ 已完成' };
const priorityLabels = { low: '🟢 低', medium: '🟡 中', high: '🟠 高', urgent: '🔴 紧急' };
html += `<div><strong>状态:</strong> ${statusLabels[aiParsedData.status] || 'pending'}</div>`;
html += `<div><strong>优先级:</strong> ${priorityLabels[aiParsedData.priority] || 'medium'}</div>`;
}
document.getElementById('aiResultContent').innerHTML = html;
document.getElementById('aiResult').style.display = 'block';
document.getElementById('aiProcessBtn').style.display = 'none';
document.getElementById('aiConfirmBtn').style.display = 'inline-block';
} catch (e) {
document.getElementById('aiLoading').style.display = 'none';
document.getElementById('aiProcessBtn').disabled = false;
alert('请求失败: ' + e.message);
}
}
async function confirmAIAdd() {
if (!aiParsedData) return;
const res = await fetch(`${API_BASE}/items`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(aiParsedData)
});
if (res.ok) {
bootstrap.Modal.getInstance(document.getElementById('aiAddModal')).hide();
refreshData();
alert('添加成功!');
} else {
const data = await res.json();
alert('添加失败: ' + data.error);
}
}
function debounce(fn, delay) { function debounce(fn, delay) {
let timer; let timer;
return function(...args) { return function(...args) {