No1: ChatGPT多模态前端自建日志
还没想好思考界面未来布局,就先优化一下目前的功能吧
光标跟随
看到官网文字后面是有一个光标,于是也想实现一下。
那么有几个问题需要先理一下。
光标什么时候加载?光标什么时候更新?光标应该在哪里更新?
首先光标需要在DOM初始化的时候就加载,否则不显示。
光标肯定是要显示在文字后面的,目前在获取数据以后会使用marded把文字渲染成html元素。所以我需要等待渲染完成再去加载光标。然后我们应该显示的实际是子节点数组的最后一个文本节点。
所以思路差不多了,生成一个文本节点,放到容器的最后一个文本节点的后面。
那么我们就写一个函数获取容器的所有子节点,然后遍历,如果是文本节点就返回,如果是元素节点就递归继续,如果找不到就返回null。
function getLastTextNode(dom) {
const children = dom.childNodes;
for (let i = children.length - 1; i >= 0; i--) {
const node = children[i];
//console.log("node:", node.nodeValue);
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim() != "") {
//console.log("TEXT_NODE:", node.nodeValue);
return node;
} else if (node.nodeType === Node.ELEMENT_NODE) {
//console.log("ELEMENT_NODE:", node.nodeType);
const last = getLastTextNode(node);
if (last) {
return last;
}
}
}
return null;
}
好了,现在我们根据这个函数,如果存在最后一个文本节点,那我们就把自己创建的文本节点放到最后一个文本节点的父元素的后面,如果没有,直接放到容器的末尾。
如此这般,看一下效果,顺利跟随文字显示。
然后就是显示光标了,光标替代文本节点就完了。使用
const range = document.createRange();
range.setStart(textNode, 0);
range.setEnd(textNode, 0);
创建了一个范围,其起始点和结束点都在文本节点的开头位置,
然后使用
// 获取范围相对于视口的距离的边界框
const chatRect = chatBox.getBoundingClientRect();
// 如果范围的上边界相对于视口顶部的偏移量过大就隐藏
if (rect.top < (chatRect.bottom - rect.height)) {
// 拿到坐标,更新光标位置
cursor.style.left = `${rect.left}px`;
ursor.style.top = `${rect.top}px`;
cursor.style.visibility = 'visible';
} else {
// 已滚动到输入区域下方,隐藏光标
cursor.style.visibility = 'hidden';
}
textNode.remove();
}
最后,把文本节点的文字替换为**“\u200b”**防止文本导致的闪烁。
回复框优化
之前,回复框中文本和代码容器是分别分开的,如果消息中代码和文本的容器都挺多的话,那么看起来确实太抽象了,所以还是放在一起吧。复制就不复制代码内容了,只复制文本内容,代码渲染之后自己就有复制那得用起来啊
设置功能
然后想想还是想设定一下预设和增加设置更改一些信息。于是又是添加两个功能预设输入和设置。
不过这样一来,要加的东西可有点多了,后面的东西恐怕要更多。新增会话也没有实现、基于图片的对话还需要完善、语音插件也想着安排上…目前就是纯粹的html+css+js,随着功能增多,需要好好调整现有的代码,该封装的封装,该删除的删除。到底怎么设计确实应该好好想一想了。