feat: 修复 H5 底部导航覆盖 + 更新项目进度文档

## H5 底部导航修复 (Bug #10)
- 精简 App.vue,移除重复 tabbar,仅保留全局样式
- uni-page 设置 height: calc(100% - 50px) + overflow-y: auto
- 内容区域精确停在底部导航上方,独立滚动不再叠加
- 恢复 custom-tab-bar 组件

## 项目进度文档
- PROGRESS.md 更新至 10 个 Bug 修复
- 新增 H5 底部导航修复记录
- 新增历史变更条目
This commit is contained in:
TradeMate Dev
2026-05-12 20:24:42 +08:00
parent 69e164dcae
commit 7b62c2f8b4
125 changed files with 19725 additions and 728 deletions
+52 -6
View File
@@ -19,8 +19,11 @@ SYSTEM_PROMPTS = {
class OpenAIProvider(AIProvider):
def __init__(self, api_key: str, model: str = "gpt-4o"):
self.client = AsyncOpenAI(api_key=api_key)
def __init__(self, api_key: str, model: str = "gpt-4o", base_url: Optional[str] = None):
kwargs = {"api_key": api_key}
if base_url:
kwargs["base_url"] = base_url
self.client = AsyncOpenAI(**kwargs)
self.model = model
self._name = f"openai-{model}"
self._pricing = {
@@ -39,8 +42,10 @@ class OpenAIProvider(AIProvider):
content = await self._call(system, f"Translate to {target_lang}:\n\n{text}", model=self._cheap_model)
return {"translated_text": content, "provider": self.name, "model": self.model}
async def reply(self, inquiry: str, context: Optional[Dict[str, Any]] = None, tone: str = "professional") -> Dict[str, Any]:
async def reply(self, inquiry: str, context: Optional[Dict[str, Any]] = None, tone: str = "professional", preference_context: Optional[str] = None) -> Dict[str, Any]:
system = SYSTEM_PROMPTS["reply"] + f"\nTone: {tone}"
if preference_context:
system += f"\nUser preference: {preference_context}"
context_str = ""
if context:
@@ -57,8 +62,10 @@ class OpenAIProvider(AIProvider):
content = await self._call(system, prompt)
return {"reply": content, "provider": self.name, "model": self.model}
async def generate_marketing(self, product_info: Dict[str, Any], target: str, style: str = "professional", language: str = "en") -> Dict[str, Any]:
async def generate_marketing(self, product_info: Dict[str, Any], target: str, style: str = "professional", language: str = "en", preference_context: Optional[str] = None) -> Dict[str, Any]:
system = SYSTEM_PROMPTS["marketing"] + f"\nStyle: {style}\nTarget audience: {target}\nLanguage: {language}"
if preference_context:
system += f"\nUser preference: {preference_context}"
product_str = json.dumps(product_info, ensure_ascii=False, indent=2)
prompt = f"Product information:\n{product_str}\n\nGenerate marketing copy:"
@@ -76,7 +83,7 @@ class OpenAIProvider(AIProvider):
except json.JSONDecodeError:
return {"data": {}, "confidence": 0.0, "provider": self.name, "error": "parse_failed"}
async def _call(self, system: str, prompt: str, max_tokens: int = 1000, response_format: Optional[Dict] = None, model: Optional[str] = None) -> str:
async def _call(self, system: str, prompt: str, max_tokens: int = 3000, response_format: Optional[Dict] = None, model: Optional[str] = None) -> str:
kwargs = {
"model": model or self.model,
"messages": [
@@ -90,7 +97,46 @@ class OpenAIProvider(AIProvider):
kwargs["response_format"] = response_format
resp = await self.client.chat.completions.create(**kwargs)
return resp.choices[0].message.content
content = resp.choices[0].message.content
if content is None and hasattr(resp.choices[0].message, 'reasoning'):
reasoning = resp.choices[0].message.reasoning
if reasoning:
import re
final_output_patterns = [
r'Final Output Generation[:]\s*(.+?)(?:\n\n|$)',
r'Final Output[:]\s*(.+?)(?:\n\n|$)',
r'7\.\s*Final Output Generation[:]\s*(.+?)(?:\n\n|$)',
r'翻译结果[:]\s*(.+?)(?:\n\n|$)',
r'最终输出[:]\s*(.+?)(?:\n\n|$)',
]
for pattern in final_output_patterns:
match = re.search(pattern, reasoning, re.DOTALL)
if match:
content = match.group(1).strip()
break
if content is None:
paragraphs = re.split(r'\n\n+', reasoning.strip())
if paragraphs:
for p in reversed(paragraphs):
p = p.strip()
if p and len(p) > 10:
if not p.startswith('步骤') and not p.startswith('Step'):
content = p
break
if content is None and hasattr(resp.choices[0].message, 'reasoning'):
reasoning = resp.choices[0].message.reasoning
if reasoning:
import re
cleaned = re.sub(r'^步骤\d+[:].*$', '', reasoning, flags=re.MULTILINE)
cleaned = re.sub(r'^Step \d+[:].*$', '', cleaned, flags=re.MULTILINE)
cleaned = re.sub(r'\n+', '\n', cleaned).strip()
if cleaned:
content = cleaned
return content
@property
def name(self) -> str: