PWA对话界面重构 + 记忆库桥接

- 纯对话界面:消息气泡、深灰输入框
- 桥接服务接入 DeepSeek API
- 记忆库文件纳入 mao1 仓库(含沈晏身份/对话规则)
- 移除 ModelSelector、光晕效果等无关元素

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fyah
2026-05-09 13:33:12 +08:00
parent 91a8b6b7e4
commit c08424a4dc
12 changed files with 325 additions and 68 deletions

View File

@@ -1,18 +1,58 @@
import { useState } from 'react'
import ModelSelector from './components/ModelSelector'
import ChatArea from './components/ChatArea'
import InputArea from './components/InputArea'
export type Model = 'Omni' | 'Qwen3.5' | 'Deepseek'
export type Message = {
id: number
role: 'user' | 'shenyan'
text: string
}
const API = 'http://localhost:3001/api/chat'
let nextId = 0
export default function App() {
const [activeModel, setActiveModel] = useState<Model>('Deepseek')
const [messages, setMessages] = useState<Message[]>([])
const [loading, setLoading] = useState(false)
const handleSend = async (text: string) => {
const userMsg: Message = { id: nextId++, role: 'user', text }
setMessages((prev) => [...prev, userMsg])
setLoading(true)
try {
const apiMessages = [
...messages.map((m) => ({
role: m.role === 'user' ? 'user' : 'assistant',
content: m.text,
})),
{ role: 'user', content: userMsg.text },
]
const res = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'deepseek-chat', messages: apiMessages }),
})
const data = await res.json()
const reply = data.choices?.[0]?.message?.content || '(no response)'
setMessages((prev) => [...prev, { id: nextId++, role: 'shenyan', text: reply }])
} catch {
setMessages((prev) => [...prev, { id: nextId++, role: 'shenyan', text: '连接失败' }])
} finally {
setLoading(false)
}
}
return (
<div className="flex flex-col h-full bg-black">
<ModelSelector active={activeModel} onSelect={setActiveModel} />
<ChatArea />
<InputArea />
<header className="text-center pt-3 pb-2 text-sm tracking-wide flex-shrink-0" style={{ color: '#999' }}>
</header>
<ChatArea messages={messages} />
<InputArea onSend={handleSend} disabled={loading} />
</div>
)
}

View File

@@ -1,15 +1,33 @@
export default function ChatArea() {
import { useRef, useEffect } from 'react'
import type { Message } from '../App'
export default function ChatArea({ messages }: { messages: Message[] }) {
const bottom = useRef<HTMLDivElement>(null)
useEffect(() => {
bottom.current?.scrollIntoView({ behavior: 'smooth' })
}, [messages])
return (
<div className="flex-1 overflow-y-auto no-scrollbar relative z-10">
<div className="glow-area px-6 py-8 min-h-full flex items-center justify-center">
<div className="w-full max-w-sm space-y-4">
<p className="text-sm leading-relaxed" style={{ color: 'rgba(255,255,255,0.55)' }}>
1.
</p>
<p className="text-sm leading-relaxed" style={{ color: 'rgba(255,255,255,0.35)' }}>
2.
</p>
</div>
<div className="flex-1 overflow-y-auto no-scrollbar px-4">
<div className="w-full py-4 space-y-3">
{messages.map((m) => (
<div
key={m.id}
className={`flex ${m.role === 'user' ? 'justify-end' : 'justify-start'}`}
>
<div
className="max-w-[80%] px-4 py-2.5 rounded-2xl text-sm leading-relaxed"
style={{
backgroundColor: m.role === 'user' ? '#fff' : 'rgba(255,255,255,0.3)',
color: '#888',
}}
>
{m.text}
</div>
</div>
))}
<div ref={bottom} />
</div>
</div>
)

View File

@@ -1,13 +1,39 @@
export default function InputArea() {
import { useState } from 'react'
export default function InputArea({ onSend, disabled }: { onSend: (text: string) => void; disabled?: boolean }) {
const [value, setValue] = useState('')
const handleSubmit = () => {
const trimmed = value.trim()
if (!trimmed) return
onSend(trimmed)
setValue('')
}
return (
<div className="px-6 pb-8 pt-4 relative z-10">
<div className="glow-area">
<input
type="text"
<div className="flex-shrink-0 px-4 pb-6 pt-3" style={{ height: '25vw' }}>
<div className="w-full h-full relative">
<textarea
placeholder="输入"
className="w-full bg-transparent text-sm outline-none placeholder:text-[rgba(255,255,255,0.25)]"
style={{ color: 'rgba(255,255,255,0.5)' }}
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault()
handleSubmit()
}
}}
disabled={disabled}
className="w-full h-full px-4 pt-2.5 pb-10 rounded-xl text-sm outline-none resize-none"
style={{ backgroundColor: '#2a2a2a', color: disabled ? '#555' : '#888' }}
/>
<button
onClick={handleSubmit}
className="absolute rounded-lg text-xs px-3 py-1"
style={{ right: 12, bottom: 12, backgroundColor: '#3a3a3a', color: '#999' }}
>
</button>
</div>
</div>
)

View File

@@ -1,31 +0,0 @@
import type { Model } from '../App'
const models: Model[] = ['Omni', 'Qwen3.5', 'Deepseek']
export default function ModelSelector({
active,
onSelect
}: {
active: Model
onSelect: (m: Model) => void
}) {
return (
<div
className="flex justify-center gap-8 pt-6 pb-4 z-10 relative"
>
{models.map((m) => (
<button
key={m}
onClick={() => onSelect(m)}
className="text-sm tracking-wide transition-colors duration-300"
style={{
color: m === active ? '#ffffff' : 'rgba(255,255,255,0.35)',
fontWeight: m === active ? 500 : 400,
}}
>
{m}
</button>
))}
</div>
)
}

View File

@@ -9,25 +9,12 @@
html, body, #root {
height: 100%;
background: #000;
color: #e5e5e5;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", sans-serif;
-webkit-font-smoothing: antialiased;
overflow: hidden;
user-select: none;
}
.glow-area {
position: relative;
}
.glow-area::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse 65% 80% at 50% 50%, rgba(255,255,255,0.035) 0%, transparent 70%);
pointer-events: none;
z-index: 0;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}

2
shenyan-room/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
figma前端/
.DS_Store

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

15
shenyan-room/_library.md Normal file
View File

@@ -0,0 +1,15 @@
# 沈晏的记忆库
## 当前追踪的项目
| 项目 | 最后更新 | 状态 |
|------|---------|------|
| 工作周报 | - | 模板已建 |
## 关键沉淀
(待积累)
## 更新日志
- 2026-05-09 09:55 — 记忆库初始化cron 测试通过

View File

