一、关于Claude
1.什么是Claude?
Claude是一款人工智能聊天机器人。它可以像朋友一样和你自然地互动聊天。和Claude聊天体验很像跟人聊天,你可以讨论任何话题,问各种各样的问题。Claude会尽量理解你说的每一句话,并给出合适的回复。相比之下,Chat GPT是一个开源的对话模型,主要用于生成对话的回复内容。和Chat GPT聊天感觉更像在和一个自动回复机器聊天,它给出的回复不太个性化,也不会真正理解语义。而Claude是一个完整的人工智能对话系统,专注于提供更富有个性的沟通交互体验。
2.Claude和Chat GPT的区别
-
理解力:Claude具有较强的语言理解能力,能理解上下文和语句意思,提取关键信息。Chat GPT主要依靠统计学习,对语义理解较为薄弱。
-
知识量:Claude有一个较为广泛的知识图谱,包括常识、词汇等,以帮助理解语言和回答问题。Chat GPT主要依靠预训练的语言模型,知识面相对有限。
-
个性化:Claude的回复更加个性化,可以根据聊天内容和上下文作出恰当的回应和提问。Chat GPT的回复比较固定和非个性化,缺乏连贯性。
-
交互体验:和Claude的对话更像人与人的自然交流,有问有答,可以交换多个轮次。而Chat GPT更类似自动问答,一问一答,交互体验稍显生硬。
总之,尽管两者都是人工智能对话技术产品,但Claude在理解力、知识量、个性化和交互体验等方面都优于Chat GPT,可以提供更加近似人的沟通互动体验。但无论哪一种技术,人机交互还需要继续进步和提高。
二、接入Claude前的准备
1.注册Slack
Slack是一个工作效率管理平台,让每个人都能够使用无代码自动化和 AI 功能,还可以无缝连接搜索和知识共享,并确保团队保持联系和参与。在世界各地,Slack 不仅受到公司的信任,同时也是人们偏好使用的平台。目前市面上使用Claude的方式都是通过Slack接入。
注册地址:https://slack.com/get-started#/createnew
注意:注册时尽量使用谷歌邮箱,这样后续操作的成功率高,不会因为各种各样的问题导致无法使用Claude。
2.创建工作区
注册成功之后我们首先需要创建一个工作区,工作区是一个独立的协作环境,每个工作区有自己的渠道(Channels)、成员、权限设置等,不同工作区之间彼此隔离,成员和资源不共享。
然后填写“工作区名称”,点击“下一步”
输入姓名,上传照片(选填),继续点击“下一步”
接着,输入频道名称,继续点击“下一步”
添加成功后便会出现如下界面:
3.添加Claude应用到工作区(此步骤需要魔法)
点击左侧菜单【浏览Slack】下的【应用】,如下图:
在应用列表中搜索Claude,并点击“添加”
点击“了解更多”,并授权添加Claude到Slack
说明:出现以上界面说明当前ip被封锁了,需要使用魔法上网(代理地区建议选择US),并使用全局代理或切换无痕浏览等方法。
切换正确的地区之后点击 “Add to Slask”将会出现以下界面,然后点击“允许”按钮
4.开通高级功能
回到工作区主界面,在左侧中会自动出现Claude应用,此时跟Claude聊天会发现它是不会回复任何消息的
然后点击左侧菜单【更多】-【Slack Connect】
接着点击“创建频道”,如下图:
注意:若出现以下界面,没有“开始免费试用”的按钮,建议重新创建一个工作区(从第二步开始重新来一遍)
正常界面如下图,点击“开始免费试用”按钮,这里不需要输入任何的信用卡等的信息
创建一个频道,随便输入一个名称,然后点击“下一步”
完成后会在左侧菜单中出现刚才新建的频道,如下图:
接下来,我们为这个频道添加Claude应用
然后,选择刚刚创建的频道
完成之后,在左侧菜单中,选择我们刚刚创建的频道,@Claude 发送任意消息,首次@会触发一个申请,点一下“Agree”即可
完成以上操作后,就相当于拥有了一个跟ChatGPT一样聪明的人工智能AI助手。同时也可以在Slack内通过@Claude的方式跟他进行对话,如下图:
5.获取Token和授权
进入Slack API官网,地址:https://api.slack.com/,然后在顶部右上角的“Your apps ”处点击“Create your first app”,如下图:
然后进入界面后,点击“Create an App”,接着选择“From scratch”
然后输入“App Name”,选择前面创建的工作空间,再点击“Create App”
创建成功之后,在左侧的菜单中找到【OAuth & Permissions】,然后在页面中间往下滑找到【User Token Scopes】,点击"Add an OAuth Scopes"按钮,依次搜索添加以下权限:
channels:history
channels:read
channels:write
groups:history
groups:read
groups:write
chat:write
im:history
im:write
mpim:history
mpim:write
一共11个权限,添加完成之后。回到顶部【OAuth Tokens for Your Workspace】栏,点击【Install to Workspace】,然后确认授权即可
至此,便拿到了App的Token,将其复制出来(后面有用)
接着,回到工作区,点击左侧菜单中的【Claude】,获取Claude ID,如下图:
三、在Laf中接入Claude
登录Laf云开发平台 https://laf.dev/,在应用列表中选择一个应用后点击【开发】按钮,进入 Laf 应用开发 IDE
1. 添加NPM依赖
点击左下角【NPM依赖】处的“+”按钮,在弹框中搜索“claude-api-slack”,选中第一个后再点击“保存并重启”,等待3秒左右依赖会添加完成
2. 添加函数
点击左上角【函数列表】处的“+”按钮,在弹框中输入函数名(比如:claude-func),其他默认,完成后点击“确认”按钮,等待3秒左右函数会添加完成
3.云函数完整代码
云函数“claude-func”的完整代码如下:
import cloud from '@lafjs/cloud'
export default async function (ctx: FunctionContext) {
// 接收一个question,和一个可选的上下文id:conversationId
const { question, conversationId } = ctx.body;
//参数校验
if (!question) {
return resultData(-1, '参数question不能为空!');
}
return await askCluadeAPi(question, conversationId);
}
async function askCluadeAPi(question, conversationId) {
// slack应用的token
const token = 'xxx-xxxxxx';
// claude的ID
const bot = 'xxx';
// 注意,这里是频道名称,不是频道id,开头不用加#
const chatId = 'channel_name';
try {
// 初始化claude
const { Authenticator } = await import('claude-api-slack');
// 通过缓存保存客户端,可以避免每次提问都是在新会话
let claudeClient = cloud.shared.get('claudeClient');
if (!claudeClient) {
claudeClient = new Authenticator(token, bot);
cloud.shared.set('claudeClient', claudeClient);
}
// 创建频道并返回房间ID:channel
const channel = await claudeClient.newChannel(chatId);
let result;
if (conversationId) {
result = await claudeClient.sendMessage({
text: question,
channel,
conversationId,
onMessage: (originalMessage) => {
console.log("loading", originalMessage);
}
});
} else {
result = await claudeClient.sendMessage({
text: question,
channel,
onMessage: (originalMessage) => {
// console.log("loading", originalMessage)
console.log("loading", originalMessage);
}
});
}
console.log("success", result);
let data = {
answer: result.text,
conversationId: result.conversationId
};
return resultData(0, '成功!', data);
}
catch (e) {
console.log('出现异常', e.message);
return resultData(-1, '出现异常:' + e.message);
}
}
//返回结果数据
async function resultData(code = -1, msg = '', data = null) {
return { code, msg, data }
}
点击发布后使用Apipost调用结果如下:
四、前端整合
前端技术栈:vue + element plus
1.创建vue项目
这里使用vue脚手架创建项目,运行以下命令来创建一个新项目:
vue create hello-world
2.安装element plus依赖并引入
建议使用包管理器(如 NPM、Yarn 或 pnpm)安装 Element Plus
# NPM
$ npm install element-plus --save
# Yarn
$ yarn add element-plus
# pnpm
$ pnpm install element-plus
官网:https://element-plus.gitee.io/zh-CN/guide/installation.html
安装完成后在main.js中引入组件
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入element-plus
import ElementPlus from 'element-plus'
import * as ElIcons from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import locale from 'element-plus/lib/locale/lang/zh-cn'
const app = createApp(App)
for (const name in ElIcons) {
app.component(name, ElIcons[name]);
}
app.use(ElementPlus, { locale })
app.use(store)
app.use(router)
app.mount('#app')
3.代码实现
修改“vue.config.js”文件中的配置,其中“target”为Laf接口访问前缀
module.exports = {
transpileDependencies: true,
lintOnSave: false,
productionSourceMap: false,
devServer: {
proxy: {
'/api': {
target: 'https://gghfpp.laf.dev',
changeOrigin: true,
ws: false,
pathRewrite: {
'^/api': ''
}
}
}
}
}
接着,完成页面布局和逻辑业务实现,以“home.vue”为例,完整代码如下:
<template>
<div class="page">
<el-container>
<el-header ref="elHeader">
<p>欢迎体验Claude AI~</p>
</el-header>
<el-main :style="{ height: scrollerHeight + 'px' }">
<el-scrollbar class="msg-list" ref="elScrollbar">
<div ref="divScroll">
<el-row v-for="(item, key) in msgList" :key="key" class="msg-item">
<el-col>
<div :class="item.type == 2 ? 'time t1' : 'time t2'">{{ item.time }}</div>
<div class="content">
<img v-if="item.type == 1" class="img_1" src="../assets/images/ic_claude.png" />
<el-card :style="{ 'margin-left': (item.type == 2 ? 'auto' : '0') }">
<div class="txt" v-html="item.content"></div>
</el-card>
<img v-if="item.type == 2" class="img_2" src="../assets/images/me.png" />
</div>
</el-col>
</el-row>
</div>
</el-scrollbar>
</el-main>
<el-footer ref="elFooter">
<el-input v-model="queryInfo.prompt" placeholder="请输入消息..." clearable size="large" :disabled="isDisabled">
<template #append>
<el-button type="primary" :disabled="isDisabled" @click="sendMessage()">
<el-icon>
<Position />
</el-icon>发送
</el-button>
</template>
</el-input>
</el-footer>
</el-container>
</div>
</template>
<script>
import topBar from '@/components/topBar.vue'
var _this;
export default {
name: 'home',
components: {
topBar
},
data() {
return {
// 容器高度初始化为0
scrollerHeight: 0,
// 查询参数信息
queryInfo: {
conversationId: null,
prompt: null,
question: null
},
// 信息集合
msgList: [],
msgIndex: 0,
isDisabled: false
}
},
created() {
_this = this;
_this.queryInfo.conversationId = Math.ceil(Math.random() * 1000000000000);
},
mounted: function () {
_this.calculateHeight();
// 当浏览器窗口大小发生变化时,重新计算容器高度
window.addEventListener('resize', () => {
_this.calculateHeight();
});
},
methods: {
// 计算容器高度
calculateHeight() {
// 获取顶部元素
let elHeader = _this.$refs.elHeader;
// 获取底部元素
let elFooter = _this.$refs.elFooter;
if (elHeader && elFooter) {
_this.scrollerHeight = document.documentElement.clientHeight - elHeader.$el.offsetHeight - elFooter.$el.offsetHeight - 1;
}
},
//发送消息
sendMessage() {
const _prompt = _this.queryInfo.prompt;
if (!_prompt) {
_this.$message.toast({
message: '请输入消息!'
});
return;
}
_this.queryInfo.question = _prompt;
_this.queryInfo.prompt = '';
_this.insertMsg(2, _prompt);
_this.isDisabled = true;
//延迟0.5秒执行
setTimeout(function () {
_this.insertMsg(1, '🤖机器人正在思考🤔中...');
}, 500);
//开始计时
let seconds = 0;
let timer = setInterval(function () {
seconds++;
_this.msgList[_this.msgList.length - 1].content = '🤖机器人正在思考🤔中...' + seconds + ' 秒';
}, 1000);
_this.sendRequest(timer);
},
//发起请求
sendRequest(timer) {
_this.$requests
.post("/api/claude-func", _this.queryInfo)
.then((res) => {
_this.isDisabled = false;
//关闭计时
clearInterval(timer);
console.log('请求结果', res);
if (res != null) {
// _this.$message.toast({
// message: res.msg,
// type: "success",
// });
if (res.data) {
let answer = res.data.answer.replaceAll(',', ',').replaceAll(':', ':').replaceAll('!', '!').replaceAll(/\n\n/g, '<br>').replaceAll(/\n/g, '<br>');
// _this.insertMsg(1, answer);
_this.msgList[_this.msgList.length - 1].content = '';
//实现打字机效果
let index = 0;
let _timer = setInterval(function () {
if (answer.length < index) {
clearInterval(_timer);
}
let str;
//遇到<br>时自动换行
if ((index + 4) <= answer.length && answer.substr(index, 4) == '<br>') {
str = answer.substr(index, 4);
index = index + 4;
}
else {
str = answer.substr(index, 1);
index++;
}
_this.msgList[_this.msgList.length - 1].content += str;
//实现自动滚动
_this.autoScroll();
}, 100);
}
}
});
},
// 插入会话
insertMsg(type, content) {
_this.msgIndex = _this.msgIndex + 1;
_this.msgList.push({
id: _this.msgIndex,
type: type || 0,//类型 1-机器人,2-自己
content: content || '',
time: _this.$date.fmtDateTime(null, 3)
});
},
//自动滚动
autoScroll() {
_this.$nextTick(() => {
let divScroll = _this.$refs.divScroll;
let elScrollbar = _this.$refs.elScrollbar;
if (divScroll.offsetHeight > elScrollbar.$el.offsetHeight) {
elScrollbar.setScrollTop(divScroll.offsetHeight - elScrollbar.$el.offsetHeight);
}
});
}
}
}
</script>
<style lang="scss">
.page {
.el-header {
line-height: 20px;
text-align: left;
background-color: var(--el-color-primary-light-7);
color: var(--el-text-color-primary);
height: auto;
padding: 8px 15px;
p {
font-size: 14px;
color: #303133;
}
.tips {
color: red;
}
}
.el-main {
padding: 12px 15px;
.msg-list {
.msg-item:last-child {
margin-bottom: 0;
}
.msg-item {
margin-bottom: 15px;
text-align: left;
.content {
display: flex;
}
.img_1 {
width: 36px;
height: 36px;
margin-right: 8px;
}
.img_2 {
width: 36px;
height: 36px;
margin-left: 8px;
}
.el-card__body {
padding: 8px 15px;
font-size: 14px;
color: #303133;
min-width: 130px;
}
.time {
margin-bottom: 5px;
color: #909399;
font-size: 12px;
}
.t1 {
text-align: right;
margin-right: 45px;
}
.t2 {
margin-left: 45px;
}
}
}
}
.el-footer {
line-height: 60px;
background-color: #ecf5ff;
color: var(--el-text-color-primary);
.el-input-group__append {
background-color: #409EFF;
color: #ffffff;
box-shadow: none;
}
}
}
</style>
说明:post请求时,第一个url参数需要使用自己在Laf中创建的云函数的名称
1)内容输出时实现打字机效果,这里采用了定时器的方式,每隔100毫秒将后端返回的内容拆分后逐个进行追加,核心代码如下:
//实现打字机效果
let index = 0;
let _timer = setInterval(function () {
if (answer.length < index) {
clearInterval(_timer);
}
let str;
//遇到<br>时自动换行
if ((index + 4) <= answer.length && answer.substr(index, 4) == '<br>') {
str = answer.substr(index, 4);
index = index + 4;
}
else {
str = answer.substr(index, 1);
index++;
}
_this.msgList[_this.msgList.length - 1].content += str;
//实现自动滚动
_this.autoScroll();
}, 100);
2)el-scrollbar实现自动滚动到最底部,官网element plus文档上说可以使用Infinite Scroll无限滚动,但是并没有自动滚动到底部的设置选项。
实现思路:首先在el-scrollbar滚动视图组件内增加加一层div,然后再在内容变更时实现自动滚动到底部的处理方法autoScroll(),核心代码如下:
<el-scrollbar class="msg-list" ref="elScrollbar">
<div ref="divScroll">
<el-row v-for="(item, key) in msgList" :key="key" class="msg-item">
<el-col>
<div :class="item.type == 2 ? 'time t1' : 'time t2'">{{ item.time }}</div>
<div class="content">
<img v-if="item.type == 1" class="img_1" src="../assets/images/ic_claude.png" />
<el-card :style="{ 'margin-left': (item.type == 2 ? 'auto' : '0') }">
<div class="txt" v-html="item.content"></div>
</el-card>
<img v-if="item.type == 2" class="img_2" src="../assets/images/me.png" />
</div>
</el-col>
</el-row>
</div>
</el-scrollbar>
//自动滚动
autoScroll() {
_this.$nextTick(() => {
let divScroll = _this.$refs.divScroll;
let elScrollbar = _this.$refs.elScrollbar;
if (divScroll.offsetHeight > elScrollbar.$el.offsetHeight) {
elScrollbar.setScrollTop(divScroll.offsetHeight - elScrollbar.$el.offsetHeight);
}
});
}
在src目录下创建utils文件夹,用于存放以上关联的js,具体代码如下:
日期格式化(date.js)
//时间处理
const date = {
/**
*
* @param {时间} dateTime
* @param {类型} type 默认:年月日
* type=1 年-月-日
* type=2 年.月.日
* type=3 年-月-日 时:分:秒
* type=4 年.月.日 时:分:秒
* @returns
*/
fmtDateTime(dateTime, type) {
if (dateTime == '' || dateTime == null) {
dateTime = new Date();
} else {
// dateTime = dateTime.substr(0, 4) + "/" + dateTime.substr(5, 2) + "/" + dateTime.substr(8, 2) + " " + dateTime.substr(11, 8);
dateTime = new Date(dateTime);
}
var y = dateTime.getFullYear();
var m = dateTime.getMonth() + 1;
var d = dateTime.getDate();
var h = dateTime.getHours() > 9 ? dateTime.getHours().toString() : '0' + dateTime.getHours();
var mm = dateTime.getMinutes() > 9 ? dateTime.getMinutes().toString() : '0' + dateTime.getMinutes();
var ss = dateTime.getSeconds() > 9 ? dateTime.getSeconds().toString() : '0' + dateTime.getSeconds();
if (type === 1) {
return y + '-' + (m < 10 ? ('0' + m) : m) + '-' + (d < 10 ? ('0' + d) : d);
}
else if (type === 2) {
return y + '.' + (m < 10 ? ('0' + m) : m) + '.' + (d < 10 ? ('0' + d) : d);
}
else if (type === 3) {
return y + '-' + (m < 10 ? ('0' + m) : m) + '-' + (d < 10 ? ('0' + d) : d) + " " + h + ":" + mm + ":" + ss;
}
else if (type === 4) {
return y + '.' + (m < 10 ? ('0' + m) : m) + '.' + (d < 10 ? ('0' + d) : d) + " " + h + ":" + mm + ":" + ss;
}
return y + '年' + (m < 10 ? ('0' + m) : m) + '月' + (d < 10 ? ('0' + d) : d) + '日';
}
}
export default date
消息弹框提示(message.js)
import { ElMessageBox, ElMessage } from "element-plus";
const message = {
//询问框
confirm(obj) {
var message = obj.message || '确定执行此操作吗?'
var title = obj.title || '温馨提示'
return ElMessageBox.confirm(message, title, {
type: 'warning',//success info warning error
showCancelButton: obj.showCancelButton || true,
cancelButtonText: obj.cancelButtonText || '取消',
confirmButtonText: obj.confirmButtonText || '确定',
}).then(obj.success || function () {
}).catch(obj.fail || function () {
});
},
//信息提示
toast(obj) {
return ElMessage({
message: obj.message || 'this is a message.',
type: obj.type || 'warning',//success warning info error
duration: obj.duration || 3000,
showClose: obj.showClose || false
});
}
}
export default message
http请求(requests.js)
import axios from 'axios'
import message from './message';
// 创建 axios 实例
const requests = axios.create({
// baseURL: 'http://localhost:8088',
withCredentials: true,
headers: {
// 'Content-Type': 'application/json',
// 'content-type' : 'application/x-www-form-urlencoded',
// 'auth': 'test'
},
timeout: 30 * 1000 // 请求超时时间 30秒
})
// 错误处理函数
const err = (error) => {
if (error.response) {
const data = error.response.data;
if (error.response.status === 403) {
message.toast({
message: data.message || data.msg
});
}
if (error.response.status === 401) {
message.toast({
type: 'warning',
message: '你没有权限。'
});
}
}
return Promise.reject(error)
}
// requests interceptor(请求拦截器)
requests.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['auth'] = token // 让每个请求携带自定义 token
}
return config
}, err)
// requests interceptor(接收拦截器)
requests.interceptors.response.use((response) => {
const res = response.data;
if (res.code == 0) {
return Promise.resolve(res).catch((e) => { });
} else {
message.toast({
message: res.msg
});
return Promise.reject(res).catch((e) => { });
}
}, err)
export default {
requests
}
同时,需要修改“main.js”,进行引入,配置全局变量
import message from './utils/message'
import requests from './utils/requests'
import date from './utils/date'
//配置全局属性
app.config.globalProperties.$requests = requests.requests
app.config.globalProperties.$message = message
app.config.globalProperties.$date = date
“App.vue”页面中样式如下:
<template>
<router-view />
</template>
<style lang="scss">
body {
padding: 0;
margin: 0;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
p {
margin: 0;
padding: 0;
}
.el-message{
width: max-content;
}
</style>
以为全部完成后,使用命令npm run serve运行项目,界面效果如下:
文章来源:https://www.toymoban.com/news/detail-842537.html
在线体验地址:http://claude.jhcrs.cn/ 文章来源地址https://www.toymoban.com/news/detail-842537.html
到了这里,关于新手小白如何使用Laf免费接入Claude,并快速拥有一个属于自己的AI助手的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!