Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions app/api/comments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import { getDb, ensureCommentsTable } from "@/lib/db";

export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const docId = searchParams.get("docId");
if (!docId) {
return NextResponse.json(
{ ok: false, error: "missing_docId" },
{ status: 400 },
);
}
const db = getDb();
if (!db) {
return NextResponse.json(
{ ok: false, error: "db_missing" },
{ status: 503 },
);
}
await ensureCommentsTable();
const { rows } = await db.query(
`SELECT id, doc_id, content, user_id, user_name, user_image, parent_id, created_at
FROM comments WHERE doc_id = $1 ORDER BY created_at DESC LIMIT 200`,
[docId],
);
return NextResponse.json({ ok: true, data: rows });
}

export async function POST(req: Request) {
const session = await auth();
if (!session?.user) {
return NextResponse.json(
{ ok: false, error: "unauthorized" },
{ status: 401 },
);
}
const db = getDb();
if (!db) {
return NextResponse.json(
{ ok: false, error: "db_missing" },
{ status: 503 },
);
}
const body = (await req.json().catch(() => null)) as {
docId?: string;
content?: string;
parentId?: number | null;
} | null;
const docId = body?.docId?.trim();
const content = body?.content?.trim();
const parentId = body?.parentId ?? null;
if (!docId || !content) {
return NextResponse.json(
{ ok: false, error: "missing_fields" },
{ status: 400 },
);
}
if (content.length > 4000) {
return NextResponse.json(
{ ok: false, error: "content_too_long" },
{ status: 400 },
);
}
await ensureCommentsTable();
const userId = (session.user as any).id ?? "unknown";
const userName = session.user.name ?? null;
const userImage = session.user.image ?? null;
const { rows } = await db.query(
`INSERT INTO comments (doc_id, content, user_id, user_name, user_image, parent_id)
VALUES ($1, $2, $3, $4, $5, $6)
RETURNING id, doc_id, content, user_id, user_name, user_image, parent_id, created_at`,
[docId, content, String(userId), userName, userImage, parentId],
);
return NextResponse.json({ ok: true, data: rows[0] }, { status: 201 });
}
153 changes: 153 additions & 0 deletions app/components/SiteComments.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
"use client";

import * as React from "react";

interface SiteCommentsProps {
docId: string;
user?: { name?: string | null; image?: string | null } | null;
}

type Comment = {
id: number;
doc_id: string;
content: string;
user_id: string;
user_name: string | null;
user_image: string | null;
parent_id: number | null;
created_at: string;
};

export default function SiteComments({ docId, user }: SiteCommentsProps) {
const [comments, setComments] = React.useState<Comment[]>([]);
const [loading, setLoading] = React.useState(true);
const [content, setContent] = React.useState("");
const [submitting, setSubmitting] = React.useState(false);
const [error, setError] = React.useState<string | null>(null);

const fetchComments = React.useCallback(async () => {
setLoading(true);
setError(null);
try {
const res = await fetch(
`/api/comments?docId=${encodeURIComponent(docId)}`,
{
cache: "no-store",
},
);
const data = await res.json();
if (!data.ok) throw new Error(data.error || "加载失败");
setComments(data.data as Comment[]);
} catch (e: any) {
setError(e?.message || "加载失败");
} finally {
setLoading(false);
}
}, [docId]);

React.useEffect(() => {
fetchComments();
}, [fetchComments]);

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
const text = content.trim();
if (!text) return;
setSubmitting(true);
setError(null);
try {
const res = await fetch(`/api/comments`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ docId, content: text }),
});
const data = await res.json();
if (!res.ok || !data.ok) throw new Error(data.error || "提交失败");
setContent("");
// 直接插入到顶部
setComments((prev) => [data.data as Comment, ...prev]);
} catch (e: any) {
setError(e?.message || "提交失败");
} finally {
setSubmitting(false);
}
}

