☀️ Profile

Aizk (Isaac)

土木工程师和软件开发者 HomePostsProjectsFeaturesAbout

Post

关于 WikiTok 的一些思考

WikiTok News Coverage 三周前,我在美国东部时间凌晨 1 点 53 分,从我在布鲁克林的卧室角落里发布了 WikiTok。所有我钦佩的伟大的软件工程师都会花时间写下他们的想法,所以我也想为 WikiTok 做同样的事情,同时也分享我的一般软件开发经验。在很短的时间内发生了这么多事情——我确信我学到的经验对任何读者来说都将是有价值的。

背景

我大约花了 2 个小时构建了 wikitok.io(但不是那个不能用的 iphone app,也不是那个 play store 山寨版,也不是 wikitok.net,但我有点超前了)。这一切都源于这条 tweet。简而言之,它就是无限滚动的 Wikipedia。初始过程的细节可以在这篇 this Ars Technica 文章中找到——我不会过多地重复这些细节,而是更多地关注代码以及病毒式传播后的影响/我从中获得的经验。

代码库

技术栈是 bun, React 和 TypeScript。我将第一个可运行的提交中的代码组合起来并粘贴在下面,我们将逐行浏览它。

interface WikiCardProps {
  title: string
  extract: string
  thumbnail?: {
    source: string
  }
  key: number
  pageid: number
}

const WikiCard: React.FC<WikiCardProps> = ({
  title,
  extract,
  thumbnail,
  key,
  pageid,
}) => {
  return (
    <div
      style={{
        width: 400,
        height: 400,
        border: '1px solid black',
        margin: 20,
        padding: 20,
      }}
    >
      {thumbnail && <img src={thumbnail.source} />}
      <h1 style={{ color: 'white' }}>{title}</h1>
      <p style={{ color: 'white' }}>{extract}</p>
      <a
        href={`https://en.wikipedia.org/?curid=${pageid}`}
        style={{ color: 'white' }}
      >
        Read more
      </a>
    </div>
  )
}

export default WikiCard

现在我们只是定义 Wiki 数据的数据结构。缩略图在技术上是可选的,这就是为什么我们在类型定义后有一个 ?,但在后来的迭代中,我完全放弃了没有缩略图的文章。这个 key 帮助 React 理解哪些条目是新的或者已经从列表中删除了,但实际上在这个例子中只是 Claude 生成的额外代码。

这个组件是 UI 的核心。我们本质上是在说,“对于每篇文章,渲染一个包含缩略图、标题、摘要和指向完整文章链接的 div”,并且它必须符合 WikiCardProps 接口的形状。唯一的小问题是 curid 参数在技术上是查询文章的错误方式——正如 Wikipedia 的一位工程师 here 所指出的。

那么,它看起来是什么样子的呢?

Initial Commit Wikitok

除了意外的白色文本在白色背景上之外,还不错!然而,它太小了,但我们稍后可以修复它。看起来 Claude 从一开始就将其设计为只有 TikTok 的大小。

import { useState, useEffect } from 'react'

interface WikiData {
  title: string
  extract: string
  thumbnail?: {
    source: string
  }
  key: number
  pageid: number
}

function useWikiArticles() {
  const [articles, setArticles] = useState<WikiData[]>([])
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    const fetchArticles = async () => {
      setLoading(true)
      const response = await fetch(
        'https://en.wikipedia.org/w/api.php?action=query&format=json&origin=*&prop=extracts|pageimages&generator=random&exsentences=3&explaintext=1&piprop=thumbnail&pithumbsize=400&pilimit=5&grnnamespace=0&grnlimit=5'
      )
      const data: any = await response.json()
      const newArticles = Object.values(data.query.pages).map((page: any) => ({
        title: page.title,
        extract: page.extract,
        thumbnail: page?.thumbnail,
        key: page.pageid,
        pageid: page.pageid,
      }))

      setArticles((prevArticles) => [...prevArticles, ...newArticles])
      setLoading(false)
    }

    fetchArticles()
  }, [])

  return { articles, loading }
}

