This commit is contained in:
shaoyongjun 2024-10-28 01:33:33 +08:00
parent 057d853f06
commit c31252f2c8
4 changed files with 958 additions and 240 deletions

329
static/js/styleCmd.js Normal file
View File

@ -0,0 +1,329 @@
/** * 样式 */
(function (utils) {
function inputHandle(e) {
if (!utils.inCompositionEvent) {
updateText(e)
}
}
function compositionstartHandle(e) {
// console.log("compositionstart")
utils.inCompositionEvent = true
}
function compositionendHandle(e) {
// console.log("compositionend")
updateText(e)
utils.inCompositionEvent = false
}
/**
* 鼠标按下事件
*/
function windowsCtrZHandle(e) {
const event = e || window.event //标准化事件处理
const keyCode = event.keyCode || event.which || event.charCode
const metaKey = event.metaKey
// console.log("windows键盘事件 ", event, keyCombination, metaKey, keyCode)
//撤销
if (metaKey && keyCode === 90) {
console.log('触发ctrl + Z 事件', e.target)
if (utils.latestOpDoc !== undefined && utils.latestOpDoc !== null) {
// //
// let cNo = parseInt(utils.latestOpDoc.getAttribute("data-order"))
// console.log("恢复", utils.latestOpDoc, cNo)
// if (cNo > 1) {
// let qq = "#noteshare p[data-order='" + (cNo - 1) + "']"
// // console.log("qq: ", qq)
// utils.insertAfter(utils.latestOpDoc, document.querySelector(qq))
// } else {
// //添加元素到首位
// yanxuelu.insertBefore(utils.latestOpDoc, utils.MyRoot.children[0])
// }
// utils.MyDocMap.get(cNo).setHidden(false)
//恢复
utils.latestOpDoc.recovery();
// utils.MyDocMap.get(cNo).setSource(latestOpDoc.innerText)
//
showTestText()
event.preventDefault()
event.returnValue = false
}
utils.latestOpDoc = null
}
}
/**
* 鼠标按下事件 & 键盘组合事件
* @param {*} e
*/
function onkeydownHandle(e) {
const event = e || window.event //标准化事件处理
// window.event 等价于 event参数
// 有些浏览器除了通过keyCode获取输入键code还可以通过whichcharCode获取这么写是出于浏览器兼容性考虑
const keyCode = event.keyCode || event.which || event.charCode
const keyCombination = event.ctrlKey
const metaKey = event.metaKey
// console.log("键盘事件 ", event, keyCombination, metaKey, keyCode)
// ctrl + c 复制
if (keyCombination && keyCode === 67) {
// 阻止默认事件
event.preventDefault()
event.returnValue = false
console.log('触发ctrl + c 事件', e.target)
}
//撤销
if (metaKey && keyCode === 90) {
// console.log('触发ctrl + Z 事件', e.target)
if (utils.latestOpDoc !== undefined && utils.latestOpDoc !== null) {
// console.log(utils.latestOpDoc)
//恢复
utils.latestOpDoc.recovery();
//
// console.log(MyDocMap.get(cNo))
let cNo = parseInt(utils.latestOpDoc.getData().getAttribute("data-order"))
utils.MyDocMap.get(cNo).setHidden(false)
// utils.MyDocMap.get(cNo).setSource(latestOpDoc.innerText)
//
showTestText()
//阻止事件
event.preventDefault()
event.returnValue = false
}
utils.latestOpDoc = null
}
//删除
if (keyCode === 46 || keyCode === 8) {
let curP = event.target
let cNo = parseInt(curP.getAttribute("data-order"))
//维护最近一次编辑的内容
if (utils.latestOpDoc === undefined || utils.latestOpDoc === null || utils.latestOpDoc.getData().getAttribute("data-id") !== curP.getAttribute("data-id")) {
utils.latestOpDoc = new utils.MyRecovery(curP.cloneNode(true), function () {
let cNo = parseInt(this.data.getAttribute("data-order"))
console.log("恢复", this.data, cNo)
if (cNo > 1) {
utils.insertAfter(this.data, document.querySelector("#noteshare p[data-order='" + (cNo - 1) + "']"))
} else {
//添加元素到首位
utils.MyRoot.insertBefore(this.data, utils.MyRoot.children[0])
}
// 恢复该元素展示
utils.MyDocMap.get(cNo).setHidden(false)
})
}
//如果是第一行
let previousSibling = curP.previousSibling
// console.log(curP, previousSibling == undefined ,previousSibling.id == undefined)
if (previousSibling === undefined || previousSibling.id === undefined) {
//显示用户的输入内容
showTestText()
return
}
// console.log('触发删除', curP.innerHTML, cNo, utils.num, utils.MyDocMap.get(cNo + 1))
let curS = window.getSelection()
// console.log("当前内容: ", curP.innerHTML, " 当前选区 ", curS)
//处理前面没有内容,后面还有内容需要拼接到上层的场景
if ((curS.isCollapsed && curS.anchorOffset === 0) || curP.innerHTML === '<br>') {
let curNodeRetainHtml = curP.innerHTML
//异步修正顺序(第一行删除不掉)
// if (curP.innerHTML === '<br>' ) {
// console.log("删除当前行。选取信息: ", curS)
//阻止事件传递
event.preventDefault()
event.returnValue = false
//设置该元素隐藏
utils.MyDocMap.get(cNo).setHidden(true)
//删除当前元素
curP.remove()
//拼接
if (curNodeRetainHtml !== '<br>') {
previousSibling.innerHTML = previousSibling.innerHTML + curNodeRetainHtml
}
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(previousSibling, 1);
}
//显示用户的输入内容
showTestText();
}
//回车事件
if (keyCode === 13 /* && currentNode === key.lastElementChild */) {
event.preventDefault()
let uuid = utils.uuid()
let curOrder = ++(utils.num)
var newParagraph = document.createElement("p")
newParagraph.setAttribute("contenteditable", "true")
newParagraph.setAttribute("data-id", uuid)
newParagraph.setAttribute("id", uuid)
newParagraph.setAttribute("data-order", curOrder)
newParagraph.onkeydown = onkeydownHandle
newParagraph.focus()
newParagraph.innerHTML = "<br>"
utils.MyRoot.appendChild(newParagraph)
utils.MyDocMap.set(curOrder, new utils.MyNode(uuid))
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(newParagraph, 0)
}
}
/**
*
*/
function showTestText() {
//显示用户的输入内容
const obj = {};
for ([k, v] of utils.MyDocMap.entries()) {
if (v.getHidden() && v.getHidden() === true) {
continue
}
obj[k] = v
}
// console.log("当前文档结构数: ",obj);
// var userInput = document.getElementById("testInput")
// userInput.value = JSON.stringify(obj)
}
/**
* 更新文档
* @param {*} e
*/
function updateText(e) {
let curP = e.target
//是否需要变更元素类型
utils.firstCmd(curP, onkeydownHandle)
let cNo = parseInt(curP.getAttribute("data-order"))
let cp = utils.MyDocMap.get(cNo)
//内容不变则不处理
let h5CurLen = curP.innerText.length
let myDocNodeLen = (cp && cp.getSource()) ? cp.getSource().length : 0
if (h5CurLen === myDocNodeLen) {
return
}
cp.setSource(curP.innerText)
// console.log("curPTx: ", curP.innerText, "MyDocMap : ", utils.MyDocMap)
//显示用户的输入内容
showTestText()
}
function surroundContentsByStyle() {
let curS = utils.getSelection()
let curSec = curS.getRangeAt(0)
let styleName = this.getAttribute("data-value");
let className = utils.parseStyleName2ClassName(styleName)
//todo 只对 nodeType = p 执行
// console.log("当前光标信息: ", curS, " 当前选区信息 : ", curSec)
for (let i = 0; i < curS.rangeCount; i++) {
let curSec = curS.getRangeAt(i)
let curEleSize = curSec.commonAncestorContainer.childNodes.length
let curStartP = curSec.startContainer.parentElement
//todo 先支持 一行。
let start = curSec.startOffset;
let end = curSec.endOffset;
// console.log(curStartP.cloneNode(true), styleName, start, end, " curEleSize: ", curEleSize)
let curPEle = null;
//第一次选择的时候将整行转换成<p><span></span></p>
if (curEleSize === 0) {
//没选择,则退出
if (start === end) {
return
}
// console.log("debug1: ", curSec.commonAncestorContainer)
//维护最近一次编辑的内容(暂时只支持恢复最近一次编辑)
utils.latestOpDoc = new utils.MyRecovery(curStartP.cloneNode(true), function () {
console.log("恢复上一步样式1", this.innerHTML)
let curEl = document.getElementById(this.data.getAttribute("id"));
curEl.innerHTML = this.data.innerHTML;
//文本映射 直接覆盖 map 中的 childrenStyle
utils.syncOnePInfo(this.data);
})
let curHtml = ""
for (let j = 0; j < curStartP.innerText.length; j++) {
// console.log(curStartP.innerText.charAt(j))
if (j >= start && j < end) {
curHtml += '<span contenteditable="true" class=' + className + '>' + curStartP.innerText.charAt(j) + '</span>';
} else {
curHtml += '<span>' + curStartP.innerText.charAt(j) + '</span>';
}
}
curStartP.innerHTML = curHtml;
curPEle = curStartP;
} else {
// console.log("debug2: ", curSec.commonAncestorContainer)
//维护最近一次编辑的内容(暂时只支持恢复最近一次编辑)
utils.latestOpDoc = new utils.MyRecovery(curSec.commonAncestorContainer.cloneNode(true), function () {
console.log("恢复上一步样式1", this.data)
let curEl = document.getElementById(this.data.getAttribute("id"));
curEl.innerHTML = this.data.innerHTML;
//文本映射 直接覆盖 map 中的 childrenStyle
utils.syncOnePInfo(this.data);
})
let myChildren = curSec.commonAncestorContainer.children
for (let j = 0; j < curEleSize; j++) {
let curEle = myChildren[j]
if (curS.containsNode(curEle, true)) {
curEle.classList.remove(className);
curEle.classList.add(className);
}
}
curPEle = curSec.commonAncestorContainer;
}
//文本映射 直接覆盖 map 中的 childrenStyle
utils.syncOnePInfo(curPEle)
}
}
window.styleCmd = {
inputHandle, compositionstartHandle, compositionendHandle,
onkeydownHandle, windowsCtrZHandle,
surroundContentsByStyle,
showTestText,
updateText,
}
})(utils)