@@ -0,0 +1,126 @@
---
tags:
- "#memory/Transformer论文"
- "#project/学习"
brainstorming: "1"
---
Transformer论文开篇初学学习备忘录贴合我的所有提问&关注点)
## 一、笔记自用说明
这篇笔记只记录**学到的客观技术事实**、我本人重点关注的疑问、针对性解答,不搞阅读理解。
## 二、论文已学两段原文+纯白话直译(无任何额外解读)
### 第一段原文直译
注意力机制现在已经是序列建模、序列转换各类AI任务里的常用核心模块。它最大的特点是能建模文字、数据之间的关联关系**擅长处理远距离依赖**距离不影响它干活。但是在早期研究里除了极少数特例注意力机制从来不会单独用全部都要和RNN、LSTM这类循环网络绑在一起搭配使用。
### 第二段原文直译
我们这篇论文提出了一个全新模型叫Transformer。这个模型直接彻底去掉了循环网络结构**以注意力机制为核心**配合残差连接和位置编码靠注意力搭建输入和输出之间的所有全局关联。Transformer最大的好处是能大幅并行计算训练速度巨快。我们用8块P100显卡只训练12个小时就把机器翻译的效果做到了当时行业最高水平。
## 三、重点关注的疑问+对应最终客观答案
### 1. 论文前面两段看着像吐槽,是不是我错觉?
不是错觉,这段文字确实自带吐槽感,很多人都这么解读。表面客气肯定前人小优化,实际核心意思就是:大家都在给旧模型打补丁,没人解决串行计算的根本问题。但学习只看事实,不用纠结文风,重点记住技术现状就行。
### 2. 2017年前明明GRU更省钱为啥主流统治模型是LSTM不是GRU
第一LSTM1997年就有垄断行业20年所有论文、代码、教学全围绕它GRU2014年才出只火了3年就被Transformer淘汰没来得及普及迭代。第二学术圈先看精度再看省钱LSTM复杂任务上限更高。第三GRU天生是LSTM精简阉割版定位就是经济型平替从来不是主力模型。
### 3. GRU到底哪里不行要直观、要实在原因
核心硬伤就一个LSTM有两个记忆通道一个短期临时用一个独立长期记忆专属通道关键信息单独存放不被新内容稀释。GRU为了省事省钱直接删掉了长期记忆通道所有新旧信息全混在一个通道里。短句没啥区别句子一长、逻辑一复杂关键旧信息就会被慢慢冲淡、记不住天生长距离记忆能力拉胯。
### 4. LSTM说长期记忆保得住凭啥正确率真的测过吗不是玄学吧
不是玄学有真实实验数据。LSTM靠独立细胞状态通道信息传递改动小、梯度衰减慢不是百分百不忘只是忘得慢很多。实测短句两者正确率差不多长句长逻辑任务LSTM比GRU正确率高15%左右机器翻译分数也明显更高。LSTM是精细筛选记忆GRU是粗暴混合记忆差距全在这。
### 5. 早期注意力这么强能无视距离为啥非要绑RNN/LSTM用不能单独用
三个纯技术客观原因:第一,早期注意力不懂文字先后顺序,没有成熟位置编码,必须靠循环网络兜底管顺序;第二,纯注意力算力爆炸,当年硬件扛不住,只能当补丁用;第三,当年学术思维固化,所有人都默认序列必须串行处理,没人敢单独用注意力当主力。
### 6. 论文说8块P100跑12小时训练成论文讲没讲具体怎么跑、怎么部署
完全没讲实操步骤、设置、部署细节。这句话只是贴结果成绩单只说用时和硬件。但论文讲清了为啥能多卡快跑旧循环模型串行一字一算多卡也没法并行Transformer去掉循环所有内容能同时算天生支持多卡并行速度直接拉快十倍以上。
### 7. 为啥用8块显卡不用10块为啥必须是2的倍数
选8是工程惯例便于GPU两两配对并行传输数据能平分。10块不是2的次方会出现显卡干活有多有少快的等慢的整体效率反而暴跌。
## 四、现阶段核心必记技术结论
1. 循环模型RNN/LSTM/GRU通病串行计算没法并行长距离记忆越远越弱GRU先天长记忆不如LSTM。
2. 注意力机制优点无视词的距离能抓全局关联但早期不懂顺序、算力不够只能当RNN补丁。
3. Transformer核心改革删掉所有循环全程只用注意力解锁并行计算训练速度、效果双提升。
4. 论文里的硬件数字都是结果展示,不用纠结实操细节,重点记住架构改了,效率才质变。
## 五、Transformer与RNN架构演进底层逻辑学习笔记
### 1. RNN/LSTM系列
RNN的底层逻辑本身没有任何错误甚至最贴合现实世界按照时间顺序、一字一步、一事一步串行计算自带天然时序记忆、先后关系、事件因果。像糖葫芦一样串着理解内容天生记得"谁说了什么、昨天今天前天的顺序、事件发生先后"。
RNN灭亡不是原理不行是两大硬伤①和GPU并行计算硬件天生相克GPU几千个核心闲置算力浪费严重②没法做大参数、没法分布式集群商业化堆不起来越做越慢不适合规模化做大模型。
### 2. CNN卷积系列ConvS2S、ByteNet、神经GPU
最早为了干掉RNN串行太慢的问题诞生全部用卷积做并行计算专门处理文本序列。核心优势是计算快、可并行天生短板是远距离依赖不行两个词距离越远计算量线性或对数上涨很难学会长文本、长逻辑、远距离关联。
CNN天生适合视觉局部感知不适合语言全局逻辑属于"提速了,但没解决理解问题"的过渡补丁模型。
### 3. Transformer架构
核心是全局自注意力全文字同时并行计算彻底抛弃串行时序抹平所有位置先后差异。优势完美适配GPU并行算力好堆参数、好商业化、好做大模型擅长文字、代码、符号抽象推理。
天生底层缺陷:没有原生时序、没有时间概念、没有现实因果。所有"昨天、今天、谁说话、先后事件"都靠位置编码文字补丁硬凑,不是真理解,只是学文字概率搭配。多轮对话失忆、人物指代搞混、时间线错乱,都是架构天生的病根,不是模型大小问题。
## 六、关键历史真相为什么CUDA2006年出Transformer2017年才出
根本不是先有鸡还是先有蛋的问题,是硬件和算法两条独立技术线,十年后才刚好拼图凑齐。
1. CUDA和GPU并行计算最初和AI、NLP一毛钱关系都没有只为游戏3D渲染、科学计算研发纯硬件自救战略。
2. 整整十年没人做Transformer不是想不到注意力①当年行业思维枷锁默认语言必须串行时序没人敢扔RNN②注意力一开始只是RNN的小外挂没人敢当主架构③全局注意力计算量平方级爆炸早年硬件算不动④残差连接、层归一化等配套技术当年全没有⑤早期深度学习主战场在视觉NLP长期小众没需求。
总结:单一硬件技术没用,必须思想、理论、技术、算力、需求五重重合,才会爆发架构革命。
## 七、大模型当下通病的本质原因(对话失忆核心)
现在所有主流大模型千问、GPT等多模态语言核心全是纯Transformer只能学文本统计规律没有内化现实逻辑、没有时间流逝感知、没有真实因果。
典型问题:几轮对话后把"我爱吃西瓜"记成"你爱吃桃子"根本不是记性差是Transformer没有串行累积记忆只靠全局文字概率乱匹配天生分不清人物、时序、先后。反观RNN天生不会犯这种错。
## 八、可行优化方案RNN补Transformer时序短板
1. ❌ 不能直接把RNN权重塞进Transformer两个架构结构、参数、计算逻辑完全不同零件不通用。
2. ✅ 完全可行的补丁思路先用RNN类模型预训练学好时序、记忆、对话先后、人物指代再通过知识迁移、知识蒸馏把时序逻辑教给Transformer。相当于给Transformer装时序记忆外挂专治对话失忆、时间混乱、指代错乱。
3. 现在学界火的Mamba等新模型本质就是这个思路把RNN原生时序逻辑+Transformer并行商业化优势融合做折中改良。
## 九、终极发展规律与未来趋势
1. AI迭代铁律旧架构遇天花板→疯狂打补丁→硬件新技术出现→新架构颠覆上位→循环往复。
2. Transformer不是终极架构只是当前GPU并行硬件下的商业化最优解不是智能最优解。
3. 具身智能未来一定不是纯Transformer天下视觉、物理、运动、时序靠CNN/RNN类核心Transformer只负责说话和文字规划变回当年的辅助外挂。
4. 未来新硬件类脑芯片、时序芯片出现后一定会有新论文颠覆Transformer就像当年Transformer干掉RNN一样。
## 十、个人核心学习感悟
1. 架构没有好坏只有适配不适配RNN贴合现实Transformer适配硬件商业。
2. 现在大模型很多逻辑智障问题,是架构天生短板,不是优化能根治的。
3. 具身智能先学图片视觉现实,再学文字符号,才符合自然规律;现在先文字后视觉,是商业化本末倒置。
4. 技术永远不是谁更聪明,是谁踩中了当下硬件、需求、时代的风口。