export default useWikiArticles

这是一个自定义的 hook,用于从 Wikipedia 的 API 获取文章。我们每次获取调用 Wikipedia API 获取 5 篇新文章,解析 JSON 响应,并使用展开运算符扩展我们现有的文章列表。唯一立即突出的是 “any” 类型——但我花了很多时间纠结于 TypeScript 项目中不必要的类型复杂性,而实际上 根本没有推动项目前进,本质上只是将其用作某种拖延工具。这是一个非常狡猾的陷阱。

“正确” 的解决方案是定义一个像 WikiApiResponse 这样的接口来描述响应的形状,然后用它来对响应进行类型化,但在这样一个简单的项目中,很明显使用 “any” 不会对代码的安全性产生影响——所以不要太担心!最重要的是你 发布 东西,任何东西都行。获取反馈,获取用户,然后迭代。自初始提交以来,代码库已经发生了重大变化,受到了用户反馈和错误修复的影响——你的用户会告诉你需要更改什么。我之前提到过这个 hook 帮助我们获取文章,但我们如何知道何时获取更多文章呢?

import { useEffect, useRef } from 'react'

type Props = {
  loadMore: () => void
}
const IntersectionObserverComponent: React.FC<Props> = ({ loadMore }) => {
  const observerRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            loadMore()
          }
        })
      },
      { threshold: 0.1 }
    )

    if (observerRef.current) {
      observer.observe(observerRef.current)
    }

    return () => {
      if (observerRef.current) {
        observer.unobserve(observerRef.current)
      }
    }
  }, [loadMore])

  return <div ref={observerRef} style={{ height: '20px' }} />
}

export default IntersectionObserverComponent

这是项目中代码中最有趣的部分。在我脑海中,我对这个的理解方式是,在当前文章下面应该始终有一个文章缓冲区,并且当用户继续滚动时会触发它。不要获取太多导致用户体验变慢,但要足以让他们永远不会到达 Wikipedia 的 “尽头”。Claude 的想法是在我们的文章列表底部创建一个不可见的 div 并将一个观察者附加到它。当这个 div 变得可见时(意味着用户已经滚动到接近底部),我们获取更多文章。总而言之,非常聪明。说实话,我以前从未设计过无限滚动界面,所以我完全是在即兴发挥。

这段代码很棒吗?不。但这段代码准备好发布了吗?甚至还差得远,我不得不回去做十几个更快的提交,然后才觉得发布该项目是令人满意的。

但这是否让一切顺利进行并让我意识到我可以构建什么?是的。

开发过程

我开发 WikiTok 的思维过程与标准软件工程原则告诉你的完全相反——我让 Claude 和 Cursor 完成了绝大部分工作,我给它我想要的规格,然后开始进行提示。我维护了一个我想要的所有功能的运行列表,逐一测试和检查,提交快速的成功。获取功能,查看一下代码,继续下一个需求,也许在这里和那里对一些代码进行划分,使其既美观又实用(我的意思是既从编程意义上讲,也从字面意义上讲)。效果很好——例如,Claude 在一开始就让我感到惊讶,告诉我我甚至不需要后端来访问 Wikipedia 的 API(没有 CORS 限制),这让我的部署变得容易得多。有趣的是,Wikimedia 后来询问了我使用他们的 API 的体验,并指出许多开发人员都在努力寻找和解析他们的文档——当我提到我根本没有使用他们的 MVP 文档时,他们感到惊讶。你可以在 here 找到他们的文档。

如果你在专业环境中使用这种方法,可能会适得其反。那么我为什么要这样做呢?

