test webui

This commit is contained in:
Saifeddine ALOUI 2025-04-07 18:49:03 +02:00
parent 665014c81e
commit e76f870cae
13 changed files with 255 additions and 236 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{b as q,d as P,q as g,r as l,L as c,u as i,s as R,t as r,E as p}from"./index-BXY6uktx.js";const b=1,$=33,m=34,v=35,x=36,d=new p(O=>{let e=O.pos;for(;;){if(O.next==10){O.advance();break}else if(O.next==123&&O.peek(1)==123||O.next<0)break;O.advance()}O.pos>e&&O.acceptToken(b)});function n(O,e,a){return new p(t=>{let u=t.pos;for(;t.next!=O&&t.next>=0&&(a||t.next!=38&&(t.next!=123||t.peek(1)!=123));)t.advance();t.pos>u&&t.acceptToken(e)})}const W=n(39,$,!1),C=n(34,m,!1),T=n(39,v,!0),f=n(34,x,!0),A=c.deserialize({version:14,states:"(jOVOqOOOeQpOOOvO!bO'#CaOOOP'#Cx'#CxQVOqOOO!OQpO'#CfO!WQpO'#ClO!]QpO'#CrO!bQpO'#CsOOQO'#Cv'#CvQ!gQpOOQ!lQpOOQ!qQpOOOOOV,58{,58{O!vOpO,58{OOOP-E6v-E6vO!{QpO,59QO#TQpO,59QOOQO,59W,59WO#YQpO,59^OOQO,59_,59_O#_QpOOO#_QpOOO#gQpOOOOOV1G.g1G.gO#oQpO'#CyO#tQpO1G.lOOQO1G.l1G.lO#|QpO1G.lOOQO1G.x1G.xO$UO`O'#DUO$ZOWO'#DUOOQO'#Co'#CoQOQpOOOOQO'#Cu'#CuO$`OtO'#CwO$qOrO'#CwOOQO,59e,59eOOQO-E6w-E6wOOQO7+$W7+$WO%SQpO7+$WO%[QpO7+$WOOOO'#Cp'#CpO%aOpO,59pOOOO'#Cq'#CqO%fOpO,59pOOOS'#Cz'#CzO%kOtO,59cOOQO,59c,59cOOOQ'#C{'#C{O%|OrO,59cO&_QpO<<GrOOQO<<Gr<<GrOOQO1G/[1G/[OOOS-E6x-E6xOOQO1G.}1G.}OOOQ-E6y-E6yOOQOAN=^AN=^",stateData:"&d~OvOS~OPROSQOVROWRO~OZTO[XO^VOaUOhWO~OR]OU^O~O[`O^aO~O[bO~O[cO~O[dO~ObeO~ObfO~ObgO~ORhO~O]kOwiO~O[lO~O_mO~OynOzoO~OysOztO~O[uO~O]wOwiO~O_yOwiO~OtzO~Os|O~OSQOV!OOW!OOr!OOy!QO~OSQOV!ROW!ROq!ROz!QO~O_!TOwiO~O]!UO~Oy!VO~Oz!VO~OSQOV!OOW!OOr!OOy!XO~OSQOV!ROW!ROq!ROz!XO~O]!ZO~O",goto:"#dyPPPPPzPPPP!WPPPPP!WPP!Z!^!a!d!dP!g!j!m!p!v#Q#WPPPPPPPP#^SROSS!Os!PT!Rt!SRYPRqeR{nR}oRZPRqfR[PRqgQSOR_SQj`SvjxRxlQ!PsR!W!PQ!StR!Y!SQpeRrf",nodeNames:"⚠ Text Content }} {{ Interpolation InterpolationContent Entity InvalidEntity Attribute BoundAttributeName [ Identifier ] ( ) ReferenceName # Is ExpressionAttributeValue AttributeInterpolation AttributeInterpolation EventName DirectiveName * StatementAttributeValue AttributeName AttributeValue",maxTerm:42,nodeProps:[["openedBy",3,"{{",15,"("],["closedBy",4,"}}",14,")"],["isolate",-4,5,19,25,27,""]],skippedNodes:[0],repeatNodeCount:4,tokenData:"0r~RyOX#rXY$mYZ$mZ]#r]^$m^p#rpq$mqr#rrs%jst&Qtv#rvw&hwx)zxy*byz*xz{+`{}#r}!O+v!O!P-]!P!Q#r!Q![+v![!]+v!]!_#r!_!`-s!`!c#r!c!}+v!}#O.Z#O#P#r#P#Q.q#Q#R#r#R#S+v#S#T#r#T#o+v#o#p/X#p#q#r#q#r0Z#r%W#r%W;'S+v;'S;:j-V;:j;=`$g<%lO+vQ#wTUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rQ$ZSO#q#r#r;'S#r;'S;=`$g<%lO#rQ$jP;=`<%l#rR$t[UQvPOX#rXY$mYZ$mZ]#r]^$m^p#rpq$mq#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR%qTyPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR&XTaPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR&oXUQWPOp'[pq#rq!]'[!]!^#r!^#q'[#q#r(d#r;'S'[;'S;=`)t<%lO'[R'aXUQOp'[pq#rq!]'[!]!^'|!^#q'[#q#r(d#r;'S'[;'S;=`)t<%lO'[R(TTVPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR(gXOp'[pq#rq!]'[!]!^'|!^#q'[#q#r)S#r;'S'[;'S;=`)t<%lO'[P)VUOp)Sq!])S!]!^)i!^;'S)S;'S;=`)n<%lO)SP)nOVPP)qP;=`<%l)SR)wP;=`<%l'[R*RTzPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR*iT^PUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR+PT_PUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR+gThPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR+}b[PUQO}#r}!O+v!O!Q#r!Q![+v![!]+v!]!c#r!c!}+v!}#R#r#R#S+v#S#T#r#T#o+v#o#q#r#q#r$W#r%W#r%W;'S+v;'S;:j-V;:j;=`$g<%lO+vR-YP;=`<%l+vR-dTwPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR-zTUQbPO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR.bTZPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR.xT]PUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR/^VUQO#o#r#o#p/s#p#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR/zTSPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#r~0^TO#q#r#q#r0m#r;'S#r;'S;=`$g<%lO#r~0rOR~",tokenizers:[d,W,C,T,f,0,1],topRules:{Content:[0,2],Attribute:[1,9]},tokenPrec:0}),V=i.parser.configure({top:"SingleExpression"}),Q=A.configure({props:[R({Text:r.content,Is:r.definitionOperator,AttributeName:r.attributeName,"AttributeValue ExpressionAttributeValue StatementAttributeValue":r.attributeValue,Entity:r.character,InvalidEntity:r.invalid,"BoundAttributeName/Identifier":r.attributeName,"EventName/Identifier":r.special(r.attributeName),"ReferenceName/Identifier":r.variableName,"DirectiveName/Identifier":r.keyword,"{{ }}":r.brace,"( )":r.paren,"[ ]":r.bracket,"# '*'":r.punctuation})]}),o={parser:V},w={parser:i.parser},U=Q.configure({wrap:l((O,e)=>O.name=="InterpolationContent"?o:null)}),y=Q.configure({wrap:l((O,e)=>{var a;return O.name=="InterpolationContent"?o:O.name!="AttributeInterpolation"?null:((a=O.node.parent)===null||a===void 0?void 0:a.name)=="StatementAttributeValue"?w:o}),top:"Attribute"}),E={parser:U},N={parser:y},s=g();function S(O){return O.configure({wrap:l(z)},"angular")}const k=S(s.language);function z(O,e){switch(O.name){case"Attribute":return/^[*#(\[]|\{\{/.test(e.read(O.from,O.to))?N:null;case"Text":return E}return null}function G(O={}){let e=s;if(O.base){if(O.base.language.name!="html"||!(O.base.language instanceof q))throw new RangeError("The base option must be the result of calling html(...)");e=O.base}return new P(e.language==s.language?k:S(e.language),[e.support,e.language.data.of({closeBrackets:{brackets:["[","{",'"']},indentOnInput:/^\s*[\}\]]$/})])}export{G as angular,k as angularLanguage};
import{b as q,d as P,q as g,r as l,L as c,u as i,s as R,t as r,E as p}from"./index-7Pb28EN0.js";const b=1,$=33,m=34,v=35,x=36,d=new p(O=>{let e=O.pos;for(;;){if(O.next==10){O.advance();break}else if(O.next==123&&O.peek(1)==123||O.next<0)break;O.advance()}O.pos>e&&O.acceptToken(b)});function n(O,e,a){return new p(t=>{let u=t.pos;for(;t.next!=O&&t.next>=0&&(a||t.next!=38&&(t.next!=123||t.peek(1)!=123));)t.advance();t.pos>u&&t.acceptToken(e)})}const W=n(39,$,!1),C=n(34,m,!1),T=n(39,v,!0),f=n(34,x,!0),A=c.deserialize({version:14,states:"(jOVOqOOOeQpOOOvO!bO'#CaOOOP'#Cx'#CxQVOqOOO!OQpO'#CfO!WQpO'#ClO!]QpO'#CrO!bQpO'#CsOOQO'#Cv'#CvQ!gQpOOQ!lQpOOQ!qQpOOOOOV,58{,58{O!vOpO,58{OOOP-E6v-E6vO!{QpO,59QO#TQpO,59QOOQO,59W,59WO#YQpO,59^OOQO,59_,59_O#_QpOOO#_QpOOO#gQpOOOOOV1G.g1G.gO#oQpO'#CyO#tQpO1G.lOOQO1G.l1G.lO#|QpO1G.lOOQO1G.x1G.xO$UO`O'#DUO$ZOWO'#DUOOQO'#Co'#CoQOQpOOOOQO'#Cu'#CuO$`OtO'#CwO$qOrO'#CwOOQO,59e,59eOOQO-E6w-E6wOOQO7+$W7+$WO%SQpO7+$WO%[QpO7+$WOOOO'#Cp'#CpO%aOpO,59pOOOO'#Cq'#CqO%fOpO,59pOOOS'#Cz'#CzO%kOtO,59cOOQO,59c,59cOOOQ'#C{'#C{O%|OrO,59cO&_QpO<<GrOOQO<<Gr<<GrOOQO1G/[1G/[OOOS-E6x-E6xOOQO1G.}1G.}OOOQ-E6y-E6yOOQOAN=^AN=^",stateData:"&d~OvOS~OPROSQOVROWRO~OZTO[XO^VOaUOhWO~OR]OU^O~O[`O^aO~O[bO~O[cO~O[dO~ObeO~ObfO~ObgO~ORhO~O]kOwiO~O[lO~O_mO~OynOzoO~OysOztO~O[uO~O]wOwiO~O_yOwiO~OtzO~Os|O~OSQOV!OOW!OOr!OOy!QO~OSQOV!ROW!ROq!ROz!QO~O_!TOwiO~O]!UO~Oy!VO~Oz!VO~OSQOV!OOW!OOr!OOy!XO~OSQOV!ROW!ROq!ROz!XO~O]!ZO~O",goto:"#dyPPPPPzPPPP!WPPPPP!WPP!Z!^!a!d!dP!g!j!m!p!v#Q#WPPPPPPPP#^SROSS!Os!PT!Rt!SRYPRqeR{nR}oRZPRqfR[PRqgQSOR_SQj`SvjxRxlQ!PsR!W!PQ!StR!Y!SQpeRrf",nodeNames:"⚠ Text Content }} {{ Interpolation InterpolationContent Entity InvalidEntity Attribute BoundAttributeName [ Identifier ] ( ) ReferenceName # Is ExpressionAttributeValue AttributeInterpolation AttributeInterpolation EventName DirectiveName * StatementAttributeValue AttributeName AttributeValue",maxTerm:42,nodeProps:[["openedBy",3,"{{",15,"("],["closedBy",4,"}}",14,")"],["isolate",-4,5,19,25,27,""]],skippedNodes:[0],repeatNodeCount:4,tokenData:"0r~RyOX#rXY$mYZ$mZ]#r]^$m^p#rpq$mqr#rrs%jst&Qtv#rvw&hwx)zxy*byz*xz{+`{}#r}!O+v!O!P-]!P!Q#r!Q![+v![!]+v!]!_#r!_!`-s!`!c#r!c!}+v!}#O.Z#O#P#r#P#Q.q#Q#R#r#R#S+v#S#T#r#T#o+v#o#p/X#p#q#r#q#r0Z#r%W#r%W;'S+v;'S;:j-V;:j;=`$g<%lO+vQ#wTUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rQ$ZSO#q#r#r;'S#r;'S;=`$g<%lO#rQ$jP;=`<%l#rR$t[UQvPOX#rXY$mYZ$mZ]#r]^$m^p#rpq$mq#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR%qTyPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR&XTaPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR&oXUQWPOp'[pq#rq!]'[!]!^#r!^#q'[#q#r(d#r;'S'[;'S;=`)t<%lO'[R'aXUQOp'[pq#rq!]'[!]!^'|!^#q'[#q#r(d#r;'S'[;'S;=`)t<%lO'[R(TTVPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR(gXOp'[pq#rq!]'[!]!^'|!^#q'[#q#r)S#r;'S'[;'S;=`)t<%lO'[P)VUOp)Sq!])S!]!^)i!^;'S)S;'S;=`)n<%lO)SP)nOVPP)qP;=`<%l)SR)wP;=`<%l'[R*RTzPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR*iT^PUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR+PT_PUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR+gThPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR+}b[PUQO}#r}!O+v!O!Q#r!Q![+v![!]+v!]!c#r!c!}+v!}#R#r#R#S+v#S#T#r#T#o+v#o#q#r#q#r$W#r%W#r%W;'S+v;'S;:j-V;:j;=`$g<%lO+vR-YP;=`<%l+vR-dTwPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR-zTUQbPO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR.bTZPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR.xT]PUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR/^VUQO#o#r#o#p/s#p#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#rR/zTSPUQO#q#r#q#r$W#r;'S#r;'S;=`$g<%lO#r~0^TO#q#r#q#r0m#r;'S#r;'S;=`$g<%lO#r~0rOR~",tokenizers:[d,W,C,T,f,0,1],topRules:{Content:[0,2],Attribute:[1,9]},tokenPrec:0}),V=i.parser.configure({top:"SingleExpression"}),Q=A.configure({props:[R({Text:r.content,Is:r.definitionOperator,AttributeName:r.attributeName,"AttributeValue ExpressionAttributeValue StatementAttributeValue":r.attributeValue,Entity:r.character,InvalidEntity:r.invalid,"BoundAttributeName/Identifier":r.attributeName,"EventName/Identifier":r.special(r.attributeName),"ReferenceName/Identifier":r.variableName,"DirectiveName/Identifier":r.keyword,"{{ }}":r.brace,"( )":r.paren,"[ ]":r.bracket,"# '*'":r.punctuation})]}),o={parser:V},w={parser:i.parser},U=Q.configure({wrap:l((O,e)=>O.name=="InterpolationContent"?o:null)}),y=Q.configure({wrap:l((O,e)=>{var a;return O.name=="InterpolationContent"?o:O.name!="AttributeInterpolation"?null:((a=O.node.parent)===null||a===void 0?void 0:a.name)=="StatementAttributeValue"?w:o}),top:"Attribute"}),E={parser:U},N={parser:y},s=g();function S(O){return O.configure({wrap:l(z)},"angular")}const k=S(s.language);function z(O,e){switch(O.name){case"Attribute":return/^[*#(\[]|\{\{/.test(e.read(O.from,O.to))?N:null;case"Text":return E}return null}function G(O={}){let e=s;if(O.base){if(O.base.language.name!="html"||!(O.base.language instanceof q))throw new RangeError("The base option must be the result of calling html(...)");e=O.base}return new P(e.language==s.language?k:S(e.language),[e.support,e.language.data.of({closeBrackets:{brackets:["[","{",'"']},indentOnInput:/^\s*[\}\]]$/})])}export{G as angular,k as angularLanguage};

26
web/dist/assets/index-CnmZeAO0.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{b as O,d as b,L as r,f as s,g as a,s as t,j as P,l as n,t as e}from"./index-BXY6uktx.js";const S={__proto__:null,anyref:34,dataref:34,eqref:34,externref:34,i31ref:34,funcref:34,i8:34,i16:34,i32:34,i64:34,f32:34,f64:34},Q=r.deserialize({version:14,states:"!^Q]QPOOOqQPO'#CbOOQO'#Cd'#CdOOQO'#Cl'#ClOOQO'#Ch'#ChQ]QPOOOOQO,58|,58|OxQPO,58|OOQO-E6f-E6fOOQO1G.h1G.h",stateData:"!P~O_OSPOSQOS~OTPOVROXROYROZROaQO~OSUO~P]OSXO~P]O",goto:"xaPPPPPPbPbPPPhPPPrXROPTVQTOQVPTWTVXSOPTV",nodeNames:"⚠ LineComment BlockComment Module ) ( App Identifier Type Keyword Number String",maxTerm:17,nodeProps:[["isolate",-3,1,2,11,""],["openedBy",4,"("],["closedBy",5,")"],["group",-6,6,7,8,9,10,11,"Expression"]],skippedNodes:[0,1,2],repeatNodeCount:1,tokenData:"0o~R^XY}YZ}]^}pq}rs!Stu#pxy'Uyz(e{|(j}!O(j!Q!R(s!R![*p!]!^.^#T#o.{~!SO_~~!VVOr!Srs!ls#O!S#O#P!q#P;'S!S;'S;=`#j<%lO!S~!qOZ~~!tRO;'S!S;'S;=`!};=`O!S~#QWOr!Srs!ls#O!S#O#P!q#P;'S!S;'S;=`#j;=`<%l!S<%lO!S~#mP;=`<%l!S~#siqr%bst%btu%buv%bvw%bwx%bz{%b{|%b}!O%b!O!P%b!P!Q%b!Q![%b![!]%b!^!_%b!_!`%b!`!a%b!a!b%b!b!c%b!c!}%b#Q#R%b#R#S%b#S#T%b#T#o%b#p#q%b#r#s%b~%giV~qr%bst%btu%buv%bvw%bwx%bz{%b{|%b}!O%b!O!P%b!P!Q%b!Q![%b![!]%b!^!_%b!_!`%b!`!a%b!a!b%b!b!c%b!c!}%b#Q#R%b#R#S%b#S#T%b#T#o%b#p#q%b#r#s%b~'ZPT~!]!^'^~'aTO!]'^!]!^'p!^;'S'^;'S;=`(_<%lO'^~'sVOy'^yz(Yz!]'^!]!^'p!^;'S'^;'S;=`(_<%lO'^~(_OQ~~(bP;=`<%l'^~(jOS~~(mQ!Q!R(s!R![*p~(xUY~!O!P)[!Q![*p!g!h){#R#S+U#X#Y){#l#m+[~)aRY~!Q![)j!g!h){#X#Y){~)oSY~!Q![)j!g!h){#R#S*j#X#Y){~*OR{|*X}!O*X!Q![*_~*[P!Q![*_~*dQY~!Q![*_#R#S*X~*mP!Q![)j~*uTY~!O!P)[!Q![*p!g!h){#R#S+U#X#Y){~+XP!Q![*p~+_R!Q![+h!c!i+h#T#Z+h~+mVY~!O!P,S!Q![+h!c!i+h!r!s-P#R#S+[#T#Z+h#d#e-P~,XTY~!Q![,h!c!i,h!r!s-P#T#Z,h#d#e-P~,mUY~!Q![,h!c!i,h!r!s-P#R#S.Q#T#Z,h#d#e-P~-ST{|-c}!O-c!Q![-o!c!i-o#T#Z-o~-fR!Q![-o!c!i-o#T#Z-o~-tSY~!Q![-o!c!i-o#R#S-c#T#Z-o~.TR!Q![,h!c!i,h#T#Z,h~.aP!]!^.d~.iSP~OY.dZ;'S.d;'S;=`.u<%lO.d~.xP;=`<%l.d~/QiX~qr.{st.{tu.{uv.{vw.{wx.{z{.{{|.{}!O.{!O!P.{!P!Q.{!Q![.{![!].{!^!_.{!_!`.{!`!a.{!a!b.{!b!c.{!c!}.{#Q#R.{#R#S.{#S#T.{#T#o.{#p#q.{#r#s.{",tokenizers:[0],topRules:{Module:[0,3]},specialized:[{term:9,get:o=>S[o]||-1}],tokenPrec:0}),i=O.define({name:"wast",parser:Q.configure({props:[s.add({App:P({closing:")",align:!1})}),a.add({App:n,BlockComment(o){return{from:o.from+2,to:o.to-2}}}),t({Keyword:e.keyword,Type:e.typeName,Number:e.number,String:e.string,Identifier:e.variableName,LineComment:e.lineComment,BlockComment:e.blockComment,"( )":e.paren})]}),languageData:{commentTokens:{line:";;",block:{open:"(;",close:";)"}},closeBrackets:{brackets:["(",'"']}}});function p(){return new b(i)}export{p as wast,i as wastLanguage};
import{b as O,d as b,L as r,f as s,g as a,s as t,j as P,l as n,t as e}from"./index-7Pb28EN0.js";const S={__proto__:null,anyref:34,dataref:34,eqref:34,externref:34,i31ref:34,funcref:34,i8:34,i16:34,i32:34,i64:34,f32:34,f64:34},Q=r.deserialize({version:14,states:"!^Q]QPOOOqQPO'#CbOOQO'#Cd'#CdOOQO'#Cl'#ClOOQO'#Ch'#ChQ]QPOOOOQO,58|,58|OxQPO,58|OOQO-E6f-E6fOOQO1G.h1G.h",stateData:"!P~O_OSPOSQOS~OTPOVROXROYROZROaQO~OSUO~P]OSXO~P]O",goto:"xaPPPPPPbPbPPPhPPPrXROPTVQTOQVPTWTVXSOPTV",nodeNames:"⚠ LineComment BlockComment Module ) ( App Identifier Type Keyword Number String",maxTerm:17,nodeProps:[["isolate",-3,1,2,11,""],["openedBy",4,"("],["closedBy",5,")"],["group",-6,6,7,8,9,10,11,"Expression"]],skippedNodes:[0,1,2],repeatNodeCount:1,tokenData:"0o~R^XY}YZ}]^}pq}rs!Stu#pxy'Uyz(e{|(j}!O(j!Q!R(s!R![*p!]!^.^#T#o.{~!SO_~~!VVOr!Srs!ls#O!S#O#P!q#P;'S!S;'S;=`#j<%lO!S~!qOZ~~!tRO;'S!S;'S;=`!};=`O!S~#QWOr!Srs!ls#O!S#O#P!q#P;'S!S;'S;=`#j;=`<%l!S<%lO!S~#mP;=`<%l!S~#siqr%bst%btu%buv%bvw%bwx%bz{%b{|%b}!O%b!O!P%b!P!Q%b!Q![%b![!]%b!^!_%b!_!`%b!`!a%b!a!b%b!b!c%b!c!}%b#Q#R%b#R#S%b#S#T%b#T#o%b#p#q%b#r#s%b~%giV~qr%bst%btu%buv%bvw%bwx%bz{%b{|%b}!O%b!O!P%b!P!Q%b!Q![%b![!]%b!^!_%b!_!`%b!`!a%b!a!b%b!b!c%b!c!}%b#Q#R%b#R#S%b#S#T%b#T#o%b#p#q%b#r#s%b~'ZPT~!]!^'^~'aTO!]'^!]!^'p!^;'S'^;'S;=`(_<%lO'^~'sVOy'^yz(Yz!]'^!]!^'p!^;'S'^;'S;=`(_<%lO'^~(_OQ~~(bP;=`<%l'^~(jOS~~(mQ!Q!R(s!R![*p~(xUY~!O!P)[!Q![*p!g!h){#R#S+U#X#Y){#l#m+[~)aRY~!Q![)j!g!h){#X#Y){~)oSY~!Q![)j!g!h){#R#S*j#X#Y){~*OR{|*X}!O*X!Q![*_~*[P!Q![*_~*dQY~!Q![*_#R#S*X~*mP!Q![)j~*uTY~!O!P)[!Q![*p!g!h){#R#S+U#X#Y){~+XP!Q![*p~+_R!Q![+h!c!i+h#T#Z+h~+mVY~!O!P,S!Q![+h!c!i+h!r!s-P#R#S+[#T#Z+h#d#e-P~,XTY~!Q![,h!c!i,h!r!s-P#T#Z,h#d#e-P~,mUY~!Q![,h!c!i,h!r!s-P#R#S.Q#T#Z,h#d#e-P~-ST{|-c}!O-c!Q![-o!c!i-o#T#Z-o~-fR!Q![-o!c!i-o#T#Z-o~-tSY~!Q![-o!c!i-o#R#S-c#T#Z-o~.TR!Q![,h!c!i,h#T#Z,h~.aP!]!^.d~.iSP~OY.dZ;'S.d;'S;=`.u<%lO.d~.xP;=`<%l.d~/QiX~qr.{st.{tu.{uv.{vw.{wx.{z{.{{|.{}!O.{!O!P.{!P!Q.{!Q![.{![!].{!^!_.{!_!`.{!`!a.{!a!b.{!b!c.{!c!}.{#Q#R.{#R#S.{#S#T.{#T#o.{#p#q.{#r#s.{",tokenizers:[0],topRules:{Module:[0,3]},specialized:[{term:9,get:o=>S[o]||-1}],tokenPrec:0}),i=O.define({name:"wast",parser:Q.configure({props:[s.add({App:P({closing:")",align:!1})}),a.add({App:n,BlockComment(o){return{from:o.from+2,to:o.to-2}}}),t({Keyword:e.keyword,Type:e.typeName,Number:e.number,String:e.string,Identifier:e.variableName,LineComment:e.lineComment,BlockComment:e.blockComment,"( )":e.paren})]}),languageData:{commentTokens:{line:";;",block:{open:"(;",close:";)"}},closeBrackets:{brackets:["(",'"']}}});function p(){return new b(i)}export{p as wast,i as wastLanguage};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

4
web/dist/index.html vendored
View File

@ -6,8 +6,8 @@
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LoLLMS WebUI</title>
<script type="module" crossorigin src="/assets/index-BXY6uktx.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-5mBsl5lX.css">
<script type="module" crossorigin src="/assets/index-7Pb28EN0.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CnmZeAO0.css">
</head>
<body>
<div id="app"></div>

View File

@ -223,8 +223,6 @@ export default {
},
getImgUrl() {
// Prefer model icon, fallback to default
console.log("model icon:")
console.log(this.model.icon)
return this.model.icon || defaultImgPlaceholder;
},
defaultImg(event) {

View File

@ -254,14 +254,17 @@ export default {
this.settingsChanged = false;
},
async saveConfiguration() {
this.isLoading = true;
this.loading_text = "Saving configuration...";
this.isLoading = true;
this.loading_text = "Saving configuration...";
try {
const res = await axios.post('/save_settings', { client_id: this.$store.state.client_id }, { headers: posts_headers });
if (res.data.status) this.$store.state.toast.showToast("Settings saved successfully.", 4, true);
else this.$store.state.messageBox.showMessage(`Error saving settings: ${res.data.error || 'Error'}`);
} catch (error) { this.$store.state.messageBox.showMessage(`Error saving settings: ${error.message}`);
} finally { this.isLoading = false; }
} finally { this.isLoading = false;
this.$store.commit('refreshBindings')
this.$store.commit('refreshModelsZoo')
}
},
reset_configuration() {
this.$store.state.yesNoDialog.askQuestion("Reset config to default? This deletes current settings.", 'Reset', 'Cancel')

View File

@ -7,6 +7,7 @@
<div class="container pt-4 pb-50 mb-50 w-full mx-auto px-4">
<TransitionGroup v-if="discussionArr && discussionArr.length > 0" name="list">
<!-- Message components remain unchanged -->
<Message v-for="msg in discussionArr"
:key="msg.id"
:message="msg"
@ -24,75 +25,63 @@
/>
</TransitionGroup>
<div v-if="discussionArr && discussionArr.length < 2 && personality && personality.prompts_list && personality.prompts_list.length > 0" class="w-full rounded-lg m-2 shadow-lg border border-blue-200 dark:border-blue-700 bg-blue-50 dark:bg-blue-900 p-4 pb-2">
<h2 class="text-2xl font-bold mb-4 text-blue-700 dark:text-blue-200 border-b border-blue-300 dark:border-blue-600 pb-2">Prompt Examples</h2>
<div class="overflow-x-auto flex-grow scrollbar">
<div class="flex flex-nowrap gap-4 p-2">
<div v-for="(prompt, index) in personality.prompts_list"
:title="extractTitle(prompt)"
:key="index"
@click="handlePromptSelection(prompt)"
class="flex-shrink-0 w-[300px] card hover:shadow-xl transition-all duration-300 ease-in-out transform hover:scale-105 flex flex-col justify-between min-h-[200px] group p-4 cursor-pointer">
<div class="space-y-2">
<h3 class="font-semibold text-lg text-blue-800 dark:text-blue-100 mb-1 truncate" :title="extractTitle(prompt)">
{{ extractTitle(prompt) || 'Prompt Example' }}
</h3>
<div :title="prompt" class="text-sm text-blue-700 dark:text-blue-300 overflow-hidden line-clamp-4 leading-relaxed">
{{ getPromptContent(prompt) }}
</div>
</div>
<div class="mt-3 text-xs font-medium link opacity-0 group-hover:opacity-100 transition-opacity duration-300">
Click to select
</div>
<!-- Use the new PromptExamples component -->
<PromptExamples
v-if="showPromptExamples"
:prompts="personality?.prompts_list || []"
@prompt-selected="handlePromptSelection"
class="my-4"
/>
<!-- Placeholder Modal (Remains the same) -->
<div v-if="showPlaceholderModal" class="fixed inset-0 bg-black bg-opacity-60 dark:bg-opacity-70 flex items-center justify-center z-50 p-4">
<!-- Modal Content -->
<div class="card max-w-4xl w-full max-h-[90vh] flex flex-col p-0">
<h3 class="text-lg font-semibold p-4 border-b border-blue-200 dark:border-blue-700 text-blue-800 dark:text-blue-100">Fill in the placeholders</h3>
<div class="flex-1 flex flex-col min-h-0 overflow-hidden p-4 space-y-4">
<div class="p-3 bg-blue-100 dark:bg-blue-800 rounded-lg border border-blue-200 dark:border-blue-700">
<h4 class="label !mb-1">Live Preview:</h4>
<!-- Use the specific content getter for preview -->
<div class="flex-1 h-[150px] overflow-y-auto scrollbar bg-white dark:bg-blue-900 p-2 rounded text-sm">
<span class="whitespace-pre-wrap text-blue-900 dark:text-blue-100">{{ getPromptContent(previewPrompt) }}</span>
</div>
</div>
<div class="flex-1 overflow-y-auto scrollbar space-y-3 pr-2">
<div v-for="(placeholder, index) in parsedPlaceholders" :key="placeholder.fullText" class="flex flex-col">
<label :for="'placeholder-'+index" class="label">{{ placeholder.label }}</label>
<!-- Input types remain the same -->
<input v-if="placeholder.type === 'text'" :id="'placeholder-'+index" v-model="placeholderValues[index]" type="text" class="input" :placeholder="placeholder.label" @input="updatePreview">
<input v-if="placeholder.type === 'int'" :id="'placeholder-'+index" v-model.number="placeholderValues[index]" type="number" step="1" class="input" @input="updatePreview">
<input v-if="placeholder.type === 'float'" :id="'placeholder-'+index" v-model.number="placeholderValues[index]" type="number" step="0.01" class="input" @input="updatePreview">
<textarea v-if="placeholder.type === 'multiline'" :id="'placeholder-'+index" v-model="placeholderValues[index]" rows="4" class="input" @input="updatePreview"></textarea>
<div v-if="placeholder.type === 'code'" class="border border-blue-300 dark:border-blue-600 rounded-md overflow-hidden">
<div class="bg-blue-200 dark:bg-blue-700 p-1 px-2 text-xs text-blue-700 dark:text-blue-200">{{ placeholder.language || 'Plain text' }}</div>
<textarea :id="'placeholder-'+index" v-model="placeholderValues[index]" rows="6" class="w-full p-2 font-mono bg-blue-50 dark:bg-blue-900 border-t border-blue-300 dark:border-blue-600 text-sm" @input="updatePreview"></textarea>
</div>
<select v-if="placeholder.type === 'options'" :id="'placeholder-'+index" v-model="placeholderValues[index]" class="input" @change="updatePreview">
<option value="" disabled>Select an option</option>
<option v-for="option in placeholder.options" :key="option" :value="option" class="text-blue-900 dark:text-blue-100 bg-blue-100 dark:bg-blue-800">{{ option }}</option>
</select>
</div>
</div>
</div>
</div>
<!-- Placeholder Modal -->
<div v-if="showPlaceholderModal" class="fixed inset-0 bg-black bg-opacity-60 dark:bg-opacity-70 flex items-center justify-center z-50 p-4">
<div class="card max-w-4xl w-full max-h-[90vh] flex flex-col p-0">
<h3 class="text-lg font-semibold p-4 border-b border-blue-200 dark:border-blue-700 text-blue-800 dark:text-blue-100">Fill in the placeholders</h3>
<div class="flex-1 flex flex-col min-h-0 overflow-hidden p-4 space-y-4">
<div class="p-3 bg-blue-100 dark:bg-blue-800 rounded-lg border border-blue-200 dark:border-blue-700">
<h4 class="label !mb-1">Live Preview:</h4>
<div class="flex-1 h-[150px] overflow-y-auto scrollbar bg-white dark:bg-blue-900 p-2 rounded text-sm">
<span class="whitespace-pre-wrap text-blue-900 dark:text-blue-100">{{ getPromptContent(previewPrompt) }}</span>
</div>
</div>
<div class="flex-1 overflow-y-auto scrollbar space-y-3 pr-2">
<div v-for="(placeholder, index) in parsedPlaceholders" :key="placeholder.fullText" class="flex flex-col">
<label :for="'placeholder-'+index" class="label">{{ placeholder.label }}</label>
<input v-if="placeholder.type === 'text'" :id="'placeholder-'+index" v-model="placeholderValues[index]" type="text" class="input" :placeholder="placeholder.label" @input="updatePreview">
<input v-if="placeholder.type === 'int'" :id="'placeholder-'+index" v-model.number="placeholderValues[index]" type="number" step="1" class="input" @input="updatePreview">
<input v-if="placeholder.type === 'float'" :id="'placeholder-'+index" v-model.number="placeholderValues[index]" type="number" step="0.01" class="input" @input="updatePreview">
<textarea v-if="placeholder.type === 'multiline'" :id="'placeholder-'+index" v-model="placeholderValues[index]" rows="4" class="input" @input="updatePreview"></textarea>
<div v-if="placeholder.type === 'code'" class="border border-blue-300 dark:border-blue-600 rounded-md overflow-hidden">
<div class="bg-blue-200 dark:bg-blue-700 p-1 px-2 text-xs text-blue-700 dark:text-blue-200">{{ placeholder.language || 'Plain text' }}</div>
<textarea :id="'placeholder-'+index" v-model="placeholderValues[index]" rows="6" class="w-full p-2 font-mono bg-blue-50 dark:bg-blue-900 border-t border-blue-300 dark:border-blue-600 text-sm" @input="updatePreview"></textarea>
</div>
<select v-if="placeholder.type === 'options'" :id="'placeholder-'+index" v-model="placeholderValues[index]" class="input" @change="updatePreview">
<option value="" disabled>Select an option</option>
<option v-for="option in placeholder.options" :key="option" :value="option" class="text-blue-900 dark:text-blue-100 bg-blue-100 dark:bg-blue-800">{{ option }}</option>
</select>
</div>
</div>
</div>
<div class="p-4 flex justify-end space-x-2 border-t border-blue-200 dark:border-blue-700">
<button @click="cancelPlaceholders" class="btn btn-secondary">Cancel</button>
<button @click="applyPlaceholders" class="btn btn-primary">Apply</button>
</div>
<div class="p-4 flex justify-end space-x-2 border-t border-blue-200 dark:border-blue-700">
<button @click="cancelPlaceholders" class="btn btn-secondary">Cancel</button>
<button @click="applyPlaceholders" class="btn btn-primary">Apply</button>
</div>
</div>
</div>
<WelcomeComponent v-if="!hasActiveDiscussion" />
<div class="h-40"></div>
<div class="h-40"></div> <!-- Padding at the bottom -->
</div>
<!-- Gradient overlay remains the same -->
<div class="sticky bottom-0 left-0 right-0 h-48 pointer-events-none bg-gradient-to-t from-blue-100 to-transparent dark:from-blue-900 z-10"></div>
</div>
<!-- ChatBox remains the same -->
<div class="sticky bottom-0 left-0 right-0 p-4 z-20 w-full max-w-4xl mx-auto" v-if="hasActiveDiscussion">
<ChatBox ref="chatBox"
:loading="isGenerating"
@ -120,35 +109,29 @@ import { mapState } from 'vuex';
import Message from './Message.vue';
import ChatBox from './ChatBox.vue';
import WelcomeComponent from '@/components/WelcomeComponent.vue';
import PromptExamples from './PromptExamples.vue'; // Import the new component
import feather from 'feather-icons';
// Keep parsePlaceholder function here as it's used for the modal
const parsePlaceholder = (placeholder) => {
const parts = placeholder.replace(/^\[|\]$/g, '').split('::'); // Use regex for cleaner removal
const parts = placeholder.replace(/^\[|\]$/g, '').split('::');
const label = parts[0];
if (parts.length === 1) return { label, type: 'text', fullText: placeholder };
const type = parts[1].toLowerCase(); // Normalize type
const type = parts[1].toLowerCase();
const result = { label, type, fullText: placeholder };
switch (type) {
case 'int':
case 'float':
case 'multiline': break; // No extra params needed
case 'code':
result.language = parts[2] || 'plaintext'; break;
case 'options':
result.options = parts[2] ? parts[2].split(',').map(o => o.trim()) : []; break;
default:
result.type = 'text'; // Fallback to text
case 'int': case 'float': case 'multiline': break;
case 'code': result.language = parts[2] || 'plaintext'; break;
case 'options': result.options = parts[2] ? parts[2].split(',').map(o => o.trim()) : []; break;
default: result.type = 'text';
}
return result;
};
export default {
name: 'ChatArea',
components: { Message, ChatBox, WelcomeComponent },
// Register the new component
components: { Message, ChatBox, WelcomeComponent, PromptExamples },
props: {
isReady: Boolean,
hasActiveDiscussion: Boolean,
@ -166,27 +149,35 @@ export default {
return {
isDragOverChat: false,
showPlaceholderModal: false,
selectedPrompt: '',
placeholders: [],
placeholderValues: {},
previewPrompt: '',
selectedPrompt: '', // The original prompt string with title and placeholders
placeholders: [], // Raw placeholder strings like "[placeholder::type]"
placeholderValues: {}, // Values entered by the user for placeholders
previewPrompt: '', // The prompt string used for live preview in the modal
};
},
computed: {
...mapState(['config']),
personality() {
// Personality computation logic remains the same
if (!this.config || !this.config.personalities || this.config.active_personality_id < 0 || this.config.active_personality_id >= this.config.personalities.length) {
return null;
}
const activePersPath = this.config.personalities[this.config.active_personality_id];
const basePath = activePersPath?.split(':')[0];
// Assuming personalities array is available in the parent or Vuex, passed as a prop or accessed directly if needed
// For now, just returning a placeholder object structure if needed, otherwise rely on parent logic
// This might need adjustment based on where the full personality details are stored/accessed
const fullPersonality = this.$store.state.personalities.find(p => p.full_path === basePath);
return fullPersonality || null; // Return the found personality or null
// Make sure this logic correctly finds the personality object containing `prompts_list`
const fullPersonality = this.$store.state.personalities.find(p => p.full_path === activePersPath);
return fullPersonality || { prompts_list: [] }; // Ensure it returns an object, even if empty
},
parsedPlaceholders() {
showPromptExamples() {
// Condition to show prompt examples
return this.hasActiveDiscussion && // Show only if a discussion exists
this.discussionArr &&
this.discussionArr.length < 2 && // Show only at the start of a conversation (e.g., 0 or 1 message)
this.personality &&
this.personality.prompts_list &&
this.personality.prompts_list.length > 0;
},
parsedPlaceholders() {
// Logic remains the same
const uniqueMap = new Map();
this.placeholders.forEach(p => {
const parsed = parsePlaceholder(p);
@ -196,17 +187,19 @@ export default {
}
},
methods: {
// Methods like getAvatar, scrollToBottom, handleDrop, handleFilesDropped remain the same
getAvatar(sender) {
if (!this.config || !sender) return null;
const senderLower = sender.toLowerCase().trim();
const userLower = this.config.user_name?.toLowerCase().trim();
if (!this.config || !sender) return null;
const senderLower = sender.toLowerCase().trim();
const userLower = this.config.user_name?.toLowerCase().trim();
if (senderLower === userLower) {
return this.config.user_avatar ? `user_infos/${this.config.user_avatar}` : null; // Handle missing user avatar
}
if (senderLower === userLower) {
return this.config.user_avatar ? `user_infos/${this.config.user_avatar}` : null;
}
const personality = this.personalityAvatars.find(p => p.name?.toLowerCase().trim() === senderLower);
return personality?.avatar ? `/${personality.avatar}` : null; // Prepend '/' for web path
// Assuming personalityAvatars is correctly populated [{ name: 'PersonalityName', avatar: 'path/to/avatar.png' }, ...]
const personality = this.personalityAvatars.find(p => p.name?.toLowerCase().trim() === senderLower);
return personality?.avatar ? `/${personality.avatar}` : null; // Ensure leading slash if needed
},
scrollToBottom() {
nextTick(() => {
@ -224,86 +217,103 @@ export default {
handleFilesDropped(files) {
this.$emit('files-dropped', files);
},
extractTitle(prompt) {
const titleMatch = prompt.match(/@<(.*?)>@/);
return titleMatch ? titleMatch[1] : null;
},
// --- Placeholder and Prompt Handling Logic ---
// Utility to get content part of the prompt (without title tag)
getPromptContent(prompt) {
if (!prompt) return '';
return prompt.replace(/@<.*?>@/, '').trim();
},
// Triggered by the 'prompt-selected' event from PromptExamples component
handlePromptSelection(prompt) {
this.selectedPrompt = prompt;
this.previewPrompt = this.getPromptContent(prompt); // Use content for preview initially
this.selectedPrompt = prompt; // Store the full original prompt
this.previewPrompt = prompt; // Initialize preview with the full prompt (will be updated)
this.placeholders = this.extractPlaceholders(prompt);
if (this.placeholders.length > 0) {
this.placeholderValues = {}; // Reset values
this.parsedPlaceholders.forEach((ph, index) => {
// Pre-fill with default values if any are defined in the placeholder syntax (future enhancement)
this.placeholderValues[index] = ''; // Initialize as empty
});
this.showPlaceholderModal = true;
this.updatePreview(); // Initial preview update
} else {
// If no placeholders, directly use the content part of the prompt
this.setPromptInChatbox(this.getPromptContent(prompt));
}
},
extractPlaceholders(prompt) {
// Extracts placeholders like [placeholder::type] or [placeholder]
const placeholderRegex = /\[(.*?)\]/g;
// Avoid duplicates if the same placeholder appears multiple times
const uniquePlaceholders = new Set([...prompt.matchAll(placeholderRegex)].map(match => match[0]));
const uniquePlaceholders = new Set([...(prompt || '').matchAll(placeholderRegex)].map(match => match[0]));
return Array.from(uniquePlaceholders);
},
updatePreview() {
let preview = this.selectedPrompt;
let preview = this.selectedPrompt; // Start with the original prompt
this.parsedPlaceholders.forEach((placeholder, index) => {
const value = this.placeholderValues[index];
// Replace all occurrences of the same placeholder using RegExp
const regex = new RegExp(this.escapeRegExp(placeholder.fullText), 'g');
// Use the original full placeholder text if the value is empty, otherwise use the value
// Replace placeholder with value, or keep original placeholder if value is empty
preview = preview.replace(regex, value || placeholder.fullText);
});
this.previewPrompt = preview; // Update the preview reactive property
this.previewPrompt = preview; // Update the reactive preview property
},
escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
},
cancelPlaceholders() {
this.showPlaceholderModal = false;
// Resetting state is handled in handlePromptSelection if re-opened
// No need to reset here, state will be reset if another prompt is selected
},
applyPlaceholders() {
let finalPrompt = this.selectedPrompt;
let finalPromptWithValues = this.selectedPrompt; // Start with original
this.parsedPlaceholders.forEach((placeholder, index) => {
const value = this.placeholderValues[index];
if (value !== undefined && value !== '') { // Apply only if a value is provided
// Only replace if a value is actually provided
if (value !== undefined && value !== null && value !== '') {
const regex = new RegExp(this.escapeRegExp(placeholder.fullText), 'g');
finalPrompt = finalPrompt.replace(regex, value);
finalPromptWithValues = finalPromptWithValues.replace(regex, value);
}
// If value is empty, the original placeholder might remain.
});
// Now, remove any remaining placeholder syntax *and* the title tag for the final chatbox input
const finalContent = this.getPromptContent(finalPromptWithValues) // Remove title tag first
.replace(/\[(.*?)\]/g, ''); // Remove any remaining empty/unfilled placeholder brackets
this.showPlaceholderModal = false;
this.setPromptInChatbox(this.getPromptContent(finalPrompt)); // Use the final processed prompt content
this.setPromptInChatbox(finalContent.trim()); // Set the processed content in the chatbox
},
setPromptInChatbox(prompt) {
setPromptInChatbox(promptContent) {
if (this.$refs.chatBox) {
this.$refs.chatBox.message = prompt;
// Optionally focus the input
this.$refs.chatBox.focusInput();
this.$refs.chatBox.message = promptContent;
this.$refs.chatBox.focusInput(); // Optional: focus the input
}
},
// --- End Placeholder Logic ---
},
watch: {
// Watchers remain the same
discussionArr: {
handler() {
this.scrollToBottom();
this.$nextTick(() => feather.replace());
//this.scrollToBottom();
this.$nextTick(() => feather.replace());
},
deep: true
},
personality: {
handler(newVal, oldVal) {
// Reset prompt example state if personality changes
if (newVal?.full_path !== oldVal?.full_path) {
// Reset placeholder state if personality changes
this.showPlaceholderModal = false;
this.selectedPrompt = '';
this.placeholders = [];
@ -315,12 +325,14 @@ export default {
}
},
mounted() {
this.scrollToBottom();
// Mounted logic remains the same
// this.scrollToBottom();
nextTick(() => {
feather.replace();
});
},
updated() {
// Updated logic remains the same
nextTick(() => {
feather.replace();
});
@ -329,5 +341,11 @@ export default {
</script>
<style scoped>
</style>
/* Scoped styles for ChatArea remain unchanged */
.pb-50 { /* Ensure enough padding at the bottom inside the scrollable area */
padding-bottom: 50px; /* Adjust as needed */
}
.mb-50 { /* Ensure enough margin at the bottom inside the scrollable area if using margin instead */
margin-bottom: 50px; /* Adjust as needed */
}
</style>