419
static/js/utils.js Normal file
View File

@ -0,0 +1,419 @@
/** *
* 工具类
*/
(function () {
/**
* 最新修改的元素
*/
const latestOpDoc = null
class MyRecovery {
constructor(data, func) {
this.data = data;
this.func = func;
}
getData() {
return this.data;
}
getFun() {
return this.func;
}
recovery() {
this.func();
}
}
/**
* 行数增加记录
*/
const num = 0
/**
* 是否开始输入中文
*/
const inCompositionEvent = false;
/**
* 我的文档数据
*/
const MyDocMap = new Map()
// push推一个在后面就是 append 的意思
// pop从后面拉走配合 push 就是栈
// unshift从前面加一个数据就是 insert(it, 0) 的意思
// shift前面拉个数据走配合 shift 就是一个反向栈,配合 push 就是队列
class MyQueue {
constructor(size) {
this.insertIndex = 0
this.delIndex = 0
this.capacity = 0;
this.size = size;
this.arr = new Array(size);
}
push(handle) {
if (this.capacity === this.arr.length) {
console.log("满了");
this.capacity--;
this.arr[this.capacity] = null;
//todo 替代
// this.end++;
}
this.arr[this.capacity] = handle;
this.capacity++;
}
pull() {
if (this.capacity <= 0) {
return null
}
this.capacity--;
//到底回退
if (this.shift > this.size) {
this.shift = 0;
}
return this.arr[this.shift];
}
}
class MyNode {
constructor(id) {
this.id = id
this.hidden = false
this.style = new InnerStyle()
}
getHidden() {
return this.hidden
}
setHidden(val) {
this.hidden = val
}
setSource(source) {
this.source = source
}
getSource() {
return this.source
}
getStyle() {
return this.style
}
}
class InnerStyle {
constructor() {
this.nodeType = "p"
//map-> index:classList
this.childrenStyle = null;
this.preStyle = null;
this.getNodeType = function getNodeType() {
return this.nodeType
}
this.setNodeType = function setNodeType(nodeType) {
this.nodeType = nodeType
}
//前置类型 如 ul ol 代码块 等
this.setPreStyle = function setPreStyle(k, v) {
if (this.preStyle == null) {
this.preStyle = new MyKV(k, v);
}
}
this.getPreStyle = function getPreStyle() {
return this.preStyle
}
}
setChildrenStyle(index, classList) {
if (this.childrenStyle === null) {
this.childrenStyle = new Map();
}
this.childrenStyle.set(index, classList)
}
getChildrenStyle(index) {
return this.childrenStyle.get(index);
}
getChildrenStyleMap() {
return this.childrenStyle;
}
setChildrenStyleMapNull() {
this.childrenStyle = null;
}
}
class MyKV {
constructor(k, v) {
this.k = k
this.v = v
this.getK = function getK() {
return this.k
}
this.getV = function getV() {
return this.v
}
}
}
/**
* 主节点元素
*/
const MyRoot = document.getElementById("noteshare")
function isNum(value) {
return !isNaN(parseFloat(value)) && isFinite(value)
}
function getSelection() {
return window.getSelection() || document.selection
}
/**
*
* @returns 生产uuid
*/
function uuid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
}).split("-")[0]
}
/**
* 在目标元素后面插叙 新元素
* @param {*} newElement
* @param {*} targetElement
*/
function insertAfter(newElement, targetElement) {
// console.log("开始: ",newElement, targetElement)
var parent = targetElement.parentNode
if (parent.lastChild == targetElement) {
// 如果最后的节点是目标元素,则直接添加。因为默认是最后
parent.appendChild(newElement)
} else {
parent.insertBefore(newElement, targetElement.nextSibling)
//如果不是,则插入在目标元素的下一个兄弟节点 的前面。也就是目标元素的后面
}
}
function parseOrder(curP) {
return parseInt(curP.getAttribute("data-order"))
}
/**
*
* @param {*} curP
* @param onkeydownHandle
*/
function firstCmd(curP, onkeydownHandle) {
let newParagraph;
let inputLength = curP.innerText.length
/**
* h1 ~ h6
*/
if (curP.innerText.startsWith("#") && curP.innerHTML.endsWith("&nbsp;")) {
let curNo = parseOrder(curP)
let mapNode = utils.MyDocMap.get(curNo)
// console.log(curP, " - ", curP.innerHTML, curP.innerHTML.startsWith("# "))
if (curP.innerHTML.startsWith("#&nbsp;") || curP.innerHTML.startsWith("# ")) {
mapNode.getStyle().setNodeType("h1")
becomeAnotherElement(curP, "h1", onkeydownHandle)
} else if (curP.innerHTML.startsWith("##&nbsp;")) {
mapNode.getStyle().setNodeType("h2")
becomeAnotherElement(curP, "h2", onkeydownHandle)
} else if (curP.innerHTML.startsWith("###&nbsp;")) {
mapNode.getStyle().setNodeType("h3")
becomeAnotherElement(curP, "h3", onkeydownHandle)
} else if (curP.innerHTML.startsWith("####&nbsp;")) {
mapNode.getStyle().setNodeType("h4")
becomeAnotherElement(curP, "h4", onkeydownHandle)
} else if (curP.innerHTML.startsWith("#####&nbsp;")) {
mapNode.getStyle().setNodeType("h5")
becomeAnotherElement(curP, "h5", onkeydownHandle)
} else {
mapNode.getStyle().setNodeType("h6")
becomeAnotherElement(curP, "h6", onkeydownHandle)
}
}
/**
* 无序列表效果
*/
if (inputLength === 2 && curP.innerText.startsWith("-") && curP.innerHTML.endsWith("&nbsp;")) {
let curNo = parseOrder(curP)
let mapNode = utils.MyDocMap.get(curNo)
mapNode.getStyle().setPreStyle("ul", true)
//clean
curP.innerHTML = ""
mapNode.setSource("")
//根据上一层级元素动态选择 todo
curP.setAttribute("style", "padding-left: 1rem;")
//新增元素
newParagraph = document.createElement("span");
newParagraph.setAttribute("contenteditable", "false")
//∙ vs ∘
newParagraph.innerHTML = "∙ &nbsp;"
curP.append(newParagraph)
//添加一个选区
var selObj = window.getSelection()
var rangeObj = document.createRange()
rangeObj.selectNode(curP)
selObj.addRange(rangeObj)
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().collapse(curP, true)
}
/**
* 有序列表效果
*/
if (inputLength > 2 && inputLength <= 5 && isNum(curP.innerText.substring(0, inputLength - 2)) && curP.innerHTML.endsWith(".&nbsp;")) {
let num = curP.innerText.substring(0, inputLength - 2)
console.log(curP.innerText, num)
let curNo = parseOrder(curP)
let mapNode = utils.MyDocMap.get(curNo)
mapNode.getStyle().setPreStyle("ol", num)
//clean
curP.innerHTML = ""
mapNode.setSource("")
//todo
curP.setAttribute("style", "padding-left: 1rem;")
//新增元素
newParagraph = document.createElement("span");
newParagraph.setAttribute("contenteditable", "false")
newParagraph.innerHTML = num + ".&nbsp;"
curP.append(newParagraph)
//添加一个选区
var selObj = window.getSelection()
var rangeObj = document.createRange()
rangeObj.selectNode(curP)
selObj.addRange(rangeObj)
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().collapse(curP, true)
}
}
/**
* 变成另一个元素
* @param {*} curP
* @param {*} elementName
*/
function becomeAnotherElement(curP, elementName, onkeydownHandle) {
var newParagraph = document.createElement(elementName)
newParagraph.setAttribute("contenteditable", "true")
newParagraph.setAttribute("data-id", curP.getAttribute("data-id"))
newParagraph.setAttribute("id", curP.getAttribute("data-id"))
newParagraph.setAttribute("data-order", curP.getAttribute("data-order"))
newParagraph.onkeydown = onkeydownHandle
//todo 支持 有数据的行 在行首输入 #
// if()
// switch (elementName){
// case "h1":
// }
newParagraph.innerHTML = "<br>"
insertAfter(newParagraph, curP)
curP.remove()
//matData
let curNo = parseOrder(curP)
let mapNode = utils.MyDocMap.get(curNo)
mapNode.setSource("")
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(newParagraph, 0)
}
function parseStyleName2ClassName(styleName) {
switch (styleName) {
case "b":
return "childStyleStrong";
case "i":
return "childStyleI";
case "u":
return "childStyleU";
case "c_red":
return "childStyleColor";
case "del":
return "childStyleDel"
}
}
function syncOnePInfo(p) {
//子元素为空不处理
if (p.children.length <= 0) {
return
}
// let id = p.getAttribute("id");
let order = parseInt(p.getAttribute("data-order"));
let curMapData = this.MyDocMap.get(order);
//清空重置
curMapData.getStyle().setChildrenStyleMapNull();
for (let i = 0; i < p.children.length; i++) {
let curItem = p.children[i];
let tmpClassList = curItem.classList;
if (tmpClassList != null && tmpClassList.length > 0) {
curMapData.getStyle().setChildrenStyle(i, tmpClassList);
}
}
console.log("curPEle : ", p, " children: ", p.children, " childrenMap: ", curMapData.getStyle().getChildrenStyleMap())
}
window.utils = {
isNum,
uuid,
parseOrder,
getSelection,
insertAfter,
firstCmd,
becomeAnotherElement,
parseStyleName2ClassName,
syncOnePInfo,
latestOpDoc,
num,
inCompositionEvent,
MyDocMap,
MyRoot,
MyNode,
InnerStyle,
MyKV,
MyQueue,
MyRecovery,
}
})()

