【Copy攻城狮日志】手摸手”零成本“撸一个自己的 Claude Chat
这里写自定义目录标题
- Claude & Laf
- 创建 Slack && 接入 Claude 应用
- Laf 云函数 && 页面托管
Laf开发者社区 人手一个AI 的布道活动已经开展到第二期了,由于时间问题,没能完成第一期 人人都能接入 Midjourney,赚取自己的第一桶金, 这次特意赶上末班车,完成第二期的作业:手摸手”零成本“撸一个自己的 Claude Chat。我们基于 laf 快速接入 Claude 的 AI 能力,实现一个简单的智能对话聊天网页。
温馨提示:本文基于 Claude 的 AIGC 能力编写,由本攻城狮和 Claude 携手完成!
Claude & Laf
Claude 是一个基于 Anthropic 研究的下一代 AI 助手,它能够通过聊天界面和 API 进行访问。它能够完成各种各样的对话和文本处理任务,同时保持高度的可靠性和可预测性。Claude 可以帮助你进行摘要、搜索、创意写作、协作写作、问答、编码等等。早期客户报告说,Claude 不太可能产生有害的输出,更容易与之交流,并且更容易操纵——这样你就可以用更少的精力获得你想要的输出。Claude 还可以根据个性、语气和行为接受指导。
简而言之,Claude 就像你团队中一个友好、勤奋的新成员——自然地与它交谈,给它非常具体的指示,告诉它你想让它做什么。看着 Claude 迭代手头的任务,就像一个投入的员工一样。
当然,大伙儿可能更熟悉 ChatGPT,以下是 Claude 和 ChatGPT 的不完全对比:
| 对比项 | Claude | ChatGPT |
|---|---|---|
| 主要功能 | 自然语言处理,知识图谱,推荐系统 | 自然语言生成 |
| NLP 功能 | 分词,词性标注,实体识别,句法分析,意图分类 | 无 |
| 知识图谱功能 | 实体链接,关系查询,路径查询 | 无 |
| 推荐功能 | 新闻推荐,商品推荐,友好用户推荐 | 无 |
| 使用方式 | 提供API和SDK,易于集成到第三方App | Chat界面 |
| 定价模式 | 免费额度和收费计划,价格亲民 | 免费 |
| 生成能力 | 无自然语言生成能力 | 生成人类词汇丰富、语法正确的对话 |
| 个性化能力 | 按照个性化需求定制分词词典,实体类型等 | 固定的生成模式,难以深度个性化 |
| 技术难度 | 较高,涉及NLP,KG,推荐系统等技术 | 中等,主要面对Seq2Seq技术实现 |
| 使用场景 | 对话机器人,智能问答,实体链接,推荐服务等 | 聊天伴侣,游戏角色语言生成 |

总体来说,Claude是一款功能全面且定价亲民的AI平台,适用于构建较为复杂的智能应用。而ChatGPT专注于自然语言生成,生成能力较强但个性化和功能相对局限,更适合消费场景的聊天应用。二者可以视情况选择使用或结合使用。
为什么笔者会选择 Claude ? 原因很显而易见了吧:“白–嫖–”两个字请打在评论区!
再来说说 Laf ,Laf 是一个云开发平台,可以快速开发应用。它是一个开源的 BaaS 开发平台(Backend as a Service),也是一个开箱即用的 serverless 开发平台。Laf 集「函数计算」、「数据库」、「对象存储」等于一身,是一个一站式开发平台。它可以是开源版的腾讯云开发、开源版的 Google Firebase、开源版的 UniCloud。Laf 让每个开发团队都可以随时拥有一个自己的云开发平台!笔者最直观的感受就是: any application that can be written in JavaScript, will eventually be written in JavaScript。