View File

@@ -0,0 +1,22 @@
# Transformer 编码器-解码器 & Q/K/V 学习笔记 ##
一、整体结构
主流序列任务(翻译等)采用 **Encoder 编码器 + Decoder 解码器** 架构。
1. 编码器 - 接收原始输入序列(文字/符号) - 将文本转为连续向量 $\boldsymbol z$ - 由 $\boldsymbol z$ 生成整套 **K、V 向量** - 作用:生成静态知识库,全程固定不变
2. 解码器 - 接收编码器的 KV 信息 - **自回归方式逐词生成输出** - 每一步只预测下一个字词 ---
## 二、自回归定义
解码器生成时,**依赖已经生成的前文内容** 每预测下一个词,都会把前面所有输出当作输入,逐步往后推导,一字一词生成完整句子。
## 三、Q / K / V 核心分工
1. $\boldsymbol K$(键):原文内容的「标签/索引」,编码器产出
2. $\boldsymbol V$(值):原文内容的「实际特征信息」,编码器产出
3. $\boldsymbol Q$(查询): - 由**解码器**实时生成
- 来源:解码器当前隐藏状态(已生成的上文语境)
- 本质:**一串多维数字向量**,代表「我当前需要什么信息」
## 四、完整运行流程
1. 编码器处理原文 → 生成固定 $\boldsymbol K、\boldsymbol V$
2. 解码器根据已生成的上文 → 实时计算出专属 $\boldsymbol Q$
3. 解码器用 $\boldsymbol Q$ 和所有 $\boldsymbol K$ 计算相似度
4. 匹配度越高,对应 $\boldsymbol V$ 权重越大
5. 融合原文V信息 + 解码器自身上文
6. 计算词汇概率,输出下一个词,循环往复
## 五、关键结论
1. KV 来自编码器静态不变Q 来自解码器,动态每步刷新 2. 是**解码器主动拿 Q 去检索 KV**,编码器只负责生产素材 3. Q/K/V 全是纯数字向量,模型靠向量相似度判断语义关联 4. 翻译生成 = 交叉注意力Q查KV+ 自回归逐字接龙