好吧,首先——我已经与不同的人工智能进行了数千次对话,并且我已经培养出了一种相当不错的雷达,可以了解它们的能力、它们何时编造东西、何时完全忽略它们等等。但需要注意的是,如果我开始使用一种我从未用过的语言进行构建,情况就不会是这样了。我从一开始就有一种直觉,认为 MVP 将少于 500 行代码,这对于任何现代 LLM 的上下文来说都足够了。最后一点是,速度才是真正的优先事项。已经有一条 tweet 获得了动力,要求 “将所有 Wikipedia 放在一页上”,这激发了整个项目。考虑到确切的 tweet 构建 WikiTok,知道人们可能会看到这两条 tweet 并排出现,这被证明 wildly helpful

为了向任何阅读此内容的初级工程师明确(我有点像是在对自己说)——使用当前 AI 模型,一旦你尝试为严肃的生产代码库做出贡献,你的 “边做边学” 的成功方式将会完全崩溃。我对此思考了很多,并且在需要为 Minecraft Benchmark 做出贡献时,我通过痛苦的方式了解到了这一点,因为该项目有成千上万行代码。但对于简单的、小型项目呢?一些随机的、非常具体的、单调的办公任务?依赖 AI。

最终,即使使用 AI,软件工程仍然是向计算机提供精确的指令来做事情的工作,但是我们如何做到这一点将会改变。这个领域每十年都会重塑自己,AI 也不例外。

从病毒式传播中吸取的教训

病毒式传播压力很大。这有点像中彩票,但有一个问题。随着你技能的提高,你可以将更多的彩票投入彩票。这一点非常关键——它仍然是彩票,这不公平,但你的几率可以不断提高,最终有一天,每个人都会注意到你所构建的东西。我以前有过病毒式的 tweet,并且我制作了很多快速的网站,但这是我第一次将两者结合起来。

另一点是,你必须知道 当你幸运时该怎么做。如果这是我制作的第一个 React 项目,我确信我会搞砸发布并完全搞砸它,但幸运的是,事实并非如此!我非常喜欢 Brandon Sanderson 的这个观点,他在他的 writing lectures 中提到,如果你还没有写过 3 本小说,那么你的工作不是卖书。你的工作是写足够多的书来了解你的流程和你所擅长的东西。

所以你成了病毒式的,现在怎么办?将你的精力集中在帮助你实现病毒式传播的平台上,因为那里有最多的关注者。你将无法回答人们向你提出的每一个问题和评论。我犯了这个错误,太过于沉迷于狂热之中。

在回答评论方面——并非所有评论部分都是平等的。我花了很多时间来回答最初的 Hacker News post 中的问题,因为我知道在 Hacker News 上进行病毒式传播并在该网站上留下良好的积极印象会推动我的帖子上升,并且从那里我会获得大量的流量。Hacker News 也往往比随机的新闻 subreddit 更挑剔具体的细节,所以你可能会从中获得更多的价值(嗯,不 always)。信息的一般流程是类似于 Twitter → Hacker News → 主流新闻媒体 → Reddit/YouTube。

还要准备好在发布的第一天睡不了多少觉。你用自己的一些健康来换取经验,但这是值得的。我在发布 WikiTok 时睡了 4 个半小时。

Sleep tracking data showing 4.5 hours of sleep during WikiTok launchSorry Bryan Johnson

在狂热的高峰期,有两个人敲我的门,我惊慌失措,想知道他们是否是记者,不知何故认为出现在我家是个好主意。谢天谢地,他们只是 2 名 Verizon 技术人员在进行常规调查。

与记者打交道

我建议任何进行病毒式传播的人仅通过电子邮件或 DM 与记者进行采访(除非是实际的面对面的新闻采访)。电话通话并没有什么明显的错误,但以书面形式表达你的观点要容易得多。写作需要更长的时间,而且通常大部分内容都会被丢弃,但是写作过程非常有价值。

当记者 确实 联系你时,请对他们的帐户进行一些研究。提醒:Twitter 上的蓝色复选标记没有任何意义,因为我在与冒充记者的诈骗者打交道时后来会记住这一点。如果有时间,请要求仅通过电子邮件和 DM 进行聊天。你真的想整理你的想法,并确保你说 完全 你想说的话,而不是自由地进行对话。当我与 Business Insider 进行对话时,我第一次了解到这一点,这是一种愉快的体验,即使我不认为我像本可以的那样很好地传达了我的信息和想法。