50
static/js/yanxuelu.js Normal file
View File

@ -0,0 +1,50 @@
(function (utils, eventHandle) {
/**
* 输入事件
*/
utils.MyRoot.addEventListener('input', styleCmd.inputHandle);
utils.MyRoot.addEventListener('compositionstart', styleCmd.compositionstartHandle);
utils.MyRoot.addEventListener('compositionend', styleCmd.compositionendHandle);
//窗口撤销事件
window.addEventListener('keydown', styleCmd.windowsCtrZHandle, true);
// window.onkeydown = styleCmd.windowsCtrZHandle
//样式事件
document.getElementById("myPlusB").addEventListener('click', styleCmd.surroundContentsByStyle);
document.getElementById("myPlusI").addEventListener('click', styleCmd.surroundContentsByStyle);
document.getElementById("myPlusU").addEventListener('click', styleCmd.surroundContentsByStyle);
document.getElementById("myPlusDel").addEventListener('click', styleCmd.surroundContentsByStyle);
document.getElementById("myPlusC").addEventListener('click', styleCmd.surroundContentsByStyle);
//窗口初始化完成事件
window.onload = function () {//do something
// yanxuelu.style.outline = 'none'
// yanxuelu.onkeydown = fn // 注册keydown事件处理函数键盘按下
// key.onkeyup = fn // 注册keyup事件处理函数键盘松开
// key.onkeypress = fn // 注册keypress事件处理函数不推荐使用
var newParagraph = document.createElement("p")
newParagraph.setAttribute("contenteditable", "true")
let uuid = utils.uuid()
let curOrder = ++utils.num
newParagraph.setAttribute("data-id", uuid)
newParagraph.setAttribute("id", uuid)
newParagraph.setAttribute("data-order", curOrder)
newParagraph.onkeydown = styleCmd.onkeydownHandle
newParagraph.focus()
newParagraph.innerHTML = "<br>"
utils.MyRoot.appendChild(newParagraph)
// MyDocMap[curOrder] = new Map()
utils.MyDocMap.set(curOrder, new utils.MyNode(uuid))
//收起选区到一个点,光标落在一个可编辑元素上
window.getSelection().setPosition(newParagraph, 0);
}
})(utils, styleCmd)