Laf 提供了多应用管理,新建、启停应用,无需折腾服务器,一分钟上线应用。它提供了云函数计算服务,可以快速实现后端业务。它为应用开发提供了开箱即用的数据库服务和专业的文件对象存储服务,兼容 S3 和其他存储服务接口。它还提供了 WebIDE,在线写代码,完善的类型提示、代码自动完成,像写博客一样写函数,随手发布上线!。此外,Laf 还支持静态网站托管,可以快速上线静态网站,无需折腾 nginx。它支持客户端使用 laf-client-sdk “直连”数据库,通过访问策略控制访问权限,极大程度提升应用开发效率。它还支持 WebSocket 长连接,业务无死角。
总之,Laf 提供了许多优势和便利,可以帮助开发者快速、高效地进行应用开发。为什么笔者会选择 Laf ? 原因也很显而易见了吧:“白–嫖–”两个字请打在评论区!(PS:当然如果大伙儿觉得好用,一起氪金吧!为开源充值。)
创建 Slack && 接入 Claude 应用
这部分笔者就不细讲了,完全可以参考社区大佬们的教程:史上最详细的使用Claude和接入Claude-api教程,整套输出就为了一个目的:获得调用 AI 能力的 token 和 Slack 频道名称,这个 token 实质是创建一个应用用于获得 Slack 专业频道获取和发送信息的能力,ClaudeId 好像都是固定的 U059XSFP3K4。
细讲一下笔者踩过的坑:
- Slack账号不具备免费试用的资格 --> 尽量使用 Gmail 或者 Outlook 邮箱注册,直到有免费使用资格。不过最近社区的小伙伴反馈车门已焊死,具体什么情况,还需您亲自尝试。不过不要紧,文末会提供免费的资格,欢迎到 Laf 进行接入部署尝试.
- 卡住了很久的一个问题,原因是粗心的本大狮错误的认为 laf 云函数中的 ChatId 是频道的 Id,其实是频道的名称,一直导致调用不成功。不过没关系,文中的配置是可用的。
- Slack 自建应用的权限配置很麻烦,好像要配11个权限,不过社区大佬也给出了 laf 的配置模板,笔者尚未尝试。
display_information:name: lafyunfeatures:bot_user:display_name: lafyunalways_online: trueoauth_config:scopes:user:- channels:history- channels:read- chat:write- groups:history- im:history- im:read- mpim:history- mpim:read- team:read- usergroups:read- users:read- channels:write.invites
模版出处: https://forum.laf.run/d/648/10
如果您配置完成了,应该能看到如下类似的结果:


Laf 云函数 && 页面托管
Laf 账号注册之类的,本文就不细说了,记住 laf.run 或者 laf.dev 就行了。直接上代码:
import cloud from '@lafjs/cloud'// 默认导出的异步函数
export default async function (ctx: FunctionContext) {// 从 ctx.query 中解构出 question 和 conversationId 两个变量const { question, conversationId } = ctx.query// 调用 askCluadeAPi 函数并返回结果return await askCluadeAPi(question, conversationId)
}// 定义一个异步函数 askCluadeAPi
async function askCluadeAPi(question, conversationId) {// 定义三个常量:token、bot 和 chatIdconst token = 'xoxp-5331240917270-5350489610657-5378095679776-bd3a72c4b7f130fc5bdb9871c331e609'const bot = 'U059XSFP3K4'const chatId = "claudebot"// 动态导入 claude-api-slack 模块并解构出 Authenticator 类const { Authenticator } = await import('claude-api-slack')// 从缓存中获取名为 'claudeClient' 的值并赋值给变量 claudeClientlet claudeClient = cloud.shared.get('claudeClient')// 如果缓存中没有这个值if (!claudeClient) {// 创建一个新的 Authenticator 实例claudeClient = new Authenticator(token, bot)// 将其存储到缓存中cloud.shared.set('claudeClient', claudeClient)}// 创建频道并返回房间ID:channelconst channel = await claudeClient.newChannel(chatId)let result// 如果传入的参数中包含 conversationIdif (conversationId) {// 发送一条消息并将返回结果赋值给变量 resultresult = await claudeClient.sendMessage({text: question,channel,conversationId,onMessage: (originalMessage) => {console.log("loading", originalMessage)}})} else {// 发送一条消息并将返回结果赋值给变量 resultresult = await claudeClient.sendMessage({text: question,channel,onMessage: (originalMessage) => {// console.log("loading", originalMessage)console.log("loading", originalMessage)}})}// 输出成功信息console.log("success", result)// 返回一个对象,其中包含三个属性:code、msg 和 conversationIdreturn {code: 0,msg: result.text,conversationId: result.conversationId}
}
前端页面代码来自 xinTianou123 大大, 咱读书人窃码不是偷,咱整个给嫖下来,不过记得替换一下自己的 laf API 地址:
DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="Content-Type" content="text/html"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,minimal-ui"/><meta name="apple-mobile-web-app-capable" content="yes"/><meta name="apple-touch-fullscreen" content="no"/><title>Claude Chattitle><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script><script src="https://cdn.bootcdn.net/ajax/libs/axios/1.3.6/axios.js">script><style>/* 样式可以根据需求自行修改 */* {margin: 0;background-color: #f3f3f3;}.chat {width: 100vw;height: 100vh;display: flex;flex-direction: column;justify-content: space-between;align-items: center;}.chat-header {width: 100%;height: 40px;background-color: #2196F3;color: white;display: flex;justify-content: center;align-items: center;}.chat-body {width: 100%;height: calc(100% - 60px);overflow-y: auto;padding: 10px;box-sizing: border-box;}.presets .item {box-sizing: border-box;padding: 10px;border-radius: 10px;margin-bottom: 10px;font-size: 14px;word-break: break-word;border: 1px solid #ddd;background-color: #e8f8ff;cursor: pointer;}.presets h3 {margin-bottom: 10px;}.chat-message {margin-bottom: 10px;display: flex;}.chat-message .username {font-weight: bold;margin-right: 10px;min-height: 20px;padding: 5px 0;width: 20px;}.chat-message .content {display: inline-block;color: #333;padding: 5px 10px;border-radius: 5px;font-size: 14px;min-height: 20px;line-height: 20px;max-width: calc(100% - 30px);box-sizing: border-box;}.chat-footer {width: 100%;border-top: 1px solid #ddd;/* background-color: #F5F5F5; */display: flex;justify-content: space-between;align-items: center;padding: 10px;box-sizing: border-box;}.chat-footer input[type="text"] {flex: 1;height: 40px;padding: 5px;border-radius: 5px;box-sizing: border-box;border: none;outline: none;}.chat-footer button {width: 72px;height: 40px;background-color: #2196F3;color: white;border: none;border-radius: 5px;cursor: pointer;}pre {display: block;width: 100%;overflow: auto;white-space: pre-wrap;word-wrap: break-word;border-radius: 10px;padding: 10px;box-sizing: border-box;line-height: 26px;}.block-code {display: block;background-color: #1a1b26;color: #ddd;border-radius: 10px;padding: 10px;box-sizing: border-box;}.line-code {padding: 3px 5px;white-space: break-spaces;background-color: #e5e7e9;border-radius: 6px;color: #333;}[v-cloak] {display: none;}style>
head>
<body><div id="app"><div class="chat"><div class="chat-body" v-if="messages.length"><div v-for="(message, index) in messages" :key="index" class="chat-message"><span class="username" v-cloak>{{ message.username }}:span><span class="content" :style="{'background-color': message.isMine ? '#98ea70' : '#fff'}"v-html="formatContent(message.content)">span>div>div><div class="chat-body presets" v-else><h3>预设问题h3><div class="item" v-for="(item, index) in presets" :key="index" @click="choosePreset(item)" v-cloak>{{ item }}div>div><div class="chat-footer"><input type="text" v-model="newMessage" placeholder="请输入消息..." @keyup.enter="sendMessage"><button @click="sendMessage" v-cloak>{{ loading ? 'loading...' : '发送' }}button>div>div>div><script>new Vue({el: '#app',data: {messages: [], // 存储聊天记录的数组newMessage: '', // 存储新消息的变量loading: false,presets: ['先有鸡还是先有蛋?','我想让你充当Midjourney提示词助手,我提供中文描述词,你要优化我的描述或重组,并翻译成英文,但是不要生硬的直接告诉我翻译的结果,而是要加以润色,并同时告诉我你调整后的对应的中文翻译,让我能够直接用于Midjourney绘图,当我说完这句话,请你直接说开始,不要废话。','我希望你充当 javascript 控制台。我将键入命令,您将回复 javascript 控制台应显示的内容。我希望您只在一个唯一的代码块内回复终端输出,而不是其他任何内容。不要写解释。除非我指示您这样做。我的第一个命令是 console.log("Hello World");','我想让你担任Web前端开发工程师面试官。我将成为候选人,您将向我询问Web前端开发工程师职位的面试问题。我希望你只作为面试官回答。不要一次写出所有的问题。我希望你只对我进行采访。问我问题,等待我的回答。不要写解释。像面试官一样一个一个问我,等我回答。我的第一句话是“面试官你好”','我想让你扮演讲故事的角色。您将想出引人入胜、富有想象力和吸引观众的有趣故事。它可以是童话故事、教育故事或任何其他类型的故事,有可能吸引人们的注意力和想象力。根据目标受众,您可以为讲故事环节选择特定的主题或主题,例如,如果是儿童,则可以谈论动物;如果是成年人,那么基于历史的故事可能会更好地吸引他们等等。我的第一个要求是“我需要一个关于毅力的有趣故事。”','我要你扮演辩手。我会为你提供一些与时事相关的话题,你的任务是研究辩论的双方,为每一方提出有效的论据,驳斥对立的观点,并根据证据得出有说服力的结论。你的目标是帮助人们从讨论中解脱出来,增加对手头主题的知识和洞察力。我的第一个请求是“我想要一篇关于 Deno 的评论文章。”']},methods: {choosePreset(str) {this.newMessage = strthis.sendMessage()},sendMessage() {if (this.newMessage.trim() === '' || this.loading) {return; // 如果消息为空,则不发送}this.messages.push({isMine: true,username: '我', // 这里可以根据需求修改用户名content: this.newMessage,dateTime: new Date().toLocaleString(),}, {isMine: false,username: 'Ai', // 这里可以根据需求修改用户名content: '思考中...',conversationId: null, // 上下文iddateTime: new Date().toLocaleString(),});this.loading = trueconst postData = {question: this.newMessage}const aiMessages = this.messages.filter(item => !item.isMine)if (aiMessages.length > 1) {postData.conversationId = aiMessages[aiMessages.length - 2].conversationId}let i = 0axios({method: 'post',url: '替换成您自己发布的 laf 服务地址',data: postData,responseType: "stream",onDownloadProgress: ({ event }) => {const { responseText } = event.targetconst lastIndex = responseText.lastIndexOf('\n', responseText.length - 2)let chunk = responseTextif (lastIndex !== -1) {chunk = responseText.substring(lastIndex)}try {const data = JSON.parse(chunk)this.messages[this.messages.length - 1].conversationId = data.conversationIdconst text = data.text.trim()let timer = setInterval(() => {this.messages[this.messages.length - 1].content = text.substring(0,i)i++if(this.messages[this.messages.length - 1].content.length == text.length){clearInterval(timer)}}, 100)} catch (error) {this.messages[this.messages.length - 1].content = '出错了❎'} finally {}},}).then(res => {}).catch(err => {console.log('[ err ] >', err)this.messages[this.messages.length - 1].content = '出错了❎'}).finally(() => {console.log('finally');this.loading = false})this.newMessage = ''; // 清空输入框},formatContent(msg) {let newMsg = msg.replace(/```([\s\S]*?)```/g, '$1'
)newMsg = newMsg.replace(/`([\s\S]*?)`/g, '$1')return newMsg.replace(/\n/g, '
')}},});script>
body>
html>
只需把上面的 html 代码拷贝到 index.html 中并上传到 laf 的云存储中就能正常使用了,记得替换自己的 laf API 服务调用地址。
同样的来看看 laf 上的云函数和☁️托管是咋回事。

发布成功即可调用。
同样的上传完html 文件就能愉快的查看网页了!

// 定义三个常量:token、bot 和 chatIdconst token = 'xoxp-5331240917270-5350489610657-5378095679776-bd3a72c4b7f130fc5bdb9871c331e609'const bot = 'U059XSFP3K4'const chatId = "claudebot"
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