我为 Washington Post 写了大约 2000 个字,这花了我大约 3 个半小时(比制作 WikiTok 花的时间长)。他们只使用了一个段落,但是现在我有一个关于我的流程和推理的详细书面记录。

但仍然不够,甚至在明确忽略了关于 Elon Musk 的问题之后,他们还是采用了 “插入 Elon Musk 以获得点击” 的角度,这让我感到有点烦恼。这里有一个教训,那就是当你进行病毒式传播时,你可以控制的东西是有限的。

WikiTok Washington Post Graphic(Illustration by Elena Lacey/The Washington Post; Tom Brenner/The Washington Post; iStock)

你需要这种精确性,因为信息会像电话游戏一样随着传播而退化。我统计了至少 5 篇国际文章的 URL 错误(有人抢注了 wikitok.net,并且人们一直在说这是我的)。你将无法纠正每一个错误,但是请指出那些重要的错误(例如 journalists screwing up 报道,我保证这会发生)。专注于主要的信息来源——这就是你真正可以控制的。最终,这就是某件事进行病毒式传播的 含义 ——在互联网上迅速传播,部分失控。

病毒式传播的另一件事让我感到震惊,而且这更具体地与软件工程有关——拥有一个开源项目进行病毒式传播是一种多么奇怪的体验。感觉就像我在一艘渔船上,突然有十几个人爬上船,并就我应该如何驾驶这艘船提出了他们的建议。有些人有很好的观点,但另一些人想完全朝着错误的方向驾驶这艘船,而我必须阻止他们。这是一种非常奇怪的感觉。一个常见的请求是向项目中添加某种 “算法”,我相信这会很棒,但这并不是我想要构建的。

一些记者从我处理这么多请求(特别是那些想要向项目中添加算法的人)的情绪和挫折感中了解到,并最终将 WikiTok 标记为 “治疗” 无休止的滚动浏览,或者说我是由于对算法的失望而明确地构建了它。我并不完全同意这一点,但这确实是一个值得讨论的话题。就我个人而言,我觉得在基于 Wikipedia 数据的一个项目上添加算法并存储用户数据是很奇怪的——这感觉不对,并且可能违反了他们的 creative commons license。我甚至如何以道德的方式将其货币化?也许每 100 次滚动,你都会得到一个巨大的、烦人的 Jimmy Wales 弹出窗口,要求捐款或其他东西。算法的无所不在,无论好坏,都值得一篇单独的博客文章。

巧合的是,Technology Connections 的 Alec Watson 刚刚上传了一个精彩的视频,讨论了在由算法服务的生活中存在的陷阱,我建议你 here 观看。他将这个问题称为 “Algorithmic Complacency”,并对其进行了详细的介绍。

注意诈骗

注意网络钓鱼的企图。我在收件箱中收到了一个复杂的网络钓鱼企图。

Phishing Attempt

一位 TechCrunch 记者 DM 说他们想聊天,并且他们提供了一个日历链接,以便我可以在他们的日历上安排一些事情。该链接会将你发送到 Twitter 的 oauth 屏幕,询问你是否希望允许 Calendly 访问你的帐户,但这是完全伪造的!这是一种巧妙的中间人攻击,完全绕过你的密码或 2FA,并且他们获得了代表你发布内容的权限。他们这一方的最后一步是使用你的帐户作为加密货币的拉高出货计划,赚取数千美元。这种货币的一个例子是某人制作的这种随机的 WikiTok coin,但是由于所有者持有该货币的 99.8%,我认为即使是最堕落的加密货币兄弟也不会投资它。