View File

@ -221,265 +221,182 @@
</div>
</header>
<section style="min-height: 800px; background-color: #f8f7fa;">
<div>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
/* 字体 */
/*:root {*/
/* !* font-size: calc(0.5em + 1vw); *!*/
/* font-size: 62.5%;*/
/*}*/
/* style sheet for "A4" printing */
@media print and (width: 21cm) and (height: 29.7cm) {
@page {
margin: 3cm;
}
}
.container {
display: flex;
height: 90vh;
/* style sheet for "letter" printing */
@media print and (width: 8.5in) and (height: 11in) {
@page {
margin: 1in;
}
}
.left-panel {
flex: 15%;
background-color: #f2f2f2;
padding: 10px;
* {
/* margin: 1px 2px;
padding: 1px 2px; */
font-family: Roboto-Regular, PingFang SC, SF Pro SC, SF Pro Text, SF Pro Icons, Helvetica Neue, Roboto, Helvetica, Arial, sans-serif;
outline: none;
/* box-sizing: border-box; */
}
.right-panel {
flex: 85%;
background-color: #ffffff;
display: flex;
flex-direction: column;
}
.chat-log {
flex: 1;
overflow-y: auto;
padding: 2rem;
}
.chat-bubble {
display: flex;
align-items: center;
margin-bottom: 1rem;
}
.user-bubble {
justify-content: flex-end;
}
.bubble-content {
padding: 1rem 1.5rem;
border-radius: 2rem;
}
.user-bubble .bubble-content {
background-color: #d6eaff;
color: #000000;
}
.ai-bubble .bubble-content {
background-color: #e5ece7;
color: #000;
}
.input-area {
display: flex;
align-items: center;
padding: 2rem;
}
.input-text {
flex: 1;
padding: 1rem;
margin-right: 1rem;
}
.submit-button {
padding: 1rem 2rem;
background-color: #2196f3;
color: #ffffff;
border: none;
cursor: pointer;
}
li {
margin-top: 1rem;
}
a {
text-decoration: none;
}
table {
border: 1px solid #000;
border-collapse: collapse;
}
table td, table th {
border: 1px solid #000;
}
table td, table th {
padding: 1rem;
}
.language-sql {
width: 95%;
background-color: #F6F6F6;
padding: 10px;
font-weight: bold;
border-radius: 5px;
word-wrap: break-word;
white-space: pre-line;
/* overflow-wrap: break-word; */
display: block;
}
select {
.my-header {
position: relative;
top: 0;
/* height: 8rem; */
z-index: 9999;
left: 0;
right: 0;
width: 100%;
height: 30px;
border: 2px solid #6089a4;
font-size: 15px;
margin-top: 5px;
}
.recommendation{
color: #1c4cf3;
margin-top: 1rem;
background: #f7dcbc;
}
#noteshare {
width: 90%;
/* width: 21cm; */
min-height: 10rem;
/* font-size: 1.5rem; */
/*border: 1px red solid;*/
margin: auto auto 10rem auto;
}
#noteshare p {
border: 1px rgb(248, 245, 245) solid;
/* border: none; */
}
#testInput {
width: 100%;
height: 50px;
border: 1px rgb(0, 140, 255) solid;
margin: 20px auto;
}
::selection {
color: antiquewhite;
background-color: cadetblue;
text-shadow: #00a9ff;
}
.my-divider-item {
background-color: grey;
width: 1px;
height: 2rem;
margin: 2px 10px 2px 8px;
}
.childStyleStrong {
font-weight: bold
}
.childStyleI {
font-style: italic;
}
.childStyleU {
text-decoration: underline;
//中划线
//text-decoration: line-through;
}
.childStyleDel {
text-decoration: line-through;
}
.childStyleColor {
color: red;
}
</style>
<div class="container">
<!-- <div class="left-panel">-->
<!-- <h2>AI文本助手</h2>-->
<!-- <h3>常用问题</h3>-->
<!-- <div class="recommendation">Java 21有什么新特性</div>-->
<!-- <div class="recommendation">红烧肉怎么做</div>-->
<!-- </div>-->
<div class="right-panel">
<div class="chat-log" id="chat-log">
<div class=".my-header">
<div style="display: flex;
padding: 1rem 4rem;
justify-content: space-between;
align-items: center;
background: #f8f6f4;
margin: 0 auto;">
<div class="head-left">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 11H7.83L13.42 5.41L12 4L4 12L12 20L13.41 18.59L7.83 13H20V11Z" fill="black"></path>
</svg>
</div>
<div class="input-area">
<input type="text" id="user-input" class="input-text" placeholder="请输入您的问题,回车或点击发送确定。">
<button id="submit" style="margin-left: 10px;width: 100px" onclick="sendMessage()" class="submit-button">
发送
</button>
<button style="margin-left: 2rem;width: 10rem;background-color: red" onclick="clearChat()"
class="submit-button">clear
</button>
<div class="head-center" style="display: flex;
justify-content: center;
align-items: center;
max-width: 650px;
background-color: aqua;">
<div style="
position: fixed;
z-index: 87;
/* top: 200px; */
/* left: 324px; */
">
<div>
<div style="display: flex;
">
<div>
<button id="myPlusB" data-value="b">加粗</button>
</div>
<div class="my-divider-item"></div>
<div>
<button id="myPlusI" data-value="i"><i>I</i></button>
</div>
<div class="my-divider-item"></div>
<div>
<button id="myPlusU" data-value="u"><u>下划线</u></button>
</div>
<div class="my-divider-item"></div>
<div>
<button id="myPlusDel" data-value="del">
<del>删除线</del>
</button>
</div>
<div class="my-divider-item"></div>
<div>
<button id="myPlusC" data-value="c_red">红色</button>
</div>
<!-- <div class="my-divider-item"></div>-->
<!-- <div>-->
<!-- <button onclick="info(this)">info</button>-->
<!-- </div>-->
</div>
</div>
</div>
</div>
<div class="head-right">
<button>同步</button>
</div>
</div>
</div>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
<script>
/*! @license DOMPurify 3.0.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.0.5/LICENSE */
// 添加AI信息
function addAIMessage(message) {
$("#chat-log").append(
"<div class=\"chat-bubble ai-bubble\">\n" +
" <div class=\"bubble-content\">" + message + "</div>\n" +
"</div>"
)
}
<div>
<div id="noteshare" spellcheck="false" translate="no">
</div>
// 添加人类信息
function addUserMessage(message) {
$("#chat-log").append(
"<div class=\"chat-bubble user-bubble\">\n" +
" <div class=\"bubble-content\">" + message + "</div>\n" +
"</div>"
)
}
</div>
</div>
// 滑动到底部
function slideBottom() {
let chatlog = document.getElementById("chat-log");
chatlog.scrollTop = chatlog.scrollHeight;
}
// 调用api
function chatApi(msg) {
slideBottom();
// messagesList.push({role: "user", content: msg});
const body = JSON.stringify({ "prompt": msg});
// alert(body)
$.ajax({
method: 'POST',
url: '/v1/ai/completionsTest',
headers: {
'Content-Type': 'application/json',
},
data: body,
success: function (res) {
// let res1=res?.choices[0]?.message?.content;
// res1 = DOMPurify.sanitize(marked.parse(res1));
// addAIMessage(res1);
if (res.code === 0) {
let answer = res.data;
// answer = marked.parse(answer);
answer = DOMPurify.sanitize(marked.parse(answer));
addAIMessage(answer);
// messageHistory = res.history;
} else {
addAIMessage("服务接口调用错误。");
}
},
error: function (e) {
addAIMessage("服务接口调用异常。");
}
});
}
// 发送消息
function sendMessage() {
let userInput = $('#user-input');
let userMessage = userInput.val();
if (userMessage.trim() === '') {
return;
}
userInput.val("");
// answer = DOMPurify.sanitize(marked.parse(userMessage));
addUserMessage(DOMPurify.sanitize(marked.parse(userMessage)));
chatApi(userMessage);
}
// 清空聊天记录
function clearChat() {
$("#chat-log").empty();
addAIMessage("你好,请输入你想问的问题。");
}
// 初始化
function init() {
addAIMessage("你好,请输入你想问的问题。");
let submit = $("#submit");
let userInput = $("#user-input");
let focus = false;
// 监听输入框焦点
userInput.focus(function () {
focus = true;
}).blur(function () {
focus = false;
});
// 回车监听事件
document.addEventListener("keydown", function (event) {
if (event.key === 'Enter') {
console.log(focus);
if (focus) {
submit.click();
}
}
});
}
init();
</script>
</section>
<footer style="display: flex; justify-content:center; height: 80%; font-size: 1.5rem; background-color:black;">
<footer style="display: flex; justify-content:center; height: 80%; font-size: 1.5rem; background-color:black; bottom: 10px;">
<p>
<a style="color:rgb(201, 201, 206);text-decoration:none;" href="https://beian.miit.gov.cn/" target="_blank"
rel="nofollow">
@ -586,4 +503,7 @@
}
</script>
<script src="/v1/static/js/utils.js"></script>
<script src="/v1/static/js/styleCmd.js"></script>
<script src="/v1/static/js/yanxuelu.js"></script>
</html>