return (
<div className="space-y-4">
<form onSubmit={onSubmit} className="space-y-3">
<div className="flex items-center gap-3">
{user?.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={user.image}
alt="avatar"
className="h-8 w-8 rounded-full"
/>
) : (
<div className="h-8 w-8 rounded-full bg-muted" />
)}
<span className="text-sm text-muted-foreground">
{user?.name || "已登录用户"}
</span>
</div>
<textarea
className="w-full min-h-[100px] rounded-md border bg-transparent p-3"
placeholder="发表你的看法……"
value={content}
onChange={(e) => setContent(e.target.value)}
maxLength={4000}
/>
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">
{content.trim().length}/4000
</span>
<button
type="submit"
className="inline-flex items-center rounded-md border px-3 py-2 text-sm hover:bg-accent disabled:opacity-60"
disabled={!content.trim() || submitting}
>
{submitting ? "提交中…" : "发表评论"}
</button>
</div>
{error && <div className="text-sm text-red-500">{error}</div>}
</form>

<div className="divide-y divide-border rounded-md border">
{loading ? (
<div className="p-4 text-sm text-muted-foreground">加载中…</div>
) : comments.length === 0 ? (
<div className="p-4 text-sm text-muted-foreground">
还没有评论,来当第一个吧。
</div>
) : (
comments.map((c) => (
<div key={c.id} className="p-4">
<div className="mb-2 flex items-center gap-2">
{c.user_image ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={c.user_image}
alt="avatar"
className="h-6 w-6 rounded-full"
/>
) : (
<div className="h-6 w-6 rounded-full bg-muted" />
)}
<span className="text-sm font-medium">
{c.user_name || "匿名"}
</span>
<span className="text-xs text-muted-foreground">
{new Date(c.created_at).toLocaleString()}
</span>
</div>
<div className="whitespace-pre-wrap text-sm leading-relaxed">
{c.content}
</div>
</div>
))
)}
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion app/docs/CommunityShare/Geek/raspberry-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ $ tmux ls
| :- | :- |
| 类型 | A |
| 名称(必需) | rasp |
| IPv4 地址(必需) | <VPS公网IP> |
| IPv4 地址(必需) | &lt;VPS公网IP&gt; |
| 代理状态 | 仅DNS |

就可以通过你的域名登录了
Expand Down
21 changes: 21 additions & 0 deletions app/docs/ai-projects/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: 实战项目
description: 团队项目集合与协作入口,包含多模态、强化学习等方向的实战项目。
date: "2025-10-17"
tags:
- projects
- ai
- collaboration
---

欢迎来到项目分区(AI Projects)。这里将汇总团队正在推进或计划开展的工程化项目,聚焦“从想法到可用原型(MVP)”的落地过程与文档化沉淀。

## 当前项目

- 前往: [多模态强化学习项目(MVP)](./multimodal-rl/)

## 使用说明

- 项目文档包含:项目愿景、MVP 范围、系统架构、里程碑与分工。
- 鼓励以小步快跑的方式推进,每个阶段产出可验证的原型与记录。
- 欢迎贡献:问题讨论、改进建议、实现细节与复盘总结。
81 changes: 81 additions & 0 deletions app/docs/ai-projects/multimodal-rl/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
title: 多模态强化学习项目(MVP 目标)
description: 构建轻量化的多模态理解与生成系统,实现从视觉感知到语言表达的闭环,并引入强化学习与答案可视化生成。
date: "2025-10-17"
tags:
- projects
- multimodal
- reinforcement-learning
- RLHF
---

# Multimodal Group – MVP 目标说明文档