对我来说,我用 Google 搜索了 TechCrunch 记者,并意识到那实际上不是他们的帐户——我也很幸运地看到了关于某人在我的 feed 上遇到网络钓鱼企图的 tweet。我想知道如果我没有看到该 tweet,我会不会上当?我可以做的最好的事情就是发布一个关于它的 tweet,写这篇博客,并确保人们知道。我将在此处留下 Alex Banks Alex Banks 和 Danny Postma Danny Postma 的另外两个非常好的帖子,他们都处理了完全相同的骗局。

回想起来,这似乎是一个明显的骗局。但是,当你有 5 个不同的 真实的 记者 DM 你并要求进行采访时,除了维护网站本身之外,我很容易上当,而且我确信其他人也上当了。

我总共查看了大约 200 条 DM。绝大多数都是非常积极的,主要是人们对 WikiTok 感到兴奋并联系我,这真是太棒了!但是我不得不筛选出可以想象到的最疯狂的事情。这是一个真正精神错乱的加密货币骗局:

Unhinged DM

但这些并不是最糟糕的。最糟糕的是死亡威胁。不是因为我害怕它们,而是因为看到人们的大脑在成长过程中接受了什么疯狂的事情进行预训练,这令人难过。我是犹太人,在我出生之前就被取消了,那是 3000 年前的事了。当我发布病毒式 tweet 或类似的东西时,我没有像往常一样收到那么多死亡威胁,但是我确实收到了一些。我不会在这篇文章中分享它们,但是你可以发挥你的想象力。也许有一天我会写一篇关于 21 世纪犹太人经历的详细博客文章。

关于我

实际上,我正处于职业转型之中。不到一年前,我还是一个土木工程师,做着从 AutoCAD 制图到 literally being in the trenches 的一切事情。我大学毕业两年后就辞职了,因为每天回家我都有一种根深蒂固的感觉,那就是我选择了错误的职业道路。我放弃了一切,搬到了布鲁克林,并找到了我可以真正学习发布的 communities。即使面对 AI 和一个完美的土木工程学位,我为什么要决定辞职?一个原因——我已经在大学实习期间获得了一些实际的编程经验。我根本没有被雇用进行编码,我只是在公司自学成才,然后开始做出重要的贡献,一次堆叠一个溢出页面,所以我知道这 一定 是可能的。这样做的不利之处是,它使大学的尾声变得相当令人沮丧,因为这种根深蒂固的内在冲突开始酝酿。我是软件工程师吗?土木工程师?这些课程有丝毫的意义吗?我想大学确实让我为现实世界做好了准备。

至于 AI 抢夺工作——这显然将在未来成为一个严重的问题。但是现在成为末日论者就像躺在停车场里等待被撞倒一样——你在向一个毫无意义的结果投降,而世界上的其他地方仍在继续前进。仍然有很多东西要构建和完成。我们不知道 AGI 何时到来,所以为什么要站在那里等待它?即使我们可以在某种程度上达成共识的事情被发布为 AGI,你认为它能够解决诸如在中东创造和平之类的问题吗?AGI 不包含人类的状况——仍然有工作要做。

接下来是什么?

对于 WikiTok 来说——不多。该网站基本上已经完成,目前我只审核简单的拉取请求。在我编写代码之前,我曾经以音乐为爱好。在音乐世界中,当你发布一首歌时,它就成定局了。我仍在适应软件与此相反的事实——不断发展,全天候运行。在看到一些关于这个想法的 tweet 之后,我确实创建了 WikiTok 的续集,名为 spacetok.io,它允许你滚动浏览 NASA 的图像。我知道续集不会像病毒一样传播,但是我制作它的原因仅仅是因为它很有趣。如果你认识 NASA 的人想要它,请让他们知道!

我的下一步是什么?我将从事更多类似 mcbench.ai 的项目。我还将深入研究理论研究,以填补 Claude 无法提供的知识空白,但是我的首要任务是寻找工作。这就是我制作 WikiTok 的原因之一——它看起来很有趣,并且可以放在我的 resume 上(所以嘿,为什么不看看它,如果你在招聘,请给我发送电子邮件)。

感谢阅读! Share GitHub Twitter