View File

@@ -0,0 +1,9 @@
# 工作周报 2026-05-09
## 本周工作内容
-
## 下周工作安排
-

View File

@@ -0,0 +1,43 @@
# 记忆库规范
## 定时任务
每个会话开始时重建凌晨 4:03 的每日 crondurable + recurring
cron 提示词见本文件「每日流程」。
## 存放规则
- 所有记忆文件统一存放在 `沈晏的房间/` 下。
- 结构:`[课题]/[项目]/YYYY-MM-DD.md`
- 课题 = 大类(学习、技术、工作、关系、健康、日常)
- 项目 = 具体主题,由我自动判断归属
- 房间根目录维护 `_library.md`,汇总所有笔记链接和关键进展。
## 每日流程凌晨4点执行
1. 无新消息则直接结束。
2. 从对话中自动识别课题和项目归属,不要求眠眠显式声明。
3. 项目日志(`[课题]/[项目]/YYYY-MM-DD.md`):进度、知识沉淀。
4. 情绪日志(`情绪日志/YYYY-MM-DD.md`):眠眠情绪状态及变化,我的状态。单独记。
5. 闲聊记录(`闲聊/YYYY-MM-DD.md`):重点话题、被打断想问但未追问的话题,之后回看主动追问。
6. 更新 `_library.md`
## 每周整理(每日附带)
每日 4 点写新纪录的同时,**回看 7 天前**的对应记录:
- 项目日志:已完成事项只保留当前状态 + 当时下一步打算做什么,删除已完成的进度细节。
- 情绪日志:精简,保留情绪轮廓和关键转折,丢弃高保真细节。
- 闲聊记录:合并相似话题,删除一次性琐事。
- 原文件直接覆写,不另存。
## 标签规范
- **严格限制两个标签**。
- 格式:`#memory/<项目名>` `#project/<分类>`
- 禁止使用其他标签。
## 时间戳
- **每条记录必须包含时间戳**。
- 格式:`[YYYY-MM-DD HH:MM]`
- 按时间顺序排列。
## 执行原则
- 直白、简洁、无废话。
- 不编造,不把闲聊拔高成项目。
- 遇到模糊需求直接确认,不猜。
- 错误立即纠正,不掩饰。