├── .nvmrc
├── pnpm-workspace.yaml
├── .eslintrc.json
├── public
├── logo.jpg
├── avatar.jpg
├── covers
│ └── cover_translation_flow2.jpeg
└── manifest.json
├── postcss.config.js
├── prettier.config.js
├── next-env.d.ts
├── src
├── app
│ ├── providers.tsx
│ ├── metadata.tsx
│ ├── sitemap.ts
│ ├── manifest.ts
│ ├── icon.tsx
│ ├── robots.ts
│ ├── apple-icon.tsx
│ ├── feed.xml
│ │ └── route.ts
│ ├── page.tsx
│ ├── categories
│ │ └── [category]
│ │ │ └── page.tsx
│ ├── error.tsx
│ ├── not-found.tsx
│ ├── posts
│ │ └── [...slug]
│ │ │ └── layout.tsx
│ └── layout.tsx
├── lib
│ ├── constants.ts
│ ├── images.ts
│ ├── routes.ts
│ ├── utils.ts
│ ├── posts.ts
│ └── metadata.ts
├── types
│ └── navigation.ts
├── components
│ ├── common
│ │ ├── VideoPlayer.tsx
│ │ ├── ThemeSwitch.tsx
│ │ ├── GradientBackground.tsx
│ │ ├── Container.tsx
│ │ ├── BlurImage.tsx
│ │ ├── ErrorBoundary.tsx
│ │ ├── Skeleton.tsx
│ │ └── Mdx.tsx
│ ├── navigation
│ │ ├── NavigationSection.tsx
│ │ ├── NavigationFooter.tsx
│ │ ├── NavigationHeader.tsx
│ │ ├── NavigationProfile.tsx
│ │ ├── NavigationItem.tsx
│ │ └── Navigation.tsx
│ ├── home
│ │ ├── AsyncPostSections.tsx
│ │ ├── FeaturedSection.tsx
│ │ ├── PostList.tsx
│ │ ├── PostCard.tsx
│ │ ├── Hero.tsx
│ │ └── FeaturedPost.tsx
│ ├── post
│ │ ├── PostContent.tsx
│ │ ├── ReadingProgress.tsx
│ │ ├── Comments.tsx
│ │ ├── PostFilter.tsx
│ │ ├── PostHeader.tsx
│ │ └── PostFooter.tsx
│ ├── ui
│ │ ├── button.tsx
│ │ └── card.tsx
│ ├── category
│ │ └── CategoryPageContent.tsx
│ └── layout
│ │ └── CategoryLayout.tsx
└── config
│ └── navigation.ts
├── tsconfig.node.json
├── components.json
├── .cursorrules
├── .gitignore
├── .github
└── FUNDING.yml
├── next.config.js
├── scripts
├── config.ts
└── utils.ts
├── tsconfig.json
├── posts
├── reading
│ ├── chat
│ │ ├── 20250222_daily_chat.mdx
│ │ ├── 20250226_daily_chat.mdx
│ │ ├── 20250223_daily_chat.mdx
│ │ ├── 20250219_daily_chat.mdx
│ │ ├── 20250306_daily_chat.mdx
│ │ ├── 20250225_daily_chat.mdx
│ │ ├── 20250221_daily_chat.mdx
│ │ ├── 20250220_daily_chat.mdx
│ │ └── 20250227_daily_chat.mdx
│ ├── ai
│ │ └── 20250221_thoughts_on_ai_coexistence_evolution.mdx
│ └── notes
│ │ └── 20251107_bestblogs_weekly_issue_71.mdx
├── ai
│ ├── model
│ │ ├── 20250218_xai_release_grok3.mdx
│ │ ├── 20250312_openai_agents_platform.mdx
│ │ └── 20250124_openai_introduce_operator.mdx
│ ├── agent
│ │ ├── 20250213_what_is_a_cognitive_architecture.mdx
│ │ ├── 20250123_what_is_ai_agent.mdx
│ │ ├── 20250316_speeding_up_llm_powered_agents.mdx
│ │ ├── 20250214_outsource_agentic_infrastructure_own_cognitive_architecture.mdx
│ │ └── 20250302_memory_for_agents.mdx
│ └── mcp
│ │ └── 20250311_mcp_fad_or_fixture.mdx
├── thoughts
│ └── learning
│ │ └── 20251026_campus_interview_insights_from_interviewer.mdx
├── dev
│ └── growth
│ │ ├── 20251007_how_to_influence_politics.mdx
│ │ └── 20240409_technical_project_manager.mdx
└── build
│ ├── wenrun
│ └── 20250308_wenrun_launch.mdx
│ └── design
│ └── 20250314_practical_ux_for_startups_surviving.mdx
├── documents
├── tasks.md
├── design.md
├── SEO.md
├── coverPrompt.md
├── project.md
└── chinese-copywriting-guidelines.md
├── package.json
├── contentlayer.config.ts
├── tailwind.config.js
├── AGENTS.md
└── CLAUDE.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22
2 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "."
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginobefun/ginonotes-blog/HEAD/public/logo.jpg
--------------------------------------------------------------------------------
/public/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginobefun/ginonotes-blog/HEAD/public/avatar.jpg
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/covers/cover_translation_flow2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ginobefun/ginonotes-blog/HEAD/public/covers/cover_translation_flow2.jpeg
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('prettier').Options} */
2 | module.exports = {
3 | singleQuote: true,
4 | semi: false,
5 | plugins: ['prettier-plugin-tailwindcss'],
6 | };
7 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | import "./.next/dev/types/routes.d.ts";
4 |
5 | // NOTE: This file should not be edited
6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
7 |
--------------------------------------------------------------------------------
/src/app/providers.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { ThemeProvider as NextThemesProvider, type ThemeProviderProps } from 'next-themes'
4 |
5 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
6 | return {children}
7 | }
8 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "target": "ES2020",
7 | "sourceMap": true,
8 | "outDir": "dist"
9 | },
10 | "include": ["scripts/**/*"],
11 | "exclude": ["node_modules"]
12 | }
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/app/global.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/metadata.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next'
2 | import { metadata as siteMetadata } from '@/lib/metadata'
3 |
4 | export const metadata: Metadata = {
5 | ...siteMetadata,
6 | icons: {
7 | icon: '/icon',
8 | shortcut: '/icon',
9 | },
10 | manifest: '/manifest.webmanifest',
11 | viewport: {
12 | width: 'device-width',
13 | initialScale: 1,
14 | maximumScale: 1,
15 | userScalable: false,
16 | }
17 | }
--------------------------------------------------------------------------------
/src/lib/constants.ts:
--------------------------------------------------------------------------------
1 | export const WEBSITE_HOST_URL =
2 | process.env.NEXT_PUBLIC_WEBSITE_URL || 'https://ginonotes.com'
3 |
4 | export const WEBSITE_NAME = 'Gino Notes'
5 | export const WEBSITE_DESCRIPTION =
6 | '记录学习和思考的内容,分享技术、人工智能、产品设计和生活随想。'
7 |
8 | export const WEBSITE_AUTHOR = 'Gino'
9 | export const WEBSITE_TWITTER = '@hongming731'
10 | export const WEBSITE_LANGUAGE = 'zh-CN'
11 | export const WEBSITE_OG_IMAGE = '/images/og.png'
12 |
--------------------------------------------------------------------------------
/.cursorrules:
--------------------------------------------------------------------------------
1 | 这是我个人的博客网站 ginonotes.com,基于 Next.js 14 + Tailwind CSS + contentlayer 构建。请遵循以下规则:
2 |
3 | 1. 确保网站的风格和布局一致,保持清新、整洁、科技的风格,保证读者的阅读体验最佳。
4 | 2. 请遵循 Next.js 14、React 18、TypeScript、Tailwind CSS、contentlayer 的官方文档和最佳实践。
5 | 3. 请保持代码的简洁、易读、易维护,增加必要的注释和说明,以便我能够理解和维护。
6 | 4. 在 posts 目录下,请按照日期和标题的格式来命名文件,例如 20240101_my_first_post.mdx。
7 | 5. 在编写或修改文章时,请使用 Markdown 格式,并遵循 Markdown 的语法和规范,不要随意删除原有的内容。
8 | 6. 请使用中文进行写作,并使用中文标点符号。注意在中文和英文、数字之间增加一个空格。
9 | 7. 优先使用 Tailwind CSS 来编写样式,尽量不要使用原生 CSS。需要考虑网站的响应式设计,特别是在网页端和手机端的阅读体验。
10 |
--------------------------------------------------------------------------------
/src/app/sitemap.ts:
--------------------------------------------------------------------------------
1 | import { WEBSITE_HOST_URL } from '@/lib/constants'
2 | import { allPosts } from 'contentlayer2/generated'
3 |
4 | export default async function sitemap() {
5 | const posts = allPosts.map((post) => ({
6 | url: `${WEBSITE_HOST_URL}${post.url}`,
7 | lastModified: post.date,
8 | }))
9 |
10 | const routes = ['', '/about'].map((route) => ({
11 | url: `${WEBSITE_HOST_URL}${route}`,
12 | lastModified: new Date().toISOString().split('T')[0],
13 | }))
14 |
15 | return [...routes, ...posts]
16 | }
17 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Gino Notes",
3 | "short_name": "Gino",
4 | "description": "记录学习和思考的内容,分享技术、人工智能、产品设计和生活随想。",
5 | "start_url": "/",
6 | "display": "standalone",
7 | "background_color": "#ffffff",
8 | "theme_color": "#ffffff",
9 | "icons": [
10 | {
11 | "src": "/icon",
12 | "sizes": "32x32",
13 | "type": "image/png"
14 | },
15 | {
16 | "src": "/apple-icon",
17 | "sizes": "180x180",
18 | "type": "image/png"
19 | }
20 | ],
21 | "orientation": "portrait",
22 | "lang": "zh-CN"
23 | }
24 |
--------------------------------------------------------------------------------
/src/types/navigation.ts:
--------------------------------------------------------------------------------
1 | import { Route } from 'next'
2 | import { IconType } from 'react-icons'
3 | import { AppRoute, ExternalRoute } from '@/lib/routes'
4 |
5 | export interface NavigationItem {
6 | href: AppRoute | ExternalRoute
7 | label: string
8 | icon: IconType
9 | count?: number
10 | }
11 |
12 | export interface NavigationSection {
13 | title: string
14 | items: NavigationItem[]
15 | }
16 |
17 | export interface NavigationConfig {
18 | main: NavigationItem[]
19 | posts: NavigationItem[]
20 | projects: NavigationItem[]
21 | online: NavigationItem[]
22 | }
--------------------------------------------------------------------------------
/src/app/manifest.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from 'next'
2 | import { metadata } from '@/lib/metadata'
3 |
4 | export default function manifest(): MetadataRoute.Manifest {
5 | return {
6 | name: metadata.title?.toString(),
7 | short_name: 'Gino Notes',
8 | description: metadata.description?.toString(),
9 | start_url: '/',
10 | display: 'standalone',
11 | background_color: '#ffffff',
12 | theme_color: '#ffffff',
13 | icons: [
14 | {
15 | src: '/icon',
16 | sizes: '32x32',
17 | type: 'image/png'
18 | }
19 | ]
20 | }
21 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # contentlayer
39 | .contentlayer
40 |
41 | # Media management
42 | .env
43 | backups/
--------------------------------------------------------------------------------
/src/app/icon.tsx:
--------------------------------------------------------------------------------
1 | import { ImageResponse } from 'next/og'
2 |
3 | // 路由段配置
4 | export const runtime = 'edge'
5 |
6 | // 图片元数据
7 | export const contentType = 'image/png'
8 |
9 | // 生成图标的图像响应
10 | export default function Icon() {
11 | return new ImageResponse(
12 | (
13 |
26 | G
27 |
28 | ),
29 | {
30 | width: 32,
31 | height: 32,
32 | }
33 | )
34 | }
--------------------------------------------------------------------------------
/src/lib/images.ts:
--------------------------------------------------------------------------------
1 | // 分类映射
2 | export const CATEGORY_MAP = {
3 | dev: '编程技术',
4 | ai: '人工智能',
5 | build: '构建之路',
6 | reading: '阅读记录',
7 | thoughts: '随想思考',
8 | } as const
9 |
10 | // 默认的封面图片
11 | export const DEFAULT_COVERS = {
12 | dev: '/images/covers/dev.jpg',
13 | ai: '/images/covers/ai.jpg',
14 | build: '/images/covers/build.jpg',
15 | reading: '/images/covers/reading.jpg',
16 | thoughts: '/images/covers/thoughts.jpg',
17 | } as const
18 |
19 | // 根据分类获取封面图片
20 | export function getRandomCover(category: keyof typeof DEFAULT_COVERS): string {
21 | return DEFAULT_COVERS[category] || DEFAULT_COVERS.dev
22 | }
23 |
24 | // 获取分类的中文名称
25 | export function getCategoryName(category: keyof typeof CATEGORY_MAP): string {
26 | return CATEGORY_MAP[category] || category
27 | }
--------------------------------------------------------------------------------
/src/components/common/VideoPlayer.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import React from 'react'
4 |
5 | interface VideoPlayerProps {
6 | src: string
7 | title?: string
8 | className?: string
9 | }
10 |
11 | const VideoPlayer: React.FC = ({ src, title, className = '' }) => {
12 | return (
13 |
14 |
21 |
22 | )
23 | }
24 |
25 | export default VideoPlayer
--------------------------------------------------------------------------------
/src/components/navigation/NavigationSection.tsx:
--------------------------------------------------------------------------------
1 | import { NavigationItem } from './NavigationItem'
2 | import { NavigationSection as NavSectionType } from '@/types/navigation'
3 |
4 | interface NavigationSectionProps {
5 | section: NavSectionType
6 | }
7 |
8 | export function NavigationSection({ section }: NavigationSectionProps) {
9 | const { title, items } = section
10 |
11 | return (
12 |
13 |
14 | {title}
15 |
16 |
17 | {items.map((item) => (
18 | -
19 |
20 |
21 | ))}
22 |
23 |
24 | )
25 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: codebushi
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/src/app/robots.ts:
--------------------------------------------------------------------------------
1 | import { MetadataRoute } from 'next'
2 | import { WEBSITE_HOST_URL } from '@/lib/constants'
3 |
4 | export default function robots(): MetadataRoute.Robots {
5 | return {
6 | rules: [
7 | {
8 | userAgent: '*',
9 | allow: '/',
10 | disallow: ['/api/', '/admin/'],
11 | crawlDelay: 2,
12 | },
13 | {
14 | userAgent: 'GPTBot',
15 | disallow: '/',
16 | },
17 | {
18 | userAgent: 'ChatGPT-User',
19 | disallow: '/',
20 | },
21 | {
22 | userAgent: 'Google-Extended',
23 | disallow: '/',
24 | },
25 | {
26 | userAgent: 'PerplexityBot',
27 | disallow: '/',
28 | },
29 | ],
30 | sitemap: `${WEBSITE_HOST_URL}/sitemap.xml`,
31 | host: WEBSITE_HOST_URL,
32 | }
33 | }
--------------------------------------------------------------------------------
/src/components/common/ThemeSwitch.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useTheme } from "next-themes"
4 | import { FaSun, FaMoon } from "react-icons/fa"
5 | import { cn } from "@/lib/utils"
6 |
7 | interface ThemeSwitchProps {
8 | className?: string
9 | }
10 |
11 | export function ThemeSwitch({ className }: ThemeSwitchProps) {
12 | const { theme, setTheme } = useTheme()
13 |
14 | return (
15 |
25 | )
26 | }
27 |
28 | export default ThemeSwitch
29 |
--------------------------------------------------------------------------------
/src/components/home/AsyncPostSections.tsx:
--------------------------------------------------------------------------------
1 | import { allPosts } from 'contentlayer2/generated'
2 | import { getFeaturedPost, getRecentPosts } from '@/lib/posts'
3 | import { FeaturedSection } from './FeaturedSection'
4 | import { PostList } from './PostList'
5 |
6 | // 添加人工延迟以模拟网络延迟(仅用于开发环境)
7 | const delay = (ms: number) => process.env.NODE_ENV === 'development' ? new Promise(resolve => setTimeout(resolve, ms)) : Promise.resolve()
8 |
9 | export async function FeaturedPostSection() {
10 | const featuredPost = getFeaturedPost(allPosts)
11 | // 添加 100ms 延迟以确保骨架屏动画效果可见
12 | await delay(100)
13 |
14 | if (!featuredPost) return null
15 | return
16 | }
17 |
18 | export async function RecentPostsSection() {
19 | const recentPosts = getRecentPosts(allPosts)
20 | // 添加 200ms 延迟以确保骨架屏动画效果可见
21 | await delay(200)
22 |
23 | return
24 | }
--------------------------------------------------------------------------------
/src/components/common/GradientBackground.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { motion } from 'framer-motion'
4 |
5 | export function GradientBackground() {
6 | return (
7 |
17 | )
18 | }
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | const { withContentlayer } = require('next-contentlayer2')
2 |
3 | /** @type {import('next').NextConfig} */
4 | const nextConfig = {
5 | reactStrictMode: true,
6 | images: {
7 | remotePatterns: [
8 | {
9 | protocol: 'https',
10 | hostname: 'media.ginonotes.com',
11 | },
12 | {
13 | protocol: 'https',
14 | hostname: 'images.unsplash.com',
15 | },
16 | {
17 | protocol: 'https',
18 | hostname: 'res.cloudinary.com',
19 | },
20 | ],
21 | formats: ['image/avif', 'image/webp'],
22 | minimumCacheTTL: 31536000,
23 | deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
24 | imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
25 | // 在开发环境中禁用图片优化以解决私有 IP 问题
26 | unoptimized: process.env.NODE_ENV === 'development',
27 | },
28 | typedRoutes: true,
29 | turbopack: {},
30 | }
31 |
32 | module.exports = withContentlayer(nextConfig)
--------------------------------------------------------------------------------
/src/lib/routes.ts:
--------------------------------------------------------------------------------
1 | import { Route } from 'next'
2 |
3 | // 定义所有可能的路由路径类型
4 | export type PostRoute = Route<`/posts/${string}`>
5 | export type CategoryRoute = Route<`/categories/${string}`>
6 | export type TagRoute = Route<`/tags/${string}`>
7 |
8 | // 定义外部链接类型
9 | export type ExternalRoute = `https://${string}`
10 |
11 | // 定义所有可能的路由类型
12 | export type AppRoute =
13 | | Route<'/'>
14 | | Route<'/about'>
15 | | PostRoute
16 | | CategoryRoute
17 | | TagRoute
18 | | ExternalRoute
19 |
20 | // 类型守卫函数
21 | export const isExternalRoute = (route: string): route is ExternalRoute => {
22 | return route.startsWith('https://')
23 | }
24 |
25 | // 路由生成函数
26 | export const createPostRoute = (slug: string): PostRoute => `/posts/${slug}` as PostRoute
27 | export const createCategoryRoute = (category: string): CategoryRoute => `/categories/${category}` as CategoryRoute
28 | export const createTagRoute = (tag: string): TagRoute => `/tags/${tag}` as TagRoute
--------------------------------------------------------------------------------
/scripts/config.ts:
--------------------------------------------------------------------------------
1 | export const config = {
2 | // 允许的文件类型和 MIME 类型映射
3 | mimeTypes: {
4 | '.jpg': 'image/jpeg',
5 | '.jpeg': 'image/jpeg',
6 | '.png': 'image/png',
7 | '.gif': 'image/gif',
8 | '.webp': 'image/webp',
9 | '.mp4': 'video/mp4',
10 | '.mov': 'video/quicktime',
11 | '.webm': 'video/webm',
12 | },
13 | // 文件大小限制(50MB)
14 | maxFileSize: 50 * 1024 * 1024,
15 | // 并发上传数量
16 | concurrency: 3,
17 | // 缓存控制
18 | cacheControl: 'public, max-age=31536000',
19 | // 备份设置
20 | backup: {
21 | enabled: true,
22 | dir: 'backups',
23 | keepDays: 7, // 保留最近 7 天的备份
24 | },
25 | // 路径设置
26 | paths: {
27 | public: 'public',
28 | images: 'images',
29 | covers: 'covers',
30 | posts: 'posts',
31 | },
32 | // 正则表达式
33 | regex: {
34 | // frontmatter 中的 cover 字段
35 | cover: /cover:\s*(['"]?)([^'"}\s\n]+)(['"]?)/,
36 | // 正文中的媒体引用
37 | media: /!\[.*?\]\(([^)]+)\)|src=["']([^"']+)["']/g,
38 | },
39 | };
--------------------------------------------------------------------------------
/src/components/common/Container.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 |
3 | interface ContainerProps extends React.HTMLAttributes {
4 | size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'default'
5 | }
6 |
7 | const sizeMap = {
8 | sm: 'max-w-2xl', // 672px - 适合简洁的文本内容
9 | md: 'max-w-3xl', // 768px - 适合文章阅读(最佳阅读宽度)
10 | lg: 'max-w-4xl', // 896px - 适合关于页、表单页等
11 | xl: 'max-w-5xl', // 1024px - 适合列表页、分类页
12 | '2xl': 'max-w-6xl', // 1152px - 适合带侧边栏的文章页
13 | '3xl': 'max-w-7xl', // 1280px - 适合宽屏文章阅读
14 | default: 'max-w-5xl' // 1024px - 默认使用 xl,适合大多数页面
15 | }
16 |
17 | export function Container({
18 | size = 'default',
19 | className,
20 | children,
21 | ...props
22 | }: ContainerProps) {
23 | return (
24 |
32 | {children}
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/navigation/NavigationFooter.tsx:
--------------------------------------------------------------------------------
1 | import { FaSearch } from 'react-icons/fa'
2 | import { ThemeSwitch } from '../common/ThemeSwitch'
3 |
4 | interface NavigationFooterProps {
5 | onSearchClick: () => void
6 | }
7 |
8 | export function NavigationFooter({ onSearchClick }: NavigationFooterProps) {
9 | return (
10 |
11 |
12 |
13 |
20 |
21 |
22 | )
23 | }
--------------------------------------------------------------------------------
/src/app/apple-icon.tsx:
--------------------------------------------------------------------------------
1 | import { ImageResponse } from 'next/og'
2 |
3 | // 路由段配置
4 | export const runtime = 'edge'
5 |
6 | // 图片元数据
7 | export const contentType = 'image/png'
8 | export const size = {
9 | width: 180,
10 | height: 180,
11 | }
12 |
13 | // 生成 Apple Touch Icon
14 | export default function Icon() {
15 | return new ImageResponse(
16 | (
17 |
31 | G
32 |
33 | ),
34 | {
35 | ...size,
36 | }
37 | )
38 | }
--------------------------------------------------------------------------------
/src/components/home/FeaturedSection.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from 'contentlayer2/generated'
2 | import { Container } from '@/components/common/Container'
3 | import { FeaturedPost } from '@/components/home/FeaturedPost'
4 |
5 | interface FeaturedSectionProps {
6 | post: Post
7 | }
8 |
9 | export function FeaturedSection({ post }: FeaturedSectionProps) {
10 | if (!post) return null
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | 精选文章
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | )
28 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "es2015",
5 | "lib": [
6 | "dom",
7 | "dom.iterable",
8 | "esnext"
9 | ],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": false,
13 | "noEmit": true,
14 | "esModuleInterop": true,
15 | "module": "esnext",
16 | "moduleResolution": "bundler",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "jsx": "react-jsx",
20 | "incremental": true,
21 | "plugins": [
22 | {
23 | "name": "next"
24 | }
25 | ],
26 | "paths": {
27 | "@/*": [
28 | "./src/*"
29 | ],
30 | "contentlayer2/generated": [
31 | "./.contentlayer/generated"
32 | ]
33 | }
34 | },
35 | "include": [
36 | "next-env.d.ts",
37 | "**/*.ts",
38 | "**/*.tsx",
39 | ".next/types/**/*.ts",
40 | ".contentlayer/generated",
41 | "scripts/**/*.ts",
42 | ".next/dev/types/**/*.ts"
43 | ],
44 | "exclude": [
45 | "node_modules"
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/src/components/common/BlurImage.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Image from 'next/image'
4 | import { useState } from 'react'
5 | import { cn } from '@/lib/utils'
6 |
7 | interface BlurImageProps {
8 | src: string
9 | alt: string
10 | width?: number
11 | height?: number
12 | fill?: boolean
13 | sizes?: string
14 | priority?: boolean
15 | className?: string
16 | }
17 |
18 | export function BlurImage({
19 | src,
20 | alt,
21 | width,
22 | height,
23 | fill,
24 | sizes,
25 | priority,
26 | className,
27 | }: BlurImageProps) {
28 | const [isLoading, setLoading] = useState(true)
29 |
30 | return (
31 | setLoading(false)}
47 | />
48 | )
49 | }
--------------------------------------------------------------------------------
/src/app/feed.xml/route.ts:
--------------------------------------------------------------------------------
1 | import { WEBSITE_HOST_URL } from '@/lib/constants'
2 | import { allPosts } from 'contentlayer2/generated'
3 | import RSS from 'rss'
4 |
5 | export async function GET() {
6 | const feed = new RSS({
7 | title: 'Gino Notes',
8 | description: '记录学习和思考的内容,分享技术、人工智能、产品设计和生活随想。',
9 | site_url: WEBSITE_HOST_URL,
10 | feed_url: `${WEBSITE_HOST_URL}/feed.xml`,
11 | language: 'zh-CN',
12 | pubDate: new Date(),
13 | copyright: `All rights reserved ${new Date().getFullYear()}, Gino Zhang`,
14 | })
15 |
16 | allPosts
17 | .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
18 | .forEach((post) => {
19 | feed.item({
20 | title: post.title,
21 | description: post.description,
22 | url: `${WEBSITE_HOST_URL}${post.url}`,
23 | date: new Date(post.date),
24 | categories: [post.category],
25 | author: 'Gino Zhang',
26 | })
27 | })
28 |
29 | return new Response(feed.xml({ indent: true }), {
30 | headers: {
31 | 'Content-Type': 'application/xml',
32 | },
33 | })
34 | }
--------------------------------------------------------------------------------
/src/components/home/PostList.tsx:
--------------------------------------------------------------------------------
1 | import { Post } from 'contentlayer2/generated'
2 | import { PostCard } from '@/components/common/PostCard'
3 |
4 | interface PostListProps {
5 | posts: Post[]
6 | }
7 |
8 | export function PostList({ posts }: PostListProps) {
9 | if (!posts.length) return null
10 |
11 | return (
12 |
13 |
14 |
15 |
16 | 最新文章
17 |
18 |
19 |
20 |
21 | {posts.map((post, index) => (
22 |
25 | ))}
26 |
27 |
28 |
29 | )
30 | }
--------------------------------------------------------------------------------
/src/components/post/PostContent.tsx:
--------------------------------------------------------------------------------
1 | interface PostContentProps {
2 | children: React.ReactNode
3 | }
4 |
5 | export const PostContent = ({ children }: PostContentProps) => {
6 | return (
7 |
16 | {children}
17 |
18 | )
19 | }
--------------------------------------------------------------------------------
/src/components/post/ReadingProgress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useEffect, useState } from 'react'
4 |
5 | export function ReadingProgress() {
6 | const [progress, setProgress] = useState(0)
7 |
8 | useEffect(() => {
9 | const updateProgress = () => {
10 | const content = document.querySelector('article')
11 | if (!content) return
12 |
13 | const contentBox = content.getBoundingClientRect()
14 | const totalHeight = contentBox.height
15 | const windowHeight = window.innerHeight
16 | const current = window.scrollY + windowHeight - contentBox.top
17 |
18 | // 计算阅读进度百分比
19 | const percent = Math.min(Math.max(current / totalHeight, 0), 1) * 100
20 | setProgress(percent)
21 | }
22 |
23 | window.addEventListener('scroll', updateProgress)
24 | updateProgress()
25 |
26 | return () => window.removeEventListener('scroll', updateProgress)
27 | }, [])
28 |
29 | return (
30 |
36 | )
37 | }
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
8 | export function formatDate(date: string) {
9 | return new Date(date).toLocaleDateString('zh-CN', {
10 | year: 'numeric',
11 | month: 'long',
12 | day: 'numeric',
13 | })
14 | }
15 |
16 | export function calculateReadingTime(text: string): number {
17 | const wordsPerMinute = 200 // 英文阅读速度:每分钟200词
18 | const charsPerMinute = 400 // 中文阅读速度:每分钟400字
19 |
20 | // 计算英文单词数
21 | const wordCount = text.match(/[a-zA-Z]+/g)?.length || 0
22 |
23 | // 计算中文字符数
24 | const chineseCount = (text.match(/[\u4e00-\u9fa5]/g) || []).length
25 |
26 | // 分别计算中英文阅读时间
27 | const englishTime = Math.ceil(wordCount / wordsPerMinute)
28 | const chineseTime = Math.ceil(chineseCount / charsPerMinute)
29 |
30 | // 取较大值,并确保至少返回1分钟
31 | return Math.max(englishTime + chineseTime, 1)
32 | }
33 |
34 | // 将中文标题转换为合法的 ID
35 | export function slugify(str: string) {
36 | return str
37 | .toLowerCase()
38 | .replace(/\s+/g, '-') // 替换空格为连字符
39 | .replace(/[^\w\u4e00-\u9fa5-]/g, '') // 只保留字母、数字、中文和连字符
40 | .replace(/--+/g, '-') // 替换多个连字符为单个连字符
41 | }
42 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import { Suspense } from 'react'
2 | import { GradientBackground } from '@/components/common/GradientBackground'
3 | import { Hero } from '@/components/home/Hero'
4 | import { Container } from '@/components/common/Container'
5 | import { FeaturedPostSection, RecentPostsSection } from '@/components/home/AsyncPostSections'
6 | import { FeaturedPostSkeleton, PostListSkeleton } from '@/components/common/Skeleton'
7 |
8 | export default function Home() {
9 | return (
10 |
11 |
12 |
13 | {/* Hero Section - 静态内容,无需 Suspense */}
14 |
15 |
16 | {/* 主要内容区域 */}
17 |
18 |
19 | {/* 特色文章 - 使用 Suspense */}
20 | }>
21 |
22 |
23 |
24 | {/* 最新文章列表 - 使用 Suspense */}
25 | }>
26 |
27 |
28 |
29 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/components/common/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Component, ErrorInfo, ReactNode } from 'react'
4 |
5 | interface Props {
6 | children?: ReactNode
7 | fallback?: ReactNode
8 | }
9 |
10 | interface State {
11 | hasError: boolean
12 | error?: Error
13 | }
14 |
15 | export class ErrorBoundary extends Component {
16 | public state: State = {
17 | hasError: false,
18 | }
19 |
20 | public static getDerivedStateFromError(error: Error): State {
21 | return { hasError: true, error }
22 | }
23 |
24 | public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
25 | console.error('Uncaught error:', error, errorInfo)
26 | }
27 |
28 | public render() {
29 | if (this.state.hasError) {
30 | return (
31 | this.props.fallback || (
32 |
33 |
34 | 出错了!
35 |
36 |
37 | {this.state.error?.message || '发生了一个错误,请稍后再试。'}
38 |
39 |
40 | )
41 | )
42 | }
43 |
44 | return this.props.children
45 | }
46 | }
--------------------------------------------------------------------------------
/posts/reading/chat/20250222_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】大语言模型逐字输出效果的原理是什么?
3 | date: 2025-02-22
4 | description: 本文探讨了在 Next.js App Router 框架下实现大语言模型(LLM)流式输出(逐字输出)的原理与实践。针对 LLM 应用中常见的长文本生成等待问题,本文详细介绍了流式输出的技术实现,深入对比了 Server-Sent Events (SSE) 和 WebSockets 两种主流方案,并重点阐述了 SSE 在此场景下的优势与应用。
5 | category: reading
6 | tags: Daily Chat, Next.js, LLM, SSE, WebSockets, Streaming Output
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250222.jpeg
8 | slug: daily-chat-20250222
9 | ---
10 |
11 | ## 引言
12 |
13 | 在与大语言模型(LLM)的交互过程中,我们经常能体验到一种独特的"逐字输出"效果,就像是与一位真实的对话者进行实时交流。这种设计不仅让交互体验更加自然,还有效缓解了用户在等待长文本生成时的焦虑感。
14 |
15 | 我一直对这种"逐字输出"背后的技术实现充满好奇,这背后具体的原理是什么,在项目里要怎么实现这个效果?今天,我就这个问题向 ChatGPT 提问,看看它能给我什么答案。
16 |
17 | ## 对话内容
18 |
19 | 
20 |
21 | ## 小记
22 |
23 | - **流式输出的本质:** 大语言模型的文本生成过程采用了流式处理机制,类似于"水流"般持续输出。模型不是一次性生成所有内容,而是将文本分成多个小片段,按照生成顺序逐步推送。这种机制让用户能够实时看到生成过程,大大提升了交互体验。
24 |
25 | - **SSE 与 WebSockets 的技术对比:** SSE (Server-Sent Events) 好比一条单向高速公路,专门用于服务器向客户端推送数据,结构简单且资源消耗低。相比之下,WebSockets 则像一座双向立交桥,支持服务器和客户端之间的实时双向通信。在单纯的文本流式输出场景下,SSE 的轻量级特性使其成为更优的选择。
26 |
27 | - **Next.js App Router 的最佳实践:** 在 Next.js App Router 框架中,SSE 的实现方式与框架的设计理念高度契合。Next.js 提供了完善的 API 支持,使得流式输出的实现变得简单直接。这种技术方案不仅开发成本低,而且能够提供更好的性能表现。
28 |
29 | - **OpenAI API 的流式集成:** OpenAI 的 API 已经内置了完善的流式输出支持。通过简单的配置(设置 stream: true),API 就能自动按序列推送文本片段。开发者只需要在 Next.js 中编写相应的接收和展示逻辑,就能实现流畅的逐字输出效果。
--------------------------------------------------------------------------------
/src/components/post/Comments.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useEffect, useState } from 'react'
4 | import { useTheme } from 'next-themes'
5 | import Giscus from '@giscus/react'
6 |
7 | interface CommentsProps {
8 | className?: string
9 | }
10 |
11 | export function Comments({ className }: CommentsProps) {
12 | const { theme, resolvedTheme } = useTheme()
13 | const [mounted, setMounted] = useState(false)
14 |
15 | // 确保组件已挂载,避免 hydration 错误
16 | useEffect(() => {
17 | setMounted(true)
18 | }, [])
19 |
20 | if (!mounted) {
21 | return (
22 |
28 | )
29 | }
30 |
31 | // 根据主题选择 Giscus 主题
32 | const giscusTheme = theme === 'dark' || resolvedTheme === 'dark'
33 | ? 'dark'
34 | : 'light'
35 |
36 | return (
37 |
38 |
52 |
53 | )
54 | }
55 |
--------------------------------------------------------------------------------
/src/lib/posts.ts:
--------------------------------------------------------------------------------
1 | import { Post } from 'contentlayer2/generated'
2 | import { compareDesc } from 'date-fns'
3 |
4 | /**
5 | * 获取按日期排序的文章列表
6 | * 对于日期相同的文章,按标题排序
7 | */
8 | export function getSortedPosts(posts: Post[]) {
9 | return posts.sort((a, b) => {
10 | // 首先按日期降序排序
11 | const dateComparison = compareDesc(new Date(a.date), new Date(b.date))
12 |
13 | // 如果日期相同,则按标题升序排序
14 | if (dateComparison === 0) {
15 | return a.title.localeCompare(b.title, 'zh-CN')
16 | }
17 |
18 | return dateComparison
19 | })
20 | }
21 |
22 | /**
23 | * 获取文章分类统计
24 | */
25 | export function getCategoryStats(posts: Post[]) {
26 | return posts.reduce((acc, post) => {
27 | acc[post.category] = (acc[post.category] || 0) + 1
28 | return acc
29 | }, {} as Record)
30 | }
31 |
32 | /**
33 | * 获取最新的特色文章
34 | */
35 | export function getFeaturedPost(posts: Post[]) {
36 | // 仅获取特色文章中最新的一篇
37 | const featuredPosts = getSortedPosts(posts.filter(post => post.featured))
38 | if (featuredPosts.length > 0) {
39 | return featuredPosts[0]
40 | }
41 |
42 | // 如果没有任何特色文章,则获取最新的文章
43 | return getSortedPosts(posts)[0]
44 | }
45 |
46 | /**
47 | * 获取最近的 N 篇文章(不包含最新的特色文章)
48 | */
49 | export function getRecentPosts(posts: Post[], count: number = 10) {
50 | // 获取最新的特色文章
51 | const featuredPost = getFeaturedPost(posts)
52 |
53 | // 从最新的文章(排除特色文章)中选取 N 篇
54 | const recentPosts = getSortedPosts(posts.filter(post => post._id !== featuredPost._id))
55 | return recentPosts.slice(0, count)
56 | }
--------------------------------------------------------------------------------
/posts/reading/ai/20250221_thoughts_on_ai_coexistence_evolution.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 读《如何与 AI 共生:进化你的不可替代性》有感
3 | date: 2025-02-21
4 | description: DeepSeek 等 AI 工具的崛起引发职业焦虑?文章提出关于 AI 时代人类如何提升自身不可替代性的四个关键策略:提问、学习、实践、拥抱不完美,助你与 AI 共赢。
5 | category: reading
6 | tags: AI, DeepSeek, Career, Human-AI Collaboration
7 | cover: https://media.ginonotes.com/covers/cover-ai-coexistence-evolution.jpeg
8 | slug: thoughts-on-ai-coexistence-evolution
9 | ---
10 |
11 | 今天读到一篇文章[《如何与 AI 共生:进化你的不可替代性》](https://www.woshipm.com/zhichang/6182802.html),讲的是像 DeepSeek 这样的 AI 工具火了之后,大家开始讨论 AI 和人类到底该怎么合作,当然,也少不了担心自己的饭碗。文章没光顾着贩卖焦虑,而是从“AI 时代,人如何进化得更厉害,让 AI 没法取代”的角度,提了四个挺关键的点子,我觉得挺有启发,和大家分享一下:
12 |
13 | 1. **别光想着找答案,多问“为什么”:** 文章里说,AI 特别擅长解决问题,但人类的价值在于“提出问题”。以前上学考试,都练成了“答案专家”,但在 AI 时代,标准答案不值钱了。AI 几秒钟就能写篇论文,但它不会自己问:“为啥这个领域好久没啥新突破了?”所以,需要练就刨根问底的本事,把问题拆解得更细,还要能把不同领域的知识串起来问。
14 |
15 | 2. **活到老,学到老,把自己变成“升级版”:** 以前在职场,经验就是金饭碗;但在 AI 眼里,经验就是一堆数据,分分钟能复制。文章里举了个例子,一个老文案因为“风格老套”被 AI 顶替了,另一个 95 后新人却因为“能用 AI 玩转科幻和京剧混搭”升职了。所以,得有个“T 型能力”:一方面得在一个领域钻研得特别深,深到 AI 都搞不定;另一方面也得多学点其他领域的知识,搞点跨界。
16 |
17 | 3. **别光说不练,多去真实世界里“折腾”:** AI 算法再牛,也比不上人在真实世界里摸爬滚打出来的“手感”。文章里讲了个故事,一个法国人用 AI 预测美国大选,赚了一大笔钱。他赢的原因不是模型多厉害,而是他比 AI 更懂“人心”。所以,得多去实践,设计师不能光让 AI 出图,得亲自去看看大家到底喜欢啥;产品经理不能光靠 AI 写需求文档,得去听听用户是怎么吐槽的。
18 |
19 | 4. **拥抱“不完美”,有时候“缺点”反而是亮点:** 有趣的是,大家喜欢 DeepSeek 的原因竟然是它会“胡说八道”,但这反而让它更像一个真实的人。AI 追求精确,而人类的魅力在于“不按套路出牌”。所以,可以试试跨界混搭,或者每天留点时间放空自己,让思维自由飞翔。
20 |
21 | 这篇文章最后总结说,AI 不是来替代人类的,而是来重新定义“人该干啥”。AI 负责“干活”,人就该专注“想问题”;AI 提供“数据”,人就该挖掘“意义”;AI 追求“精确”,人就该保留“意外”。真正厉害的人,能像水一样,既能适应 AI 的算法,又能不断变化,永远有改变规则的力量。“不被替代”的答案,不在技术层面,而在人能否守住生而为人的特质——对未知的好奇、对成长的渴望,以及对不完美却鲜活的创造力的信仰。
--------------------------------------------------------------------------------
/posts/reading/chat/20250226_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】人机交互和智能体交互的未来是什么样的?
3 | date: 2025-02-26
4 | description: 本报告调研了人机交互和智能体间交互模式的现状、发展趋势及未来应用。报告指出,人机交互正朝着更自然、多模态、智能化的方向发展,而智能体交互则强调去中心化、自主决策和群体智能。未来,人机交互与智能体交互将深度融合,构建人机共融的智能世界。本报告的研究对于理解智能系统的交互机制、设计更高效的交互界面、开发更智能的协作系统具有重要意义。
5 | category: reading
6 | tags: Daily Chat, HCI, Agent, UX
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250226.jpeg
8 | slug: daily-chat-20250226
9 | ---
10 |
11 | ## 引言
12 |
13 | 在日益智能化的世界中,无论是人与机器的互动,还是机器与机器之间的协同,交互模式都扮演着至关重要的角色。从我们日常使用的智能手机、智能家居,到复杂的工业自动化系统、无人驾驶车队,各种形式的交互无处不在,深刻影响着系统的效率、可用性和用户体验。
14 |
15 | 人机交互(HCI)和智能体之间的交互模式到底会如何塑造未来的智能世界?不同的人机交互方式各有什么样的优势和局限?如何设计出更自然、高效、以人为本的交互界面?在多智能体系统中,各个智能体又是如何相互沟通、协作与竞争,以实现共同的目标?这些交互模式背后又有哪些关键的技术挑战和伦理考量?
16 |
17 | 带着这些问题,我让 ChatGPT 进行了一番深入的调研,并整理成这份报告。
18 |
19 | ## 对话内容
20 |
21 | 
22 | 
23 | 
24 |
25 | ## 小记
26 |
27 | - **人机交互像"多语种翻译":** 计算机和人交流,就像不同语言的人对话。人机交互方式有很多,像打字(文本)、鼠标点(图形界面)、说话(语音)、比划(手势)、甚至动脑子(脑机接口),就像掌握了多种语言,可以和不同的人交流。
28 |
29 | - **最好的交互是"无感":** 理想的人机交互,就像和一个老朋友聊天,自然顺畅,不用刻意去想怎么说怎么做。 未来的目标就是让交互越来越“隐形”,让技术融入生活,而不是让人去适应技术。
30 |
31 | - **多模态交互是"全方位沟通":** 就像和人交流不光用嘴说,还会用眼神、手势一样,多模态交互就是把语音、手势、表情等多种方式结合起来,让机器能更全面地理解人的意思,沟通更高效。
32 |
33 | - **智能体交互像"团队协作":** 多个智能体(比如一群无人机、一堆机器人)一起工作,就像一个团队。它们之间要沟通、协调,有时还要竞争,才能完成任务。
34 |
35 | - **未来是"人机共融":** 未来,人机交互会更自然、智能,智能体之间的协作也会更紧密。人、机器、智能体会形成一个互相融合的整体,共同创造更美好的生活。
--------------------------------------------------------------------------------
/posts/reading/chat/20250223_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】AI SaaS 产品的定价策略应该如何选择?
3 | date: 2025-02-23
4 | description: 本文探讨了当前热门 AI SaaS 产品常用的三种定价策略:订阅制、按使用量付费和免费增值模式。通过分析每种模式的适用场景、优缺点和用户接受度,并结合具体案例,为 AI SaaS 初创公司提供定价策略方面的实用建议。
5 | category: reading
6 | tags: Daily Chat, AI SaaS, Pricing Strategies, Subscription Model, Usage-Based Pricing, Free-Plus-Paid Model
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250223.jpeg
8 | slug: daily-chat-20250223
9 | ---
10 |
11 | ## 引言
12 |
13 | 在与各种 AI 应用的互动中,我们越来越熟悉各种巧妙的设计和功能。其中,定价策略就像 AI 产品幕后的"大脑",决定着产品的市场表现和用户接受度。不同的定价模式,就像不同的"性格",吸引着不同的用户群体。
14 |
15 | 我一直对 AI SaaS 产品的定价策略充满好奇,这些产品是如何决定自己的"身价"的?不同的定价模式对用户和公司有什么影响?初创公司又该如何选择适合自己的定价策略?
16 |
17 | 今天,我就这些问题进行了一番调研,并与 ChatGPT 深入探讨,一起来看看这些问题的答案。
18 |
19 | ## 对话内容
20 |
21 | 
22 | 
23 |
24 | ## 小记
25 |
26 | - **定价就像选套餐:** AI 产品的定价方式有很多种,就像去餐厅点菜一样。你可以选择"包月畅享"(订阅制),适合经常使用的人;也可以"按需点单"(按使用量付费),用多少付多少,更加灵活;还可以先"免费试吃"(免费增值),觉得好再付费升级。没有最好的套餐,只有最适合你的。
27 |
28 | - **免费的午餐,也要算好账:** 很多 AI 产品提供免费版,这能吸引很多人来尝试。但公司也要考虑成本,免费用户多了,服务器、算力等开销也会很大。因此,免费版通常会有一些限制,或者需要巧妙设计,让一部分免费用户愿意升级成付费用户,这样公司才能持续运营。
29 |
30 | - **定价不是一锤子买卖:** 定价策略需要持续优化和调整。要经常关注用户反馈,分析数据,了解用户对价格的接受程度,同时也要留意竞争对手的价格变化。根据这些情况,及时调整价格策略,找到用户和公司都能接受的平衡点。
31 |
32 | - **价值决定价格:** 最根本的,产品的定价要和它能给用户带来的价值相匹配。用户觉得你的产品能帮他们省时间、省钱、提高效率、或者解决问题,他们才愿意付费。因此,定价前一定要想清楚,你的产品最大的价值是什么,如何把这个价值清晰地传达给用户。
33 |
34 | - **灵活组合,效果更佳:** 很多 AI 产品并不是只用一种定价方式,而是把几种方式结合起来。比如,提供一个基础的免费版,再加上高级功能的订阅版;或者订阅套餐里包含一定的免费使用额度,超出的部分再额外收费。这样可以满足不同用户的需求,让定价更灵活。
--------------------------------------------------------------------------------
/documents/tasks.md:
--------------------------------------------------------------------------------
1 | # GinoNotes 博客优化任务清单
2 |
3 | ## 布局优化
4 |
5 | ### 首页
6 |
7 | - [ ] 优化首页加载性能,考虑使用 React Suspense 和 loading 状态
8 | - [ ] 实现无限滚动加载更多文章,替代分页
9 | - [ ] 添加文章分类快速筛选功能
10 | - [ ] 考虑添加热门标签云展示
11 |
12 | ### 文章详情页
13 |
14 | - [ ] 优化文章目录的滚动交互,添加当前阅读位置高亮
15 | - [ ] 实现文章进度条,显示阅读进度
16 | - [ ] 添加文章分享功能
17 | - [ ] 优化代码块的展示,添加代码复制功能
18 | - [ ] 考虑添加文章评论功能
19 |
20 | ### 响应式设计
21 |
22 | - [ ] 优化移动端导航体验
23 | - [ ] 改进移动端文章目录的展示方式
24 | - [ ] 确保所有图片在不同设备上都能正确展示
25 |
26 | ## 样式优化
27 |
28 | ### 主题
29 |
30 | - [ ] 完善深色模式的配色方案
31 | - [ ] 添加主题切换动画
32 | - [ ] 考虑提供多套主题配色方案
33 |
34 | ### 动效
35 |
36 | - [ ] 优化页面切换动画
37 | - [ ] 添加滚动时的渐入效果
38 | - [ ] 改进图片加载的过渡效果
39 |
40 | ### 交互
41 |
42 | - [ ] 优化按钮和链接的悬停效果
43 | - [ ] 添加页面滚动到顶部的按钮
44 | - [ ] 改进搜索框的交互体验
45 |
46 | ## 字体优化
47 |
48 | ### 排版
49 |
50 | - [ ] 优化中英文混排的间距
51 | - [ ] 考虑使用更好的中文字体
52 | - [ ] 优化代码字体的显示效果
53 |
54 | ### 阅读体验
55 |
56 | - [ ] 优化文章正文的行高和段落间距
57 | - [ ] 改进标题的层级视觉效果
58 | - [ ] 优化列表和引用块的样式
59 |
60 | ## 性能优化
61 |
62 | ### 加载性能
63 |
64 | - [ ] 优化图片加载策略
65 | - [ ] 实现组件的懒加载
66 | - [ ] 优化字体加载
67 |
68 | ### SEO
69 |
70 | - [ ] 完善 meta 标签
71 | - [ ] 添加结构化数据
72 | - [ ] 优化 sitemap
73 |
74 | ## 功能增强
75 |
76 | ### 搜索
77 |
78 | - [ ] 优化搜索算法
79 | - [ ] 添加搜索结果高亮
80 | - [ ] 支持按标签和分类筛选
81 |
82 | ### 内容展示
83 |
84 | - [ ] 添加文章系列功能
85 | - [ ] 优化相关文章推荐算法
86 | - [ ] 添加文章归档页面
87 |
88 | ## 技术改进
89 |
90 | ### 代码质量
91 |
92 | - [ ] 统一组件的 Props 类型定义
93 | - [ ] 提取可复用的样式到组件
94 | - [ ] 优化 Tailwind 类名的组织
95 |
96 | ### 工程化
97 |
98 | - [ ] 添加 ESLint 规则
99 | - [ ] 完善 TypeScript 类型定义
100 | - [ ] 添加自动化测试
101 |
--------------------------------------------------------------------------------
/src/components/navigation/NavigationHeader.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import { FaBars, FaSearch } from 'react-icons/fa'
3 | import { ThemeSwitch } from '../common/ThemeSwitch'
4 |
5 | interface NavigationHeaderProps {
6 | onMenuClick: () => void
7 | onSearchClick: () => void
8 | }
9 |
10 | export function NavigationHeader({ onMenuClick, onSearchClick }: NavigationHeaderProps) {
11 | return (
12 |
43 | )
44 | }
--------------------------------------------------------------------------------
/documents/design.md:
--------------------------------------------------------------------------------
1 | # GinoNotes 博客设计规范
2 |
3 | ## 布局规范
4 |
5 | ### 整体布局
6 | - 采用响应式设计,适配桌面端和移动端
7 | - 最大宽度限制在合理范围内,确保良好的阅读体验
8 | - 使用 Container 组件统一内容区域的宽度和边距
9 |
10 | ### 文章列表页
11 | - 精选文章(Featured Post)完整展示文章摘要
12 | - 最新文章列表采用网格布局,每行显示 2 篇文章
13 | - 文章卡片(Post Card)展示封面图、标题、3 行摘要、分类和日期
14 |
15 | ### 文章详情页
16 | - 采用单栏布局,最大宽度限制确保阅读舒适度
17 | - 右侧固定显示文章目录(Table of Contents)
18 | - 底部显示相关文章推荐
19 |
20 | ## 样式规范
21 |
22 | ### 颜色系统
23 | - 主色:使用 Tailwind 的 primary 颜色
24 | - 文本颜色:
25 | - 标题:text-gray-900 (dark: text-gray-100)
26 | - 正文:text-gray-600 (dark: text-gray-400)
27 | - 次要文本:text-muted-foreground
28 | - 背景色:
29 | - 主背景:white/50 (dark: gray-800/50)
30 | - 卡片:white/50 (dark: gray-800/50)
31 | - 渐变:from-blue-50 via-indigo-50 to-emerald-50
32 |
33 | ### 圆角和阴影
34 | - 卡片圆角:rounded-2xl
35 | - 图片圆角:rounded-xl
36 | - 标签圆角:rounded-full
37 | - 阴影:shadow-md (hover: shadow-lg)
38 |
39 | ## 字体规范
40 |
41 | ### 标题
42 | - 文章标题:text-3xl md:text-4xl lg:text-5xl,font-bold
43 | - 卡片标题:text-xl,font-semibold
44 | - 精选文章标题:text-3xl lg:text-4xl,font-bold
45 | - 章节标题:text-2xl lg:text-3xl,font-bold
46 |
47 | ### 正文
48 | - 文章内容:text-base lg:text-lg,leading-7 lg:leading-8
49 | - 卡片摘要:text-base,line-clamp-3
50 | - 精选文章摘要:text-lg lg:text-xl
51 | - 元信息(日期、阅读时间等):text-sm
52 |
53 | ### 其他文本
54 | - 分类标签:text-sm
55 | - 标签:text-xs
56 | - 导航链接:text-sm
57 |
58 | ## 间距规范
59 |
60 | ### 垂直间距
61 | - 章节间距:space-y-8
62 | - 卡片内部间距:space-y-4
63 | - 文章段落间距:space-y-6
64 |
65 | ### 水平间距
66 | - 容器边距:px-4 sm:px-6 lg:px-8
67 | - 卡片内边距:p-6
68 | - 元素间距:gap-2 或 gap-4
69 |
70 | ## 交互规范
71 |
72 | ### 悬停效果
73 | - 链接:颜色变化,使用 hover:text-primary
74 | - 卡片:阴影加深
75 | - 图片:轻微放大 scale-105
76 |
77 | ### 过渡动画
78 | - 使用 transition-all 实现平滑过渡
79 | - 动画持续时间:duration-300
80 | - 图片缩放:transition-transform
--------------------------------------------------------------------------------
/posts/reading/chat/20250219_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】如何全面评测大语言模型?
3 | date: 2025-02-19
4 | description: 通过与 ChatGPT 的对话,学习大语言模型的评测方法。从 MMLU、SuperGLUE、HumanEval 等基准测试,到 Chatbot Arena 等评测平台,了解模型评测的基本概念和发展趋势。
5 | category: reading
6 | tags: Daily Chat, LLM Evaluation, Benchmark, MMLU, HumanEval, Chatbot Arena, AI Assessment
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250219.jpeg
8 | slug: daily-chat-20250219
9 | ---
10 |
11 | ## 引言
12 |
13 | 今天我咬牙开通了 ChatGPT Pro 的订阅。为了最大化这项投资的价值,我计划开启一个新的系列:每天与 ChatGPT 深入探讨一个重要话题,并将这些有见地的对话整理成文,分享给大家。
14 |
15 | 今天,我选择了一个与 AI 发展密切相关的话题:大语言模型的全面评测方法。这个问题的灵感来自于最近 Grok 3 的发布。在各大模型发布时,我们经常会看到各种评测数据和对比图表,比如下面这张来自 Grok 3 发布会的评测结果:
16 |
17 | 
18 |
19 | 这些评测数据背后究竟有着怎样的评估体系?评测指标是如何设定的?评测的基本原理是什么?更进一步,当我们需要将 AI 技术整合到实际产品中时,应该如何设计一个全面而有效的评测方案?带着这些问题,我让 ChatGPT 帮我进行了深度研究。
20 |
21 | ## 对话内容
22 |
23 | 
24 |
25 | 
26 |
27 | 
28 |
29 | 
30 |
31 | 
32 |
33 | ## 小记
34 |
35 | - 评测方法多样化:现在评测大模型,不仅看它在考试(各种数据集)里答题准不准,还会让人和模型聊聊天(像Chatbot Arena那样),或者让模型之间互相打分,方法更多样了。
36 |
37 | - 考察维度更全面:除了看模型聪明不聪明(准确性),还要看它靠不靠谱(安全性、会不会瞎编)、好不好用(流畅性、能不能听懂人话)、省不省钱(计算效率)等等。
38 |
39 | - 模型自己当评委:厉害的大模型(比如GPT-4)可以给别的模型打分,这比人来打分快得多,而且打得还挺准,特别适合用来评估聊天机器人。
40 |
41 | - 评测推动技术进步:大模型公司都想在评测里拿高分,这就像一场比赛,促使大家不断改进模型,也让用户能选到更好的模型。
42 |
43 | - 评测方式不断进步:评测本身也在变得更智能、更实用,会更像真实使用场景,也会更透明,让大家更放心。
--------------------------------------------------------------------------------
/src/components/navigation/NavigationProfile.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image'
2 | import { FaEnvelope } from 'react-icons/fa'
3 |
4 | export function NavigationProfile() {
5 | return (
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 | Gino
21 |
22 |
31 |
32 | Just be funny.
33 |
34 |
35 |
36 |
37 | )
38 | }
--------------------------------------------------------------------------------
/posts/reading/chat/20250306_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】MCP 是什么?有什么用?
3 | date: 2025-03-06
4 | description: 大型语言模型 (LLM) 能力虽强,但缺乏与外部世界有效连接的标准化方式。模型上下文协议 (MCP) 正是为了解决这一问题而生,它定义了一套开放协议,让 AI 模型能够以统一的方式扩展上下文信息、调用外部工具,从而突破自身局限,更好地服务于各种应用场景。
5 | category: reading
6 | tags: Daily Chat, MCP, LLM, Tool, Agent, Integration
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250306.jpeg
8 | slug: daily-chat-20250306
9 | ---
10 |
11 | ## 引言
12 |
13 | 在人工智能浪潮汹涌澎湃的当下,大型语言模型(LLM)正以前所未有的速度重塑人机交互的未来。它们在自然语言理解和生成方面展现出惊人的能力,然而,LLM 潜力的充分发挥,很大程度上受限于其与外部世界的连接能力 -- 如何有效获取必要的上下文信息,以及如何灵活调用各种工具和服务,已成为制约 LLM 应用发展的关键瓶颈。
14 |
15 | 长期以来,不同的 AI 厂商和应用开发者各自为政,针对特定模型或平台构建定制化的工具集成方案,导致生态碎片化严重,资源难以复用,开发成本居高不下。正是在这样的背景下,模型上下文协议(Model Context Protocol, MCP)应运而生。它犹如 AI 应用领域的 "USB-C 接口",旨在建立一套开放、标准化的协议,规范应用程序如何向 LLM 提供上下文数据和调用外部工具,从而从根本上变革 LLM 与外部世界交互的方式。
16 |
17 | MCP 的出现意味着什么?它能否真正打破 AI 工具集成的壁垒,构建一个互联互通、繁荣发展的 AI 应用生态?它又将如何赋能开发者,加速各种创新型 AI 应用的落地?为了深入剖析 MCP 的技术架构、核心优势、应用场景,以及它在 AI 生态系统中所扮演的角色,我让 ChatGPT 进行了一次深入研究,并形成了这份详尽的报告,欢迎阅读。
18 |
19 | ## 对话内容
20 |
21 | 
22 | 
23 | 
24 |
25 | ## 小记
26 |
27 | - **开放标准协议,促进行业互联互通**: 模型上下文协议 (MCP) 是一套公开的技术规范,任何 AI 模型和应用都可以遵循,旨在打破当前 AI 工具集成各自为政的局面,构建一个更加开放和互通的 AI 生态系统。
28 |
29 | - **标准化 LLM 上下文扩展和工具调用**: MCP 的核心在于定义了 "资源" 和 "工具" 这两个关键组件,为大型语言模型 (LLM) 提供了标准化的方式来获取外部数据作为上下文,并调用外部功能来扩展自身能力,从而解决 LLM 能力局限性问题。
30 |
31 | - **客户端-服务器架构,保障数据安全**: MCP 采用客户端 (LLM 应用) 和服务器 (数据/工具提供方) 分离的架构,通过标准协议进行通信。这种架构允许用户在本地或企业内部署 MCP 服务器,安全地将私有数据提供给 LLM 使用,无需将敏感数据直接暴露给模型提供商。
32 |
33 | - **解决核心痛点,应用场景广泛**: MCP 主要解决 LLM 应用开发中普遍存在的上下文不足、数据访问安全、工具集成复杂等痛点。因此,MCP 在代码助手、知识库问答、自然语言数据库查询、智能助手等众多场景中都展现出巨大的应用潜力,能够显著提升 LLM 的实用性。
34 |
35 | - **生态系统正在兴起,未来值得期待**: 目前包括 Claude、Cursor、Windsurf 等在内的多款主流 AI 工具和平台已经开始支持 MCP,并且有越来越多的开发者和社区加入到 MCP 生态建设中。随着 MCP 标准的不断完善和生态的日益壮大,它有望成为未来 AI 应用开发的基础设施,推动 AI 技术更深入地融入各行各业。
--------------------------------------------------------------------------------
/src/components/home/PostCard.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import Image from 'next/image'
3 | import { Post } from 'contentlayer2/generated'
4 | import { formatDate } from '@/lib/utils'
5 | import { getCategoryName, CATEGORY_MAP } from '@/lib/images'
6 |
7 | interface PostCardProps {
8 | post: Post
9 | }
10 |
11 | export function PostCard({ post }: PostCardProps) {
12 | const categoryName = getCategoryName(post.category as keyof typeof CATEGORY_MAP)
13 |
14 | return (
15 |
16 |
17 |
23 |
24 |
25 |
26 | {/* 分类和日期 */}
27 |
28 |
32 | {categoryName}
33 |
34 | ·
35 |
36 |
37 |
38 | {/* 标题 */}
39 |
40 |
41 | {post.title}
42 |
43 |
44 |
45 | {/* 摘要 */}
46 |
47 |
48 | {post.description}
49 |
50 |
51 |
52 |
53 | )
54 | }
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjs-blog-v2",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "contentlayer2 build && next build",
8 | "start": "next start",
9 | "lint": "next lint",
10 | "upload-media": "tsx scripts/uploadMedia.ts",
11 | "postinstall": "contentlayer2 build"
12 | },
13 | "dependencies": {
14 | "@aws-sdk/client-s3": "^3.740.0",
15 | "@giscus/react": "^3.1.0",
16 | "@radix-ui/react-slot": "^1.1.0",
17 | "@types/glob": "^8.1.0",
18 | "@types/node": "^22.10.5",
19 | "@types/react": "^19.0.6",
20 | "@types/react-dom": "^19.0.3",
21 | "@types/rss": "^0.0.32",
22 | "@vercel/analytics": "^1.4.1",
23 | "@vercel/speed-insights": "^1.1.0",
24 | "autoprefixer": "^10.4.20",
25 | "class-variance-authority": "^0.7.1",
26 | "clsx": "^2.1.1",
27 | "contentlayer2": "^0.5.5",
28 | "date-fns": "^4.1.0",
29 | "dotenv": "^16.4.7",
30 | "eslint": "^9.18.0",
31 | "eslint-config-next": "16.0.0",
32 | "framer-motion": "^11.15.0",
33 | "fuse.js": "^7.1.0",
34 | "glob": "^11.0.1",
35 | "lucide-react": "^0.468.0",
36 | "next": "^16.0.10",
37 | "next-contentlayer2": "^0.5.5",
38 | "next-themes": "^0.4.4",
39 | "postcss": "^8.4.49",
40 | "react": "^19.0.1",
41 | "react-dom": "^19.0.1",
42 | "react-icons": "^5.4.0",
43 | "rehype-autolink-headings": "^7.1.0",
44 | "rehype-pretty-code": "^0.14.1",
45 | "rehype-slug": "^6.0.0",
46 | "remark-gfm": "^4.0.0",
47 | "rss": "^1.2.2",
48 | "shiki": "^1.24.5",
49 | "tailwind-merge": "^2.6.0",
50 | "tailwindcss": "^3.4.17",
51 | "tailwindcss-animate": "^1.0.7",
52 | "typescript": "^5.7.3"
53 | },
54 | "engines": {
55 | "node": ">=22.0.0"
56 | },
57 | "devDependencies": {
58 | "@tailwindcss/typography": "^0.5.16",
59 | "prettier": "^3.4.2",
60 | "prettier-plugin-tailwindcss": "^0.6.11",
61 | "ts-node": "^10.9.2",
62 | "tsx": "^4.19.2"
63 | },
64 | "packageManager": "pnpm@10.25.0"
65 | }
66 |
--------------------------------------------------------------------------------
/src/lib/metadata.ts:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next'
2 | import { WEBSITE_HOST_URL } from './constants'
3 |
4 | const meta = {
5 | title: 'Gino Notes',
6 | description: "Gino Zhang's personal blog. I write about product, development and life.",
7 | image: `${WEBSITE_HOST_URL}/logo.jpg`,
8 | author: 'Gino Zhang',
9 | }
10 |
11 | // Schema.org 结构化数据
12 | export const jsonLd = {
13 | '@context': 'https://schema.org',
14 | '@type': 'WebSite',
15 | name: meta.title,
16 | description: meta.description,
17 | url: WEBSITE_HOST_URL,
18 | author: {
19 | '@type': 'Person',
20 | name: meta.author,
21 | url: WEBSITE_HOST_URL,
22 | },
23 | publisher: {
24 | '@type': 'Person',
25 | name: meta.author,
26 | url: WEBSITE_HOST_URL,
27 | },
28 | image: meta.image,
29 | inLanguage: 'zh-CN',
30 | }
31 |
32 | export const metadata: Metadata = {
33 | metadataBase: new URL(WEBSITE_HOST_URL),
34 | title: {
35 | default: meta.title,
36 | template: '%s | Gino Notes',
37 | },
38 | description: meta.description,
39 | openGraph: {
40 | title: meta.title,
41 | description: meta.description,
42 | url: WEBSITE_HOST_URL,
43 | siteName: meta.title,
44 | locale: 'zh-CN',
45 | type: 'website',
46 | images: [
47 | {
48 | url: meta.image,
49 | width: 1200,
50 | height: 630,
51 | alt: meta.title,
52 | },
53 | ],
54 | },
55 | twitter: {
56 | title: meta.title,
57 | description: meta.description,
58 | images: [
59 | {
60 | url: meta.image,
61 | width: 1200,
62 | height: 630,
63 | alt: meta.title,
64 | }
65 | ],
66 | card: 'summary_large_image',
67 | creator: '@hongming731',
68 | },
69 | alternates: {
70 | canonical: WEBSITE_HOST_URL,
71 | },
72 | robots: {
73 | index: true,
74 | follow: true,
75 | googleBot: {
76 | index: true,
77 | follow: true,
78 | 'max-video-preview': -1,
79 | 'max-image-preview': 'large',
80 | 'max-snippet': -1,
81 | },
82 | }
83 | }
--------------------------------------------------------------------------------
/contentlayer.config.ts:
--------------------------------------------------------------------------------
1 | import { defineDocumentType, makeSource } from 'contentlayer2/source-files'
2 | import rehypeAutolinkHeadings from 'rehype-autolink-headings'
3 | import rehypePrettyCode from 'rehype-pretty-code'
4 | import rehypeSlug from 'rehype-slug'
5 | import remarkGfm from 'remark-gfm'
6 | import { PostRoute, createPostRoute } from './src/lib/routes'
7 |
8 | export const Post = defineDocumentType(() => ({
9 | name: 'Post',
10 | filePathPattern: '**/*.mdx',
11 | contentType: 'mdx',
12 | fields: {
13 | title: { type: 'string', required: true },
14 | date: { type: 'string', required: true },
15 | description: { type: 'string' },
16 | category: { type: 'string', required: true },
17 | tags: { type: 'string' },
18 | cover: { type: 'string' },
19 | slug: { type: 'string' },
20 | featured: { type: 'boolean', default: false },
21 | },
22 | computedFields: {
23 | url: {
24 | type: 'string',
25 | resolve: (post): PostRoute => {
26 | const slug = (post.slug || post._raw.flattenedPath).replace(/_/g, '-')
27 | return createPostRoute(slug)
28 | },
29 | },
30 | categoryPath: {
31 | type: 'string',
32 | resolve: (post) => {
33 | const pathParts = post._raw.flattenedPath.split('/')
34 | return pathParts[0]
35 | },
36 | },
37 | },
38 | }))
39 |
40 | export default makeSource({
41 | contentDirPath: 'posts',
42 | documentTypes: [Post],
43 | mdx: {
44 | remarkPlugins: [remarkGfm],
45 | rehypePlugins: [
46 | rehypeSlug,
47 | [rehypePrettyCode, {
48 | theme: {
49 | dark: 'github-dark',
50 | light: 'github-light',
51 | },
52 | keepBackground: false,
53 | onVisitLine(node: any) {
54 | if (node.children.length === 0) {
55 | node.children = [{ type: 'text', value: ' ' }]
56 | }
57 | },
58 | }],
59 | [rehypeAutolinkHeadings, {
60 | properties: {
61 | className: ['anchor'],
62 | },
63 | }],
64 | ],
65 | },
66 | })
67 |
--------------------------------------------------------------------------------
/posts/reading/chat/20250225_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】大语言模型应用中,向量检索技术是如何工作的?
3 | date: 2025-02-25
4 | description: 本文深入探讨了向量检索技术在大语言模型(LLM)应用中的重要作用。通过分析向量索引算法(如 HNSW、IVF、PQ)的特点和各类向量数据库的应用场景,为构建高性能的 LLM 应用提供技术选型建议。
5 | category: reading
6 | tags: Daily Chat, Vector Search, Text Embedding, Vector Database, ANN, HNSW, RAG, LLM
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250225.jpeg
8 | slug: daily-chat-20250225
9 | ---
10 |
11 | ## 引言
12 |
13 | 在与大语言模型(LLM)驱动的各种应用交互时,我们越来越多地体验到由语义理解带来的智能与便捷。这背后,向量检索技术扮演着至关重要的角色,如同一个高效的"图书管理员",帮助 LLM 快速找到最相关的知识片段,从而给出精准的回答或生成高质量的内容。
14 |
15 | 我对向量检索技术如何支撑起 LLM 的强大能力充满好奇。这些高维的数字向量是如何被高效地组织和检索的?不同的索引算法和数据库又有哪些权衡和取舍?在构建 LLM 应用时,如何选择最适合的向量检索方案?
16 |
17 | 带着这些问题,我让 ChatGPT 进行了一番深入的调研,并整理成这份报告。
18 |
19 | ## 对话内容
20 |
21 | 
22 | 
23 | 
24 | 
25 |
26 | ## 小记
27 |
28 | - **向量就像文本的"DNA":** LLM 把文本变成一串串数字(向量),就像给每个词、每句话都提取了"DNA"。这些"DNA"包含了文本的语义信息,意思相近的文本,"DNA"也更相似。
29 |
30 | - **向量检索就像"超级搜索引擎":** 有了文本的"DNA",我们就可以进行快速的相似度搜索。向量检索就像一个超级搜索引擎,但它不只看关键词,还能理解语义,找到意思相近的内容。
31 |
32 | - **ANN 算法是"加速神器":** 如果要在一大堆"DNA"里找相似的,一个个比对太慢了。ANN(近似最近邻)算法就像加速神器,它用一些巧妙的方法(比如建索引),不用全部比较就能快速找到最相似的那些。
33 |
34 | - **HNSW、IVF、PQ,各有所长:** 常见的 ANN 算法有 HNSW、IVF、PQ 等。HNSW 速度快、精度高,但占内存多;IVF 内存占用适中,速度也还行;PQ 把向量压缩存储,省空间但精度会下降。选哪个算法,要看具体情况,就像选工具一样,没有最好,只有最合适。
35 |
36 | - **向量数据库是"专用仓库":** 向量数据库是专门用来存储和检索向量的,就像一个专门存放"DNA"的仓库。常见的向量数据库有 Faiss、Weaviate、Milvus、Pinecone 等,它们各有特点,有的速度快,有的功能全,有的容易上手。
37 |
38 | - **传统数据库也能存向量:** 像 PostgreSQL 这样的传统数据库,也可以通过插件(pgvector)来存储和检索向量。但如果数据量特别大,或者对速度要求特别高,还是专用向量数据库更给力。
39 |
40 | - **混合检索是"双保险":** 向量检索能理解语义,但有时会漏掉关键词。混合检索把关键词搜索和向量搜索结合起来,既能找到语义相关的,又能保证包含关键词,就像加了双保险。
41 |
42 | - **概念漂移是"与时俱进"** 词语的意思可能会随时间变化(例如一些新的网络用语)。概念漂移是指,随着时间的推移,语言或数据的含义发生了变化,导致模型变得不准确。这需要持续关注和处理, 要定期检查模型是否需要"更新",以适应新的变化。
43 |
44 |
--------------------------------------------------------------------------------
/posts/reading/chat/20250221_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】Next.js App Router 开发 AI SaaS,如何选择最佳身份验证方案?
3 | date: 2025-02-21
4 | description: 本文深入调研了 Next.js App Router 框架下多种身份验证方案,旨在为"文润"AI 文字润色网站的 MVP 开发选择最佳的登录和鉴权方案。文章对比了 NextAuth.js、Better Auth、Clerk、Supabase Auth 以及自建方案的接入成本、开发工作量、数据隐私、可扩展性和用户体验,并结合国内实际需求(微信登录、短信登录)进行了分析。
5 | category: reading
6 | tags: Daily Chat, Next.js, Authentication, NextAuth.js, Better Auth, Clerk, Supabase Auth, SaaS, Open Source
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250221.jpeg
8 | slug: daily-chat-20250221
9 | ---
10 |
11 | ## 引言
12 |
13 | 最近,我萌生了一个想法:打造一个名为"文润"的网站,专注于提供 AI 文字润色服务,帮助用户更好地表达。在开发 MVP(最小可行产品)的过程中,我意识到一个不可忽视的问题 -- 如何实现登录和鉴权功能?
14 |
15 | 毕竟,AI 技术的强大,也伴随着被滥用的风险。为了防止"文润"沦为恶意行为的工具,同时为了更好地控制服务的使用,一个完善的登录鉴权系统必不可少。
16 |
17 | 初步调研发现,在 Next.js 这个流行的前端框架下,实现登录鉴权的方式可谓五花八门。然而,一番探索下来,我却遇到了不小的挑战:要么是接入文档晦涩难懂,配置过程极其繁琐;要么是清一色的 SaaS 服务集成,上手虽快,但后续的定制化和扩展能力却让人心存疑虑。
18 |
19 | 面对这样的困境,我决定求助于 AI,让它帮我进行一次深入研究,全面剖析 Next.js App Router 架构下各种身份验证方案的优劣,为"文润"的 MVP 开发找到最合适的"守护神"。
20 |
21 | ## 对话内容
22 |
23 | 
24 | 
25 | 
26 | 
27 | 
28 |
29 | ## 小记
30 |
31 | - **NextAuth.js 核心优势:自主、可控、对国内支持较好。** 报告里提到 NextAuth.js 开源免费,数据放自己数据库,这点很关键。虽然报告里说支持微信登录,但这点要再确认,可能需要一些配置或定制。短信登录确定要自己写代码,好在有 Credentials Provider 机制,不算太难。
32 |
33 | - **Clerk、Supabase Auth 快速开发很诱人,但长期看可能有坑。** 它们主要问题是不支持微信登录,这在国内行不通。另外,使用这些服务后就被"绑定"了,以后想换别的方案会很麻烦。虽然它们上手快,适合快速做个 demo,但不适合我们这种要做长期产品的。
34 |
35 | - **自建方案,目前不现实。** 从头开发所有登录功能,太费时费力,2 个月肯定搞不定,安全性也难以保证。除非有特别的理由,否则不建议考虑。
36 |
37 | - **访问速度要重视。** 报告里提到 Clerk 服务器可能在国外,国内访问可能较慢。NextAuth.js 因为是自己部署,理论上速度更快、更可控。这对于用户体验很重要,需要实际测试验证。
38 |
39 | - **不想被"绑定",选型要留后路。** 数据隐私虽然不是最重要的考虑因素,但我们不想被某个服务"锁死"。NextAuth.js 这种开源方案,灵活性更好,以后想换别的技术栈、或者自己加功能,都更容易。这对于长期维护和迭代很重要。
--------------------------------------------------------------------------------
/src/app/categories/[category]/page.tsx:
--------------------------------------------------------------------------------
1 | import { allPosts } from 'contentlayer2/generated'
2 | import { compareDesc } from 'date-fns'
3 | import { CATEGORY_MAP } from '@/lib/images'
4 | import { notFound } from 'next/navigation'
5 | import { CategoryPageContent } from '@/components/category/CategoryPageContent'
6 | import { Container } from '@/components/common/Container'
7 | import { Breadcrumb } from '@/components/navigation/Breadcrumb'
8 |
9 | const POSTS_PER_PAGE = 10
10 |
11 | interface CategoryPageProps {
12 | params: Promise<{
13 | category: string
14 | }>
15 | searchParams: Promise<{
16 | page?: string
17 | }>
18 | }
19 |
20 | export default async function CategoryPage({ params, searchParams }: CategoryPageProps) {
21 | const { category } = await params
22 | const { page } = await searchParams
23 |
24 | // 验证分类是否有效
25 | if (!Object.keys(CATEGORY_MAP).includes(category)) {
26 | notFound()
27 | }
28 |
29 | // 获取当前分类的所有文章
30 | const categoryPosts = allPosts
31 | .filter((post) => post.category === category)
32 | .sort((a, b) => compareDesc(new Date(a.date), new Date(b.date)))
33 |
34 | // 分页逻辑
35 | const currentPage = Number(page) || 1
36 | const totalPages = Math.ceil(categoryPosts.length / POSTS_PER_PAGE)
37 | const paginatedPosts = categoryPosts.slice(
38 | (currentPage - 1) * POSTS_PER_PAGE,
39 | currentPage * POSTS_PER_PAGE
40 | )
41 |
42 | return (
43 |
44 |
45 |
46 |
52 |
53 |
54 | )
55 | }
56 |
57 | // 生成静态路由
58 | export function generateStaticParams() {
59 | const categories = Object.keys(CATEGORY_MAP)
60 | return categories.map((category) => ({
61 | category,
62 | }))
63 | }
--------------------------------------------------------------------------------
/posts/reading/chat/20250220_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】深度研究到底是什么黑科技?
3 | date: 2025-02-20
4 | description: 深度研究(Deep Research)是一种创新的 AI 研究助手,它能够像真实研究员一样自主进行多步骤的信息搜索、分析和整理。不同于传统搜索引擎,它能够深入理解内容,自主决策研究方向,并生成带有详细引用的研究报告。通过自主决策、定制化模型训练、异步工作流等技术,它能够完成复杂的研究任务,为用户提供深入、可靠的研究成果。
5 | category: reading
6 | tags: Daily Chat, Deep Research, AI Agent, Autonomous Agent, LLM
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250220.jpeg
8 | slug: daily-chat-20250220
9 | ---
10 |
11 | ## 引言
12 |
13 | 昨天开通 ChatGPT Pro 之后,我第一次体验到深度研究(Deep Research)这项新技术,完全被惊艳到了。今天刚好读到 Latent Space [深度研究的缔造者](https://www.latent.space/p/gdr) 这篇访谈后,我对这项技术产生了浓厚的兴趣。
14 |
15 | 作为一名技术爱好者,我有很多想要深入了解的问题:什么是深度研究?它与传统搜索、AI 搜索有什么本质区别?它的关键技术和实现难点是什么?面对这些技术挑战,目前有哪些解决方案?更重要的是,这项技术未来会如何发展,会给行业带来什么样的变革?
16 |
17 | 让我们打开魔法,来探索这些问题的答案。
18 |
19 | ## 对话内容
20 |
21 | 
22 | 
23 | 
24 | 
25 |
26 | ## 小记
27 |
28 | - 深度研究是一种自主研究代理,能在互联网上进行多步骤信息搜集和分析。它不仅仅是回答问题,而是帮你做研究!想象一下,你平时做 research,是不是要先搞清楚问题,然后去网上各种搜资料,看很多网页,最后把找到的信息整理成一份报告?深度研究就是帮你自动完成这个过程的!它不像普通搜索引擎只给你一堆链接,而是真的会去"读"这些网页,然后把关键信息提炼出来,写成一份有理有据、带引用来源的报告给你。重点是,它会告诉你"什么是重要的",而不是"什么是热门的"。
29 |
30 | - 深度研究代理具备代理性,能在没有人类干预的情况下自主规划行动。它像人一样会思考,会自己做决定!深度研究最厉害的地方在于,它能像人一样"思考"下一步该做什么。比如,它会先想"我需要找哪些信息?",然后决定用什么工具去找,找到信息后,再根据这些信息调整下一步的计划。整个过程就像一个真正的研究员在工作,你甚至可以看到它每一步在做什么。
31 |
32 | - 深度研究采用异步工作流,你可以让它慢慢"想",不用一直等!因为深度研究需要时间去"读"很多资料,所以它不是马上给你答案的。你提交任务后,它会在后台默默工作,你可以去做别的事,等它完成了会通知你。这就像你交给一个研究员一个任务,他需要时间去完成,你不用一直催他。重要的是,就算中间出了什么问题(比如网页打不开),它也能自己想办法解决,或者从上次中断的地方继续,不用从头再来。
33 |
34 | - 深度研究具有持久上下文和知识管理能力,它有超强的"记忆力",不会忘记之前找到的信息!在研究过程中,它会记住所有找到的资料、中间的想法,甚至你上传的 PDF、表格等等。它会把这些信息都存起来,就像有一个自己的"知识库"。这样,它在写报告的时候,就能准确地引用这些信息,保证报告里的每句话都有依据。你也可以提供你自己的资料给它,它会把这些资料也一起考虑进去,让研究更全面。
35 |
36 | - 总的来说,深度研究就是把一个复杂的研究过程自动化了。它通过自主决策、异步工作流和持久化知识管理等技术,能像一个真正的研究员一样,帮你从海量的信息中找到关键内容,并整理成一份高质量的报告。关键是,它会思考、会学习、会自己做决定,而且还能处理各种复杂的情况。这种技术将为我们的研究和学习带来革命性的改变!
--------------------------------------------------------------------------------
/posts/reading/chat/20250227_daily_chat.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 【每日一问】AI 真的能像人一样操作电脑了吗?
3 | date: 2025-02-27
4 | description: OpenAI 最新发布的 Operator 不再局限于对话式交互,而是赋予 AI “视觉”和“行动”能力,使其能够像人一样直接操作计算机图形界面(GUI)。本报告深入调研了 Operator 的核心技术、实现原理、应用场景、与其他 AI 代理的对比优势,以及它对未来人机协作模式的潜在影响。
5 | category: reading
6 | tags: Daily Chat, OpenAI, AI, Agent, Operator, CUA
7 | cover: https://media.ginonotes.com/covers/cover_daily_chat_20250227.jpeg
8 | slug: daily-chat-20250227
9 | ---
10 |
11 | ## 引言
12 |
13 | 在数字化浪潮席卷全球的今天,人工智能正以前所未有的速度渗透到我们工作和生活的方方面面。从自动执行重复性任务的机器人流程自动化(RPA),到能够理解自然语言并生成文本的聊天机器人,再到如今能够像人类一样操作计算机图形界面的智能代理,AI 的能力边界不断拓展,人机协作的方式也随之发生深刻变革。
14 |
15 | OpenAI 近期发布的 Operator,正是这一变革中的一个重要里程碑。它不再局限于传统的对话式交互,而是赋予 AI “视觉”和“行动”能力,使其能够直接“看懂”并操作我们日常使用的各种网页和应用程序。 这意味着什么?这是否预示着一种全新的人机交互范式的到来?AI 是否能够真正成为我们工作和生活中的“数字助手”,而不仅仅是“工具”?
16 |
17 | 为了深入理解 OpenAI Operator 的技术原理、应用场景、与其他类似技术的对比优势,以及它对开发者、企业乃至整个社会可能产生的影响,我让 ChatGPT 进行了一次深入研究,并整理成这份报告。
18 |
19 | ## 对话内容
20 |
21 | 
22 | 
23 | 
24 | 
25 |
26 | ## 小记
27 |
28 | - **Operator 像“数字员工”:** OpenAI 的 Operator 不再只是听你说话的“助理”,而是能像真人一样,用鼠标键盘操作电脑网页的“员工”。你可以用自然语言告诉它要做什么,它会“看”屏幕、“点”按钮、“填”表格,帮你完成各种线上任务。
29 |
30 | - **“看懂”屏幕是关键:** Operator 的核心技术是它能“理解”屏幕上的内容。它会截取屏幕截图,然后像人一样分析图像,理解网页上的文字、按钮、布局等,再决定下一步该怎么操作。这和过去通过解析网页代码的方式有着本质的不同。
31 |
32 | - **“感知-决策-行动”循环:** Operator 的工作方式就像一个循环:先“看”屏幕(感知),然后“思考”下一步怎么做(决策),最后“动手”操作(行动)。这个循环不断重复,直到完成任务。
33 |
34 | - **“自然语言”是桥梁:** 你不需要写代码,只需要用平时说话的方式告诉 Operator 你的需求,它就能理解并执行。这就像你给“数字员工”布置任务一样,大幅降低了使用门槛。
35 |
36 | - **“云端大脑”在工作:** Operator 的所有“思考”和“行动”都在云端进行。你的电脑只是一个显示器和输入设备,真正的工作是由 OpenAI 的服务器完成的,这保证了强大的计算能力和灵活性。
37 |
38 | - **“多任务并行”是亮点:** Operator 可以同时处理多个任务,就像你有多个“数字员工”在同时帮你工作,这极大地提高了效率。
39 |
40 | - **“安全和隐私”很重要:** 因为 Operator 会“看到”你的屏幕,所以 OpenAI 采取了多种措施来保护你的隐私和安全,比如限制它访问敏感网站,涉及隐私或金钱的操作时需要你手动确认。
41 |
42 | - **“赋能开发者”是未来:** Operator 不仅能帮你做事,还能帮助开发者更轻松地构建自动化应用。开发者可以用自然语言描述任务流程,而不用编写复杂的代码,这会极大改变软件开发的模式。
43 |
44 | - **“人机协作”新时代:** Operator 的出现,预示着人机协作进入了一个新的时代。AI 不再只是被动执行指令的“工具”,而是可以像“伙伴”一样,与你一起完成工作和生活中的各种任务。这种“伙伴”关系是紧密协作,而不是替代。
--------------------------------------------------------------------------------
/src/config/navigation.ts:
--------------------------------------------------------------------------------
1 | import { NavigationConfig } from '@/types/navigation'
2 | import {
3 | FaHome,
4 | FaUser,
5 | FaCode,
6 | FaBrain,
7 | FaLaptopCode,
8 | FaRocket,
9 | FaBook,
10 | FaLightbulb,
11 | FaGithub,
12 | FaTwitter,
13 | FaRobot,
14 | FaWeixin,
15 | FaFeather,
16 | } from 'react-icons/fa'
17 | import { allPosts } from 'contentlayer2/generated'
18 | import { createCategoryRoute } from '@/lib/routes'
19 |
20 | // 获取每个分类的文章数量
21 | const getCategoryCount = (category: string) => {
22 | return allPosts.filter((post) => post.category === category).length
23 | }
24 |
25 | export const navigation: NavigationConfig = {
26 | main: [
27 | { href: '/', label: '首页', icon: FaHome },
28 | { href: '/about', label: '关于我', icon: FaUser },
29 | ],
30 | posts: [
31 | {
32 | href: createCategoryRoute('dev'),
33 | label: '编程开发',
34 | icon: FaLaptopCode,
35 | count: getCategoryCount('dev'),
36 | },
37 | {
38 | href: createCategoryRoute('ai'),
39 | label: '人工智能',
40 | icon: FaBrain,
41 | count: getCategoryCount('ai'),
42 | },
43 | {
44 | href: createCategoryRoute('build'),
45 | label: '构建之路',
46 | icon: FaRocket,
47 | count: getCategoryCount('build'),
48 | },
49 | {
50 | href: createCategoryRoute('reading'),
51 | label: '阅读记录',
52 | icon: FaBook,
53 | count: getCategoryCount('reading'),
54 | },
55 | {
56 | href: createCategoryRoute('thoughts'),
57 | label: '思考随笔',
58 | icon: FaLightbulb,
59 | count: getCategoryCount('thoughts'),
60 | },
61 | ],
62 | projects: [
63 | { href: 'https://bestblogs.dev', label: 'BestBlogs.dev', icon: FaCode },
64 | { href: 'https://wenrun.ai', label: 'WenRun.ai', icon: FaFeather },
65 | { href: 'https://xgo.ing', label: 'XGo.ing', icon: FaRobot },
66 | { href: 'https://tiky.ai', label: 'Tiky.ai', icon: FaLightbulb },
67 | ],
68 | online: [
69 | { href: 'https://github.com/ginobefun', label: 'GitHub', icon: FaGithub },
70 | {
71 | href: 'https://twitter.com/hongming731',
72 | label: 'Twitter',
73 | icon: FaTwitter,
74 | },
75 | {
76 | href: 'https://mp.weixin.qq.com/s/5ulE6cqhyHDNhhDT08hOXA',
77 | label: '微信公众号',
78 | icon: FaWeixin,
79 | },
80 | ],
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/post/PostFilter.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useState, useCallback } from 'react'
4 | import { allPosts } from 'contentlayer2/generated'
5 | import { Post } from 'contentlayer2/generated'
6 |
7 | type FilterProps = {
8 | onFilter: (posts: Post[]) => void
9 | }
10 |
11 | export function PostFilter({ onFilter }: FilterProps) {
12 | const [selectedCategory, setSelectedCategory] = useState('')
13 | const [selectedTag, setSelectedTag] = useState('')
14 |
15 | // 获取所有分类
16 | const categories = Array.from(new Set(allPosts.map((post) => post.category)))
17 |
18 | // 获取所有标签
19 | const tags = Array.from(new Set(allPosts.flatMap((post) => post.tags.split(','))))
20 |
21 | const handleFilter = useCallback(() => {
22 | let filtered = allPosts
23 |
24 | if (selectedCategory) {
25 | filtered = filtered.filter((post) => post.category === selectedCategory)
26 | }
27 |
28 | if (selectedTag) {
29 | filtered = filtered.filter((post) => post.tags.includes(selectedTag))
30 | }
31 |
32 | onFilter(filtered)
33 | }, [selectedCategory, selectedTag, onFilter])
34 |
35 | return (
36 |
37 |
52 |
53 |
68 |
69 | )
70 | }
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ['class'],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: '2rem',
14 | screens: {
15 | '2xl': '1400px',
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: 'hsl(var(--border))',
21 | input: 'hsl(var(--input))',
22 | ring: 'hsl(var(--ring))',
23 | background: 'hsl(var(--background))',
24 | foreground: 'hsl(var(--foreground))',
25 | primary: {
26 | DEFAULT: 'hsl(var(--primary))',
27 | foreground: 'hsl(var(--primary-foreground))',
28 | },
29 | secondary: {
30 | DEFAULT: 'hsl(var(--secondary))',
31 | foreground: 'hsl(var(--secondary-foreground))',
32 | },
33 | destructive: {
34 | DEFAULT: 'hsl(var(--destructive))',
35 | foreground: 'hsl(var(--destructive-foreground))',
36 | },
37 | muted: {
38 | DEFAULT: 'hsl(var(--muted))',
39 | foreground: 'hsl(var(--muted-foreground))',
40 | },
41 | accent: {
42 | DEFAULT: 'hsl(var(--accent))',
43 | foreground: 'hsl(var(--accent-foreground))',
44 | },
45 | popover: {
46 | DEFAULT: 'hsl(var(--popover))',
47 | foreground: 'hsl(var(--popover-foreground))',
48 | },
49 | card: {
50 | DEFAULT: 'hsl(var(--card))',
51 | foreground: 'hsl(var(--card-foreground))',
52 | },
53 | },
54 | borderRadius: {
55 | lg: 'var(--radius)',
56 | md: 'calc(var(--radius) - 2px)',
57 | sm: 'calc(var(--radius) - 4px)',
58 | },
59 | keyframes: {
60 | 'accordion-down': {
61 | from: { height: 0 },
62 | to: { height: 'var(--radix-accordion-content-height)' },
63 | },
64 | 'accordion-up': {
65 | from: { height: 'var(--radix-accordion-content-height)' },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | 'accordion-down': 'accordion-down 0.2s ease-out',
71 | 'accordion-up': 'accordion-up 0.2s ease-out',
72 | },
73 | },
74 | },
75 | plugins: [require('tailwindcss-animate'), require('@tailwindcss/typography')],
76 | }
77 |
--------------------------------------------------------------------------------
/src/components/navigation/NavigationItem.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link'
2 | import { usePathname } from 'next/navigation'
3 | import { cn } from '@/lib/utils'
4 | import { NavigationItem as NavItemType } from '@/types/navigation'
5 | import { isExternalRoute } from '@/lib/routes'
6 |
7 | interface NavigationItemProps {
8 | item: NavItemType
9 | }
10 |
11 | export function NavigationItem({ item }: NavigationItemProps) {
12 | const pathname = usePathname()
13 | const { href, label, icon: Icon, count } = item
14 | const isActive = pathname === href
15 | const isExternal = isExternalRoute(href)
16 |
17 | const className = cn(
18 | 'flex flex-1 items-center space-x-3 rounded-md px-2 py-1.5 text-base font-medium',
19 | isActive
20 | ? 'bg-black text-white hover:bg-black hover:text-white dark:bg-gray-700 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white'
21 | : 'text-gray-700 dark:text-gray-200 sm:hover:bg-gray-200 sm:hover:text-gray-900 sm:dark:hover:bg-gray-700 sm:dark:hover:text-gray-200'
22 | )
23 |
24 | const content = (
25 | <>
26 |
27 |
28 |
29 | {label}
30 | {count !== undefined && (
31 |
32 | {count}
33 |
34 | )}
35 | {isExternal && (
36 |
37 |
40 |
41 | )}
42 | >
43 | )
44 |
45 | if (isExternal) {
46 | return (
47 |
53 | {content}
54 |
55 | )
56 | }
57 |
58 | return (
59 |
60 | {content}
61 |
62 | )
63 | }
--------------------------------------------------------------------------------
/AGENTS.md:
--------------------------------------------------------------------------------
1 | # Repository Guidelines
2 |
3 | ## Project Structure & Module Organization
4 |
5 | This Next.js 14 App Router project keeps page logic under `src/app`, with server and client components split by segment. Reusable UI sits in `src/components`, shared utilities in `src/lib`, runtime configuration in `src/config`, and types in `src/types`. Content lives in topic-based folders under `posts`, planning docs land in `documents`, static assets belong in `public`, automation scripts live in `scripts`, and `contentlayer.config.ts` stays aligned with the post frontmatter schema.
6 |
7 | ## Build, Test, and Development Commands
8 |
9 | Run `pnpm install` to sync dependencies after cloning or pulling. Use `pnpm dev` for local development; it rebuilds Contentlayer on save at http://localhost:3000. `pnpm build` validates the production bundle, `pnpm start` serves the built output for smoke checks, `pnpm lint` covers ESLint plus TypeScript, and `pnpm upload-media` pushes `public/media/**` to S3 once credentials and prefixes are configured.
10 |
11 | ## Coding Style & Naming Conventions
12 |
13 | Stick to TypeScript functional components with two-space indentation. Component files are PascalCase (`src/components/Hero.tsx`), hooks and helpers stay camelCase, and posts use kebab-case slugs such as `posts/ai/agent-tools.mdx`. Tailwind handles styling; Prettier with the Tailwind plugin keeps class orders stable, so run `pnpm lint --fix` and `pnpm prettier --write "src/**/*.{ts,tsx}"` before committing when you touch layout-heavy files.
14 |
15 | ## Testing Guidelines
16 |
17 | There is no dedicated automated suite yet, so rely on `pnpm dev` for interactive checks and `pnpm build` to surface type or Contentlayer issues. When introducing tests, colocate `*.test.tsx` beside the component and document any setup quirks in the PR. Capture manual QA notes for navigation, RSS generation, and theme toggling before requesting review.
18 |
19 | ## Commit & Pull Request Guidelines
20 |
21 | Commits follow short, present-tense messages like `add new post` or `update projects`, and should stay scoped to a single concern. Pull requests need a concise summary, linked issues or tasks, and screenshots or recordings for UI work. Call out manual verification steps and any media or content changes, especially if `pnpm upload-media` ran.
22 |
23 | ## Security & Configuration Tips
24 |
25 | Keep `.env` entries out of version control and lean on the provided variables for local auth and API keys. Ensure post frontmatter keys (`title`, `description`, `publishedAt`, `tags`) match the shapes defined in `contentlayer.config.ts` to avoid build failures. Coordinate with maintainers before adjusting `scripts/uploadMedia.ts` or S3 paths to prevent breaking production syncs.
26 |
--------------------------------------------------------------------------------
/src/components/category/CategoryPageContent.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Post } from 'contentlayer2/generated'
4 | import { PostCard } from '@/components/common/PostCard'
5 | import { motion } from 'framer-motion'
6 | import { getCategoryName, CATEGORY_MAP } from '@/lib/images'
7 |
8 | const container = {
9 | hidden: { opacity: 0 },
10 | show: {
11 | opacity: 1,
12 | transition: {
13 | staggerChildren: 0.1
14 | }
15 | }
16 | }
17 |
18 | const fadeInUp = {
19 | hidden: { opacity: 0, y: 20 },
20 | show: {
21 | opacity: 1,
22 | y: 0,
23 | transition: {
24 | type: "spring",
25 | stiffness: 100,
26 | damping: 15
27 | }
28 | }
29 | }
30 |
31 | interface CategoryPageContentProps {
32 | category: string
33 | posts: Post[]
34 | currentPage: number
35 | totalPages: number
36 | }
37 |
38 | export function CategoryPageContent({
39 | category,
40 | posts,
41 | currentPage,
42 | totalPages
43 | }: CategoryPageContentProps) {
44 | return (
45 |
51 |
52 |
53 | {getCategoryName(category as keyof typeof CATEGORY_MAP)}
54 |
55 |
56 | 共 {posts.length} 篇文章
57 |
58 |
59 |
60 | {/* 文章列表 */}
61 |
62 | {posts.map((post, index) => (
63 |
67 |
68 |
69 | ))}
70 |
71 |
72 | {/* 分页 */}
73 | {totalPages > 1 && (
74 |
78 | {Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
79 |
88 | {page}
89 |
90 | ))}
91 |
92 | )}
93 |
94 | )
95 | }
--------------------------------------------------------------------------------
/src/components/common/Skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from '@/lib/utils'
2 |
3 | interface SkeletonProps {
4 | className?: string
5 | }
6 |
7 | export function Skeleton({ className }: SkeletonProps) {
8 | return (
9 |
10 | )
11 | }
12 |
13 | export function PostCardSkeleton() {
14 | return (
15 |
16 | {/* 图片占位 */}
17 |
18 |
19 | {/* 标题和描述占位 */}
20 |
21 |
22 |
23 |
24 |
25 |
26 | {/* 元信息占位 */}
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export function FeaturedPostSkeleton() {
36 | return (
37 |
38 | {/* 图片占位 */}
39 |
40 |
41 | {/* 内容占位 */}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {/* 元信息占位 */}
53 |
54 |
55 |
56 |
57 |
58 |
59 | )
60 | }
61 |
62 | export function PostListSkeleton() {
63 | return (
64 |
65 |
66 | {/* 标题占位 */}
67 |
68 |
69 | {/* 文章列表占位 */}
70 |
71 | {[...Array(4)].map((_, i) => (
72 |
73 | ))}
74 |
75 |
76 |
77 | )
78 | }
--------------------------------------------------------------------------------
/documents/SEO.md:
--------------------------------------------------------------------------------
1 | # SEO 策略和实现方案
2 |
3 | ## 整体架构
4 |
5 | 采用 Next.js 14 的最佳实践,通过 `layout.tsx` 和 `metadata` API 来管理 SEO 配置。
6 |
7 | ### 文件结构
8 |
9 | ```
10 | src/
11 | ├── app/
12 | │ ├── layout.tsx # 根布局,包含全站默认 SEO 配置
13 | │ ├── posts/
14 | │ │ └── [...slug]/
15 | │ │ └── layout.tsx # 文章页面的 SEO 配置
16 | │ └── page.tsx # 首页内容(不包含 SEO 配置)
17 | └── lib/
18 | └── constants.ts # SEO 相关常量配置
19 | ```
20 |
21 | ## SEO 配置策略
22 |
23 | ### 1. 元数据管理
24 |
25 | - 使用 `layout.tsx` 集中管理 metadata,避免在 `page.tsx` 中配置
26 | - 根布局提供默认配置和模板
27 | - 子页面通过各自的 layout 覆盖特定配置
28 |
29 | ### 2. 图片策略
30 |
31 | - 所有文章必须包含封面图(cover)
32 | - 封面图尺寸:1200x630 像素
33 | - 使用完整的绝对 URL 路径
34 | - 首页使用默认的 OG 图片:`/images/og.png`
35 |
36 | ### 3. URL 管理
37 |
38 | - 使用 `WEBSITE_HOST_URL` 环境变量管理基础 URL
39 | - 所有链接使用绝对路径
40 | - 确保 canonical URL 正确配置
41 |
42 | ### 4. 结构化数据
43 |
44 | - 使用 Schema.org 标记
45 | - 文章页面使用 `BlogPosting` 类型
46 | - 包含作者、发布日期、修改日期等信息
47 |
48 | ## 具体实现
49 |
50 | ### 根布局配置 (app/layout.tsx)
51 |
52 | ```typescript
53 | export const metadata: Metadata = {
54 | metadataBase: new URL(WEBSITE_HOST_URL),
55 | title: {
56 | default: WEBSITE_NAME,
57 | template: `%s - ${WEBSITE_NAME}`,
58 | },
59 | description: WEBSITE_DESCRIPTION,
60 | openGraph: {
61 | // ... OG 配置
62 | },
63 | twitter: {
64 | // ... Twitter Card 配置
65 | },
66 | // ... 其他配置
67 | }
68 | ```
69 |
70 | ### 文章页面配置 (app/posts/[...slug]/layout.tsx)
71 |
72 | ```typescript
73 | export async function generateMetadata({ params }: PostLayoutProps): Promise {
74 | // 获取文章数据
75 | const post = allPosts.find(...)
76 | if (!post) return {}
77 |
78 | return {
79 | title: post.title,
80 | description: post.description,
81 | openGraph: {
82 | // ... 文章特定的 OG 配置
83 | },
84 | // ... 其他配置
85 | }
86 | }
87 | ```
88 |
89 | ## 最佳实践
90 |
91 | 1. **关注点分离**
92 |
93 | - SEO 配置集中在 layout 文件中
94 | - 页面组件只关注内容渲染
95 | - 使用常量文件管理配置值
96 |
97 | 2. **图片处理**
98 |
99 | - 确保所有图片有正确的尺寸
100 | - 使用 alt 文本提供图片描述
101 | - 使用绝对 URL 路径
102 |
103 | 3. **URL 管理**
104 |
105 | - 所有链接使用绝对路径
106 | - 正确配置 canonical URL
107 | - 处理多语言支持(如果需要)
108 |
109 | 4. **性能优化**
110 | - 图片优化(使用适当的格式和尺寸)
111 | - 合理的缓存策略
112 | - 页面加载性能优化
113 |
114 | ## 维护建议
115 |
116 | 1. 定期检查:
117 |
118 | - 使用 Google Search Console 监控性能
119 | - 检查 404 错误和死链
120 | - 验证结构化数据
121 |
122 | 2. 发布新文章时:
123 |
124 | - 确保包含高质量的封面图
125 | - 填写完整的 meta 信息
126 | - 测试社交媒体分享效果
127 |
128 | 3. 工具验证:
129 | - Facebook Sharing Debugger
130 | - Twitter Card Validator
131 | - Google Rich Results Test
132 |
133 | ## 相关工具
134 |
135 | - Google Search Console
136 | - Google Rich Results Test
137 | - Facebook Sharing Debugger
138 | - Twitter Card Validator
139 | - Schema.org Validator
140 |
--------------------------------------------------------------------------------
/src/components/post/PostHeader.tsx:
--------------------------------------------------------------------------------
1 | import { format, parseISO } from 'date-fns'
2 | import Link from 'next/link'
3 | import { getCategoryName, CATEGORY_MAP } from '@/lib/images'
4 |
5 | interface PostHeaderProps {
6 | title: string
7 | date: string
8 | readingTime: number
9 | category?: string
10 | }
11 |
12 | export const PostHeader = ({ title, date, readingTime, category }: PostHeaderProps) => {
13 | return (
14 |
15 |
16 | {title}
17 |
18 |
19 |
25 |
26 |
27 |
30 | {readingTime} 分钟阅读
31 |
32 | {category && (
33 | <>
34 |
35 |
39 |
42 | {getCategoryName(category as keyof typeof CATEGORY_MAP)}
43 |
44 | >
45 | )}
46 |
47 |
48 | )
49 | }
--------------------------------------------------------------------------------
/src/components/common/Mdx.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useMDXComponent } from 'next-contentlayer2/hooks'
4 | import Link from 'next/link'
5 | import { BlurImage } from './BlurImage'
6 |
7 | const components = {
8 | Image: ({ alt = '', src, ...props }: { alt?: string; src: string; [key: string]: any }) => (
9 |
18 | ),
19 | h1: ({ children }: { children: React.ReactNode }) => (
20 | {children}
21 | ),
22 | h2: ({ children, id }: { children: React.ReactNode; id?: string }) => (
23 |
24 | {children}
25 |
26 | ),
27 | h3: ({ children, id }: { children: React.ReactNode; id?: string }) => (
28 |
29 | {children}
30 |
31 | ),
32 | h4: ({ children, id }: { children: React.ReactNode; id?: string }) => (
33 |
34 | {children}
35 |
36 | ),
37 | a: ({ href, children }: { href: string; children: React.ReactNode }) => {
38 | if (href.startsWith('/')) {
39 | return (
40 |
41 | {children}
42 |
43 | )
44 | }
45 |
46 | if (href.startsWith('#')) {
47 | return (
48 |
49 | {children}
50 |
51 | )
52 | }
53 |
54 | return (
55 |
61 | {children}
62 |
63 | )
64 | },
65 | pre: ({ children }: { children: React.ReactNode }) => (
66 |
67 | {children}
68 |
69 | ),
70 | code: ({ children }: { children: React.ReactNode }) => (
71 | {children}
72 | ),
73 | }
74 |
75 | interface MdxProps {
76 | code: string
77 | }
78 |
79 | export function Mdx({ code }: MdxProps) {
80 | const Component = useMDXComponent(code)
81 |
82 | return (
83 |
84 |
85 |
86 | )
87 | }
--------------------------------------------------------------------------------
/posts/ai/model/20250218_xai_release_grok3.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Grok 3 发布:20万 GPU 训练的新一代模型
3 | date: 2025-02-18
4 | description: xAI 发布 Grok 3 系列模型,包含完整版和 mini 版本,以及对应的推理增强版本。模型采用 20 万张 H100 GPU 训练,在 LMSYS Arena 首次突破 1400 分。文章总结了发布会重要信息,包括性能数据、核心功能、商业策略,并对产品定位和行业影响进行简要点评。
5 | category: ai
6 | tags: xAI, Grok, AI Model, Benchmark, DeepSearch, LLM
7 | cover: https://media.ginonotes.com/covers/cover_grok.jpeg
8 | slug: xai-release-grok3
9 | ---
10 |
11 | ## xAI 发布 Grok 3 系列模型
12 |
13 | 
14 |
15 | xAI 发布 Grok 3 系列模型,包括 Grok 3 (Beta)、Grok 3 mini、Grok 3 Reasoning (Beta)、Grok 3 mini Reasoning,以及智能体 DeepSearch,被马斯克称为“地表最强的 AI”。
16 |
17 | 马斯克介绍 Grok 的名字来自小说《异乡异客》,主角是在火星上长大的人类,Grok 也是一个“火星词”,代表充分而深刻地理解事物。
18 |
19 | ## 性能大幅提升
20 |
21 | 
22 |
23 | Grok 3 在多个基准测试中超越现有主流模型。
24 |
25 | 
26 |
27 | Reasoning 版本在推理能力上表现突出,超越 o3-mini 和 DeepSeek-R1,另外 Grok 3 允许模型在测试时进行更长时间的思考和推理,提高准确性。
28 |
29 |
30 | 
31 |
32 | Grok 3 在数学、科学、编码等多个基准测试中超越了包括 GPT-4o、Claude 3.5 Sonnet、DeepSeek-V3 和 Gemini-2 Pro 在内的现有模型。在 AIME 2025 考试中,Grok 3 Reasoning 和 mini Reasoning 分别获得了 93 分和 90 分的优异成绩。另外在 LMSYS Arena 评测中首次突破 1400 分,成功登顶,较 Grok 2(1280分)提升约 10%。
33 |
34 | ## 超大规模算力支持
35 |
36 | 
37 |
38 | Grok 3 的训练使用了 20 万块 NVIDIA H100 GPU 的超算集群 Colossus,计算量是 Grok 2 的 10 倍。超算中心分两个阶段建设完成,第一阶段 122 天建成 10 万卡集群,第二阶段 92 天扩建至 20 万卡规模,总耗电量达到 1/4 吉瓦。
39 |
40 | ## Think 和 Big Brain 模式
41 |
42 | 
43 |
44 | 用户界面提供两种模式:Think 可以让模型进行较为仔细的思考与自我检验,Big Brain 适合更复杂的问题,模型会调用更强的推理流程与内部逻辑来给出更缜密、深入的结果。
45 |
46 | ## 推出 DeepSearch 智能体
47 |
48 | 
49 |
50 | 这是一个基于 Grok 3 的深度搜索工具,能够进行更深入的网络搜索,分析信息并提供详细、合理的答案,同时过程更透明。支持 X 平台实时数据,用户可以控制信息源选择。
51 |
52 | ## 新的订阅模式
53 |
54 | 
55 |
56 | X Premium+ 用户可优先体验 Grok 3,xAI 还推出了 SuperGrok 订阅计划($30/月或年付 $300),提供完整推理能力、DeepSearch 高级功能和无限量图像生成等特性。
57 |
58 | ## 未来计划
59 |
60 | 一周内上线所有功能(包括语音模式),几周内对企业用户推出 API,几个月后开源 Grok 2。语音模式将支持语音理解和生成,API 将包含推理和深度搜索功能。
61 |
62 | xAI 还承诺将持续优化模型性能,保持每日更新迭代。
63 |
64 | ## 点评
65 |
66 | 1. **算力驱动创新**:xAI 通过 20 万张 H100 GPU 的投入,在短短一年内就达到了业界领先水平,再次证明了在当前阶段,算力资源对 AI 发展的关键作用。这也提醒我们需要关注算力分配的普惠性问题。
67 |
68 | 2. **性能领先但竞争激烈**:虽然 Grok 3 在多个基准测试中展现出领先优势,但考虑到 Claude 4 和 GPT 4.5 即将发布,AI 领域的竞争格局可能很快会发生变化。
69 |
70 | 3. **社交平台协同有待加强**:作为 X 平台背景的模型,Grok 3 与平台的深度整合还有提升空间。相比之下,微信与 DeepSeek 的结合展示了更多社交场景下的 AI 应用可能性。
71 |
72 | 4. **使用门槛与普惠性**:目前 Grok 3 仅向 Premium+ 和 SuperGrok 会员开放,API 也将优先面向企业客户。这种策略虽然合理,但与开源社区的期待存在一定差距。期待在稳定性验证后能更快地向更广泛的用户群体开放。
--------------------------------------------------------------------------------
/posts/thoughts/learning/20251026_campus_interview_insights_from_interviewer.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 从面试官视角看校招:学校教育的缺失与面试准备建议
3 | date: 2025-10-26
4 | description: 作为面试官参与校招后的思考:面试十几位硕士生发现,优秀毕业生的能力更多源于自学而非学校教育。核心问题在于学生缺乏思考、总结和表达能力,只是被动完成任务。文章深度剖析 CS 教育的缺失环节,并提供 8 条实用建议,涵盖模拟面试、结构化表达、项目深度理解等,帮助应届生提升面试竞争力。
5 | category: thoughts
6 | tags: Job Interview, Technical Interview, Job Hunting, Career Development, CS Education, Soft Skills
7 | cover: https://media.ginonotes.com/covers/cover_interviewer-insights.png
8 | slug: campus-interview-insights-from-interviewer
9 | ---
10 |
11 | 在推特上看到宝玉老师探讨 [我们的 CS 教育到底缺了什么?](https://x.com/dotey/status/1981812786889208058) 的话题,刚好这周参与校招,面试了十几个硕士生,作为面试官,感触特别深,也分享一下我的看法。
12 |
13 | 从企业角度看,我们看重的核心经验,几乎都来自实习。这批面试者里只有一个没有实习经验的,但是科研能力特别强,在国家重点实验室,有几篇核心论文,有很强的学习和钻研能力,所以综合排名比较靠前。其他缺少实习经验的,我估计 HR 在筛选简历的时候可能就直接拒了。所以学校、专业、成绩顶多算个敲门砖,我们真正花时间细问的,全是实打实的工程问题:**对业务的理解、框架和中间件的运用、异常处理,以及出了问题,你到底怎么定位和解决的?**
14 |
15 | 当然,面试中也遇到了非常优秀的候选人。他们不光工程实践扎实,思考和表达也都很到位。
16 |
17 | 但这也恰恰暴露了更深的问题:**这些优秀毕业生的突出能力,很大程度上得益于个人的自学、领悟和主动思考,学校教育在其中的贡献相对有限。**
18 |
19 | 这就让「平庸」和「优秀」之间的差距变得特别大。
20 |
21 | 很多学生,你一看就知道他动手能力不差,确实干了活。可一让他总结项目亮点,就卡壳了,抓不住重点。你再一深挖,就发现他只是在完成任务,没有进一步的思考。他知道自己做了A、B、C,但对背后的「为什么」知之甚少:
22 |
23 | - 为什么要做这个需求?(对业务的理解)
24 | - 为什么选这个技术方案,而不是别的?(设计的权衡)
25 | - 你做的这个功能,在整个系统里处于什么位置?(全局观)
26 | - 万一线上出错了,你打算从哪几个方面下手排查?(问题定位能力)
27 |
28 | 这就暴露出一个培养上的脱节。现在的情况是:
29 |
30 | - 学校:教基础理论(科学)。
31 | - 实习:给工程实践(工程)。
32 | - 缺失的环:**个人的思考、总结和表达能力。**
33 |
34 | 很多学生只是在被动地完成实习,缺乏把做过转化为「学会」的能力,最终表达和总结出来的都是相对零散的知识点。当然也可以理解,大部分学生实习也就三四个月,中间还有很多业务、学习和培训等事务,真正投入开发也不会从很核心的需求入手,况且这些软技能的缺失在工作多年经验丰富的工程师上也很常见。
35 |
36 | 所以,我觉得现在的教育,**除了要补上「工程实践」课,更要引导学生去重视这些「软技能」-- 也就是你如何从做过的事情里提炼思考,又如何清晰地把这些思考表达出来。**
37 |
38 | 说了这么多问题,不如回到现实。作为即将求职的学生,既然我们短期内可能改变不了很多现状,那该怎么办?根据这次面试经历和自己的经验,整理了一些实用的建议,希望能对大家有所帮助。
39 |
40 | 1. **思想上要重视面试,光想没用,得练。** 别怕丢人,找同学、师兄师姐帮你搞几轮模拟面试。让他们对着你的简历使劲问,你练的就是怎么把话说清楚、说明白,顺便也提前排排雷。同时学会记录和总结之前面试的经历,总结薄弱的地方,重点改进。
41 |
42 | 2. **准备一个结构化的自我介绍。** 别从头到尾念简历,或者过于简单。一两句话讲完个人背景,就赶紧把你最牛的那个项目抛出来。你要主动引导面试官去聊你最熟的东西,把节奏带起来。
43 |
44 | 3. **讲科研项目?请说人话。** 如果你要讲的是科研项目,千万别掉书袋。对面的面试官大概率对你的项目和其中的理论知之甚少,尽量用大白话和实际的例子,把你要解决的问题、你的方案和创新点讲清楚。实在不知道怎么组织语言,可以把材料发给 AI,让它帮你生成几个通俗易懂的参考版本。
45 |
46 | 4. **讲实习项目,要有一定的结构和技巧。** 别东一榔头西一棒子,面试官听着累。可以试试这个思路:
47 |
48 | - 背景:「这项目是干嘛的...」
49 | - 问题:「我们当时要解决的问题是...」
50 | - 职责:「我主要负责哪块...」
51 | - 行动和结果:「我用了什么方法,最后效果怎么样。」
52 |
53 | 一条线下来,别人才听得懂。这些背景和问题,以及其中的思考和决策,可以多和 mentor 或者 leader 交流,尝试跳出仅仅实现一个功能的视角,了解更多的信息并把它们组织和表达出来。
54 |
55 | 5. **把你的「王牌项目」吃透。** 简历上肯定有个你最想聊的项目,对吧?这个项目你得下功夫。首先是要了解业务流程、技术链路,所采用的技术栈、框架和库、中间件、核心的代码实现,准备面试官可以感兴趣的问题。更进一步的是继续思考背后的「为什么」,比如当初为啥这么选型?和别的方案比,好处在哪,坏处在哪?碰到的一些挑战和问题背后的原因是什么,你怎么找到和解决的?这些有一定深度的问题会是拉开差距的地方。你要是自己也说不清,可以去问带你的 mentor 和其他同事,把这些思考挖出来。
56 |
57 | 6. **个人项目,别玩玩而已。** 我强烈建议去做点真正有意思、能解决实际问题的东西。别只是本地跑跑就完事了,花点钱买个域名和服务器,把它正儿八经部署到线上,让周围人能用起来,这个过程的收获可能远超想象。然后,你可以借助 AI 帮你梳理整个项目的设计、核心原理,带着问题去深入了解,这才能从「跑通 Demo」变成「真正搞懂」,作为面试官,会非常欣赏这些热情和解决问题的能力。
58 |
59 | 7. **简历上写的每个字,你都得心里有底。** 你写了 Redis,就要准备好被问缓存问题;写了数据库,就得懂点索引;写了 ElasticSearch,就得了解创建索引和查询的原理;写了智能体和 MCP,至少要能说清楚这些概念和原理。千万别给自己挖坑,被问住就尴尬了。
60 |
61 | 8. **了解对方是干嘛的。** 最后,花点时间研究下你面的岗位是干嘛的。是做通信的、金融的、互联网的还是 AI 的?业务特性是啥?了解对方在做什么,想一想自己的优势和他们的需求,是一个不错的加分项。
62 |
--------------------------------------------------------------------------------
/posts/dev/growth/20251007_how_to_influence_politics.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 作为一名资深工程师,我是如何影响公司政治的[译]
3 | description: 软件工程师不应该试图玩权力游戏,而是要学会「等风来」:通过顺势而为、准备技术方案库、在正确时机推动项目,实现技术目标并获得高层支持。这是一种更适合技术人的公司政治参与策略。
4 | date: 2025-10-07
5 | category: dev
6 | tags: Career Development, Technical Decision, Project Management
7 | slug: how-to-influence-politics
8 | cover: https://media.ginonotes.com/covers/cover_how_to_influence_politics.jpg
9 | ---
10 |
11 | > 一提到「公司政治」,多数工程师的第一反应是远离。但如果有一种方法,既能让你告别内耗,又能让你的技术方案获得高层鼎力支持,你是否愿意了解?
12 | >
13 | > 这篇博客的作者认为,工程师最大的错误就是试图去玩一场自己不擅长的「权力的游戏」。相反,真正有效的策略是看懂规则,然后选择在正确的时间,将你早已准备好的技术方案,递到最需要它的高管手中。这篇文章揭示的就是这种「等风来」的智慧,一种更适合技术人的生存法则。
14 |
15 | 原文地址:[How I influence tech company politics as a staff software engineer](https://www.seangoedecke.com/how-to-influence-politics/)
16 |
17 | 作者:Sean Goedecke
18 |
19 | ---
20 |
21 | 很多软件工程师对公司政治都抱着一种听天由命的态度。他们觉得掺和进去纯属白费力气,因为:
22 |
23 | - 技术决策的背后往往是纯粹的私心,一个善意的工程师根本影响不了。
24 | - 那些手握重权的利益相关者,通常既愚蠢又混乱,你根本没法搞懂他们的真实需求,更别提拿出解决方案了。
25 | - 所谓的政治博弈,靠的是工程师压根接触不到的内部消息。你要是贸然参与,只会在里面瞎转悠。
26 | - 管理层和高管们大部分时间都在搞政治,而工程师大部分时间都在搞技术。所以工程师还没下场,就已经输在了起跑线上。
27 |
28 | 总而言之,大家普遍认为,软件工程师根本就不是玩这套游戏的料。**没错!** 工程师要是觉得自己也能像《权力的游戏》里那样运筹帷幄、搞阴谋诡计,那可就大错特错了。你的那点小九九很快就会被识破,然后被别人利用,让你吃亏,让他们获利。玩心计需要经验和权力,这两样东西工程师都没有。
29 |
30 | 事实就是,在大公司里,软件工程师往往是政治博弈中的**棋子**,而不是**棋手**。不过,不玩权谋,不代表你不能参与政治。
31 |
32 | ---
33 |
34 | ### 顺势而为,而非凭空创造
35 |
36 | 最简单的方法,就是**主动投入,把一个高调的项目做成功**。这差不多是你本来就该做好的分内工作。如果公司正在大力投资某个新项目——比如现在最火的 AI 项目——你用自己的技术把它做成,对于主导这个项目的副总裁或高管来说,就是送上了一份政治大礼。作为回报,你也能得到高管们能给的好处:奖金、晋升上的帮助,以及未来参与更多明星项目的机会。
37 |
38 | 一个稍微难点但让你更有主动权的方法,是让你自己的「私藏项目」**搭上高层关注的顺风车**。
39 |
40 | 比如,你早就想把某个现有功能抽出来做成一个独立服务。想实现它,你有两种办法。
41 |
42 | 难的办法是消耗你自己的政治资本:到处游说,争取支持,告诉你的经理这对你有多重要,慢慢磨到大家都不反对了,项目才可能被正式批准。
43 |
44 | 简单的办法是,**让某个高管用他那多得多的政治资本来推你的项目**。你要做的,是等到公司自上而下地推行某个目标,而这个目标正好和你的项目方向一致时(比如,在发生了一次重大线上事故后,公司开始强调可靠性)。这时候,你再跟你的经理提议,说你的项目可能很适合这个大方向。如果你判断对了,你的整个组织都会支持你的项目。而且,这非但不会消耗你的政治资本,反而会增加它。
45 |
46 | ---
47 |
48 | ### 准备好你的「弹药库」
49 |
50 | 公司的关注点总是一阵一阵的。强调「可靠性」的时候,VP 们都急于「做点什么」。他们想找一些听起来靠谱的可靠性项目来投资,因为他们需要向自己的老板汇报工作,但他们自己又没能力凭空想出来。所以,工程团队提上来的任何建议,他们通常都很乐意买单。反过来,当公司的注意力在别的地方——比如要发布一个重磅新产品——他们最不希望看到的,就是工程师把时间花在客户完全感知不到的、为了内部可靠性而做的重构上。
51 |
52 | 所以,如果你想在科技公司里把某件技术性的事情做成,**你就得学会等风来**。
53 |
54 | 一个好方法是提前准备好几个不同方向的技术方案。厉害的工程师在日常工作中就会有意识地做这些储备。比如,你可能已经有了一些初步计划:
55 |
56 | - 把计费代码从缓存 API 调用改成基于 webhook 更新存储数据。
57 | - 用 Vite 替换掉那个老掉牙的手摇构建流程。
58 | - 把一个笨重的、高流量的 Python 服务用 Golang 重写。
59 | - 把那个加载缓慢的、支撑着公开文档的 CMS 前端,换成一个快速的静态网站。
60 |
61 | 当高管们开始关心计费问题时,你就可以把计费重构方案作为可靠性改进提出来。当他们关心开发者体验时,你可以建议替换构建流程。当客户抱怨性能时,你可以指出用 Golang 重写是个好选择。当 CEO 看了眼公开文档,觉得很丢人时,你就可以提议把它重建成静态网站。**最关键的是,无论当下的「月度风向」是什么,你手里都要有一个详细、可行的方案随时准备好。**
62 |
63 | ---
64 |
65 | ### 你的职责所在
66 |
67 | 不管你做不做这些准备,反正总得有项目被推行。但如果你不这样做,你就没法控制到底是什么项目。依我之见,这恰恰是公司做出最烂技术决策的时刻:当「必须做点什么」的政治需求,撞上了「没啥好点子」的窘境。没有好主意,那烂主意也只能凑合了。
68 |
69 | 但没人想要这种结果。高管们很难受,因为他们得把一个令人失望的技术成果包装成成功案例去汇报;工程师们也很难受,因为他们得把时间和精力浪费在错误的事情上。
70 |
71 | 如果你是一个非常资深的工程师,VP 们私下里会把这笔账算在你头上。**而且他们这么做是对的!在正确的时间点,准备好正确的方案,这本身就是你的职责。**
72 |
73 | 你可以从两种角度来看待这一切。从悲观的角度看,我这是在建议你把自己变成一个方便的工具,好让那些管理公司的野心家们,在他们无休止的内部权力斗争中利用你。从乐观的角度看,我这是在建议你让高管们去设定公司的总体优先事项——毕竟那是他们的工作——然后你再把自己的技术规划与之对齐。
74 |
75 | 无论你怎么看,只要能在正确的时间点推动正确的计划,你就能实现更多的技术目标。
76 |
--------------------------------------------------------------------------------
/posts/ai/agent/20250213_what_is_a_cognitive_architecture.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 什么是认知架构?[译]
3 | date: 2025-02-13
4 | description: 本文探讨了 LLM 应用中的认知架构概念,从纯代码到自主智能体,详细阐述了六个不同的自主程度。通过对比不同认知架构的特点和应用场景,帮助开发者选择最适合自己项目的架构方案。文章同时介绍了 LangChain 和 LangGraph 如何支持灵活的认知架构设计。
5 | category: ai
6 | tags: AI, Cognitive Architecture, LLM, AI Agent, LangChain, LangGraph
7 | cover: https://media.ginonotes.com/covers/cover_cognitive_architecture.jpeg
8 | slug: what-is-a-cognitive-architecture
9 | ---
10 |
11 |
12 | 本文是 LangChain 博客上关于智能体 (AI Agent) 的系列文章的第二篇,探讨了 LLM 应用中的认知架构概念,从纯代码到自主智能体,详细阐述了六个不同的自主程度。通过对比不同认知架构的特点和应用场景,帮助开发者选择最适合自己项目的架构方案。
13 |
14 |
15 | **更新说明:** "认知架构"一词在神经科学和计算认知科学领域有着[深厚的历史渊源](https://en.wikipedia.org/wiki/Cognitive_architecture)。维基百科将其定义为"关于人类思维结构的理论及其计算实现"。这一学术定义比本文的解释更为全面和深入。本文应被视为作者基于过去一年构建 LLM 驱动应用的经验,与该研究领域的一次理论映射尝试。
16 |
17 | 在过去半年中,我经常使用"认知架构"这个术语,未来还会更频繁地使用它。这个词最初来自 [Flo Crivello](https://x.com/Altimor),在此特别感谢他提出了这个精准的术语。那么,这个概念究竟想表达什么?
18 |
19 | 简而言之,认知架构指的是_系统的思考方式_ —— 也就是说,从接收用户输入到执行操作或生成响应的过程中,代码、提示(prompts)和 LLM 调用之间的协作流程。
20 |
21 | 我选择"认知"一词,是因为具备代理能力的系统需要依靠 LLM 来推理下一步行动。而使用"架构"一词,则是因为这些智能体的设计仍然需要严谨的工程思维,类似于传统的系统架构。
22 |
23 | ## 认知架构的六个层次
24 |
25 | 以下这张来自[我的 TED 演讲](https://www.ted.com/talks/harrison_chase_the_magical_ai_assistants_of_the_future_and_the_engineering_behind_them)的图示,展示了 LLM 应用中不同层次的自主程度,也反映了不同类型的认知架构:
26 |
27 | 
28 |
29 | 1. **纯代码实现**:完全硬编码的方案,严格来说还不能称为认知架构。
30 |
31 | 2. **单次 LLM 调用**:在调用前后可能有简单的数据处理,但核心仍是单一的 LLM 调用。典型如基础聊天机器人。
32 |
33 | 3. **LLM 调用链**:将复杂任务分解为多个步骤,或服务于不同目的。例如高级 RAG(检索增强生成)系统:先用 LLM 生成搜索查询,再用另一次调用生成最终答案。
34 |
35 | 4. **路由架构**:引入 LLM 作为决策者,由其决定执行哪些操作。这增加了系统的灵活性和不确定性。
36 |
37 | 5. **状态机架构**:结合了 LLM 的路由能力和循环机制。理论上系统可以无限循环调用 LLM,具有更高的不确定性。
38 |
39 | 6. **自主智能体**:最高级的自治水平。突破了状态机的限制,系统能够自主决定可执行的步骤和指令,可以通过动态更新提示、工具或系统代码来实现。
40 |
41 | ## 如何选择合适的认知架构
42 |
43 | 选择认知架构时,没有绝对的"最佳方案" —— 不同架构适合不同的应用场景。就像调整提示词一样,你可能需要反复试验不同的认知架构。这正是我们开发 [LangChain](https://www.langchain.com/langchain) 和 [LangGraph](https://www.langchain.com/langgraph) 的初衷。
44 |
45 | 过去一年,我们的重点是构建底层的、高度可控的编排框架(LCEL 和 LangGraph)。这与早期 LangChain 主打"开箱即用"的策略有所不同。早期的方案虽然便于快速入门,但在定制化方面存在局限。随着领域的快速发展,我们意识到灵活性的重要性。
46 |
47 | 我为 LangChain 和 LangGraph 在过去一年的进步感到自豪。如果你之前只使用过 LangChain 的高级接口,建议深入了解其底层组件。这些组件提供了更大的灵活性,让你能够真正掌控应用的认知架构。
48 |
49 | 原文地址:[What is a "cognitive architecture"?](https://blog.langchain.dev/what-is-a-cognitive-architecture/)
50 |
51 | ---
52 |
53 | LangChain 智能体系列文章:
54 |
55 | - [1. 什么是智能体?](https://www.ginonotes.com/posts/what-is-ai-agents)
56 | - [2. 什么是认知架构?](https://www.ginonotes.com/posts/what-is-a-cognitive-architecture)
57 | - [3. 为什么你应该外包智能体基础设施,但保留自己的认知架构](https://www.ginonotes.com/posts/outsource-agentic-infrastructure-own-cognitive-architecture)
58 | - [4. 智能体的规划能力](https://www.ginonotes.com/posts/planning-for-agents)
59 | - [5. 智能体的交互模式](https://www.ginonotes.com/posts/ux-for-agents)
60 | - [6. 智能体的记忆](https://www.ginonotes.com/posts/memory-for-agents)
61 | - [7. 沟通:你所需要的一切](https://www.ginonotes.com/posts/communication-is-all-you-need)
--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------
1 | # CLAUDE.md
2 |
3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4 |
5 | ## Commands
6 |
7 | ### Development
8 |
9 | - `pnpm dev` - Start development server
10 | - `pnpm build` - Build the application for production
11 | - `pnpm start` - Start production server
12 | - `pnpm lint` - Run ESLint to check code quality
13 |
14 | ### Media Management
15 |
16 | - `pnpm upload-media` - Upload local media files to Cloudflare R2 and update MDX references
17 | - `pnpm upload-media --dry-run` - Test run without making changes
18 | - `pnpm upload-media --skip-backup` - Skip creating backups
19 | - `pnpm upload-media --skip-cleanup` - Skip cleaning up unused files
20 |
21 | ## Architecture
22 |
23 | This is a Chinese-language personal blog built with Next.js 14 App Router. Key architectural components:
24 |
25 | ### Content Management
26 |
27 | - **Posts**: Located in `posts/` directory, organized by category folders (`ai/`, `build/`, `dev/`, `reading/`)
28 | - **Contentlayer**: Processes MDX files from `posts/` directory using configuration in `contentlayer.config.ts`
29 | - **Post Schema**: Requires `title`, `date`, `category` fields; supports `description`, `tags`, `cover`, `slug`, `featured`
30 | - **Routing**: Posts use computed URLs based on slug or filename, accessible via `/posts/[slug]`
31 |
32 | ### Project Structure
33 |
34 | - `src/app/` - Next.js App Router pages and layouts
35 | - `src/components/` - React components organized by feature (home, post, navigation, common, ui)
36 | - `src/lib/` - Utility functions, constants, routing logic, and metadata helpers
37 | - `scripts/` - Build and deployment scripts, including media upload automation
38 |
39 | ### Key Features
40 |
41 | - **Multi-theme support**: Dark/light mode via next-themes
42 | - **Responsive design**: Tailwind CSS with mobile-first approach
43 | - **Navigation**: Fixed sidebar on desktop (`lg:ml-64` offset), collapsible on mobile
44 | - **Media handling**: Cloudflare R2 integration with automated upload and path replacement
45 | - **SEO**: Comprehensive metadata, OpenGraph, Twitter cards, RSS feed, sitemap
46 |
47 | ### Styling Guidelines
48 |
49 | - **Framework**: Tailwind CSS (avoid custom CSS)
50 | - **Responsive**: Consider both desktop and mobile reading experience
51 | - **Design**: Clean, tech-focused aesthetic prioritizing readability
52 | - **Spacing**: Add spaces between Chinese and English/numbers
53 |
54 | ### Writing Guidelines
55 |
56 | - **Language**: Chinese content with Chinese punctuation
57 | - **File naming**: Use date + title format (e.g., `20240101_my_first_post.mdx`)
58 | - **Content**: Use MDX format, preserve existing content when editing
59 | - **Categories**: Organize posts into `ai/`, `build/`, `dev/`, `reading/` folders
60 |
61 | ### Environment Configuration
62 |
63 | Required for media upload functionality:
64 |
65 | - `CLOUDFLARE_ACCOUNT_ID`
66 | - `R2_ACCESS_KEY_ID`
67 | - `R2_SECRET_ACCESS_KEY`
68 | - `R2_BUCKET_NAME`
69 | - `R2_PUBLIC_URL`
70 |
71 | ### Content Processing
72 |
73 | - **Markdown**: Uses remark-gfm for GitHub Flavored Markdown
74 | - **Code highlighting**: rehype-pretty-code with github-dark theme
75 | - **Links**: Automatic heading anchors via rehype-autolink-headings
76 | - **Slugs**: Auto-generated for heading navigation
77 |
--------------------------------------------------------------------------------
/src/app/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { useEffect } from 'react'
4 | import Link from 'next/link'
5 | import { Container } from '@/components/common/Container'
6 | import { Button } from '@/components/ui/button'
7 | import { Home, RefreshCw, AlertTriangle } from 'lucide-react'
8 |
9 | export default function Error({
10 | error,
11 | reset,
12 | }: {
13 | error: Error & { digest?: string }
14 | reset: () => void
15 | }) {
16 | useEffect(() => {
17 | // 记录错误到错误报告服务
18 | console.error('Error boundary caught:', error)
19 | }, [error])
20 |
21 | return (
22 |
23 |
24 | {/* 错误图标 */}
25 |
33 |
34 | {/* 错误信息 */}
35 |
36 |
37 | 哎呀,出错了
38 |
39 |
40 | 应用程序遇到了意外错误。我们已经记录了这个问题,并会尽快修复。
41 |
42 |
43 |
44 | {/* 错误详情(仅开发环境) */}
45 | {process.env.NODE_ENV === 'development' && (
46 |
47 |
48 | 错误详情:
49 |
50 |
51 | {error.message}
52 |
53 | {error.digest && (
54 |
55 | 错误 ID: {error.digest}
56 |
57 | )}
58 |
59 | )}
60 |
61 | {/* 操作按钮 */}
62 |
63 |
67 |
73 |
74 |
75 | {/* 帮助信息 */}
76 |
77 |
78 | 如果问题持续存在,请尝试:
79 |
80 |
81 | - • 刷新页面
82 | - • 清除浏览器缓存
83 | - • 稍后再试
84 |
85 |
86 |
87 |
88 | )
89 | }
90 |
--------------------------------------------------------------------------------
/src/components/layout/CategoryLayout.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { format, parseISO } from 'date-fns'
4 | import Image from 'next/image'
5 | import Link from 'next/link'
6 | import { Post } from 'contentlayer2/generated'
7 | import { getCategoryName, CATEGORY_MAP } from '@/lib/images'
8 | import { PostRoute } from '@/lib/routes'
9 |
10 | interface CategoryLayoutProps {
11 | posts: Post[]
12 | category: string
13 | }
14 |
15 | export function CategoryLayout({ posts, category }: CategoryLayoutProps) {
16 | return (
17 |
18 | {/* 分类头部 */}
19 |
27 |
28 | {/* 文章列表 */}
29 |
30 | {posts.map(post => (
31 |
35 | {/* 封面图 */}
36 | {post.cover && (
37 |
38 |
45 |
46 | )}
47 |
48 | {/* 文章内容 */}
49 |
50 |
65 |
66 | {post.description}
67 |
68 |
69 | {/* 文章链接 */}
70 |
75 |
76 |
77 |
78 | ))}
79 |
80 |
81 | )
82 | }
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 | import { cn } from "@/lib/utils"
4 |
5 | const cardVariants = cva(
6 | "rounded-lg transition-all duration-normal",
7 | {
8 | variants: {
9 | variant: {
10 | elevated: "bg-white/50 shadow-md dark:bg-gray-800/50 hover:shadow-lg border border-transparent",
11 | outlined: "border border-gray-200 dark:border-gray-800 hover:border-blue-500 dark:hover:border-blue-400 bg-transparent",
12 | ghost: "border border-transparent hover:bg-gray-50 dark:hover:bg-gray-800/50",
13 | solid: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700",
14 | },
15 | padding: {
16 | none: "",
17 | sm: "p-4",
18 | md: "p-6",
19 | lg: "p-8",
20 | },
21 | },
22 | defaultVariants: {
23 | variant: "elevated",
24 | padding: "md",
25 | },
26 | }
27 | )
28 |
29 | export interface CardProps
30 | extends React.HTMLAttributes,
31 | VariantProps {
32 | asChild?: boolean
33 | }
34 |
35 | const Card = React.forwardRef(
36 | ({ className, variant, padding, ...props }, ref) => {
37 | return (
38 |
43 | )
44 | }
45 | )
46 | Card.displayName = "Card"
47 |
48 | const CardHeader = React.forwardRef<
49 | HTMLDivElement,
50 | React.HTMLAttributes
51 | >(({ className, ...props }, ref) => (
52 |
57 | ))
58 | CardHeader.displayName = "CardHeader"
59 |
60 | const CardTitle = React.forwardRef<
61 | HTMLParagraphElement,
62 | React.HTMLAttributes
63 | >(({ className, ...props }, ref) => (
64 |
72 | ))
73 | CardTitle.displayName = "CardTitle"
74 |
75 | const CardDescription = React.forwardRef<
76 | HTMLParagraphElement,
77 | React.HTMLAttributes
78 | >(({ className, ...props }, ref) => (
79 |
84 | ))
85 | CardDescription.displayName = "CardDescription"
86 |
87 | const CardContent = React.forwardRef<
88 | HTMLDivElement,
89 | React.HTMLAttributes
90 | >(({ className, ...props }, ref) => (
91 |
92 | ))
93 | CardContent.displayName = "CardContent"
94 |
95 | const CardFooter = React.forwardRef<
96 | HTMLDivElement,
97 | React.HTMLAttributes
98 | >(({ className, ...props }, ref) => (
99 |
104 | ))
105 | CardFooter.displayName = "CardFooter"
106 |
107 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent, cardVariants }
108 |
--------------------------------------------------------------------------------
/src/components/post/PostFooter.tsx:
--------------------------------------------------------------------------------
1 | import { format, parseISO } from 'date-fns'
2 | import Link from 'next/link'
3 | import { getCategoryName, CATEGORY_MAP } from '@/lib/images'
4 |
5 | interface Post {
6 | _id: string
7 | title: string
8 | url: string
9 | date: string
10 | }
11 |
12 | interface PostFooterProps {
13 | tags?: string
14 | recommendedPosts: Post[]
15 | category?: string
16 | categoryPostsCount: number
17 | }
18 |
19 | export const PostFooter = ({
20 | tags,
21 | recommendedPosts,
22 | category,
23 | categoryPostsCount
24 | }: PostFooterProps) => {
25 | return (
26 |
27 | {/* 标签区域 */}
28 | {tags && (
29 |
30 | {tags.split(',').map((tag) => (
31 |
35 | {tag.trim()}
36 |
37 | ))}
38 |
39 | )}
40 |
41 | {/* 推荐文章区域 */}
42 |
43 |
相关文章
44 |
45 | {recommendedPosts.map(post => (
46 |
51 |
52 | {post.title}
53 |
54 |
57 |
58 | ))}
59 |
60 | {category && categoryPostsCount > 4 && (
61 |
62 |
66 | 查看更多 {getCategoryName(category as keyof typeof CATEGORY_MAP)} 的文章
67 |
70 |
71 |
72 | )}
73 |
74 |
75 | )
76 | }
--------------------------------------------------------------------------------
/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import Link from 'next/link'
4 | import { Container } from '@/components/common/Container'
5 | import { Button } from '@/components/ui/button'
6 | import { Home, ArrowLeft } from 'lucide-react'
7 |
8 | export default function NotFound() {
9 | return (
10 |
11 |
12 | {/* 404 图标 */}
13 |
14 |
15 | 404
16 |
17 |
20 |
21 |
22 | {/* 错误信息 */}
23 |
24 |
25 | 页面未找到
26 |
27 |
28 | 抱歉,您访问的页面不存在或已被移动。请检查 URL 是否正确,或返回首页继续浏览。
29 |
30 |
31 |
32 | {/* 操作按钮 */}
33 |
34 |
40 |
46 |
47 |
48 | {/* 推荐链接 */}
49 |
50 |
51 | 您可能想访问:
52 |
53 |
54 |
58 | AI 分类
59 |
60 |
64 | 开发分类
65 |
66 |
70 | 构建分类
71 |
72 |
76 | 关于我
77 |
78 |
79 |
80 |
81 |
82 | )
83 | }
--------------------------------------------------------------------------------
/posts/build/wenrun/20250308_wenrun_launch.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 文润上线:AI 驱动,让你的文字清晰、准确、流畅、地道
3 | date: 2025-03-08
4 | description: 还在为文本润色耗费大量时间?文润智能润色平台基于先进 AI 技术,让文字清晰、流畅、地道,首月免费体验,立即开启高效创作之旅!
5 | category: build
6 | tags: Build, AI, AI-Driven Text Enhancement, Translation, LLM, Wenrun, Dify
7 | cover: https://media.ginonotes.com/covers/cover_translation_flow2.jpeg
8 | slug: wenrun-launch
9 | featured: true
10 | ---
11 |
12 | 
13 |
14 | ## 引言
15 |
16 | 我们很高兴地宣布,经过数周的开发和测试,文润智能文本润色平台正式上线。文润是一个基于先进 AI 技术打造的智能文本润色平台,旨在解决传统文本创作中的各种痛点,帮助用户快速提升文本质量。
17 |
18 | ## 项目背景:为什么我们创建文润
19 |
20 | 随着互联网内容爆炸式增长,高质量的文本内容越来越受到重视。无论是个人博客、企业网站、学术论文、营销文案还是社交媒体内容,都需要清晰、准确、流畅、地道的语言表达。
21 |
22 | 大型语言模型(LLM)的快速发展为内容创作提供了新的可能。LLM 不仅能理解和生成自然语言,还能进行风格转换、内容优化和润色。我们希望通过文润,让更多人能够享受到 AI 技术带来的内容创作便利。
23 |
24 | ## 文润解决的痛点
25 |
26 | 我们发现,传统的文本创作和编辑方式主要存在以下几个痛点:
27 |
28 | - **耗时费力**:撰写和修改文本需要花费大量时间和精力。在传统的文本创作过程中,作者需要反复修改、校对,这不仅消耗大量时间,还会影响创作效率和质量。
29 | - **质量参差不齐**:受限于个人能力和经验,文本质量难以保证。不同作者的写作水平和专业背景存在差异,导致最终文本质量不稳定,难以达到统一的高标准。
30 | - **"翻译腔"问题**:将外文翻译成中文时,容易出现生硬、不自然的表达。直接翻译往往会保留原文的语言特点,导致译文读起来不够流畅自然,缺乏地道的中文表达方式。
31 | - **风格难以统一**:多人协作时,文本风格难以保持一致,影响整体阅读体验。
32 |
33 | ## 文润的应用场景
34 |
35 | 文润适用于多种内容创作场景,包括但不限于:
36 |
37 | - **技术文档**:将英文技术文档转化为流畅的中文,保持专业性的同时提升可读性。
38 | - **学术论文**:规范学术用语,确保行文严谨,提升论文质量。
39 | - **营销文案**:优化表达方式,突出重点,增强文案感染力。
40 | - **日常写作**:博客、社交媒体文章、邮件等日常写作内容的润色与优化。
41 |
42 | ## 当前版本功能与特性 (MVP)
43 |
44 | 在文润的第一个版本中,我们专注于提供核心功能,确保稳定性和用户体验。当前版本包含以下主要功能:
45 |
46 | ### 核心功能
47 |
48 | - **网页内容转译**:只需输入网页 URL,文润就能自动抓取内容并进行智能转译,支持各种语言各种文章类型到中文的转译。
49 | - **多轮评审润色**:基于 Dify 平台构建的 AI 驱动多轮评审 Workflow,从多个维度(流畅性、准确性、风格一致性等)对文本进行评审和润色。
50 | - **专业模式选择**:提供基础和高级两种转译模式,满足不同用户的需求。
51 | - **实时转译进度**:清晰展示转译过程中的各个阶段和总体进度。
52 |
53 | ### 用户体验
54 |
55 | - **直观的界面设计**:简约专业的用户界面,减少学习成本,提升使用体验。
56 | - **转译前后对比**:方便查看原文和转译结果,直观感受改进效果。
57 | - **历史记录管理**:自动保存转译历史,方便后续查看和再次使用。
58 | - **响应式设计**:适配不同设备,无论是桌面电脑还是移动设备,都能获得良好的使用体验。
59 |
60 | ### 技术亮点
61 |
62 | 文润平台在技术实现上有以下特点:
63 |
64 | - **AI 模型组合**:使用多个先进的 LLM 模型组合(包括Gemini、o3-mini、DeepSeek、Qwen 等),提高转译质量和效率。
65 | - **FireCrawl 网页抓取**:使用专业的 FireCrawl 服务,确保网页内容抓取的准确性和完整性。
66 | - **现代 Web 技术**:采用 Next.js、Tailwind CSS 和Shadcn UI 等现代 Web 技术栈,提供流畅、美观的用户界面。
67 | - **流式响应**:支持流式响应,实时展示转译进度和结果。
68 |
69 | ## 工作原理
70 |
71 | 文润的核心是基于 Dify 平台构建的多轮评审润色 Workflow,主要包含以下步骤:
72 |
73 | 1. **内容抓取**:利用 FireCrawl 工具,根据提供的 URL 抓取文章内容,并自动清理导航栏、广告等无关内容
74 | 2. **初步改写**:将抓取到的英文原文输入 LLM 进行初步中文改写,生成基础稿件
75 | 3. **多轮评审**(高级模式专属):
76 | - 语言流畅性与地道性评审
77 | - 内容准确性与逻辑性评审
78 | - 风格一致性与目标读者适配性评审
79 | 4. **综合改进**:根据多轮评审结果,对文本进行综合优化
80 | 5. **最后润色**:进行最终的文本润色和一致性检查
81 |
82 | 这种多轮评审的思路,借鉴了软件开发中的"代码审查"(Code Review)理念,通过不同角度的评审发现并解决潜在问题,全面提升文本质量。
83 |
84 | 更多技术细节和提示词可参考博客文章 [告别生硬翻译腔:构建 AI 驱动的多轮评审润色流程](https://www.ginonotes.com/posts/ai-driven-multi-round-review-translation-workflow)。
85 |
86 | ## 未来规划
87 |
88 | 我们将持续迭代优化文润平台,计划在未来的版本中加入更多功能,包括:
89 |
90 | - 局部内容改进
91 | - 文本润色
92 | - 更多文件格式支持(PDF、Word、PPT等)
93 | - 自定义词汇表
94 | - API 接口提供
95 | - 浏览器插件
96 |
97 | ## 开始使用文润
98 |
99 | 文润现已正式上线,首月用户可免费体验高级功能。我们诚挚邀请您试用文润平台,感受 AI 技术为文本创作带来的便捷与高效。
100 |
101 | 如果您有任何问题、建议或反馈,欢迎通过邮箱 [gino@wenrun.ai](mailto:gino@wenrun.ai) 与我联系。
102 |
103 |
108 |
--------------------------------------------------------------------------------
/posts/ai/agent/20250123_what_is_ai_agent.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 什么是智能体?[译]
3 | date: 2025-01-23
4 | description: LangChain 探讨了智能体的定义,认为其核心是利用大型语言模型控制应用流程,而非类人实体。文章引入"智能化程度"概念,类比自动驾驶,强调开发高自主性 AI 应用需专用工具如 LangGraph 和 LangSmith。
5 | category: ai
6 | tags: AI, Agent, Autonomy, LLM, LangChain
7 | cover: https://media.ginonotes.com/covers/cover_agent.jpeg
8 | slug: what-is-ai-agents
9 | ---
10 |
11 |
12 | 本文是 LangChain 博客上关于智能体 (AI Agent) 的系列文章的第一篇,探讨了智能体的定义,认为其核心是利用大型语言模型控制应用流程,而非类人实体。文章引入"智能化程度"概念,类比自动驾驶,强调开发高自主性 AI 应用需专用工具如 LangGraph 和 LangSmith。
13 |
14 |
15 | ## 什么是智能体 (AI Agent)?
16 |
17 | 我几乎每天都被问到这个问题。在 LangChain,我们开发工具,帮助开发者构建大型语言模型 (LLM) 应用。这些应用就像会思考的引擎,能与外部信息和计算资源互动。人们常把这类系统叫做 "智能体"。
18 |
19 | 对于智能体 (AI Agent),每个人的理解都有些不同。我的理解可能更技术化:
20 |
21 |
22 | 💡 智能体是一个系统,它用 LLM 来决定应用程序的控制流程 (Control Flow)。
23 |
24 |
25 | 即使这样,我也觉得我的定义不完全到位。人们常觉得智能体应该是高级的、自主的、像人一样的。但如果有个简单的系统,LLM 只是在两个选项里选一个呢?这符合我的技术定义,但和大家印象中的 "智能体" 能力不太一样。 准确定义 "智能体" 真不容易!
26 |
27 | 所以我很喜欢 Andrew Ng 上周发的 [推文](https://x.com/AndrewYNg/status/1801295202788983136)。 他说,"与其争论哪些算不算真正的智能体,不如承认系统有不同程度的自主性 (Agentic)。" 正如自动驾驶汽车有不同等级的自动化一样,智能体的能力也是一个范围。我非常同意这个观点,Andrew 说得很好。以后再有人问我什么是智能体,我会把话题转到讨论 "自主性" 上来。
28 |
29 | ## 什么是自主性 (Agentic)?
30 |
31 | 去年我做了一个关于 LLM 系统的 TED 演讲,用下面这张图展示了 LLM 应用的不同自主程度。
32 |
33 | 
34 |
35 | **一个系统越 "具有自主性",就越是由 LLM 决定系统的行为方式。**
36 |
37 | 用 LLM 把输入信息送到不同的处理流程,就有一点 "自主性" 了。这就像上图里的 `路由器 (Router)` 类别。
38 |
39 | 如果用多个 LLM 来做多次选择呢?那就介于 `路由器` 和 `状态机 (State Machine)` 之间。
40 |
41 | 如果其中一个选择是决定是否继续下一步还是结束——让系统可以循环运行直到完成呢?那就属于 `状态机`。
42 |
43 | 如果系统能自己创建工具,记住工具,并在之后使用呢? 这就像 [Voyager 论文](https://arxiv.org/abs/2305.16291) 里实现的那样,自主性非常高,属于更高级的 `自主智能体 (Autonomous Agent)` 类别。
44 |
45 | 这些关于 "自主性" 的定义还是比较技术化。我更喜欢这种技术性的定义,因为它在设计和描述 LLM 系统时很有用。
46 |
47 | ## 为什么 "自主性" 这个概念有用?
48 |
49 | 和所有概念一样,我们需要问问为什么要有 "自主性" 这个概念。它有什么帮助?
50 |
51 | 了解你的系统有多 "自主",可以帮助你更好地进行开发决策——包括构建、运行、交互、评估,甚至监控系统。
52 |
53 | 系统越 "自主",编排框架就越有用。 如果你要设计一个复杂的自主系统,一个好的框架可以帮助你更快地开发。这个框架应该能很好地支持分支选择和循环运行。
54 |
55 | 系统越 "自主",运行起来就越复杂。 它会变得更复杂,有些任务会花费很长时间。 这意味着你需要把一些任务放到后台运行。 这也意味着你需要有稳定的运行机制来处理运行过程中出现的错误。
56 |
57 | 系统越 "自主",你就越需要在它运行时与它互动。 你会想观察系统内部发生了什么,因为具体的步骤可能事先不知道。 你会希望可以修改智能体的状态或指令,以便在它偏离方向时把它拉回来。
58 |
59 | 系统越 "自主",你就越需要一个专门为这类应用设计的评估框架。 你需要多次运行评估,因为随机性会累积。 你不仅需要测试最终结果,还需要测试中间步骤,看看智能体的效率如何。
60 |
61 | 系统越 "自主",你就越需要一种新的监控框架。 你需要能够详细查看智能体执行的每一步。 你还需要能够根据智能体执行的步骤来查找运行记录。
62 |
63 | 理解和利用系统自主性的不同程度,可以提高开发效率和系统的稳定性。
64 |
65 | ## 智能体是个新事物
66 |
67 | 我经常思考,在这股热潮中,真正的新东西是什么? 对于大家正在构建的 LLM 应用,我们需要新的工具和基础设施吗? 还是用以前的通用工具就够了?
68 |
69 | 我认为,你的应用越 "自主",新的工具和基础设施就越重要。 这正是我们开发 LangGraph (智能体编排工具,用于帮助构建、运行和交互智能体) 和 LangSmith (LLM 应用的测试和可观测平台) 的原因。 随着我们不断探索更高程度的自主性,整个支持工具的生态系统都需要重新设计。
70 |
71 | 原文地址:[What is an AI agent?](https://blog.langchain.dev/what-is-an-agent/)
72 |
73 | ---
74 |
75 | LangChain 智能体系列文章:
76 |
77 | - [1. 什么是智能体?](https://www.ginonotes.com/posts/what-is-ai-agents)
78 | - [2. 什么是认知架构?](https://www.ginonotes.com/posts/what-is-a-cognitive-architecture)
79 | - [3. 为什么你应该外包智能体基础设施,但保留自己的认知架构](https://www.ginonotes.com/posts/outsource-agentic-infrastructure-own-cognitive-architecture)
80 | - [4. 智能体的规划能力](https://www.ginonotes.com/posts/planning-for-agents)
81 | - [5. 智能体的交互模式](https://www.ginonotes.com/posts/ux-for-agents)
82 | - [6. 智能体的记忆](https://www.ginonotes.com/posts/memory-for-agents)
83 | - [7. 沟通:你所需要的一切](https://www.ginonotes.com/posts/communication-is-all-you-need)
--------------------------------------------------------------------------------
/src/components/home/Hero.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { motion } from 'framer-motion'
4 |
5 | export function Hero() {
6 | return (
7 |
8 | {/* 科技感网格背景 */}
9 |
12 |
13 | {/* 装饰性元素 - 调整大小和位置 */}
14 |
15 |
16 |
17 |
18 |
28 |
29 | 探索技术的无限可能
30 |
42 | ✨
43 |
44 |
45 |
46 | 分享开发、产品和生活的思考
47 |
48 |
49 | {/* 技能标签 */}
50 |
51 | {['Java', 'Spring', 'MongoDB', 'Next.js', 'Tailwind CSS', 'LLM', 'Workflow', 'Agent'].map((skill) => (
52 |
59 | {skill}
60 |
61 | ))}
62 |
63 |
64 |
65 | )
66 | }
--------------------------------------------------------------------------------
/posts/ai/agent/20250316_speeding_up_llm_powered_agents.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 加速智能体性能的技巧[译]
3 | date: 2025-03-16
4 | description: 本文为 AI 智能体的开发者提供了加速其性能的实用技巧。文章强调了识别延迟来源的重要性,并探讨了通过优化用户体验、减少 LLM 调用次数、加速 LLM 调用以及利用并行处理等多种策略来提升智能体速度和效率。文章还强调了在性能、成本和能力之间进行权衡的必要性。
5 | category: ai
6 | tags: AI, Agent, Performance Optimization, LLM, LangGraph, LangSmith
7 | cover: https://media.ginonotes.com/covers/cover_speeding_up_llm_powered_agents.jpeg
8 | slug: speeding-up-llm-powered-agents
9 | ---
10 |
11 | 开发者通常首先致力于确保智能体正常运行,之后才会关注其速度和成本优化。我们发现开发者们通常会采取以下几种方法:
12 |
13 | * 找出延迟的来源
14 | * 改变用户体验(UX)以减少"感知"延迟
15 | * 减少大语言模型的调用次数
16 | * 加速大语言模型的调用
17 | * 并行执行大语言模型的调用
18 |
19 | ## 找出延迟的来源
20 |
21 | 这看似简单,但减少延迟的方法很大程度上取决于你的具体瓶颈所在。延迟是源于单次大型 LLM 调用,还是多次小型调用的累积?在尝试加速之前,你需要诊断清楚这些问题。
22 |
23 | [LangSmith](http://langchain.com/langsmith) 是一个非常有用的工具,它能让你全面监控智能体的各个交互环节。你可以追踪智能体每个步骤的延迟,而且我们最近还引入了一个"瀑布"视图,可以轻松识别哪些步骤对整体延迟的贡献最大。
24 |
25 | 
26 |
27 | ## 改变用户体验(UX)以减少"感知"延迟
28 |
29 | 有时,降低延迟最简单的方法……竟然是不减少延迟。
30 |
31 | 乍一看可能有些违反直觉,但如果我们思考一下延迟为什么重要——通常是因为人们担心智能体运行时间过长,用户会不喜欢使用它。通常,优化智能体的用户体验(UX)可以有效缓解这一问题。我们看到人们主要通过两种方式来实现这一点:
32 |
33 | * **流式输出结果。** 流式传输对于大多数大语言模型应用程序来说都很常见,如果你还没有这样做,那么绝对应该考虑。它可以向用户传达大语言模型正在工作的信息,并且他们不太可能离开页面。除了流式传输响应令牌(token)之外,你还可以流式传输最终结果以外的其他信息。例如,你可以流式传输智能体正在采取的计划步骤、检索结果或思考令牌(token)。[Perplexity 的搜索界面在这方面表现出色](https://www.langchain.com/breakoutagents/perplexity)。他们发现,通过更改用户界面(UI)来显示这些中间步骤,用户满意度得到了提高——尽管总完成时间保持不变。
34 |
35 | * **在后台运行智能体。** 让智能体在后台运行。对于我的电子邮件助手,我无法看到电子邮件智能体需要多长时间。这是因为它是由一个事件(一封电子邮件)触发的,我只会在它卡住时收到通知。我向用户隐藏了**所有**延迟,智能体只是在后台运行。
36 |
37 | 
38 |
39 | ## 减少大语言模型的调用次数
40 |
41 | 并非所有任务都必须通过调用大型语言模型来完成。如果能够采用其他方法,则更优!目前构建的智能体通常结合了大型语言模型调用和代码。这种将代码与大语言模型调用相结合的混合方法是 LangGraph 的指导原则之一,也是 [Replit、Uber、LinkedIn 和 Klarna 等公司采用它的核心原因](https://blog.langchain.dev/is-langgraph-used-in-production/)。
42 |
43 | 我们通常看到的路径是"单次大语言模型调用"→"ReAct 智能体"→"多智能体"→"LangGraph"。
44 |
45 | 
46 |
47 | 最初,人们通常只调用一次大语言模型;但当他们遇到局限时,就会选择升级为智能体。这还算可以,但当他们尝试给它更多的工具时,他们意识到单个智能体只能支持这么多工具。然后他们转向"多智能体"设置,使用监督者或蜂群架构(即 swarm architecture)。
48 |
49 | 这样做的问题是,这些架构使用了大量的大语言模型调用。它们在不同智能体之间的通信效率不高。这是设计使然——它们是通用架构,因此没有针对你的用例进行优化。
50 |
51 | 这时,我们看到人们开始使用 [LangGraph](http://langchain.com/langgraph)。LangGraph 是底层工具,允许你精确地指定这些智能体应该如何相互通信(或者何时仅仅是进行一次大语言模型调用)。通常,这会导致大语言模型调用次数显著减少,从而使智能体更快、更便宜(而且通常更可靠)。
52 |
53 | ## 加速大语言模型的调用
54 |
55 | 我们通常看到开发者通过两种方式来加速大语言模型的调用:
56 |
57 | **更快的模型。** 有些模型比其他模型更快。例如,谷歌的 Gemini Flash 模型专为速度优化,性能卓越。OpenAI 和 Anthropic 也有更小、更快的模型。像 Groq 和 Fireworks 这样的开源模型托管平台也在不断努力使最好的开源模型越来越快。注意:这通常需要权衡,因为较快的模型往往体积较小,可能在准确性上有所牺牲。
58 |
59 | **更少的上下文。** 大语言模型响应所需的时间与输入的长度成正比。为了获得更快的结果,你可以传递更少的输入!这就是为什么你需要对**确切**进入每次大语言模型调用的内容拥有**完全的控制和可见性**。如果框架对这些细节进行模糊处理(或难以控制),则可能不太理想。LangGraph 的设计理念恰恰相反,不包含任何隐藏提示,确保用户拥有完全的控制权。如果你正在寻找一种方法来更好地**了解**进入大语言模型调用的内容,请查看 LangSmith。
60 |
61 | 
62 |
63 | ## 并行执行大语言模型的调用
64 |
65 | 虽然这种方法并不适用于所有场景,但如果适合你的需求,就应当采用。LangGraph 提供开箱即用的并行处理能力,方便用户实现并行计算。你可以考虑在以下情况下这样做:
66 |
67 | * 并行执行安全护栏检查和生成
68 | * 并行从多个文档中提取信息
69 | * 并行调用多个模型,然后组合输出
70 |
71 | 
72 |
73 | ## 结论
74 |
75 | 加速你的 AI 智能体最终是在性能、成本和能力之间做出战略性权衡。首先要了解你的具体性能瓶颈,然后根据你的用例有选择地应用这些技术。而且,有时最有效的方法根本不是技术性的——而是重新思考用户如何体验与你的智能体的交互。
76 |
77 | 原文链接:[How do I speed up my agent?](https://blog.langchain.dev/how-do-i-speed-up-my-agent/)
--------------------------------------------------------------------------------
/src/components/navigation/Navigation.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { useState, useEffect, useCallback } from 'react'
4 | import { usePathname } from 'next/navigation'
5 | import { cn } from '@/lib/utils'
6 | import { FaTimes } from 'react-icons/fa'
7 | import { navigation } from '@/config/navigation'
8 | import { NavigationItem } from './NavigationItem'
9 | import { NavigationSection } from './NavigationSection'
10 | import { NavigationProfile } from './NavigationProfile'
11 | import { NavigationHeader } from './NavigationHeader'
12 | import { NavigationFooter } from './NavigationFooter'
13 | import { SearchDialog } from './SearchDialog'
14 |
15 | export function Navigation() {
16 | const pathname = usePathname()
17 | const [isSearchOpen, setIsSearchOpen] = useState(false)
18 | const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
19 |
20 | // 监听移动端返回按钮
21 | useEffect(() => {
22 | if (isMobileMenuOpen) {
23 | document.body.style.overflow = 'hidden'
24 | } else {
25 | document.body.style.overflow = 'unset'
26 | }
27 | }, [isMobileMenuOpen])
28 |
29 | // 监听路由变化,关闭移动端菜单和搜索
30 | useEffect(() => {
31 | setIsMobileMenuOpen(false)
32 | setIsSearchOpen(false)
33 | }, [pathname])
34 |
35 | const handleOpenSearch = useCallback(() => {
36 | setIsSearchOpen(true)
37 | setIsMobileMenuOpen(false) // 打开搜索时关闭移动端菜单
38 | }, [])
39 |
40 | const handleCloseSearch = useCallback(() => {
41 | setIsSearchOpen(false)
42 | }, [])
43 |
44 | return (
45 | <>
46 |
51 | setIsMobileMenuOpen(true)}
53 | onSearchClick={handleOpenSearch}
54 | />
55 |
56 | {/* 移动端菜单遮罩 */}
57 | {isMobileMenuOpen && (
58 | setIsMobileMenuOpen(false)}
61 | />
62 | )}
63 |
64 | {/* 导航菜单 */}
65 |
101 |
102 | {/* 移动端内容区域 padding */}
103 |
104 | >
105 | )
106 | }
--------------------------------------------------------------------------------
/posts/ai/agent/20250214_outsource_agentic_infrastructure_own_cognitive_architecture.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 为什么你应该外包智能体基础设施,但保留自己的认知架构[译]
3 | date: 2025-02-14
4 | description: LangChain 创始人 Harrison Chase 提出:在构建智能体时,应将基础设施(状态管理、任务队列等)交由第三方处理,而专注于开发应用特有的认知架构(决策流程、状态表示)。虽然 OpenAI Assistants API 提供了完善的基础设施,但其通用认知架构限制了复杂应用的开发。开发者应将精力集中在能带来差异化优势的认知架构设计上。
5 | category: ai
6 | tags: AI, Agent, Cognitive Architecture, LLM, LangChain, LangGraph, OpenAI, Assistants API
7 | cover: https://media.ginonotes.com/covers/cover-own-cognitive-architecture.jpeg
8 | slug: outsource-agentic-infrastructure-own-cognitive-architecture
9 | ---
10 |
11 |
12 | 本文是 LangChain 博客上关于智能体 (AI Agent) 的系列文章的第三篇,提出在构建智能体时,应将基础设施(状态管理、任务队列等)交由第三方处理,而专注于开发应用特有的认知架构(决策流程、状态表示)。
13 |
14 |
15 | 当 OpenAI Assistants API 发布时,这标志着 AI 领域向智能体时代迈出了重要一步。它使 OpenAI 从一家提供大型语言模型(LLM)API 的公司,转变为一家提供智能体 API 的企业。
16 |
17 | 我认为 OpenAI Assistants API 在多个方面都表现出色,它引入了许多创新且实用的基础设施,专门用于运行智能体应用。然而,这也在某种程度上限制了开发者在其基础上构建真正复杂(且极具价值!)智能体时所能采用的"[认知架构](https://blog.langchain.dev/what-is-a-cognitive-architecture/)"的灵活性。
18 |
19 | 这凸显了"智能体基础设施"与"[认知架构](https://blog.langchain.dev/what-is-a-cognitive-architecture/)"之间的本质区别。正如亚马逊创始人杰夫·贝索斯那句经典箴言所言:["专注于让你的啤酒更美味"](https://www.acquired.fm/episodes/amazon-com)。如果将这一比喻应用到构建智能体的公司身上,我们可以这样理解:
20 |
21 |
22 | 💡 智能体基础设施本身并不能让你的啤酒更美味。
23 |
24 | 💡 而认知架构则绝对能够提升你啤酒的口感。
25 |
26 |
27 | ## 对智能体基础设施的需求
28 |
29 | OpenAI 准确把握了开发者对更优质基础设施的需求,特别是在运行智能体应用方面。具体表现在:
30 |
31 | - 通过提示词和工具"配置"助手的能力,使创建不同智能体变得便捷
32 | - 支持助手在后台运行,简化了长时间运行工作流程的管理
33 | - 内置的消息持久化功能,便于进行状态管理
34 |
35 | 所有这些功能都是开发者不应该过多考虑的部分。用杰夫·贝索斯的话说,它们不会让你的"啤酒"味道更好,也就是说,它们并不能使你的应用脱颖而出。
36 |
37 | 事实上,还有更多的基础设施可以进一步构建以辅助开发者!例如,在当前的 OpenAI Assistants API 中,你无法在同一对话上运行多个任务,也难以轻松修改对话的状态。尽管如此,Assistants API 无疑朝着正确的方向迈出了重要一步。
38 |
39 | ## 应用专属认知架构的重要性
40 |
41 | Assistants API 的局限性在于,它在支持构建更复杂应用方面显得不够灵活。
42 |
43 | 如果你的目标是开发一个聊天机器人,那么这个 API 确实非常适合!因为对话中的"状态"仅需要一个消息列表就足够了。
44 |
45 | 如果你想构建一个基础的 ReAct 模式智能体,这同样可行,本质上就是在 `while` 循环中反复调用大型语言模型。
46 |
47 | 但智能体应用绝不仅仅是一个不断重复调用相同工具和提示的单一聊天模型。它们需要追踪比消息列表更为复杂的状态,而对应用状态和流程的精细控制正是确保智能体可靠运行的关键所在。
48 |
49 | 通过与数千名开发者的合作,我们发现那些迈向生产化的智能体,各自都具备略有不同的认知架构。应用的认知架构正是让它 **真正卓越** 的关键,各团队正是在这一领域不断创新,力图通过构建独具特色的认知架构来使他们的应用脱颖而出,就像让啤酒更美味一样。
50 |
51 | 这并不是说你无法利用 Assistants API 实现更复杂的功能,理论上可以,但 API 设计上并不鼓励这种做法。OpenAI 押注于一种通用认知架构,这在一定程度上使得构建面向特定应用需求的、能够提升智能体可靠性的专属认知架构变得困难重重。
52 |
53 | ## 我们为何如此关注?
54 |
55 | 为何我对此如此关切?为何要写这么多文字来讨论这个话题?原因在于我坚信 OpenAI 在很多方面做得非常出色,他们早已预见到市场对智能体基础设施的迫切需求,让开发者无需烦恼智能体状态的存储、任务队列的管理等问题,这一点无疑非常振奋人心。
56 |
57 | 在 LangChain,我们的目标是让构建智能体应用变得简单至极,而这正需要这样的基础设施。
58 |
59 | 我们希望将智能体基础设施的优势与 LangGraph 所赋予你对认知架构的掌控力相结合,这正是我们开发 LangGraph Cloud 的初衷。你可以利用 [LangGraph](https://www.langchain.com/langgraph) 设计专属的认知架构,再通过 [LangGraph Cloud](https://blog.langchain.dev/langgraph-cloud/) 进行部署,从而享受智能体基础设施带来的所有优势。
60 |
61 | LangGraph Cloud 提供了容错性极强的可扩展性,专为真实世界的交互场景而优化。我们设计了横向扩展的任务队列和服务器,内置持久化层针对高负载进行了优化,同时支持跨运行的节点缓存配置。这样一来,你就可以掌控应用中那些决定性、具有差异化优势的部分,将其余部分外包处理。
62 |
63 | 总之,专注于让你的"啤酒"味道更美味的关键在于构建优秀的认知架构,而不仅仅是依赖基础设施。
64 |
65 | 原文地址:[Why you should outsource your agentic infrastructure, but own your cognitive architecture](https://blog.langchain.dev/why-you-should-outsource-your-agentic-infrastructure-but-own-your-cognitive-architecture/)
66 |
67 | ---
68 |
69 | LangChain 智能体系列文章:
70 |
71 | - [1. 什么是智能体?](https://www.ginonotes.com/posts/what-is-ai-agents)
72 | - [2. 什么是认知架构?](https://www.ginonotes.com/posts/what-is-a-cognitive-architecture)
73 | - [3. 为什么你应该外包智能体基础设施,但保留自己的认知架构](https://www.ginonotes.com/posts/outsource-agentic-infrastructure-own-cognitive-architecture)
74 | - [4. 智能体的规划能力](https://www.ginonotes.com/posts/planning-for-agents)
75 | - [5. 智能体的交互模式](https://www.ginonotes.com/posts/ux-for-agents)
76 | - [6. 智能体的记忆](https://www.ginonotes.com/posts/memory-for-agents)
77 | - [7. 沟通:你所需要的一切](https://www.ginonotes.com/posts/communication-is-all-you-need)
--------------------------------------------------------------------------------
/scripts/utils.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import { config } from './config';
4 |
5 | // 显示进度
6 | export function showProgress(current: number, total: number, message: string = '处理进度'): void {
7 | const percentage = Math.round((current / total) * 100);
8 | const bar = '='.repeat(Math.floor(percentage / 2)) + '-'.repeat(50 - Math.floor(percentage / 2));
9 | process.stdout.write(`${message}: [${bar}] ${percentage}% (${current}/${total})\r`);
10 | if (current === total) {
11 | process.stdout.write('\n');
12 | }
13 | }
14 |
15 | // 验证文件
16 | export function validateFile(filePath: string): { valid: boolean; reason?: string } {
17 | const ext = path.extname(filePath).toLowerCase();
18 |
19 | if (!Object.keys(config.mimeTypes).includes(ext)) {
20 | return { valid: false, reason: `不支持的文件类型:${ext}` };
21 | }
22 |
23 | try {
24 | const stats = fs.statSync(filePath);
25 | if (stats.size > config.maxFileSize) {
26 | return {
27 | valid: false,
28 | reason: `文件过大:${(stats.size / 1024 / 1024).toFixed(2)}MB(最大限制:${(config.maxFileSize / 1024 / 1024).toFixed(2)}MB)`
29 | };
30 | }
31 | } catch (error) {
32 | return { valid: false, reason: `无法读取文件:${filePath}` };
33 | }
34 |
35 | return { valid: true };
36 | }
37 |
38 | // 备份文件
39 | export async function backupFile(filePath: string): Promise
{
40 | const date = new Date().toISOString().split('T')[0];
41 | const backupDir = path.join(process.cwd(), config.backup.dir, date);
42 |
43 | // 创建备份目录
44 | await fs.promises.mkdir(backupDir, { recursive: true });
45 |
46 | // 保持原始目录结构
47 | const relativePath = path.relative(process.cwd(), filePath);
48 | const backupPath = path.join(backupDir, relativePath);
49 | const backupFileDir = path.dirname(backupPath);
50 |
51 | // 创建备份文件的目录
52 | await fs.promises.mkdir(backupFileDir, { recursive: true });
53 |
54 | // 复制文件
55 | await fs.promises.copyFile(filePath, backupPath);
56 |
57 | return backupPath;
58 | }
59 |
60 | // 清理旧备份
61 | export async function cleanupOldBackups(): Promise {
62 | const backupDir = path.join(process.cwd(), config.backup.dir);
63 | if (!fs.existsSync(backupDir)) return;
64 |
65 | const now = new Date();
66 | const dirs = await fs.promises.readdir(backupDir);
67 |
68 | for (const dir of dirs) {
69 | const dirPath = path.join(backupDir, dir);
70 | const dirDate = new Date(dir);
71 | const daysDiff = (now.getTime() - dirDate.getTime()) / (1000 * 60 * 60 * 24);
72 |
73 | if (daysDiff > config.backup.keepDays) {
74 | await fs.promises.rm(dirPath, { recursive: true });
75 | console.log(`已删除旧备份:${dir}`);
76 | }
77 | }
78 | }
79 |
80 | // 分块处理数组
81 | export async function processInChunks(
82 | items: T[],
83 | processor: (item: T) => Promise,
84 | options: {
85 | chunkSize?: number;
86 | onProgress?: (current: number, total: number) => void;
87 | } = {}
88 | ): Promise {
89 | const { chunkSize = config.concurrency, onProgress } = options;
90 | const results: R[] = [];
91 |
92 | for (let i = 0; i < items.length; i += chunkSize) {
93 | const chunk = items.slice(i, i + chunkSize);
94 | const chunkResults = await Promise.all(chunk.map(processor));
95 | results.push(...chunkResults);
96 |
97 | if (onProgress) {
98 | onProgress(Math.min(i + chunkSize, items.length), items.length);
99 | }
100 | }
101 |
102 | return results;
103 | }
104 |
105 | // 获取文件的 MIME 类型
106 | export function getMimeType(filePath: string): string {
107 | const ext = path.extname(filePath).toLowerCase();
108 | return config.mimeTypes[ext as keyof typeof config.mimeTypes] || 'application/octet-stream';
109 | }
110 |
111 | // 格式化文件大小
112 | export function formatFileSize(bytes: number): string {
113 | const units = ['B', 'KB', 'MB', 'GB'];
114 | let size = bytes;
115 | let unitIndex = 0;
116 |
117 | while (size >= 1024 && unitIndex < units.length - 1) {
118 | size /= 1024;
119 | unitIndex++;
120 | }
121 |
122 | return `${size.toFixed(2)} ${units[unitIndex]}`;
123 | }
--------------------------------------------------------------------------------
/src/components/home/FeaturedPost.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Post } from 'contentlayer2/generated'
4 | import Link from 'next/link'
5 | import { motion } from 'framer-motion'
6 | import { formatDate } from '@/lib/utils'
7 | import { getCategoryName, CATEGORY_MAP } from '@/lib/images'
8 | import Image from 'next/image'
9 |
10 | interface FeaturedPostProps {
11 | post: Post
12 | }
13 |
14 | export function FeaturedPost({ post }: FeaturedPostProps) {
15 | const categoryName = getCategoryName(post.category as keyof typeof CATEGORY_MAP)
16 |
17 | return (
18 |
22 |
26 |
27 |
28 |
29 |
30 |
34 | {categoryName}
35 |
36 | ·
37 |
38 |
39 |
40 |
41 |
42 | {post.title}
43 |
44 |
45 |
46 |
47 |
48 | {post.description}
49 |
50 |
51 |
52 |
56 | 阅读更多
57 |
70 |
71 |
72 |
73 |
74 |
81 |
82 |
83 |
84 | )
85 | }
--------------------------------------------------------------------------------
/documents/coverPrompt.md:
--------------------------------------------------------------------------------
1 | # GinoNotes Blog Cover Image Prompt Guide
2 |
3 | 本文档用于指导生成博客文章封面图的提示词编写。所有封面图应保持统一的设计语言和视觉风格,确保整个博客的一致性。
4 |
5 | ## 设计原则
6 |
7 | 1. 视觉风格
8 | - 极简主义(Minimalist)
9 | - 科技感(Tech-oriented)
10 | - 清新整洁(Clean and Neat)
11 | - 专业性(Professional)
12 |
13 | 2. 色彩方案
14 | - 主色调:深科技蓝 (#0B4F6C)
15 | - 背景色:纯白 (#FFFFFF)
16 | - 强调色:根据主题适当选择
17 | - 辅助色:浅灰 (#F5F5F5)
18 |
19 | 3. 设计元素
20 | - 抽象几何图形
21 | - 网格系统
22 | - 适当留白
23 | - 清晰的层次结构
24 | - 矢量风格图形
25 | - 细腻的渐变效果
26 |
27 | 4. 图片规格
28 | - 分辨率:1200x630px(社交媒体优化尺寸)
29 | - 格式:JPEG/PNG
30 | - 质量:高清晰度
31 |
32 | ## 提示词模板
33 |
34 | ```text
35 | A minimalist tech illustration [SPECIFIC_THEME]. Isometric design with a clean grid system. [MAIN_ELEMENT_DESCRIPTION].
36 |
37 | Key elements:
38 | - [ELEMENT_1]
39 | - [ELEMENT_2]
40 | - [ELEMENT_3]
41 | - [ELEMENT_4]
42 |
43 | Style specifications:
44 | - Primary colors: deep tech blue (#0B4F6C), white (#FFFFFF)
45 | - Secondary colors: [ACCENT_COLOR] for emphasis
46 | - Background: Clean white with subtle grid lines
47 | - Lighting: Soft ambient glow with minimal shadows
48 | - Design style: Flat design with subtle depth
49 |
50 | Technical aspects:
51 | - High contrast for readability
52 | - Sharp vector-style graphics
53 | - Balanced negative space
54 | - Professional tech diagram aesthetic
55 |
56 | Additional details:
57 | - [DETAIL_1]
58 | - [DETAIL_2]
59 | - Subtle particle effects for tech feel
60 | ```
61 |
62 | ## 示例一:AI Agent 文章封面
63 |
64 | ```text
65 | A minimalist tech illustration of a cognitive architecture diagram, showing six ascending levels from pure code to autonomous agent, cyberpunk style. Clean white background with subtle blue gradient. Each level represented by interconnected neural pathways becoming increasingly complex from bottom to top.
66 |
67 | Key elements:
68 | - Geometric shapes representing different AI levels
69 | - Interconnected neural pathways
70 | - Digital elements and data flows
71 | - Modern UI design elements
72 |
73 | Style specifications:
74 | - Primary colors: deep tech blue (#0B4F6C), white (#FFFFFF)
75 | - Secondary colors: electric blue (#0066CC) for emphasis
76 | - Background: Clean white with subtle grid lines
77 | - Lighting: Soft ambient glow with minimal shadows
78 | - Design style: Flat design with subtle depth
79 |
80 | Technical aspects:
81 | - High contrast for readability
82 | - Sharp vector-style graphics
83 | - Balanced negative space
84 | - Professional tech diagram aesthetic
85 |
86 | Additional details:
87 | - Floating UI elements showing level indicators
88 | - Abstract code patterns in background
89 | - Subtle particle effects for tech feel
90 | ```
91 |
92 | ## 示例二:Cloud Computing 文章封面
93 |
94 | ```text
95 | A minimalist tech illustration representing cloud computing and developer tools. Isometric design with a clean grid system. Central focus on interconnected hexagonal nodes representing global network, with subtle floating UI elements showing code snippets and database symbols.
96 |
97 | Key elements:
98 | - Geometric hexagonal grid pattern
99 | - Abstract cloud computing symbols
100 | - Minimalist code editor interfaces
101 | - Database and API connection lines
102 |
103 | Style specifications:
104 | - Primary colors: deep tech blue (#0B4F6C), white (#FFFFFF)
105 | - Secondary colors: cloud orange (#F48120) for emphasis
106 | - Background: Clean white with subtle grid lines
107 | - Lighting: Soft ambient glow with minimal shadows
108 | - Design style: Flat design with subtle depth
109 |
110 | Technical aspects:
111 | - High contrast for readability
112 | - Sharp vector-style graphics
113 | - Balanced negative space
114 | - Professional tech diagram aesthetic
115 |
116 | Additional details:
117 | - Small floating UI windows with code symbols
118 | - Thin connecting lines representing data flow
119 | - Abstract worker/compute nodes
120 | - Subtle particle effects for tech feel
121 | ```
122 |
123 | ## 注意事项
124 |
125 | 1. 提示词应清晰描述主要视觉元素和整体构图
126 | 2. 保持设计语言的一致性
127 | 3. 确保图片主题与文章内容相关
128 | 4. 避免过于复杂或花哨的设计
129 | 5. 预留足够空间放置文字
130 | 6. 考虑社交媒体分享时的显示效果
131 |
132 | ## 使用建议
133 |
134 | 1. 根据文章主题选择合适的模板
135 | 2. 调整关键元素以匹配具体内容
136 | 3. 保持核心设计语言不变
137 | 4. 可以适当调整强调色以匹配主题
138 | 5. 确保所有必要的设计元素都在提示词中明确描述
139 |
--------------------------------------------------------------------------------
/src/app/posts/[...slug]/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Metadata } from 'next'
2 | import { allPosts } from 'contentlayer2/generated'
3 | import {
4 | WEBSITE_HOST_URL,
5 | WEBSITE_AUTHOR,
6 | WEBSITE_LANGUAGE,
7 | WEBSITE_TWITTER
8 | } from '@/lib/constants'
9 |
10 | interface PostLayoutProps {
11 | children: React.ReactNode
12 | params: Promise<{
13 | slug: string[]
14 | }>
15 | }
16 |
17 | function getOgImage(post: { cover?: string }) {
18 | if (!post.cover) {
19 | throw new Error('Post must have a cover image')
20 | }
21 |
22 | // 如果封面图是完整的 URL,直接返回
23 | if (post.cover.startsWith('http')) {
24 | return post.cover
25 | }
26 | // 否则拼接完整的 URL
27 | return `${WEBSITE_HOST_URL}${post.cover}`
28 | }
29 |
30 | function generatePostJsonLd(post: any) {
31 | const keywords = post.tags?.split(',').map((tag: string) => tag.trim()) || []
32 | return {
33 | '@context': 'https://schema.org',
34 | '@type': 'BlogPosting',
35 | headline: post.title,
36 | description: post.description,
37 | author: {
38 | '@type': 'Person',
39 | name: WEBSITE_AUTHOR,
40 | url: WEBSITE_HOST_URL,
41 | },
42 | datePublished: post.date,
43 | dateModified: post.date,
44 | image: getOgImage(post),
45 | url: `${WEBSITE_HOST_URL}${post.url}`,
46 | publisher: {
47 | '@type': 'Person',
48 | name: WEBSITE_AUTHOR,
49 | url: WEBSITE_HOST_URL,
50 | },
51 | inLanguage: WEBSITE_LANGUAGE,
52 | mainEntityOfPage: {
53 | '@type': 'WebPage',
54 | '@id': `${WEBSITE_HOST_URL}${post.url}`,
55 | },
56 | keywords: keywords,
57 | }
58 | }
59 |
60 | export async function generateMetadata({ params }: PostLayoutProps): Promise {
61 | const { slug } = await params
62 | const post = allPosts.find((post) => {
63 | const urlPath = post.url.replace('/posts/', '')
64 | return urlPath === slug.join('/')
65 | })
66 |
67 | if (!post) return {}
68 |
69 | const ogImage = getOgImage(post)
70 | const postUrl = `${WEBSITE_HOST_URL}${post.url}`
71 | const keywords = post.tags?.split(',').map(tag => tag.trim()) || []
72 |
73 | return {
74 | title: post.title,
75 | description: post.description,
76 | keywords: keywords,
77 | authors: [{ name: WEBSITE_AUTHOR }],
78 | openGraph: {
79 | title: post.title,
80 | description: post.description,
81 | type: 'article',
82 | publishedTime: post.date,
83 | authors: [WEBSITE_AUTHOR],
84 | url: postUrl,
85 | images: [
86 | {
87 | url: ogImage,
88 | width: 1200,
89 | height: 630,
90 | alt: post.title,
91 | },
92 | ],
93 | },
94 | twitter: {
95 | card: 'summary_large_image',
96 | site: WEBSITE_TWITTER,
97 | creator: WEBSITE_TWITTER,
98 | title: post.title,
99 | description: post.description,
100 | images: [ogImage],
101 | },
102 | alternates: {
103 | canonical: postUrl,
104 | },
105 | }
106 | }
107 |
108 | export default async function PostLayout({ children, params }: PostLayoutProps) {
109 | // 获取文章数据用于生成结构化数据
110 | const { slug } = await params
111 | const post = allPosts.find((post) => {
112 | const urlPath = post.url.replace('/posts/', '')
113 | return urlPath === slug.join('/')
114 | })
115 |
116 | return (
117 |
118 | {post && (
119 |
125 | )}
126 |
127 | {children}
128 |
129 |
130 | )
131 | }
--------------------------------------------------------------------------------
/posts/dev/growth/20240409_technical_project_manager.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 聊聊开发人员兼任技术 PM 的心得
3 | date: 2024-04-09
4 | description: 作为一名开发人员,如何平衡好技术和项目管理的角色,在保持技术深度的同时,也能够有效地推进项目进展。
5 | category: dev
6 | tags: Project Management, Technical PM, Personal Growth
7 | cover: https://media.ginonotes.com/covers/cover1.jpeg
8 | slug: technical-project-manager
9 | ---
10 |
11 | 作为一名开发人员,如何平衡好技术和项目管理的角色,在保持技术深度的同时,也能够有效地推进项目进展。
12 |
13 | ## 引言
14 |
15 | 在这个数字化飞速发展的时代,技术人才的角色正在经历一场革命。过去,开发人员只需专注于编码和解决技术难题,项目管理由专职的 PM 承担。但在项目日益复杂、市场需求不断演变的今天,越来越多的开发人员开始兼任技术 PM 的角色。常见的情况是,对于一些中小复杂度的项目,核心开发人员和产品经理兼任 PM 的角色,以更好地推动项目的进展。
16 |
17 | 技术 PM 是项目成功的关键推手,他们不仅需要深厚的技术背景,还要掌握项目管理的精髓,包括需求分析、资源协调、风险控制和市场洞察等。这一角色的转变,对开发人员来说既是机遇也是挑战。机遇在于,技术 PM 能够更全面地影响产品的发展方向,提升用户体验,并为企业创造更大的价值。挑战则在于,从专注于技术细节的开发人员,到需要全面考虑项目各个方面的技术 PM,这一过程中需要克服的心理和技术障碍不容小觑。
18 |
19 | 在过去的几年里,我也承担过一些技术 PM 的角色,从最初的不适应到逐渐适应,再到后来的熟练掌握,这一过程中积累了一些经验和心得。本文将分享一些关于开发人员兼任项目技术 PM 的心得,希望能够对正在转型的开发人员有所帮助。
20 |
21 | ## 技术 PM 的新角色与职责
22 |
23 | 技术 PM 的角色是在技术团队和关键项目目标之间架起桥梁。这一角色要求个人不仅要有深厚的技术背景,还要具备项目管理的能力,以及对市场和用户需求的敏锐洞察。对于从开发岗位转型而来的技术 PM 来说,这是一个全新的挑战。
24 |
25 | ### 核心职责
26 |
27 | 1. **需求理解与转化**:技术 PM 需要深入理解业务需求,将其转化为可执行的产品功能。例如,与业务团队紧密合作,将抽象的业务需求转化为具体的产品特性,并从技术角度对需求进行评估和优化。
28 |
29 | 2. **技术战略规划**:技术 PM 负责制定技术路线图,确保技术发展与产品演进、公司战略相匹配。这包括选择合适的技术栈、架构设计以及技术预研,以支持产品的长期发展。
30 |
31 | 3. **项目管理**:技术 PM 要管理项目的整个生命周期,从项目启动、规划、执行、监控到项目收尾。这涉及到时间线管理、资源分配、预算控制以及风险管理,确保项目按时按质完成。
32 |
33 | 4. **团队协作**:技术 PM 需要在几乎没有职权领导力的情况下领导和激励技术团队,推动团队成员朝着共同的目标努力。同时,技术 PM 还要与其他部门协作,确保项目顺利进行,这可能包括与各个业务部门合作,以确保完成业务目标。
34 |
35 | 5. **沟通协调**:技术 PM 是团队内部、团队与利益相关者之间沟通的关键节点。他们需要确保所有参与者对项目目标、进度和预期结果有清晰的认识,并通过有效沟通解决项目过程中的任何问题。
36 |
37 | ### 角色转变
38 |
39 | 对于习惯了专注于代码和具体技术问题的开发者来说,转型为技术 PM 意味着思维方式和工作重点的转变。技术 PM 不再只是解决技术问题,而是要站在更高的层面,从项目整体出发,考虑如何将技术创新转化为用户价值和商业成功。此外,技术 PM 还需要培养自己的领导力和影响力,以便在没有直接管理权限的情况下,有效地推动项目前进。这通常意味着要通过建立信任、展示专业知识和提供指导来赢得团队成员的尊重和支持。
40 |
41 | 总结而言,技术 PM 的新角色和职责要求他们在保持技术专业性的同时,发展出项目管理和领导能力,以确保项目的成功交付和商业价值的实现。
42 |
43 | ## 转型关键:综合能力与项目管理思维
44 |
45 | 开发人员转型为技术 PM 的过程中,关键在于培养和提升一系列综合能力和项目管理思维。这些能力不仅涵盖了技术知识的应用,还包括了对项目管理的理解和实践。
46 |
47 | ### 技术洞察与业务理解
48 |
49 | 技术 PM 首先需要保持对技术趋势的敏感性,同时深入理解业务流程和市场动态。这种洞察力使他们能够在项目规划阶段做出明智的技术选择,并确保技术解决方案与业务目标保持一致。例如,通过参加行业会议、阅读最新的技术材料,技术 PM 可以预见技术变革对产品和市场的影响,从而引导团队进行必要的技术储备和创新。
50 |
51 | ### 沟通与协调
52 |
53 | 沟通是技术 PM 工作的核心。他们需要与不同背景的团队成员进行有效沟通,包括技术人员、设计师、业务人员甚至高层管理者。技术 PM 必须能够清晰地传达技术概念和项目进度,同时也要能够倾听和理解他人的观点和需求。此外,协调能力使技术 PM 能够在团队内部和跨部门之间建立合作,解决冲突,确保项目资源的最优配置。
54 |
55 | ### 风险管理
56 |
57 | 技术 PM 必须具备识别和应对项目风险的能力。这包括技术风险、资源风险、市场风险等。通过风险评估和管理,技术 PM 能够制定应对策略,减少不确定性对项目的影响。这种能力要求技术 PM 具备前瞻性思维和应急处理能力,以及在面对压力时保持冷静和理性。
58 |
59 | ### 目标导向的决策制定
60 |
61 | 技术 PM 需要具备基于目标的决策制定能力。这意味着他们必须明确项目的目标,制定实现这些目标的策略,并在项目执行过程中不断调整和优化。这种能力要求技术 PM 能够基于数据和直觉做出合理的判断,并能够在面对复杂情况时迅速做出决策。
62 |
63 | ### 持续学习与适应
64 |
65 | 技术 PM 的角色要求持续学习和适应新技术、新方法和新流程。他们需要不断更新自己的技术知识库,同时也要学习项目管理的最佳实践。这种持续学习的态度有助于技术 PM 保持竞争力,并能够适应不断变化的市场和技术环境。例如,通过在线课程、行业交流等,技术 PM 可以不断提升自己的专业技能和项目管理能力。
66 |
67 | 总结来说,开发人员转型为技术 PM 的成功关键在于综合能力的提升和项目管理思维的培养。这不仅要求技术 PM 在技术领域保持专业和前瞻性,还要求他们在项目管理、沟通协调、风险控制和决策制定等方面展现出卓越的能力。
68 |
69 | ## 应对挑战的策略:技术 PM 的实践心得
70 |
71 | 在技术 PM 的角色中,面对项目的各种挑战,采取恰当的策略是确保项目成功的关键。项目管理知识体系十分庞大,有五大过程组和十大知识领域,以下是一些个人在实践中认为比较关键、实用的策略。
72 |
73 | ### 组织关键会议
74 |
75 | 1. **项目启动会议**:项目启动是确立项目基调的重要时刻。在这个阶段,技术 PM 应组织一个包含业务方、团队领导和所有相关利益相关者的会议。这次会议的目的是明确项目目标、期望成果和关键里程碑,确保所有参与者对项目有共同的理解。
76 |
77 | 2. **需求评审会议**:需求评审是确保项目按照正确方向前进的关键环节。技术 PM 应该在评审前邀请核心人员进行预审,确保需求的清晰和可实施性,解决分歧、对齐认知。在正式评审时邀请业务方、产品经理、技术团队和测试团队参与,进一步讨论和细化需求。这有助于提前发现潜在问题,减少返工。
78 |
79 | ### 任务分解与时间点管理
80 |
81 | 3. **明确关键时间点**:技术 PM 应做好任务分解,将项目划分为具体的工作项,一个工作项小于 3 人天的粒度,并明确开发、联调、提测、发布等关键时间点。这不仅有助于团队成员理解自己的任务和责任,也便于技术 PM 监控项目进度和及时调整计划。
82 |
83 | ### 沟通与信息同步
84 |
85 | 4. **建立有效的沟通机制**:技术 PM 应通过定期的晨会、群聊、评审会议等方式,及时对齐团队成员的认知。这包括分享项目进度、讨论技术难题、更新变更请求等。确保信息的透明度和及时性,及时更新文档,知会相关人员,有助于减少误解和提高团队效率。
86 |
87 | ### 功能演示与质量控制
88 |
89 | 5. **组织功能演示**:通过组织功能演示,技术 PM 可以将需求中的典型场景具体化,提前接收来自业务、产品、测试等多方的反馈。这不仅有助于确保提测质量,还能促进团队成员对产品功能的理解,提前发现并解决问题。
90 |
91 | ## 小结
92 |
93 | 最后,我想引用一下雷蓓蓓老师在[《雷蓓蓓的项目管理实战课》](https://time.geekbang.org/column/intro/100038501) 专栏中提到的两句话作为小结。
94 |
95 | > **项目管理的底层思维很简单,就是“对焦”两个字。**
96 |
97 | 这句话很精辟的概况了项目管理的精髓。技术 PM 需要对焦业务目标和技术实现,对焦团队成员和项目进度,对焦需求和交付质量。这种对焦能力是技术 PM 成功的关键,很多项目的失败往往是因为对焦不够,导致目标模糊、分歧严重、进度滞后等问题。
98 |
99 | > **所谓的独当一面,就是从一个人做好自己的事,到带领一群人从头到尾把事做成。而学习项目管理,就是在习得这种“使众人行”的协同能力。**
100 |
101 | 技术 PM 和开发人员最大的区别在于,技术 PM 需要从一个人做好自己的事,转变为带领团队从头到尾把事做成。这需要技术 PM 具备协同能力,能够识别不同参与人的性格特质,能够激励团队成员,推动项目进展,确保项目的成功交付,参与人员得到成长。这种能力和发心是技术 PM 成功的关键,也是开发人员转型为技术 PM 需要培养的核心能力。
102 |
--------------------------------------------------------------------------------
/posts/build/design/20250314_practical_ux_for_startups_surviving.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 初创公司实用 UX 指南:无设计师生存之道[译]
3 | date: 2025-03-14
4 | description: 本文为没有设计师的初创公司提供了实用的用户体验设计指南。核心建议包括借鉴成熟的设计模式和竞争对手经验,避免从零开始;明确设计目标并思考用户可能遇到的极端情况;利用 AI 工具进行快速评估;关注用户体验的可衡量指标;保持设计的简洁和用户友好;以及在非核心功能上遵循已有的用户习惯。文章强调实用性胜过创新性,尤其是在资源有限的情况下,借鉴成熟模式能更快地提升用户体验并降低用户学习成本。
5 | category: build
6 | tags: UX, Design, Startups, AI, User Experience
7 | cover: https://media.ginonotes.com/covers/cover_practical_ux_for_startups_surviving.jpeg
8 | slug: practical-ux-for-startups-surviving
9 | ---
10 |
11 | 在资源有限,尤其是**没有设计师**的情况下,如何确定用户体验的设计方向?本文将提供一些实用的建议,帮助那些**没有时间、预算或设计团队**的初创公司,也能打造出色的用户体验。
12 |
13 | 我曾在两家初创公司工作过,当时虽然都希望能聘请产品设计师,但实际操作上这一愿望始终难以实现,只能算是一种**奢望**。即使我们最终决定需要一位设计师,但从面试、通知离职到正式入职,至少需要 3 个月的时间。因此,即使没有设计师,我们也不得不在这段时间内推进项目进展。
14 |
15 | 
16 |
17 | 一个常见的捷径是使用预制组件库,例如 Google 的 [Material UI](https://m2.material.io/design)。它们为你提供了构建模块,但不会帮你考虑整个用户流程。你仍然需要弄清楚如何将所有东西组合在一起。
18 |
19 | 但很多时候,我们做的都不是什么**新鲜玩意儿**。仔细观察大多数软件产品,会发现它们的用户流程大都存在共通之处,因此对于账户注册或密码重置等基础功能,往往不必**从零开始**设计。
20 |
21 | 既然你的时间应该花在能让你的产品**与众不同**的地方,那么如何才能尽快地定义一个好的用户体验呢?
22 |
23 | ### 避免空白页陷阱
24 |
25 | 不要在一块空白的画布前犹豫不决,而疑惑"电子邮件输入框究竟该如何设计?"
26 |
27 | 
28 |
29 | 那些拥有更大团队、价值数百万美元的公司,早已对此进行了深入研究。你可以借鉴他们的经验,更快地获得出色的用户体验。
30 |
31 | 避免参考以下内容:
32 |
33 | * **设计奖网站**:关注原创,而非实用。
34 | * **Dribbble**:重美观,轻功能。
35 |
36 | 相反,可以参考以下内容:
37 |
38 | * **竞争对手网站**:注册体验,截图记录。
39 | * **聚合网站**:如 [PageFlows](https://pageflows.com/) 或 [Mobbin](https://mobbin.com/),方便快速参考。
40 |
41 | 
42 |
43 | 记录以下内容:
44 |
45 | * 常见的用户界面 (UI) 元素,如电子邮件、密码字段、确认流程。
46 | * 视觉布局惯例:如居中表单、响应式设计、清晰按钮、顶部 Logo。
47 |
48 | 想象一个 **维恩图**。如果你的领域中所有产品都以相同方式处理,那很可能 **事出有因**。如果某家公司做法不同,请自问:这是 **有意为之**,还是仅仅是 **失误**?
49 |
50 | 有时,设置摩擦是 **刻意的**。有些公司要求提前提供信用卡详细信息,不是非要这么做,而是为了筛选出真正 **有意的用户**。体验不快,但目的明确。
51 |
52 | 如果你正在构建的东西不简单,请在你的行业之外寻找灵感。假设你正在设计一个收集医疗数据以进行处方续订的功能。
53 |
54 | 如果找不到直接对标,不妨 **扩大视野**:还有哪些机构需要收集敏感信息?比如抵押贷款机构、税务部门,他们同样面临高风险的数据处理。观察他们如何建立信任,解释风险,引导用户完成复杂流程。
55 |
56 | ### 明确你的目标
57 |
58 | 如果你正在设计一个注册页面,那么目标不仅仅是"两个文本字段和一个注册按钮"。而应设定更具体的目标,例如:"**让注册流程尽可能轻松便捷**"。
59 |
60 | 进一步提问:"**如何才能让注册流程更简单、更直观?**"
61 |
62 | 一些答案:
63 |
64 | * 在用户点击提交之前显示密码强度。
65 | * 给他们一个注册的理由,而不仅仅是一个要填写的表格。
66 |
67 | 这也提出了新的问题:
68 |
69 | * 用户是立即登录,还是先验证邮箱?
70 | * 他们应该进入确认页面,还是只收到一条细微的成功消息?
71 |
72 | 你不会预先获得所有答案,但提出正确的问题可以让你专注于重要的事情。
73 |
74 | ### 考虑边缘情况
75 |
76 | 真实用户不会 **按套路出牌**:他们会匆忙操作,跳过指示,或者注意力不集中。
77 |
78 | 时刻反思:**哪里可能出错?**
79 |
80 | * 针对每个输入框思考:如果用户在该处操作仓促并出错,会引发怎样的问题?
81 | * 接着再从整体角度审视——整个流程是否存在其他问题?
82 |
83 | 如果能确保那些 **不耐烦、易分心** 的用户也能获得顺畅的体验,其他用户自然不在话下。
84 |
85 | 糟糕的用户体验并不总是关于丑陋的设计——它通常只是一个 **困惑的用户**,不知道哪里出了问题或如何解决它。
86 |
87 | 例如:
88 |
89 | * 如果他们在创建密码时没有注意会怎么样?他们会设置一个糟糕的密码,然后在以后被锁定。
90 | * **修复方案**:增加"确认密码"字段,要求用户再次输入密码以确保准确。
91 | * 如果密码不匹配会怎么样?他们会点击"注册"并收到一个错误。这令人沮丧。
92 | * **修复方案**:在用户输入第二次密码后,立即显示密码不匹配的警告。
93 |
94 | ### 使用 AI 发现盲点
95 |
96 | 利用 [ChatGPT](https://openai.com/blog/chatgpt) 等工具,可以发现你可能忽略的 UX 问题。这是一个快速的理智检查——虽然并非完美方案,但**胜过盲目猜测**。这种方式比单纯依靠直觉要可靠得多。
97 |
98 | 以下是一些可以尝试的提示:
99 |
100 | * **红蓝对抗**:「挑刺:注册流程哪里会让用户卡壳?」vs.「辩护:这个设计的亮点何在?」
101 | * **对标行业**:「头部 SaaS 企业的注册流程是怎样的?」
102 | * **极端情况**:「如果用户输错邮箱地址却未察觉,会发生什么?」
103 |
104 | 
105 |
106 | 
107 |
108 | ### 其他提示
109 |
110 | * 明确衡量标准。「好的用户体验」可能意味着转化率、用户留存率或用户满意度。设计在某种程度上总是主观的,所以尽量将其客观化,这能让你在项目结束时保持理智。
111 | * 保持颜色简单。一种主色、一种辅助色和一种强调色。[Coolors](https://coolors.co/) 是一个很棒的小工具。
112 | * 用用户能理解的语言,而不是开发者能理解的语言。不要说「数据库错误」,而要说「我们无法保存更改」。
113 |
114 | ### 结束语
115 |
116 | 初创公司需要快速迭代,**完美主义**,尤其是在美学方面,往往是**多余的**。
117 |
118 | 如果团队中没有设计师,更应该**注重实用性**,而不是一味追求新颖。**简洁明了的用户流程**胜过**华而不实的设计**。
119 |
120 | 如果确实需要创新,请明确目标:「**我们真正需要差异化的地方在哪里?**」
121 |
122 | 遵循已有的设计模式,实际上是**借助其他公司来教育用户**,降低用户的学习成本。
123 |
124 | 在你的核心价值上创新;对于其他一切,坚持有效的方法。
125 |
126 | ---
127 |
128 | 原文链接:[Practical UX for startups surviving without a designer](https://www.tibinotes.com/p/practical-ux-for-startups-surviving)
--------------------------------------------------------------------------------
/documents/project.md:
--------------------------------------------------------------------------------
1 | # GinoNotes 博客项目文档
2 |
3 | 这是一个基于 Next.js 14 + Tailwind CSS + contentlayer 构建的个人博客项目。
4 |
5 | ## 1. 技术栈
6 |
7 | - **框架**: Next.js 14 (App Router)
8 | - **样式**: Tailwind CSS
9 | - **内容管理**: contentlayer
10 | - **语言**: TypeScript
11 | - **包管理**: pnpm
12 |
13 | ## 2. 项目结构
14 |
15 | ```
16 | src/
17 | ├── app/ # Next.js App Router 目录
18 | │ ├── page.tsx # 首页
19 | │ ├── global.css # 全局样式
20 | │ ├── layout.tsx # 根布局
21 | │ ├── posts/ # 文章页面
22 | │ └── categories/ # 分类页面
23 | ├── components/ # 可复用组件
24 | │ ├── Navigation.tsx # 导航组件
25 | │ ├── ArticleLayout.tsx # 文章布局
26 | │ ├── CategoryLayout.tsx # 分类布局
27 | │ └── TableOfContents.tsx# 文章目录
28 | ├── lib/ # 工具函数
29 | │ ├── utils.ts # 通用工具
30 | │ └── images.ts # 图片处理
31 | └── content/ # 文章内容
32 | └── posts/ # MDX 文章
33 | ```
34 |
35 | ## 3. 主要功能实现
36 |
37 | ### 3.1 导航系统
38 | - 响应式左侧导航
39 | - 移动端抽屉式菜单
40 | - 文章分类导航
41 | - 搜索功能(Command+K 快捷键)
42 |
43 | ### 3.2 文章系统
44 |
45 | ```typescript
46 | // 文章结构
47 | interface Post {
48 | title: string
49 | date: string
50 | category: string
51 | tags?: string[]
52 | description?: string
53 | cover?: string
54 | featured?: boolean
55 | url: string
56 | body: {
57 | raw: string
58 | code: string
59 | }
60 | }
61 | ```
62 |
63 | ### 3.3 样式系统
64 |
65 | ```css
66 | /* Tailwind CSS 层级组织 */
67 | @layer base {
68 | /* 基础样式:颜色、排版等 */
69 | }
70 |
71 | @layer components {
72 | /* 组件样式:导航、卡片等 */
73 | }
74 |
75 | @layer utilities {
76 | /* 工具类:自定义工具类 */
77 | }
78 | ```
79 |
80 | ## 4. 开发规范
81 |
82 | ### 4.1 文章编写规范
83 | - 文件命名:`YYYYMMDD_title.mdx`
84 | - 必填字段:title, date, category
85 | - 图片路径:使用相对路径,存放在 public/images
86 | - 代码块:使用 rehype-pretty-code 语法
87 |
88 | ### 4.2 样式规范
89 | - 优先使用 Tailwind CSS 类
90 | - 遵循移动优先的响应式设计
91 | - 响应式断点:
92 | ```css
93 | sm: 640px - 小屏手机
94 | md: 768px - 大屏手机/平板
95 | lg: 1024px - 桌面
96 | xl: 1280px - 大屏桌面
97 | ```
98 |
99 | ### 4.3 组件开发规范
100 | - 使用 TypeScript 类型定义
101 | - 组件文件使用 PascalCase 命名
102 | - 客户端组件添加 "use client" 指令
103 | - 提取可复用逻辑到 hooks
104 |
105 | ### 4.4 性能优化
106 | - 使用 Next.js Image 组件优化图片
107 | - 实现组件懒加载
108 | - 优化字体加载
109 | - 合理使用缓存策略
110 |
111 | ## 5. 注意事项
112 |
113 | ### 5.1 类型安全
114 | ```typescript
115 | // 总是定义明确的类型
116 | interface Props {
117 | post: Post
118 | prevPost?: Post
119 | nextPost?: Post
120 | }
121 | ```
122 |
123 | ### 5.2 路由处理
124 | ```typescript
125 | // 使用正确的路由类型
126 | href={post.url as `/posts/${string}/${string}`}
127 | ```
128 |
129 | ### 5.3 暗色模式
130 | - 使用 Tailwind 的 dark: 前缀
131 | - 遵循系统主题设置
132 |
133 | ### 5.4 可访问性
134 | - 添加适当的 ARIA 标签
135 | - 确保键盘导航可用
136 | - 保持足够的颜色对比度
137 |
138 | ## 6. 后续开发建议
139 |
140 | ### 6.1 新功能开发
141 | - 评论系统集成
142 | - 文章统计和分析
143 | - 社交分享功能
144 | - 订阅系统
145 |
146 | ### 6.2 性能优化
147 | - 实现增量静态再生成 (ISR)
148 | - 优化大型文章的加载
149 | - 添加预加载策略
150 |
151 | ### 6.3 用户体验
152 | - 添加加载状态反馈
153 | - 优化移动端交互
154 | - 改进搜索体验
155 |
156 | ### 6.4 维护建议
157 | - 定期更新依赖
158 | - 监控性能指标
159 | - 备份文章内容
160 | - 保持代码风格一致
161 |
162 | ## 7. 开发流程
163 |
164 | 1. 克隆项目后,首先安装依赖:
165 | ```bash
166 | pnpm install
167 | ```
168 |
169 | 2. 启动开发服务器:
170 | ```bash
171 | pnpm dev
172 | ```
173 |
174 | 3. 构建生产版本:
175 | ```bash
176 | pnpm build
177 | ```
178 |
179 | 4. 运行生产版本:
180 | ```bash
181 | pnpm start
182 | ```
183 |
184 | ## 8. 写作指南
185 |
186 | ### 8.1 创建新文章
187 | 1. 在 `content/posts` 目录下创建新的 MDX 文件
188 | 2. 文件名格式:`YYYYMMDD_title.mdx`
189 | 3. 添加必要的 frontmatter:
190 | ```mdx
191 | ---
192 | title: 文章标题
193 | date: 2024-01-01
194 | category: dev
195 | tags: Next.js,React,TypeScript
196 | description: 文章描述
197 | cover: /images/cover.jpg
198 | ---
199 | ```
200 |
201 | ### 8.2 文章格式
202 | - 使用 Markdown 语法
203 | - 代码块指定语言
204 | - 图片使用相对路径
205 | - 保持段落间距
206 |
207 | ### 8.3 图片处理
208 | - 图片存放在 `public/images` 目录
209 | - 使用 Next.js Image 组件
210 | - 提供合适的图片尺寸
211 | - 添加 alt 文本
212 |
213 | ## 9. 部署
214 |
215 | ### 9.1 部署前检查
216 | - 运行所有测试
217 | - 检查构建输出
218 | - 验证页面性能
219 | - 确认环境变量
220 |
221 | ### 9.2 部署流程
222 | 1. 提交代码到 GitHub
223 | 2. 触发自动部署
224 | 3. 验证部署结果
225 | 4. 监控性能指标
226 |
227 | ## 10. 贡献指南
228 |
229 | 1. Fork 项目
230 | 2. 创建功能分支
231 | 3. 提交更改
232 | 4. 发起 Pull Request
233 |
234 | ## 11. 问题反馈
235 |
236 | 如果发现问题或有改进建议,请:
237 | 1. 检查是否是已知问题
238 | 2. 创建详细的 Issue
239 | 3. 提供复现步骤
240 | 4. 附上相关日志或截图
241 |
242 | ---
243 |
244 | **注意**:本文档会随项目发展持续更新,请定期查看最新版本。
--------------------------------------------------------------------------------
/posts/ai/mcp/20250311_mcp_fad_or_fixture.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: MCP:昙花一现还是未来之选?[译]
3 | date: 2025-03-11
4 | description: LangChain 的 Harrison Chase 认为 MCP 有潜力成为未来标准,尤其在为用户无法控制的智能体便捷扩展工具方面具有重要意义,并能赋能非开发者。他将其比作 AI 领域的 Zapier,看好其在长尾应用场景的潜力。 然而,LangGraph 的 Nuno Campos 对此持怀疑态度,认为 MCP 当前形式复杂、实用性有限,且模型在工具调用方面仍存在挑战。他强调工具与智能体架构深度集成的必要性,并指出 MCP 需要在易用性、服务器部署和质量保证方面做出重大改进才能真正普及。
5 | category: ai
6 | tags: MCP, AI, Agent, LLM, Tools
7 | cover: https://media.ginonotes.com/covers/cover_mcp_fad_or_fixture.jpeg
8 | slug: mcp-fad-or-fixture
9 | ---
10 |
11 | *模型上下文协议 (MCP) 近来在 Twitter 上引发热议,但它究竟是真有价值,还是仅仅是又一次炒作? 在这场辩论中,LangChain 首席执行官 Harrison Chase 和 LangGraph 负责人 Nuno Campos 展开交锋,深入探讨 MCP 的前景与价值。*
12 |
13 | **Harrison:** 我认为 MCP 确实大有可为。最初我对它持保留态度,但现在我逐渐看到了它的潜力。 核心价值在于: **当你需要为你无法直接控制的智能体扩展工具时,MCP 就显得尤为重要。**
14 |
15 | 举例来说,对于像 Claude Desktop、Cursor、Windsurf 这样的应用,作为用户,我们无法触及它们底层的智能体逻辑。 这些智能体只能使用预先集成的少量内置工具。
16 |
17 | 但如果我们希望它们能够使用默认配置之外的工具呢? 这就需要一种协议规范来解决问题,否则智能体如何知晓并调用这些外部工具?
18 |
19 | 我相信 MCP 对于非开发者构建智能体也将大有裨益。 当前的一个趋势是,越来越多的行业专家希望能够参与到智能体的构建中来,无论他们是否具备深厚的技术背景。 这些"智能体构建者"通常不希望,或者说不具备能力,去修改智能体底层的代码逻辑,但他们一定希望能够为智能体赋予特定的工具能力。 而 MCP 正好可以满足这种需求。
20 |
21 | **Nuno:** 我认为你可能低估了工具与智能体其他组件之间深度适配的重要性。 诚然,如果像 Windsurf 这样的应用(天哪!)内置了一个糟糕的网页搜索工具,你想用更好的工具来替换它,这或许可行。 但这并非 MCP 真正有价值的应用场景。
22 |
23 | 真正令人兴奋的应用场景是,用户可以通过注入你"独门秘籍"般的工具,赋予 Cursor 等应用开发者都意想不到的全新能力。 但在实际应用中,这种情况往往难以实现。 我所见过的几乎所有生产级别的智能体,都需要针对所集成的工具进行深度定制,包括调整系统提示,甚至是智能体的整体架构。
24 |
25 | **Harrison:** 话虽如此,这些集成了 MCP 工具的智能体,即使不能达到 99% 的完美可靠,也可能已经足够好用,能够满足一定的需求,不是吗? 工具的描述和使用说明固然重要! 但我们也要看到:
26 |
27 | 1. **MCP 本身就支持工具定义。** 优秀的 MCP 服务器提供的工具描述,很可能比我们临时编写的描述更加完善和有效。
28 | 2. **MCP 允许自定义提示 (Prompts)。** 这意味着我们可以通过提示来引导智能体更好地使用工具。
29 | 3. **随着底层大模型的不断进化,开箱即用的工具调用能力必将显著提升。** 我不认为会有人仅仅依靠 MCP 集成和通用的工具调用智能体,就能打造出媲美 Cursor 的下一代应用。 但 MCP 肯定有其内在价值,尤其是在构建内部或个人智能体方面。
30 |
31 | **Nuno:** 我们自己进行的工具调用基准测试表明,即使是那些架构和提示词都针对特定工具集量身定制的智能体,当前的模型在工具选择上的正确率也只有一半左右。 设想一下,一个工具调用成功率只有一半的个人智能体,其可用性实在令人担忧……
32 |
33 | 诚然,模型会不断进步,但用户的期望也会随之水涨船高。 正如杰夫·贝索斯所说:"我欣赏顾客的一点是,他们天生就'不满'。 他们的期望从不是一成不变的,而是不断攀升的。 这就是人性。"
34 |
35 | 如果你掌控了整个技术栈——从用户界面 (UI) 到提示词,再到智能体架构和工具本身——你或许还能勉力满足这些日益增长的期望。 否则,恐怕只能祝你好运了。
36 |
37 | **Harrison:** 模型性能持续提升是毋庸置疑的大趋势,对此我充满信心。 因此,无论当前智能体的工具调用成功率如何,未来都只会不断提高。
38 |
39 | 我认为,将 MCP 方案构建的智能体,与那些针对特定工具深度优化的成熟智能体直接比较,是不恰当的。 MCP 的真正价值在于其能够实现大量的、个性化的长尾连接和集成。
40 |
41 | 就像 Zapier 那样,它可以将电子邮件与 Google Sheets、Slack 等各种应用和服务无缝连接。 我们可以基于 MCP 构建出数量庞大、种类繁多的定制化工作流,而不可能为每一种工作流都开发一个完美打磨的专属智能体。 借助 MCP,我们就能快速创建满足自身特定需求的"定制版 Zapier"。
42 |
43 | 你觉得用 Zapier 来类比 MCP 恰当吗?
44 |
45 | **Nuno:** 在 LangChain,我们早在两年前就推出了包含 500 多个工具的工具库,但遗憾的是,我并没有看到这些工具在实际生产环境中得到广泛应用。 这些工具都遵循相同的"协议"标准开发,与各种模型兼容,并且支持即插即用。 那么,MCP 的独特之处究竟在哪里呢? 难道仅仅是它那"令人惊叹"的形式——需要在本地终端运行无数个服务器,而且只能在桌面应用中使用? 这怎么看都不像是什么优势。 坦白说,我认为 Zapier 已经是 MCP 能够达到的上限了。
46 |
47 | **Harrison:** 我认为 LangChain 工具和 MCP 工具的核心区别在于,MCP 并非为智能体的开发者而设计。 当你需要为你**无法**自行开发的智能体扩展工具能力时,MCP 的价值才能真正体现。
48 |
49 | 需要明确的是——如果我要开发一个全新的智能体来完成 XYZ 任务,我肯定不会选择 MCP。 因为我并不认为那是 MCP 的目标应用场景。 MCP 的目标是为那些你无法控制的智能体添加工具。 此外,MCP 还能够让非开发者也能轻松为智能体扩展工具能力(而 LangChain 工具主要面向开发者)。 要知道,**非开发者的数量远超**开发者。
50 |
51 | 至于 MCP 目前这种略显笨拙的使用形式? 确实有待改进。 但它一定会变得更好,对吗? 我设想的 MCP 未来形态是: 用户可以一键安装 MCP 应用(无需再在本地终端运行服务器),并且可以在 Web 应用中直接使用。 我相信这才是 MCP 的发展方向。
52 |
53 | 你认为 MCP 需要做出哪些改变,才能让你相信它真的有价值?
54 |
55 | **Nuno:** 好吧,你的意思是 MCP 需要变得更像 OpenAI 的 Custom GPTs,这样当前的这波热潮才算师出有名。 但问题是,Custom GPTs 本身也并不算非常流行。 那么,Custom GPTs 究竟缺了什么,而 MCP 又拥有什么独特的优势呢?
56 |
57 | **Harrison:** 我的意思是,MCP 其实更像是插件 (Plugins),而插件的尝试也并未获得成功,不是吗? 我对插件的体验已经有些模糊了(甚至不确定自己是否真正用过 Plugins),所以我的类比可能不完全准确。 但我认为:
58 |
59 | * **MCP 的生态系统已经远超插件。**
60 | * **如今的模型性能更强大,也更有能力有效地利用这些工具。**
61 |
62 | **Nuno:** 嗯,我不确定 MCP 的生态系统是否真的更大。 我随便在网上搜了一下,找到一个[随机的 MCP 服务器目录](https://glama.ai/mcp/servers),它在撰写本文时只列出了 893 个服务器。 我怀疑你是不是把 Twitter 时间线上提到 MCP 的推文数量,当成了实际基于 MCP 构建的应用数量了? 🙂 言归正传,回到你刚才提出的问题,我认为,如果 MCP 不想仅仅成为人工智能发展史上的一个短暂注脚,它就必须做到以下几点:
63 |
64 | * **降低复杂性。** 为什么一个工具协议需要同时承担提示词处理和 LLM 补全 (completions) 的功能?
65 | * **简化实现难度。** 为什么一个服务于工具的协议需要双向通信? 是的,我读过 MCP 官方列出的种种理由,但恕我直言,仅仅为了从服务器接收日志,并非一个充分合理的理由。
66 | * **支持在服务器端部署。** 无状态协议是实现服务器端部署的关键——不能因为我们现在都在用大语言模型 (LLM) 进行开发,就忘记了那些在 Web 服务规模化扩展方面积累的宝贵经验教训。 一旦支持服务器端部署,一系列新的问题也会浮出水面,例如身份验证 (Auth)(这在分布式环境下并非易事)。
67 | * **解决集成随机工具带来的质量损失问题。** 将未经适配的工具随意接入智能体,势必会影响智能体的整体性能和用户体验。
68 |
69 | **Harrison:** 你说的或许有道理,我最近可能确实因为过于关注 Twitter 上的讨论,而产生了一些认知偏差。 但 Twitter 上对 MCP 的质疑声音也同样不少啊!
70 |
71 | 所以,不如让我们把这个问题抛给大众,听听大家的看法。 参与下面的 Twitter 投票,投出你的一票——你认为 MCP 会是昙花一现,还是会成为未来的行业标准?
72 |
73 | 
74 |
75 | 原文地址:[MCP: Flash in the Pan or Future Standard?](https://blog.langchain.dev/mcp-fad-or-fixture/)
76 |
77 |
--------------------------------------------------------------------------------
/posts/ai/model/20250312_openai_agents_platform.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: OpenAI 全新智能体平台 [译]
3 | date: 2025-03-12
4 | description: OpenAI 发布全新智能体平台,包括 Responses API、Web Search、Computer Use 等工具,以及开源的 Agents SDK,为开发者提供更强大的智能体开发能力。
5 | category: ai
6 | tags: OpenAI, Agents Platform, Responses API, Web Search, Computer Use, Agents SDK
7 | cover: https://media.ginonotes.com/covers/cover_openai_agents_platform.jpeg
8 | slug: openai-agents-platform
9 | ---
10 |
11 | import VideoPlayer from '@/components/common/VideoPlayer'
12 |
13 | 尽管现在业界普遍认为 **[2025 年将是「智能体之年」](https://www.youtube.com/watch?v=5N33E9tC400)**,OpenAI 却在默默发力,为此目标积极布局。今年年初的两个月,他们相继推出了 **Operator** 和 **Deep Research**,堪称目前为止最成功的智能体之一。而今天,他们正在将这些能力中的诸多部分开放到 API 接口。
14 |
15 | * [Responses API](https://platform.openai.com/docs/quickstart?api-mode=responses)(响应 API)
16 | * [Web Search Tool](https://platform.openai.com/docs/guides/tools-web-search)(网页搜索工具)
17 | * [Computer Use Tool](https://platform.openai.com/docs/guides/tools-computer-use)(计算机使用工具)
18 | * [File Search Tool](https://platform.openai.com/docs/guides/tools-file-search)(文件搜索工具)
19 | * 一个全新的开源 [Agents SDK](https://platform.openai.com/docs/guides/agents)(智能体开发工具包),集成了 [Observability Tools](https://platform.openai.com/docs/guides/agents#orchestration)(可观测性工具)
20 |
21 | 我们将在今天的 [YouTube](https://www.youtube.com/watch?v=QU9QLi1-VvU) 闪电播客中介绍所有这些内容以及更多精彩信息!
22 |
23 |
24 |
25 | 下面是细节部分。
26 |
27 | ### Responses API(响应 API)
28 |
29 | 
30 |
31 | 在我们的 [Michelle Pokrass 那期节目](https://www.latent.space/p/openai-api-and-o1) 中,我们讨论过 Assistants API(助手 API)需要重新设计。今天,OpenAI 推出了 Responses API,这是一个更灵活的基础,供开发者构建智能体应用。它不仅是 Chat Completion API 的超集,更是开发者探索 OpenAI 模型的理想起点。
32 |
33 | 其中一个重要的升级是为响应 API 提供了一套新的内置工具:网页搜索、计算机使用和文件搜索。
34 |
35 | ### Web Search Tool(网页搜索工具)
36 |
37 | 之前我们曾在播客中邀请了 [Exa AI](https://www.latent.space/p/exa) 来讨论 AI 的网页搜索。现在 OpenAI 也加入了这场网页搜索的竞争;Web Search API(网页搜索 API)实际上是一个新的"模型",它公开了两个 4o 微调模型:`gpt-4o-search-preview` 和 `gpt-4o-mini-search-preview`。这些模型与 ChatGPT Search 使用的模型相同,价格分别为每 1000 次查询 30 美元和 25 美元。
38 |
39 | 其强大的功能是内联引用:你不仅可以获得指向相关页面的链接,更能直接定位到结果页面中精准解答你疑问的具体位置。
40 |
41 | 
42 |
43 | ### Computer Use Tool(计算机使用工具)
44 |
45 | 驱动 Operator 的模型名为 Computer-Using-Agent (CUA,计算机使用智能体),现在也可在 API 中使用。`computer-use-preview` 模型在大多数基准测试中都处于领先地位,在 OSWorld 上实现了 38.1% 的完整计算机使用任务成功率,在 WebArena 上实现了 58.1% 的成功率,在 WebVoyager 上实现了 87% 的基于 Web 的交互成功率。
46 |
47 | 正如文档所示,`computer-use-preview` 不仅是一个模型,还是一个允许你自定义运行环境的强大工具。
48 |
49 | 
50 |
51 | 使用价格为每 100 万个输入 tokens 3 美元,每 100 万个输出 tokens 12 美元,目前仅对第 3-5 层的用户开放。
52 |
53 | ### File Search Tool(文件搜索工具)
54 |
55 | 文件搜索也可在 Assistants API(助手 API)中使用,现在也已加入 Responses API。OpenAI 正在将搜索和 RAG(检索增强生成)技术集成到一个统一的平台中,我们肯定会看到更多人尝试在 OpenAI 上找到构建一体化应用的新方法。
56 |
57 | 使用价格为每千次查询 2.50 美元,文件存储价格为每天每 GB 0.10 美元,首 GB 免费。
58 |
59 | 
60 |
61 | ### Agent SDK(智能体 SDK):Swarms++!
62 |
63 | https://github.com/openai/openai-agents-python
64 |
65 | 
66 |
67 | 为了实现更强大的整合,在 [Swarm](https://github.com/openai/swarm/) 广受欢迎之后,OpenAI 正式发布官方支持的智能体框架,该框架在 [我们的 AI Engineer Summit 上进行了预览](https://www.youtube.com/watch?v=joHR2pmxDQE),包含4 个核心部分:
68 |
69 | * **智能体(Agents)**:可轻松配置的 LLM,具备明确的指令和内置工具。
70 | * **移交(Handoffs)**:智能地实现智能体之间的控制权转移。
71 | * **安全护栏(Guardrails)**:可配置的安全检查机制,用于输入和输出的有效性验证。
72 | * **追踪与可观测性(Tracing & Observability)**:将智能体执行的轨迹可视化呈现,用于调试和性能优化。
73 |
74 | 多智能体工作流程将长期存在!
75 |
76 | 
77 |
78 | 为了实现更完善的智能体应用,OpenAI 目前正在设计一系列 [常见的智能体应用模式](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns) ,这些模式包括:工作流、移交、智能体即工具、大语言模型即裁判、并行化和安全护栏。OpenAI 在纽约演讲的第二部分中透露了这一点:
79 |
80 |
81 |
82 | 当然,也可以在观看以下发布会的直播内容:
83 |
84 |
85 |
86 | 原文链接:[⚡️ The new OpenAI Agents Platform](https://www.latent.space/p/openai-agents-platform)
87 |
--------------------------------------------------------------------------------
/documents/chinese-copywriting-guidelines.md:
--------------------------------------------------------------------------------
1 | # 中文文案排版指北
2 |
3 | 统一中文文案、排版的相关用法,降低团队成员之间的沟通成本,增强网站气质。
4 |
5 | * * *
6 |
7 | ## 空格
8 |
9 | > 「有研究显示,打字的时候不喜欢在中文和英文之间加空格的人,感情路都走得很辛苦,有七成的比例会在 34 岁的时候跟自己不爱的人结婚,而其余三成的人最后只能把遗产留给自己的猫。毕竟爱情跟书写都需要适时地留白。
10 | >
11 | > 与大家共勉之。」——[vinta/paranoid-auto-spacing](https://github.com/vinta/pangu.js)
12 |
13 | ### 中英文之间需要增加空格
14 |
15 | 正确:
16 |
17 | > 在 LeanCloud 上,数据存储是围绕 `AVObject` 进行的。
18 |
19 | 错误:
20 |
21 | > 在LeanCloud上,数据存储是围绕`AVObject`进行的。
22 | >
23 | > 在 LeanCloud上,数据存储是围绕`AVObject` 进行的。
24 |
25 | 完整的正确用法:
26 |
27 | > 在 LeanCloud 上,数据存储是围绕 `AVObject` 进行的。每个 `AVObject` 都包含了与 JSON 兼容的 key-value 对应的数据。数据是 schema-free 的,你不需要在每个 `AVObject` 上提前指定存在哪些键,只要直接设定对应的 key-value 即可。
28 |
29 | 例外:「豆瓣FM」等产品名词,按照官方所定义的格式书写。
30 |
31 | ### 中文与数字之间需要增加空格
32 |
33 | 正确:
34 |
35 | > 今天出去买菜花了 5000 元。
36 |
37 | 错误:
38 |
39 | > 今天出去买菜花了 5000元。
40 | >
41 | > 今天出去买菜花了5000元。
42 |
43 | ### 数字与单位之间需要增加空格
44 |
45 | 正确:
46 |
47 | > 我家的光纤入屋宽带有 10 Gbps,SSD 一共有 20 TB
48 |
49 | 错误:
50 |
51 | > 我家的光纤入屋宽带有 10Gbps,SSD 一共有 20TB
52 |
53 | 例外:度数/百分比与数字之间不需要增加空格:
54 |
55 | 正确:
56 |
57 | > 角度为 90° 的角,就是直角。
58 | >
59 | > 新 MacBook Pro 有 15% 的 CPU 性能提升。
60 |
61 | 错误:
62 |
63 | > 角度为 90 ° 的角,就是直角。
64 | >
65 | > 新 MacBook Pro 有 15 % 的 CPU 性能提升。
66 |
67 | ### 全角标点与其他字符之间不加空格
68 |
69 | 正确:
70 |
71 | > 刚刚买了一部 iPhone,好开心!
72 |
73 | 错误:
74 |
75 | > 刚刚买了一部 iPhone ,好开心!
76 | >
77 | > 刚刚买了一部 iPhone, 好开心!
78 |
79 | ### 用 `text-spacing` 来挽救?
80 |
81 | CSS Text Module Level 4 的 [`text-spacing`](https://www.w3.org/TR/css-text-4/#text-spacing-property) 和 Microsoft 的 [`-ms-text-autospace`](https://msdn.microsoft.com/library/ms531164(v=vs.85).aspx) 可以实现自动为中英文之间增加空白。不过目前并未普及,另外在其他应用场景,例如 macOS、iOS、Windows 等用户界面目前并不存在这个特性,所以请继续保持随手加空格的习惯。
82 |
83 | ## 标点符号
84 |
85 | ### 不重复使用标点符号
86 |
87 | 虽然中国大陆的标点符号用法允许重复使用标点符号,但是这么做会破坏句子的美观性。
88 |
89 | 正确:
90 |
91 | > 德国队竟然战胜了巴西队!
92 | >
93 | > 她竟然对你说「喵」?!
94 |
95 | 错误:
96 |
97 | > 德国队竟然战胜了巴西队!!
98 | >
99 | > 德国队竟然战胜了巴西队!!!!!!!!
100 | >
101 | > 她竟然对你说「喵」??!!
102 | >
103 | > 她竟然对你说「喵」?!?!??!!
104 |
105 | ## 全角和半角
106 |
107 | 不明白什么是全角(全形)与半角(半形)符号?请查看维基百科条目『[全角和半角](https://zh.wikipedia.org/wiki/%E5%85%A8%E5%BD%A2%E5%92%8C%E5%8D%8A%E5%BD%A2)』。
108 |
109 | ### 使用全角中文标点
110 |
111 | 正确:
112 |
113 | > 嗨!你知道嘛?今天前台的小妹跟我说「喵」了哎!
114 | >
115 | > 核磁共振成像(NMRI)是什么原理都不知道?JFGI!
116 |
117 | 错误:
118 |
119 | > 嗨! 你知道嘛? 今天前台的小妹跟我说 "喵" 了哎!
120 | >
121 | > 嗨!你知道嘛?今天前台的小妹跟我说"喵"了哎!
122 | >
123 | > 核磁共振成像 (NMRI) 是什么原理都不知道? JFGI!
124 | >
125 | > 核磁共振成像(NMRI)是什么原理都不知道?JFGI!
126 |
127 | 例外:中文句子内夹有英文书籍名、报刊名时,不应借用中文书名号,应以英文斜体表示。
128 |
129 | ### 数字使用半角字符
130 |
131 | 正确:
132 |
133 | > 这个蛋糕只卖 1000 元。
134 |
135 | 错误:
136 |
137 | > 这个蛋糕只卖 1000 元。
138 |
139 | 例外:在设计稿、宣传海报中如出现极少量数字的情形时,为方便文字对齐,是可以使用全角数字的。
140 |
141 | ### 遇到完整的英文整句、特殊名词,其内容使用半角标点
142 |
143 | 正确:
144 |
145 | > 乔布斯那句话是怎么说的?「Stay hungry, stay foolish.」
146 | >
147 | > 推荐你阅读 *Hackers & Painters: Big Ideas from the Computer Age*,非常地有趣。
148 |
149 | 错误:
150 |
151 | > 乔布斯那句话是怎么说的?「Stay hungry,stay foolish。」
152 | >
153 | > 推荐你阅读《Hackers&Painters:Big Ideas from the Computer Age》,非常的有趣。
154 |
155 | ## 名词
156 |
157 | ### 专有名词使用正确的大小写
158 |
159 | 大小写相关用法原属于英文书写范畴,不属于本 wiki 讨论内容,在这里只对部分易错用法进行简述。
160 |
161 | 正确:
162 |
163 | > 使用 GitHub 登录
164 | >
165 | > 我们的客户有 GitHub、Foursquare、Microsoft Corporation、Google、Facebook, Inc.。
166 |
167 | 错误:
168 |
169 | > 使用 github 登录
170 | >
171 | > 使用 GITHUB 登录
172 | >
173 | > 使用 Github 登录
174 | >
175 | > 使用 gitHub 登录
176 | >
177 | > 使用 gイんĤЦ8 登录
178 | >
179 | > 我们的客户有 github、foursquare、microsoft corporation、google、facebook, inc.。
180 | >
181 | > 我们的客户有 GITHUB、FOURSQUARE、MICROSOFT CORPORATION、GOOGLE、FACEBOOK, INC.。
182 | >
183 | > 我们的客户有 Github、FourSquare、MicroSoft Corporation、Google、FaceBook, Inc.。
184 | >
185 | > 我们的客户有 gitHub、fourSquare、microSoft Corporation、google、faceBook, Inc.。
186 | >
187 | > 我们的客户有 gイんĤЦ8、キouЯƧquムгє、๓เςг๏ร๏Ŧt ς๏гק๏гคtเ๏ภn、900913、ƒ4ᄃëв๏๏к, IПᄃ.。
188 |
189 | 注意:当网页中需要配合整体视觉风格而出现全部大写/小写的情形,HTML 中请使用标淮的大小写规范进行书写;并通过 `text-transform: uppercase;`/`text-transform: lowercase;` 对表现形式进行定义。
190 |
191 | ### 不要使用不地道的缩写
192 |
193 | 正确:
194 |
195 | > 我们需要一位熟悉 TypeScript、HTML5,至少理解一种框架(如 React、Next.js)的前端开发者。
196 |
197 | 错误:
198 |
199 | > 我们需要一位熟悉 Ts、h5,至少理解一种框架(如 RJS、nextjs)的 FED。
200 |
201 | ## 争议
202 |
203 | 以下用法略带有个人色彩,即:无论是否遵循下述规则,从语法的角度来讲都是**正确**的。
204 |
205 | ### 链接之间增加空格
206 |
207 | 用法:
208 |
209 | > 请 [提交一个 issue](#) 并分配给相关同事。
210 | >
211 | > 访问我们网站的最新动态,请 [点击这里](#) 进行订阅!
212 |
213 | 对比用法:
214 |
215 | > 请[提交一个 issue](#)并分配给相关同事。
216 | >
217 | > 访问我们网站的最新动态,请[点击这里](#)进行订阅!
218 |
219 | ### 简体中文使用直角引号
220 |
221 | 用法:
222 |
223 | > 「老师,『有条不紊』的『紊』是什么意思?」
224 |
225 | 对比用法:
226 |
227 | > “老师,‘有条不紊’的‘紊’是什么意思?”
--------------------------------------------------------------------------------
/posts/ai/model/20250124_openai_introduce_operator.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 介绍 Operator [译]
3 | date: 2025-01-24
4 | description: OpenAI 推出新型 AI 工具「操作员」(Operator),它能像人类一样浏览网页、操作界面完成任务。基于 GPT-4o 和 CUA 技术,它可以自主执行网页操作,目前正在美国地区进行小范围测试。文章详细介绍了其工作原理、使用方法、安全措施和未来发展规划。
5 | category: ai
6 | tags: OpenAI, Operator, AI Agent, GPT-4o, CUA, Web Automation, AI Assistant
7 | cover: https://media.ginonotes.com/images/20250124_openai_introduce_operator/operator.webp
8 | slug: openai-introduce-operator
9 | ---
10 |
11 | import VideoPlayer from '@/components/common/VideoPlayer'
12 |
13 | 
14 |
15 | 我们正在测试一个名为「操作员」(Operator)的 AI 工具,它像一个帮你上网干活的助手。它可以使用自己的浏览器,像你一样浏览网页、输入文字、点击按钮和滚动页面。
16 |
17 | 目前,**「操作员」还处于测试阶段**,功能相对有限,我们会根据大家的使用反馈不断改进它。这是我们推出的首批「智能助手」之一,它能独立完成你交给它的任务。
18 |
19 | 「操作员」可以帮你处理许多重复的网页操作,比如填写表格、订购食品,甚至制作表情包。它能像人一样使用各种网站和工具,这大大扩展了 AI 的应用范围,可以帮助人们节省时间,也为企业提供了新的机会。
20 |
21 | 为了确保安全可靠,我们先进行小范围试用。现在,美国地区的 Pro 用户可以在 [operator.chatgpt.com](https://operator.chatgpt.com) 试用「操作员」。这次测试是为了让我们更好地了解用户需求,并持续改进。我们计划未来将「操作员」推广到 Plus、Team 和 Enterprise 用户,并把它集成到 ChatGPT 中。
22 |
23 |
27 |
28 | ## 操作员的工作原理
29 |
30 | 「操作员」使用一种叫做 [「使用计算机代理」(Computer-Using Agent,简称 CUA)](https://openai.com/index/computer-using-agent/) 的新型 AI 模型。它结合了 GPT-4o 的视觉能力和强大的推理能力,通过学习如何操作我们平时看到的屏幕界面(例如按钮、菜单和文本框)来工作。
31 |
32 | 「操作员」可以通过截图「看到」网页,并像用鼠标和键盘一样「操作」网页。这样,它就能在网上执行任务,而不需要专门为每个网站开发接口。
33 |
34 | 如果遇到困难或犯错,「操作员」会利用自己的推理能力进行自我纠正。如果它实在解决不了,就会把控制权还给用户,确保使用过程顺利协作。
35 |
36 | 虽然 CUA 还处于早期阶段,功能有限,但它在 WebArena 和 WebVoyager 这两个浏览器使用测试中已经取得了很好的成绩。你可以在我们的 [研究博客](https://openai.com/index/computer-using-agent/) 中了解更多关于测试和「操作员」背后的研究内容。
37 |
38 | ## 如何使用操作员
39 |
40 | 你只需要告诉「操作员」你想做什么,它就能帮你完成。你可以随时接管浏览器的控制权。「操作员」也会主动在你需要登录、支付或解决验证码时请求你接管。
41 |
42 | 你还可以自定义「操作员」的工作流程,添加自定义指令。这些指令可以适用于所有网站,也可以针对特定网站,例如在 Booking.com 上设置首选航空公司。你可以在主页上保存常用指令,方便快速执行重复任务,例如在 Instacart 上重新订购食品。就像在浏览器中使用多个标签一样,你可以同时让「操作员」运行多个任务,例如在 Etsy 上订购个性化马克杯,同时在 Hipcamp 上预订露营地。
43 |
44 | ### 使用自定义指令
45 |
46 |
50 |
51 | ### 使用已保存的提示
52 |
53 |
57 |
58 | ## 生态系统和用户
59 |
60 | 「操作员」将 AI 从被动工具转变为数字生态系统中的活跃参与者。它可以简化用户任务,并为希望提供创新客户体验和提高转化率的公司带来好处。我们正在与 DoorDash、Instacart、OpenTable、Priceline、StubHub、Thumbtack、Uber 等公司合作,确保「操作员」能够满足实际需求,并遵守相关规范。我们还认为它在公共部门应用中具有巨大潜力,可以提高某些工作流程的可访问性和效率。例如,我们正在与斯托克顿市合作,让居民更容易注册城市服务和项目。
61 |
62 | 斯托克顿市信息技术主管贾米尔 · 尼亚齐表示:"通过在测试阶段了解更多关于「操作员」的信息,我们将更好地找到 AI 能够为我们的居民提供更便捷的公民参与方式。"
63 |
64 | 通过首先向有限的受众发布「操作员」,我们的目标是从实际反馈中快速学习并改进其功能,从而平衡创新与信任和安全。这种合作方式有助于确保「操作员」为用户、创作者、企业和公共部门组织带来有意义的价值。
65 |
66 | Instacart 首席产品官丹尼尔 · 丹克尔表示:"OpenAI 的「操作员」是一项技术突破,它使订购食品等过程变得非常容易。"
67 |
68 | ## 安全和隐私
69 |
70 | 确保「操作员」的安全使用是我们的首要任务。我们采取了三层保护措施,以防止滥用并确保用户始终处于控制之下。
71 |
72 | 首先,「操作员」经过训练,确保用户始终处于控制之下,并在关键时刻征求用户意见。
73 |
74 | - **接管模式:** 当需要在浏览器中输入敏感信息(例如登录凭据或支付信息)时,「操作员」会要求用户接管。在接管模式下,「操作员」不会收集或截图用户输入的信息。
75 | - **用户确认:** 在完成任何重要操作(例如提交订单或发送电子邮件)之前,「操作员」应请求用户批准。
76 | - **任务限制:** 「操作员」经过训练,会拒绝某些敏感任务,例如银行交易或需要高风险决策的任务(例如决定工作申请)。
77 | - **监视模式:** 在特别敏感的网站(例如电子邮件或金融服务)上,「操作员」需要密切监督其行为,允许用户直接发现任何潜在错误。
78 |
79 | 其次,我们简化了「操作员」中的数据隐私管理。
80 |
81 | - **退出培训:** 在 ChatGPT 设置中关闭"改进每个人的模型"意味着「操作员」中的数据也不会用于训练我们的模型。
82 | - **透明的数据管理:** 用户可以在「操作员」设置的"隐私"部分一键删除所有浏览数据并退出所有网站。也可以一键删除「操作员」中的历史对话。
83 |
84 | 最后,我们构建了针对恶意网站的防御机制,这些网站可能会试图通过隐藏提示、恶意代码或网络钓鱼攻击来误导「操作员」:
85 |
86 | - **谨慎导航:** 「操作员」旨在检测并忽略提示注入。
87 | - **监控:** 专用的"监控模型"会监视可疑行为,如果出现异常情况,可以暂停任务。
88 | - **检测流程:** 自动化和人工审核流程会不断识别新的威胁并快速更新保护措施。
89 |
90 | 我们知道不法分子可能会试图滥用这项技术。这就是为什么我们将「操作员」设计为拒绝有害请求并阻止不允许的内容的原因。我们的审核系统会发出警告,甚至可能会因重复违规而撤销访问权限,并且我们已经集成了额外的审核流程来检测和解决滥用问题。我们还提供了关于如何根据我们的使用政策与「操作员」进行交互的 [指南](https://openai.com/policies/using-operator-in-line-with-our-policies/)。
91 |
92 | 虽然「操作员」的设计考虑了这些保护措施,但没有系统是完美无缺的,这仍然是一个研究预览阶段;我们致力于通过实际反馈和严格测试来持续改进。有关我们方法的更多信息,请访问「操作员」[研究博客](https://openai.com/index/computer-using-agent/) 的安全部分。
93 |
94 | ## 局限性
95 |
96 | 「操作员」目前处于早期研究预览阶段,虽然它已经能够处理各种任务,但它仍在学习、发展,并且可能会犯错。例如,它目前在处理复杂界面(如创建幻灯片或管理日历)时会遇到挑战。早期用户反馈将在提高其准确性、可靠性和安全性方面发挥至关重要的作用,帮助我们为每个人改进「操作员」。
97 |
98 | ## 下一步
99 |
100 | - **API 能力:** 我们计划很快在 API 中公开为「操作员」提供支持的模型 CUA,以便开发人员可以使用它来构建自己的计算机使用代理。
101 | - **增强功能:** 我们将继续提高「操作员」处理更长、更复杂工作流程的能力。
102 | - **更广泛的访问:** 一旦我们对其大规模使用的安全性和可用性充满信心,我们计划将「操作员」(打开新窗口)扩展到 Plus、Team 和 Enterprise 用户,并在未来将其功能直接集成到 ChatGPT 中,从而实现无缝的实时和异步任务执行。
103 |
104 | 原文链接:[https://openai.com/index/introducing-operator/](https://openai.com/index/introducing-operator/)
105 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata, Viewport } from 'next'
2 | import { ThemeProvider } from '@/app/providers'
3 | import { Analytics } from "@vercel/analytics/react"
4 | import { SpeedInsights } from "@vercel/speed-insights/next"
5 | import { ErrorBoundary } from '@/components/common/ErrorBoundary'
6 | import { Navigation } from '@/components/navigation/Navigation'
7 | import { jsonLd } from '@/lib/metadata'
8 | import './global.css'
9 | import {
10 | WEBSITE_HOST_URL,
11 | WEBSITE_NAME,
12 | WEBSITE_DESCRIPTION,
13 | WEBSITE_AUTHOR,
14 | WEBSITE_TWITTER,
15 | WEBSITE_LANGUAGE,
16 | WEBSITE_OG_IMAGE
17 | } from '@/lib/constants'
18 |
19 | // 视口配置
20 | export const viewport: Viewport = {
21 | width: 'device-width',
22 | initialScale: 1,
23 | maximumScale: 1,
24 | }
25 |
26 | // Metadata 配置
27 | export const metadata: Metadata = {
28 | metadataBase: new URL(WEBSITE_HOST_URL),
29 | title: {
30 | default: WEBSITE_NAME,
31 | template: `%s - ${WEBSITE_NAME}`,
32 | },
33 | description: WEBSITE_DESCRIPTION,
34 | applicationName: WEBSITE_NAME,
35 | authors: [{ name: WEBSITE_AUTHOR, url: WEBSITE_HOST_URL }],
36 | generator: 'Next.js',
37 | keywords: ['博客', '技术', '人工智能', '产品设计', '编程', 'Java', 'Workflow', 'Agent'],
38 | referrer: 'origin-when-cross-origin',
39 | creator: WEBSITE_AUTHOR,
40 | publisher: WEBSITE_AUTHOR,
41 | formatDetection: {
42 | email: true,
43 | address: true,
44 | telephone: true,
45 | },
46 | openGraph: {
47 | title: {
48 | default: WEBSITE_NAME,
49 | template: `%s - ${WEBSITE_NAME}`,
50 | },
51 | description: WEBSITE_DESCRIPTION,
52 | siteName: WEBSITE_NAME,
53 | locale: WEBSITE_LANGUAGE,
54 | type: 'website',
55 | url: WEBSITE_HOST_URL,
56 | images: [{
57 | url: `${WEBSITE_HOST_URL}/images/og.png`,
58 | width: 1200,
59 | height: 630,
60 | alt: WEBSITE_NAME,
61 | }],
62 | },
63 | twitter: {
64 | card: 'summary_large_image',
65 | title: {
66 | default: WEBSITE_NAME,
67 | template: `%s - ${WEBSITE_NAME}`,
68 | },
69 | description: WEBSITE_DESCRIPTION,
70 | site: WEBSITE_TWITTER,
71 | creator: WEBSITE_TWITTER,
72 | images: [`${WEBSITE_HOST_URL}/images/og.png`],
73 | },
74 | robots: {
75 | index: true,
76 | follow: true,
77 | googleBot: {
78 | index: true,
79 | follow: true,
80 | 'max-video-preview': -1,
81 | 'max-image-preview': 'large',
82 | 'max-snippet': -1,
83 | },
84 | },
85 | alternates: {
86 | canonical: WEBSITE_HOST_URL,
87 | types: {
88 | 'application/rss+xml': `${WEBSITE_HOST_URL}/feed.xml`,
89 | },
90 | },
91 | icons: {
92 | icon: [
93 | { url: `${WEBSITE_HOST_URL}/icon`, type: 'image/png', sizes: '32x32' }
94 | ],
95 | apple: `${WEBSITE_HOST_URL}/apple-icon`,
96 | },
97 | manifest: '/manifest.webmanifest',
98 | }
99 |
100 | export default function RootLayout({
101 | children,
102 | }: {
103 | children: React.ReactNode
104 | }) {
105 | return (
106 |
107 |
108 |
109 |
113 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | {children}
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | )
138 | }
139 |
--------------------------------------------------------------------------------
/posts/reading/notes/20251107_bestblogs_weekly_issue_71.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 📋 BestBlogs 周刊第 71 期:AI 智能体的技术进展与落地挑战
3 | date: 2025-11-07
4 | description: 本期关注 AI 智能体的最新进展,包括月之暗面 Kimi K2 的模型即 Agent 理念、Anthropic 的 MCP 优化方案、开发者使用 Claude Code 的实战经验,以及企业应用智能体时面临的实际挑战。
5 | category: reading
6 | tags: BestBlogs, AI Agent, Kimi K2, Claude Code, MCP
7 | cover: https://media.ginonotes.com/covers/cover_bestblogs_71.png
8 | slug: bestblogs-weekly-issue-71
9 | ---
10 |
11 | 本周 AI 智能体相关的内容比较集中,从模型能力提升、开发工具优化,到企业应用的实际挑战,都有一些值得关注的进展。我从中挑选了一些有代表性的文章分享给你。
12 |
13 | ## 模型能力进展
14 |
15 | 
16 |
17 | 月之暗面发布的 [Kimi K2 Thinking](https://www.bestblogs.dev/article/6a155840) 提出了「模型即 Agent」的概念。传统的智能体需要外部框架来编排工具调用,而 Kimi K2 把这个能力直接内置到模型里,可以自主执行 200-300 次连续工具调用。在几个基准测试中的表现超过了 GPT-5。
18 |
19 | [MiniMax 和 Kimi 的注意力机制之争](https://www.bestblogs.dev/article/74435998) 也值得关注。MiniMax M2 选择回归 Full Attention,坦诚解释了放弃 Efficient Attention 的原因,认为 GPU 性能会快速进步。而 Kimi 则推出了 Linear 版本,采用 KDA 和 MLA 混合架构。这场技术路线之争,反映了业界对 AI 发展方向的不同判断。
20 |
21 | ## 开发优化与实践经验
22 |
23 | 
24 |
25 | [Anthropic 的这篇文章](https://www.bestblogs.dev/article/861c5e7e) 提出了一个实用的优化方案。AI 智能体使用 MCP 协议时,工具定义和中间结果会过度消耗 Token,导致成本问题。他们的解决方案是把 MCP 服务器视为代码 API,让智能体通过编写代码而非直接调用工具来交互,这种方式能将 Token 使用量削减 98.7%。
26 |
27 | [阿里云团队构建云小二 Aivis 的实战经验](https://www.bestblogs.dev/article/c4ada82c) 总结了一些具体的经验。他们指出,Agent 输出不符预期的核心原因往往在于模糊的预期和上下文管理不足,文章分享的十大经验涵盖了从上下文工程到 Multi-Agent 架构优化等方面。
28 |
29 | [Perplexity 的技术拆解](https://www.bestblogs.dev/article/9156efbd) 介绍了他们的实现方案。通过 RAG 流程、与模型无关的编排层,以及 Vespa AI 的混合搜索能力,打造了一个 AI 搜索引擎。文章详细分析了他们如何平衡性能、成本和战略灵活性。
30 |
31 | [这篇结合 LangGraph 实践的文章](https://www.bestblogs.dev/article/a8347476) 介绍了 ReAct 范式的应用。作者通过 PPT 大纲生成的案例,分享了架构演进、工具设计原则、提示词工程等方面的经验。
32 |
33 | ## 开发工具更新
34 |
35 | 
36 |
37 | [Chrome DevTools MCP 的介绍](https://www.bestblogs.dev/article/fede6593) 展示了一个新功能——让 AI 编码助手直接访问浏览器环境,进行 DOM 检查、读取控制台输出、执行 JavaScript,对前端开发调试比较实用。
38 |
39 | [Spring AI 1.1.0-M4 的递归增强器](https://www.bestblogs.dev/article/64bc9208) 带来了一些新特性。它允许增强器链循环多次,支持顺序工具调用、输出验证和自主智能体循环,适合用 Spring 生态构建 AI 应用的开发者。
40 |
41 | [这位 Claude Code 用户的实战分享](https://www.bestblogs.dev/article/e1a5b426) 涵盖了 CLAUDE.md、上下文管理、子智能体架构、Skills、MCP、SDK 等功能。他的核心建议是设定清晰的上下文和护栏,让智能体自主决策,而不是微观管理每个步骤。
42 |
43 | [这位开发者 6 个月完成 30 万行代码的经历](https://www.bestblogs.dev/article/cf71e21c) 比较有代表性。为了更好地使用 AI 工具,他构建了 Skills 自动激活系统、结合 PM2 的后端服务管理,以及一套完整的质量保证 Hooks 系统。这个案例说明,理解工具的能力边界并构建合适的支撑系统,可以明显提升开发效率。
44 |
45 | [软件工程师角色演进的探讨](https://www.bestblogs.dev/article/776c72d2) 提出了一个观点:我们正在从直接的代码执行者转变为 AI 智能体团队的管理者,这个视角值得思考。
46 |
47 | ## 产品与商业应用
48 |
49 | 
50 |
51 | [20 岁大学生开发的微舆 BettaFish](https://www.bestblogs.dev/article/68545480) 登上了 GitHub 热榜。这个多 Agent 舆情分析助手通过四个 Agent 的协作,实现了自动化的数据收集、分析和报告撰写。
52 |
53 | [AI 评测体系变革的分析](https://www.bestblogs.dev/article/d401c1c9) 讨论了评估方法的演进。传统 Benchmark 因题库泄露而逐渐失效,LMArena 通过匿名对战和 Elo 排名来评估模型。但它也面临一些挑战——人类偏见、模型刷榜、商业化带来的中立性问题。
54 |
55 | [Gemini 的 PPT 生成功能介绍](https://www.bestblogs.dev/article/99ab42da) 比较实用。基于前端代码实现,用户可以通过提示词控制风格与内容,并能导出至 Google 幻灯片。文章分享了四种 PPT 风格提示词模板。
56 |
57 | [AI 智能体创始人调查报告](https://www.bestblogs.dev/article/bd292a62) 指出了一个现象:企业落地面临的最大挑战已经不是技术本身,而是工作流集成、人机界面、员工抵触和数据隐私等问题。部署正从创新预算转向核心业务线预算,但员工日常使用率仍不高。
58 |
59 | [CB Insights 的 AI Agent 行业报告](https://www.bestblogs.dev/article/09bd570b) 给出了一些数据。编程类 Agent 收入最高,客服类 Agent 估值溢价最高,高昂的推理成本正在影响商业模式。行业正在探索基于工作量的任务定价。
60 |
61 | ## 行业观察
62 |
63 | 
64 |
65 | [斯坦福李飞飞团队的《2025 人工智能指数报告》](https://www.bestblogs.dev/article/886c25e3) 给出了一些数据:产业界已主导 AI 研发,开源模型性能正在接近闭源模型,全球对负责任 AI 的关注在增长。
66 |
67 | [这期播客对 2025 年 AI 行业的分析](https://www.bestblogs.dev/podcast/b744862) 基于一份 170 页的 PPT,覆盖技术、产品、资本和泡沫四大板块,信息量比较大。
68 |
69 | [奥特曼与纳德拉的对话](https://www.bestblogs.dev/article/98f39a2a) 探讨了 OpenAI 与微软的合作架构、百亿投资、AGI 前景、算力瓶颈等话题。他们认为算力是当前 AI 发展的主要制约因素。
70 |
71 | [a16z 的 Marc Andreessen 和 Ben Horowitz 的讨论](https://www.bestblogs.dev/video/52c7aa1) 讨论了 AI 泡沫和具身智能的话题。他们认为,虽然西方在 AI 软件方面有优势,但中国在制造业方面的地位可能影响未来的机器人时代。
72 |
73 | [ElevenLabs CEO 的分享](https://www.bestblogs.dev/video/7720915) 讨论了语音作为 AI 界面的潜力,以及如何快速交付 AI 产品。他认为语音 AI 和支付基础设施是未来 1-2 年值得关注的方向。
74 |
75 | [Canva 创始人 Melanie Perkins 的创业故事](https://www.bestblogs.dev/video/f80e11c) 分享了她的思维方式。她强调的 B 类思维——设想远大目标并倒推实现,以及平衡商业与社会价值的理念,对产品公司有一定参考价值。
76 |
77 | ## 创意工具
78 |
79 | [Suno V5 在 B 站的应用案例](https://www.bestblogs.dev/article/816c5a5b) 展示了音乐创作工具的进展。技术门槛的降低让创作者可以更专注于创意表达,文章介绍了完整的制作流程。
80 |
81 | ---
82 |
83 | 本期内容围绕 AI 智能体的几个方面:模型能力在持续提升,开发工具在不断优化,实践经验在逐步积累,但商业落地仍面临工作流集成、组织适应等现实挑战。
84 |
85 | 从技术角度看,无论是模型内置智能体能力,还是 MCP 协议的优化,都在降低开发门槛。从应用角度看,开发者的实践案例提供了一些可参考的经验。从商业角度看,企业应用智能体需要考虑的因素远不止技术本身。
86 |
87 | 这些内容反映了当前 AI 智能体发展的现状——技术在进步,工具在完善,应用在探索,挑战依然存在。
88 |
89 | 所有文章详情,欢迎访问 [BestBlogs.dev](https://www.bestblogs.dev/newsletter/issue71) 查看完整内容。
90 |
--------------------------------------------------------------------------------
/posts/ai/agent/20250302_memory_for_agents.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: 智能体的记忆[译]
3 | date: 2025-03-02
4 | description: 深入探讨智能体系统中记忆的重要性、类型及实现方式。本文详细介绍了程序性记忆、语义记忆和情节记忆在 AI 智能体中的应用,以及如何在 LangChain 生态系统中实现这些记忆机制。
5 | category: ai
6 | tags: AI, Agent, Memory, LangChain, LangGraph, LangSmith, LLM
7 | cover: https://media.ginonotes.com/covers/cover_memory_for_agents.jpeg
8 | slug: memory-for-agents
9 | ---
10 |
11 | _在三月份红杉资本举办的AI Ascent 大会上,我谈到了智能体的三个局限性:规划、用户体验和记忆。点击 [这里](https://www.youtube.com/watch?v=pBBe1pk8hf4) 查看当时的演讲。在这篇文章中,我将更深入地探讨记忆。关于规划的上一篇文章请见 [此处](https://blog.langchain.dev/planning-for-agents/),关于用户体验的系列文章请见 [此处](https://blog.langchain.dev/ux-for-agents-part-1-chat-2/)、[此处](https://blog.langchain.dev/ux-for-agents-part-2-ambient/) 和 [此处](https://blog.langchain.dev/ux-for-agents-part-3/)。_
12 |
13 | 如果说 2024 年大语言模型 (LLM) 应用开发领域最火的词汇是"智能体",那么"记忆"可能就是第二火的词汇了。但"记忆"究竟是什么呢?
14 |
15 | 从宏观层面来看,记忆只是一个用来记住先前交互信息的系统。这对于构建良好的智能体体验至关重要。试想一下,如果你的同事从不记得你告诉过他们的事,以至于你不得不一遍又一遍地重复相同的信息 -- 那将是多么令人沮丧!
16 |
17 | 人们通常认为大语言模型系统应该天生就具备记忆能力,或许是因为大语言模型已经让人感觉非常像人类了。然而,大语言模型本身并不会固有地记住事情 -- 因此你需要有意识地为其添加记忆功能。但是,究竟应该如何考虑实现这一点呢?
18 |
19 | ## 记忆是特定于应用的
20 |
21 | 我们对记忆问题进行了深入研究,并且我们认为记忆是特定于应用的。
22 |
23 | [Replit 的编程智能体](https://blog.langchain.dev/customers-replit/) 和 [Unify 的研究智能体](https://blog.langchain.dev/unify-launches-agents-for-account-qualification-using-langgraph-and-langsmith/),两者选择记住的关于特定用户的信息可能大相径庭。例如,Replit 可能会选择记录用户喜爱的 Python库,而 Unify 则可能着重保存用户所关注公司的行业信息。
24 |
25 | 正如 [之前的一篇文章](https://blog.langchain.dev/ux-for-agents-part-1-chat-2/) 中讨论的那样,用户体验 (UX) 是智能体的一个关键方面。不同的用户体验提供了独特的方式来收集反馈并进行更新。
26 |
27 | 那么,我们在 LangChain 是如何处理记忆问题的呢?
28 |
29 |
30 | 💡 与我们对待智能体的方法非常相似:我们的目标是让用户能够对记忆进行底层控制,并能够根据自己的需要进行定制。
31 |
32 |
33 | 这种理念引导了我们上周在 LangGraph 中推出 [Memory Store](https://blog.langchain.dev/launching-long-term-memory-support-in-langgraph/) 时的大部分设计和开发工作。
34 |
35 | ## 记忆的类型
36 |
37 | 虽然你的智能体拥有的记忆 **形式** 可能因应用而异,但我们确实可以看到一些 **常见的** 记忆类型。这些记忆类型并不新鲜 -- 它们模仿了 [人类的记忆类型](https://www.psychologytoday.com/us/basics/memory/types-of-memory)。
38 |
39 | 已经有一些出色的研究将这些人类记忆类型**对应到**智能体记忆。我最喜欢的是 [CoALA 论文](https://arxiv.org/pdf/2309.02427)。以下是我对每种类型的粗略、通俗易懂的解释,以及当今智能体可能使用和更新这种记忆类型的 **实际方法**。
40 |
41 | 
42 |
43 | CoALA 论文中的决策程序图(Sumers, Yao, Narasimhan, Griffiths 2024)
44 |
45 | ### 程序性记忆 (Procedural Memory)
46 |
47 | 这个术语指的是长期记忆中关于如何执行任务的部分,类似于大脑的核心指令集。
48 |
49 | 人类的程序性记忆:记住如何骑自行车。
50 |
51 | 智能体中的程序性记忆:CoALA 论文将程序性记忆描述为大语言模型权重和智能体代码的结合,从根本上决定了智能体的工作方式。
52 |
53 | 在实践中,我们几乎没有见到能够自动更新其大语言模型权重或重写代码的智能体系统。然而,我们确实看到一些智能体更新自身系统提示的例子。虽然这是最接近的实际例子,但仍然相对少见。
54 |
55 | ### 语义记忆 (Semantic Memory)
56 |
57 | 这是一个人长期存储的知识库。
58 |
59 | 人类的语义记忆:它由各种信息片段组成,例如在学校学到的事实、概念的含义以及它们之间的关系。
60 |
61 | 智能体中的语义记忆:CoALA 论文将语义记忆描述为关于世界的各种事实的存储库。
62 |
63 | 如今,智能体最常使用语义记忆来实现应用的个性化定制。
64 |
65 | 在实践中,我们看到这种做法通常是通过使用大语言模型从智能体进行的对话或交互中提取信息来实现的。这些信息的具体形式通常是特定于应用的。然后在未来的对话中检索这些信息,并将其插入到系统提示中,以影响智能体的响应。
66 |
67 | ### 情节记忆 (Episodic Memory)
68 |
69 | 这指的是回想起过去发生的特定事件(亦称情景记忆)。
70 |
71 | 人类的情节记忆:当一个人回忆起过去经历的特定事件(或"情节")时。
72 |
73 | 智能体中的情节记忆:CoALA 论文将情节记忆定义为存储智能体过去动作的序列。
74 |
75 | 情节记忆主要用于确保智能体能够按照预定方式执行任务。
76 |
77 | 在实践中,情节记忆是通过少样本示例提示来实现的。如果你收集了足够多的这类序列,那么就可以通过动态少样本提示来完成。如果对于之前执行过的特定操作存在 **正确** 的执行方式,那么这通常非常适合引导智能体。相比之下,如果并非必须存在正确的做事方式,或者如果智能体不断地做新的事情,以至于之前的例子没有太大帮助,那么语义记忆就更相关。
78 |
79 | ## 如何更新记忆
80 |
81 | 除了思考在智能体中更新哪种类型的记忆之外,我们还看到开发者也在思考 **更新智能体记忆的方式**。
82 |
83 | 更新智能体记忆的一种方法是 ["在热路径中(in the hot path)"](https://langchain-ai.github.io/langgraph/concepts/memory/#writing-memories-in-the-hot-path)。在这种方法中,智能体系统在响应之前显式地决定记住事实(通常通过工具调用)。ChatGPT 就采用了这种方法。
84 |
85 | 另一种更新记忆的方法是 ["在后台(in the background)"](https://langchain-ai.github.io/langgraph/concepts/memory/#writing-memories-in-the-background)。在这种情况下,后台进程在对话期间或之后运行,以更新记忆。
86 |
87 | 
88 |
89 | 比较这两种方法,"在热路径中"的方法的缺点是会在交付任何响应之前引入一些额外的延迟。它还需要将记忆逻辑与智能体逻辑相结合。
90 |
91 | 然而,在后台运行避免了这些问题 -- 不会增加延迟,并且记忆逻辑保持独立。但是"在后台"运行也有其自身的缺点:记忆不会立即更新,并且需要额外的逻辑来确定何时启动后台进程。
92 |
93 | 另一种更新记忆的方法涉及用户反馈,这与情节记忆尤其相关。例如,如果用户将某次交互标记为积极,你可以保存该反馈,供将来参考。
94 |
95 | ## 为什么我们关心智能体的记忆?
96 |
97 | 记忆极大地影响了智能体系统的实用性,因此我们非常有兴趣尽可能轻松地为应用程序利用记忆。
98 |
99 | 为此,我们在我们的产品中构建了许多与此相关的功能。这包括:
100 |
101 | - LangGraph 中 [用于记忆存储的底层抽象](https://blog.langchain.dev/launching-long-term-memory-support-in-langgraph/),使你能够完全控制智能体的记忆
102 | - 用于在 LangGraph 中"在热路径中"和"在后台"运行记忆的 [模板](https://github.com/langchain-ai/memory-template)
103 | - LangSmith 中用于快速迭代的 [动态少样本示例选择](https://blog.langchain.dev/dynamic-few-shot-examples-langsmith-datasets/)
104 |
105 | 我们甚至构建了 [我们自己的一些应用程序](https://github.com/langchain-ai/open-canvas) 来利用记忆!虽然现在还处于早期阶段,但我们将继续学习关于智能体记忆以及它可以有效应用的领域 🙂
106 |
107 | 原文地址:[Memory for agents](https://blog.langchain.dev/memory-for-agents/)
108 |
109 | ---
110 |
111 | LangChain 智能体系列文章:
112 |
113 | - [1. 什么是智能体?](https://www.ginonotes.com/posts/what-is-ai-agents)
114 | - [2. 什么是认知架构?](https://www.ginonotes.com/posts/what-is-a-cognitive-architecture)
115 | - [3. 为什么你应该外包智能体基础设施,但保留自己的认知架构](https://www.ginonotes.com/posts/outsource-agentic-infrastructure-own-cognitive-architecture)
116 | - [4. 智能体的规划能力](https://www.ginonotes.com/posts/planning-for-agents)
117 | - [5. 智能体的交互模式](https://www.ginonotes.com/posts/ux-for-agents)
118 | - [6. 智能体的记忆](https://www.ginonotes.com/posts/memory-for-agents)
119 | - [7. 沟通:你所需要的一切](https://www.ginonotes.com/posts/communication-is-all-you-need)
--------------------------------------------------------------------------------