**项目版本:** v0.1
**仓库:** [involutionhell.github.io](https://github.com/InvolutionHell/involutionhell.github.io)

---

## 一、项目愿景

构建一个轻量化的多模态理解与生成系统,让模型能够看懂图片、检索相关信息,并生成逻辑清晰的文字内容。
目标是实现从视觉感知到语言表达的完整闭环,并进一步具备以图解释答案的能力。

## 二、MVP 阶段目标

### 阶段 1:基础多模态闭环

- 图像内容识别(物体、场景、语义标签)。
- 语义检索(图→文 / 文→图)。
- 生成式理解与文本输出。
- 模型参考:CLIP / SigLIP / BLIP-2 / LLaVA / Qwen-VL。

### 阶段 2:多模态强化学习(Multimodal RL)

- 引入用户反馈和奖励信号,优化模型生成与检索表现。
- 主要方向:
1. RLHF / DPO 微调,学习用户偏好。
2. 基于行为数据的检索策略优化。
3. 生成质量控制与一致性提升。

- 目标:让系统具备自我学习与偏好适应能力。

### 阶段 2.5:答案可视化生成(Answer-to-Image)

- 根据模型生成的答案内容自动生成配图,辅助理解。
- 实现方式:使用 Stable Diffusion / SDXL,将回答文本转为图像提示词。
- 应用示例:
- 回答“黑洞形成过程”→ 生成结构示意图。
- 解释小说场景 → 生成概念画面。

- 目标:让系统不仅能理解图片并回答,还能用图像解释答案。

## 三、系统架构

```
[Frontend] → 上传图片 / 展示结果
[Backend API] → FastAPI + LangChain + Vector Search
[Multimodal Models] → CLIP / BLIP / LLaVA / Qwen-VL
[RL Module + Answer-to-Image] (阶段 2 与 2.5)
```

## 四、里程碑

| 阶段 | 目标 | 产出 |
| --------- | ---------------- | ------------------------ |
| Phase 1 | 多模态识别与生成 | 图像识别、检索、文本生成 |
| Phase 2 | 强化学习优化 | RLHF / DPO、检索策略优化 |
| Phase 2.5 | 答案可视化生成 | 自动生成配图 |
| Phase 3 | 扩展与部署 | Web 展示与 API 接口 |

## 五、组员分工

| 模块 | 负责人 |
| -------------------- | ------ |
| 图像识别与编码 | 组员 A |
| 语义检索与数据处理 | 组员 B |
| 生成模块与模型集成 | 组员 C |
| 强化学习与可视化输出 | 组员 D |
15 changes: 8 additions & 7 deletions app/docs/ai/MoE/moe-update.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ $$
> **目标**:在第 $t$ 轮,专家 $m_t$ 要拟合任务数据集 $(X_t, y_t)$
> $$ \min\_{w}\ \|X_t^\top w - y_t\|\_2^2 $$
>
> **问题**:过参数化 ($s_t < d$) 时解不唯一,直接算最小二乘解会丢掉历史信息。
> 所以论文改成 **约束优化**:
> **问题**:过参数化 ($s_t &lt; d$) 时解不唯一,直接算最小二乘解会丢掉历史信息。
> &gt; 所以论文改成 **约束优化**:
>
> $$
> \min_w \ \|w - w_{t-1}^{(m_t)}\|_2^2 \quad
Expand Down Expand Up @@ -203,23 +203,24 @@ $$
- 在前章节使用的方法
- 保证同一专家在相邻任务上的参数差异不要太大。


- **表示相似性 (Representation Locality)**
- 可以直接对专家输出的表示(hidden states)施加约束。
- **表示相似性 (Representation Locality)** - 可以直接对专家输出的表示(hidden states)施加约束。

- 比如:

$$
L^{loc}_{repr} = \sum_{m \in [M]} \pi_m(X_t,\Theta_t)\,\|f_m(X_t) - f_m(X_{t-1})\|_2
$$

- 让相似输入在同一专家上输出保持稳定。

- **路由概率连续性 (Routing Locality)**
- 约束 router 的分配概率不要随任务跳跃太大。
- **路由概率连续性 (Routing Locality)** - 约束 router 的分配概率不要随任务跳跃太大。

- 形式类似:

$$
L^{loc}_{route} = \sum_{m \in [M]} \|\pi_m(X_t,\Theta_t) - \pi_m(X_{t-1},\Theta_{t-1})\|_2
$$

- **语义/任务嵌入的相似性 (Task Embedding Locality)**
- 如果能为任务构建一个 task embedding(比如通过元学习或对比学习),可以定义:
- 相似任务 → 路由到同一专家
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ sudo dnf groupinstall "Development Tools" -y

将这几个头文件放入MinGW的include下。

1. 代码中引入头文件将include 改为include <mingw.thread.h> (这个点在上述仓库中的ReadMe里有写)
1. 代码中引入头文件将include 改为include &lt;mingw.thread.h&gt; (这个点在上述仓库中的ReadMe里有写)
2. 如果是命令行编译,加上-D_WIN32_WINNT=0x0501这个参数,让编译器知道你正在针对 **Windows XP**(或更高版本)进行编译。(不知道是不是我的版本是win32的原因,也许mingw-win64版本不需要)

这是我踩坑的版本
Expand Down
Loading
Loading