@qlover/create-app 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/configs/_common/.github/workflows/general-check.yml +1 -1
  3. package/dist/configs/_common/.github/workflows/release.yml +2 -2
  4. package/dist/index.cjs +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/templates/react-app/__tests__/src/uikit/components/chatMessage/ChatRoot.test.tsx +274 -0
  7. package/dist/templates/react-app/config/IOCIdentifier.ts +3 -0
  8. package/dist/templates/react-app/config/Identifier/components/component.chatMessage.ts +56 -0
  9. package/dist/templates/react-app/config/Identifier/components/component.messageBaseList.ts +103 -0
  10. package/dist/templates/react-app/config/Identifier/pages/index.ts +1 -0
  11. package/dist/templates/react-app/config/Identifier/pages/page.message.ts +20 -0
  12. package/dist/templates/react-app/config/app.router.ts +23 -0
  13. package/dist/templates/react-app/config/i18n/chatMessageI18n.ts +17 -0
  14. package/dist/templates/react-app/config/i18n/messageBaseListI18n.ts +22 -0
  15. package/dist/templates/react-app/config/i18n/messageI18n.ts +14 -0
  16. package/dist/templates/react-app/docs/en/components/chat-message-component.md +314 -0
  17. package/dist/templates/react-app/docs/en/components/chat-message-refactor.md +270 -0
  18. package/dist/templates/react-app/docs/en/components/message-base-list-component.md +172 -0
  19. package/dist/templates/react-app/docs/zh/components/chat-message-component.md +314 -0
  20. package/dist/templates/react-app/docs/zh/components/chat-message-refactor.md +270 -0
  21. package/dist/templates/react-app/docs/zh/components/message-base-list-component.md +172 -0
  22. package/dist/templates/react-app/playwright.config.ts +6 -6
  23. package/dist/templates/react-app/public/locales/en/common.json +44 -1
  24. package/dist/templates/react-app/public/locales/zh/common.json +44 -1
  25. package/dist/templates/react-app/src/pages/base/MessagePage.tsx +40 -0
  26. package/dist/templates/react-app/src/uikit/components/MessageBaseList.tsx +240 -0
  27. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatMessageBridge.ts +176 -0
  28. package/dist/templates/react-app/src/uikit/components/chatMessage/ChatRoot.tsx +21 -0
  29. package/dist/templates/react-app/src/uikit/components/chatMessage/FocusBar.tsx +106 -0
  30. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageApi.ts +271 -0
  31. package/dist/templates/react-app/src/uikit/components/chatMessage/MessageItem.tsx +102 -0
  32. package/dist/templates/react-app/src/uikit/components/chatMessage/MessagesList.tsx +86 -0
  33. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @qlover/create-app
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ #### ✨ Features
8
+
9
+ - **react-app:** integrate chat message components and enhance messaging functionality ([84af0f3](https://github.com/qlover/fe-base/commit/84af0f3913f191e79d747bffcc7d26283f6b861f)) ([#537](https://github.com/qlover/fe-base/pull/537))
10
+ - Introduced ChatMessage component system, including ChatRoot, MessagesList, and MessageItem for improved chat interactions.
11
+ - Added MessageBaseList component for testing message gateway functionality.
12
+ - Implemented internationalization support for chat and message components, enhancing user experience.
13
+ - Updated routing configuration to include a new MessagePage for managing chat messages.
14
+ - Enhanced Playwright testing setup for chat components, ensuring robust testing coverage.
15
+
16
+ These changes aim to provide a comprehensive chat experience, streamline message handling, and improve overall application functionality.
17
+
18
+ Co-authored-by: QRJ <github-actions[bot]@users.noreply.github.com>
19
+
3
20
  ## 0.8.0
4
21
 
5
22
  ### Minor Changes
@@ -24,7 +24,7 @@ jobs:
24
24
  - name: Install Node.js
25
25
  uses: actions/setup-node@v4
26
26
  with:
27
- node-version: '18.20.0'
27
+ node-version: '20.16.0'
28
28
  - run: npm install -g pnpm
29
29
  - run: pnpm install --frozen-lockfile
30
30
  - run: pnpm lint
@@ -31,7 +31,7 @@ jobs:
31
31
  - name: Install Node.js
32
32
  uses: actions/setup-node@v2
33
33
  with:
34
- node-version: '18.20.0'
34
+ node-version: '20.16.0'
35
35
 
36
36
  - run: npm install -g pnpm
37
37
  - run: pnpm install --frozen-lockfile
@@ -66,7 +66,7 @@ jobs:
66
66
  - name: Install Node.js
67
67
  uses: actions/setup-node@v2
68
68
  with:
69
- node-version: '18.20.0'
69
+ node-version: '20.16.0'
70
70
 
71
71
  - run: npm install -g pnpm
72
72
  - run: pnpm install --frozen-lockfile
package/dist/index.cjs CHANGED
@@ -8,4 +8,4 @@ ${t}`,_n=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),mn=Objec
8
8
  `))this.#e+=Math.max(1,Math.ceil(Ye(D,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#D.frames.length,this.#c=t);let{frames:r}=this.#D,u=r[this.#i];this.color&&(u=d[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",D=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+D+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#n;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#n=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#n=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&$e.hide(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&(this.#r=!0,Je.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
9
9
  `),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&$e.show(this.#u),this.#t.discardStdin&&V.default.stdin.isTTY&&this.#r&&(Je.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:H.success,text:t})}fail(t){return this.stopAndPersist({symbol:H.error,text:t})}warn(t){return this.stopAndPersist({symbol:H.warning,text:t})}info(t){return this.stopAndPersist({symbol:H.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",D=t.text??this.text,s=typeof D=="string"?(i?" ":"")+D:"",a=t.suffixText??this.#o,l=this.#g(a," "),p=u+i+s+l+`
10
10
  `;return this.stop(),this.#u.write(p),this}};function rr(e){return new Ze(e)}async function ur(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:D}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=rr(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(D===void 0?void 0:typeof D=="string"?D:D(s)),s}}var bt=require("fs");var P=require("path"),me=require("fs"),fr=h(cr(),1),Dt=require("fs");var de=require("fs"),X=class{static ensureDir(t){(0,de.existsSync)(t)||(0,de.mkdirSync)(t,{recursive:!0})}};var{copyFile:us,stat:is}=Dt.promises,_e=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=(0,P.join)(t,this.ignoreFile);if(!(0,me.existsSync)(r))return;let D=(0,me.readFileSync)(r,"utf8").split(`
11
- `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,fr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await is(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await us(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),$D=h(kD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,$D.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var UD=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,bt.existsSync)(r))throw new Error("template path not exit");this.ora=ur,this.context=new WD.ScriptContext("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await HD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return UD.includes(t)}async getGeneratorContext(){let t=wt(this.subPackages,UD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=Pt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=(0,v.join)(D,u);if(!(0,bt.existsSync)(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var vt={name:"@qlover/create-app",version:"0.8.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{lint:"eslint src --fix",build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Wl(){let e=new YD.Command;return e.version(vt.version,"-v, --version","Show version").description(vt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function VD(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Wl(),i=(0,xt.resolve)(e,"./templates"),D=(0,xt.resolve)(e,"./configs");(0,yt.existsSync)(i)||(console.error("Template is empty!"),process.exit(1)),(0,yt.existsSync)(D)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:D}}).generate()}VD(__dirname).catch(e=>{console.error(e),process.exit(1)});
11
+ `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,fr.default)().add(D)}async copyFiles(t,r,u,i){let D=await Dt.promises.readdir(t);await Promise.all(D.map(async o=>{let s=(0,P.join)(t,o),a=(0,P.join)(r,o);if(u&&u.ignores(o))return;if(X.ensureDir((0,P.dirname)(a)),(await is(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await us(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){X.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var L=require("fs"),$D=h(kD(),1),xe=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return(0,L.readFileSync)(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){(0,L.writeFileSync)(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let D=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof D=="string"?D:JSON.stringify(D))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,$D.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let D=this.getRealTemplateFilePath(u);return(0,L.existsSync)(D)?(this.mergeJSONFile(D,JSON.parse(i)),!0):(this.writeFile(D,i),!0)}return this.writeFile(u,i),!0}return!1}};var UD=["pack-app"],ye=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!(0,bt.existsSync)(r))throw new Error("template path not exit");this.ora=ur,this.context=new WD.ScriptContext("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new _e((0,v.join)(this.context.options.configsRootPath,"_common")),this.compose=new xe}get logger(){return this.context.logger}async steps(t){try{return await HD.default.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return UD.includes(t)}async getGeneratorContext(){let t=wt(this.subPackages,UD),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=Pt(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=(0,v.join)(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:D,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=(0,v.join)(D,u);if(!(0,bt.existsSync)(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:(0,v.join)(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:D}=this.context.options;for(let o of u){let s=(0,v.join)(D,o),a=(0,v.join)(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var vt={name:"@qlover/create-app",version:"0.9.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{lint:"eslint src --fix",build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Wl(){let e=new YD.Command;return e.version(vt.version,"-v, --version","Show version").description(vt.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function VD(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Wl(),i=(0,xt.resolve)(e,"./templates"),D=(0,xt.resolve)(e,"./configs");(0,yt.existsSync)(i)||(console.error("Template is empty!"),process.exit(1)),(0,yt.existsSync)(D)||(console.error("Configs is empty!"),process.exit(1)),await new ye({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:D}}).generate()}VD(__dirname).catch(e=>{console.error(e),process.exit(1)});
package/dist/index.js CHANGED
@@ -8,4 +8,4 @@ ${t}`,pD=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),hD=Objec
8
8
  `))this.#e+=Math.max(1,Math.ceil(Le(n,{countAnsiEscapeCodes:!0})/t))}get isEnabled(){return this.#a&&!this.#F}set isEnabled(t){if(typeof t!="boolean")throw new TypeError("The `isEnabled` option must be a boolean");this.#a=t}get isSilent(){return this.#F}set isSilent(t){if(typeof t!="boolean")throw new TypeError("The `isSilent` option must be a boolean");this.#F=t}frame(){let t=Date.now();(this.#i===-1||t-this.#c>=this.interval)&&(this.#i=++this.#i%this.#n.frames.length,this.#c=t);let{frames:r}=this.#n,u=r[this.#i];this.color&&(u=E[this.color](u));let i=typeof this.#s=="string"&&this.#s!==""?this.#s+" ":"",n=typeof this.text=="string"?" "+this.text:"",o=typeof this.#o=="string"&&this.#o!==""?" "+this.#o:"";return i+u+n+o}clear(){if(!this.#a||!this.#u.isTTY)return this;this.#u.cursorTo(0);for(let t=0;t<this.#D;t++)t>0&&this.#u.moveCursor(0,-1),this.#u.clearLine(1);return(this.#l||this.lastIndent!==this.#l)&&this.#u.cursorTo(this.#l),this.lastIndent=this.#l,this.#D=0,this}render(){return this.#F?this:(this.clear(),this.#u.write(this.frame()),this.#D=this.#e,this)}start(t){return t&&(this.text=t),this.#F?this:this.#a?this.isSpinning?this:(this.#t.hideCursor&&je.hide(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&(this.#r=!0,We.start()),this.render(),this.#f=setInterval(this.render.bind(this),this.interval),this):(this.text&&this.#u.write(`- ${this.text}
9
9
  `),this)}stop(){return this.#a?(clearInterval(this.#f),this.#f=void 0,this.#i=0,this.clear(),this.#t.hideCursor&&je.show(this.#u),this.#t.discardStdin&&ce.stdin.isTTY&&this.#r&&(We.stop(),this.#r=!1),this):this}succeed(t){return this.stopAndPersist({symbol:k.success,text:t})}fail(t){return this.stopAndPersist({symbol:k.error,text:t})}warn(t){return this.stopAndPersist({symbol:k.warning,text:t})}info(t){return this.stopAndPersist({symbol:k.info,text:t})}stopAndPersist(t={}){if(this.#F)return this;let r=t.prefixText??this.#s,u=this.#E(r," "),i=t.symbol??" ",n=t.text??this.text,s=typeof n=="string"?(i?" ":"")+n:"",a=t.suffixText??this.#o,l=this.#g(a," "),p=u+i+s+l+`
10
10
  `;return this.stop(),this.#u.write(p),this}};function zt(e){return new He(e)}async function Kt(e,t){let r=typeof e=="function",u=typeof e.then=="function";if(!r&&!u)throw new TypeError("Parameter `action` must be a Function or a Promise");let{successText:i,failText:n}=typeof t=="object"?t:{successText:void 0,failText:void 0},o=zt(t).start();try{let a=await(r?e(o):e);return o.succeed(i===void 0?void 0:typeof i=="string"?i:i(a)),a}catch(s){throw o.fail(n===void 0?void 0:typeof n=="string"?n:n(s)),s}}import{existsSync as Nn}from"fs";var nr=ue(ir(),1);import{dirname as QD,join as Ze}from"path";import{existsSync as es,readFileSync as ts}from"fs";import{promises as Dr}from"fs";import{existsSync as JD,mkdirSync as ZD}from"fs";var H=class{static ensureDir(t){JD(t)||ZD(t,{recursive:!0})}};var{copyFile:rs,stat:us}=Dr,Ce=class e{constructor(t,r=e.IGNORE_FILE){this.ignoreTargetPath=t;this.ignoreFile=r}static IGNORE_FILE=".gitignore.template";getIg(t=this.ignoreTargetPath){let r=Ze(t,this.ignoreFile);if(!es(r))return;let n=ts(r,"utf8").split(`
11
- `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,nr.default)().add(n)}async copyFiles(t,r,u,i){let n=await Dr.readdir(t);await Promise.all(n.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(QD(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var jn=ue(In(),1);import{readFileSync as $l,writeFileSync as Ul,existsSync as Wl}from"fs";var _e=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return $l(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){Ul(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let n=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof n=="string"?n:JSON.stringify(n))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,jn.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let n=this.getRealTemplateFilePath(u);return Wl(n)?(this.mergeJSONFile(n,JSON.parse(i)),!0):(this.writeFile(n,i),!0)}return this.writeFile(u,i),!0}return!1}};var Gn=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Nn(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Hl("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new _e}get logger(){return this.context.logger}async steps(t){try{return await Yl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return Gn.includes(t)}async getGeneratorContext(){let t=mt(this.subPackages,Gn),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=_t(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:n,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=N(n,u);if(!Nn(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:n}=this.context.options;for(let o of u){let s=N(n,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.8.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{lint:"eslint src --fix",build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Kl(){let e=new zl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function kn(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Kl(),i=Mn(e,"./templates"),n=Mn(e,"./configs");Ln(i)||(console.error("Template is empty!"),process.exit(1)),Ln(n)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:n}}).generate()}import{fileURLToPath as Xl}from"url";import{dirname as Jl}from"path";var Zl=Xl(import.meta.url),Ql=Jl(Zl);kn(Ql).catch(e=>{console.error(e),process.exit(1)});
11
+ `).map(o=>o.trim()).filter(o=>o&&!o.startsWith("#"));return(0,nr.default)().add(n)}async copyFiles(t,r,u,i){let n=await Dr.readdir(t);await Promise.all(n.map(async o=>{let s=Ze(t,o),a=Ze(r,o);if(u&&u.ignores(o))return;if(H.ensureDir(QD(a)),(await us(s)).isDirectory())await this.copyFiles(s,a,u,i);else{if(i&&await i(s,a))return;await rs(s,a)}}))}copyPaths({sourcePath:t,targetPath:r,copyCallback:u}){H.ensureDir(r);let i=this.getIg();return this.copyFiles(t,r,i,u)}};var jn=ue(In(),1);import{readFileSync as $l,writeFileSync as Ul,existsSync as Wl}from"fs";var _e=class{constructor(){}isJSONFilePath(t){return t.endsWith(".json")||t.endsWith(".json.template")}isTemplateFilePath(t){return t.endsWith(".template")}getRealTemplateFilePath(t){return t.replace(".template","")}readFile(t){return $l(t,"utf-8")}readJSONFile(t){return JSON.parse(this.readFile(t))}writeFile(t,r){Ul(this.getRealTemplateFilePath(t),r,{encoding:"utf-8"})}replaceFile(t,r){let u=this.readFile(t);return Object.keys(r).forEach(i=>{let n=r[i];u=u.replace(new RegExp(`\\[TPL:${i}\\]`,"g"),typeof n=="string"?n:JSON.stringify(n))}),u}mergeJSONFile(t,r){let u=this.readJSONFile(t),i=(0,jn.default)(r,u);this.writeFile(t,JSON.stringify(i,null,2))}composeConfigFile(t,r,u){if(this.isTemplateFilePath(r)){let i=this.replaceFile(r,t);if(this.isJSONFilePath(r)&&this.isJSONFilePath(u)){let n=this.getRealTemplateFilePath(u);return Wl(n)?(this.mergeJSONFile(n,JSON.parse(i)),!0):(this.writeFile(n,i),!0)}return this.writeFile(u,i),!0}return!1}};var Gn=["pack-app"],Be=class{ora;context;subPackages;copyer;compose;constructor(t){let r=t.options?.templateRootPath;if(!r)throw new Error("template path not exit");if(!Nn(r))throw new Error("template path not exit");this.ora=Kt,this.context=new Hl("create-app",t),this.subPackages=["node-lib","react-app","next-app"],this.copyer=new Ce(N(this.context.options.configsRootPath,"_common")),this.compose=new _e}get logger(){return this.context.logger}async steps(t){try{return await Yl.prompt(t)}catch(r){throw r.isTtyError,this.logger.error(r),r}}async action({label:t,task:r}){let u=r();u instanceof Promise||(u=Promise.resolve(u));let i=t;return this.ora(u,i),u}isPackageTemplate(t){return Gn.includes(t)}async getGeneratorContext(){let t=mt(this.subPackages,Gn),r=await this.steps(t);if(this.isPackageTemplate(r.template)){let u=_t(this.subPackages),i=await this.steps(u);Object.assign(r,i)}return r.targetPath=N(process.cwd(),r.projectName),r.releasePath=r.releasePath||"src",r}async generate(){let t=await this.getGeneratorContext();if(this.logger.debug("context is:",t,this.context.options.templateRootPath),t.subPackages){await this.action({label:"Generate Directories(subPackages)",task:async()=>{await this.generateTemplateDir(t),await this.generateSubPackages(t),await this.generateConfigs(t,t.targetPath,"_common")}});return}await this.action({label:"Generate Directory",task:async()=>{await this.generateTemplateDir(t),await this.generateConfigs(t,t.targetPath,"_common"),await this.generateConfigs(t,t.targetPath,t.template)}})}async generateConfigs(t,r,u){let i=(a,l)=>(this.logger.debug("copyCallback",a,l),this.compose.composeConfigFile(t,a,l)),{configsRootPath:n,config:o}=this.context.options;if(!o){this.logger.debug("no copy config files");return}let s=N(n,u);if(!Nn(s)){this.logger.debug(`Config path not found: ${s}`);return}await this.copyer.copyPaths({sourcePath:s,targetPath:r,copyCallback:i})}generateTemplateDir(t){return this.copyer.copyPaths({sourcePath:N(this.context.options.templateRootPath,t.template),targetPath:t.targetPath})}async generateSubPackages(t){let{packagesNames:r="packages",subPackages:u=[],targetPath:i=""}=t,{templateRootPath:n}=this.context.options;for(let o of u){let s=N(n,o),a=N(i,r,o);this.logger.debug("copy sub package",s,a),await this.copyer.copyPaths({sourcePath:s,targetPath:a})}}};var Et={name:"@qlover/create-app",version:"0.9.0",description:"Create a new app with a single command",private:!1,type:"module",files:["dist","package.json","README.md","CHANGELOG.md"],bin:{"create-app":"dist/index.js"},scripts:{lint:"eslint src --fix",build:"tsup","create:app":"node ./dist/index.js"},repository:{type:"git",url:"git+https://github.com/qlover/fe-base.git",directory:"packages/create-app"},homepage:"https://github.com/qlover/fe-base#readme",keywords:["create-app","fe-scripts","scripts"],author:"qlover",license:"ISC",publishConfig:{access:"public"},devDependencies:{"@qlover/logger":"workspace:*",ignore:"^7.0.3",lodash:"^4.17.21",ora:"^8.1.1"},dependencies:{"@qlover/scripts-context":"workspace:*",commander:"^13.1.0",inquirer:"^12.3.2"}};function Kl(){let e=new zl;return e.version(Et.version,"-v, --version","Show version").description(Et.description).option("-d, --dry-run","Do not touch or write anything, but show the commands").option("-V, --verbose","Show more information").option("--config","Copy config files (default: true)",!0).option("--no-config","Do not copy config files"),e.parse(),e.opts()}async function kn(e=process.cwd()){let{dryRun:t,verbose:r,...u}=Kl(),i=Mn(e,"./templates"),n=Mn(e,"./configs");Ln(i)||(console.error("Template is empty!"),process.exit(1)),Ln(n)||(console.error("Configs is empty!"),process.exit(1)),await new Be({dryRun:t,verbose:r,options:{...u,templateRootPath:i,configsRootPath:n}}).generate()}import{fileURLToPath as Xl}from"url";import{dirname as Jl}from"path";var Zl=Xl(import.meta.url),Ql=Jl(Zl);kn(Ql).catch(e=>{console.error(e),process.exit(1)});
@@ -0,0 +1,274 @@
1
+ import { BootstrapTest } from '@__mocks__/BootstrapTest';
2
+ import { TestApp } from '@__mocks__/components';
3
+ import * as chatI18nKeys from '@config/Identifier/components/component.chatMessage';
4
+ import { ChatMessageRole, ChatMessageStore } from '@qlover/corekit-bridge';
5
+ import { render, screen } from '@testing-library/react';
6
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { logger } from '@/core/globals';
8
+ import { ChatMessageBridge } from '@/uikit/components/chatMessage/ChatMessageBridge';
9
+ import { ChatRoot } from '@/uikit/components/chatMessage/ChatRoot';
10
+ import type { ChatMessageI18nInterface } from '@config/i18n/chatMessageI18n';
11
+ import type { MessageGetwayInterface } from '@qlover/corekit-bridge';
12
+
13
+ // Mock i18n values matching the actual i18n keys
14
+ const mockI18n: ChatMessageI18nInterface = {
15
+ send: chatI18nKeys.COMPONENT_CHAT_SEND,
16
+ stop: chatI18nKeys.COMPONENT_CHAT_STOP,
17
+ loading: chatI18nKeys.COMPONENT_CHAT_LOADING,
18
+ inputPlaceholder: chatI18nKeys.COMPONENT_CHAT_INPUT_PLACEHOLDER,
19
+ empty: chatI18nKeys.COMPONENT_CHAT_EMPTY,
20
+ start: chatI18nKeys.COMPONENT_CHAT_START,
21
+ retry: chatI18nKeys.COMPONENT_CHAT_RETRY,
22
+ duration: chatI18nKeys.COMPONENT_CHAT_DURATION
23
+ };
24
+
25
+ // Simple mock gateway for testing
26
+ class MockMessageGateway implements MessageGetwayInterface {
27
+ sendMessage = vi.fn();
28
+ }
29
+
30
+ describe('ChatRoot Component', () => {
31
+ let messagesStore: ChatMessageStore<string>;
32
+ let mockGateway: MockMessageGateway;
33
+ let bridge: ChatMessageBridge<string>;
34
+
35
+ beforeAll(async () => {
36
+ await BootstrapTest.main({
37
+ root: globalThis,
38
+ bootHref: 'http://localhost:3000/en/test'
39
+ });
40
+ });
41
+
42
+ beforeEach(() => {
43
+ // Create fresh instances for each test
44
+ messagesStore = new ChatMessageStore<string>();
45
+ mockGateway = new MockMessageGateway();
46
+ bridge = new ChatMessageBridge<string>(messagesStore, {
47
+ gateway: mockGateway,
48
+ logger: logger,
49
+ senderName: 'TestSender',
50
+ gatewayOptions: {}
51
+ });
52
+
53
+ // Reset mocks
54
+ vi.clearAllMocks();
55
+ });
56
+
57
+ describe('Component Rendering', () => {
58
+ it('should render ChatRoot with all required components', () => {
59
+ render(
60
+ <TestApp>
61
+ <ChatRoot bridge={bridge} tt={mockI18n} />
62
+ </TestApp>
63
+ );
64
+
65
+ // Check main container
66
+ const chatRoot = screen.getByTestId('ChatRoot');
67
+ expect(chatRoot).toBeDefined();
68
+
69
+ // Check MessagesList component
70
+ const messagesList = screen.getByTestId('MessagesList');
71
+ expect(messagesList).toBeDefined();
72
+
73
+ // Check FocusBar component
74
+ const focusBar = screen.getByTestId('FocusBar');
75
+ expect(focusBar).toBeDefined();
76
+ });
77
+
78
+ it('should display empty state initially', () => {
79
+ render(
80
+ <TestApp>
81
+ <ChatRoot bridge={bridge} tt={mockI18n} />
82
+ </TestApp>
83
+ );
84
+
85
+ // Empty state text should be visible
86
+ // Note: The actual text will be the i18n key, not the translated text in tests
87
+ const emptyText = screen.getByText(/component_chat:empty/i);
88
+ expect(emptyText).toBeDefined();
89
+ });
90
+
91
+ it('should render textarea for input', () => {
92
+ render(
93
+ <TestApp>
94
+ <ChatRoot bridge={bridge} tt={mockI18n} />
95
+ </TestApp>
96
+ );
97
+
98
+ const textarea = screen.getByRole('textbox');
99
+ expect(textarea).toBeDefined();
100
+ });
101
+
102
+ it('should render send button', () => {
103
+ render(
104
+ <TestApp>
105
+ <ChatRoot bridge={bridge} tt={mockI18n} />
106
+ </TestApp>
107
+ );
108
+
109
+ const sendButton = screen.getByTestId('FocusBar-Button-Send');
110
+ expect(sendButton).toBeDefined();
111
+ });
112
+ });
113
+
114
+ describe('Props Integration', () => {
115
+ it('should accept and use bridge prop', () => {
116
+ render(
117
+ <TestApp>
118
+ <ChatRoot bridge={bridge} tt={mockI18n} />
119
+ </TestApp>
120
+ );
121
+
122
+ // Verify bridge is connected by checking store state
123
+ const state = messagesStore.state;
124
+ expect(state).toBeDefined();
125
+ expect(state.messages).toEqual([]);
126
+ expect(state.draftMessages).toEqual([]);
127
+ });
128
+
129
+ it('should accept different bridge instances', () => {
130
+ const newStore = new ChatMessageStore<string>();
131
+ const newBridge = new ChatMessageBridge<string>(newStore, {
132
+ gateway: mockGateway,
133
+ logger: logger,
134
+ senderName: 'NewTestSender',
135
+ gatewayOptions: {}
136
+ });
137
+
138
+ render(
139
+ <TestApp>
140
+ <ChatRoot bridge={newBridge} tt={mockI18n} />
141
+ </TestApp>
142
+ );
143
+
144
+ const chatRoot = screen.getByTestId('ChatRoot');
145
+ expect(chatRoot).toBeDefined();
146
+ });
147
+ });
148
+
149
+ describe('Bridge Integration', () => {
150
+ it('should connect to message store through bridge', () => {
151
+ render(
152
+ <TestApp>
153
+ <ChatRoot bridge={bridge} tt={mockI18n} />
154
+ </TestApp>
155
+ );
156
+
157
+ // Bridge should have access to store
158
+ const messageStore = bridge.getMessageStore();
159
+ expect(messageStore).toBe(messagesStore);
160
+ });
161
+
162
+ it('should update UI when content is changed via bridge', () => {
163
+ render(
164
+ <TestApp>
165
+ <ChatRoot bridge={bridge} tt={mockI18n} />
166
+ </TestApp>
167
+ );
168
+
169
+ // Initially, send button should be disabled (no content)
170
+ const sendButton = screen.getByTestId('FocusBar-Button-Send');
171
+ expect(sendButton).toHaveProperty('disabled', true);
172
+
173
+ // Change content via bridge
174
+ bridge.onChangeContent('Test message');
175
+
176
+ // Now send button should be enabled (has content)
177
+ // Note: This requires the component to re-render based on store updates
178
+ const state = messagesStore.state;
179
+ expect(state.draftMessages.length).toBeGreaterThan(0);
180
+ expect(state.draftMessages[0]?.content).toBe('Test message');
181
+ });
182
+ });
183
+
184
+ describe('Store State Management', () => {
185
+ it('should reflect message store state', () => {
186
+ render(
187
+ <TestApp>
188
+ <ChatRoot bridge={bridge} tt={mockI18n} />
189
+ </TestApp>
190
+ );
191
+
192
+ // Initial state should be empty
193
+ const initialState = messagesStore.state;
194
+ expect(initialState.messages).toEqual([]);
195
+ expect(initialState.draftMessages).toEqual([]);
196
+ // These properties might be undefined initially, which is fine
197
+ expect(initialState.disabledSend).toBeFalsy();
198
+ expect(initialState.streaming).toBeFalsy();
199
+ });
200
+
201
+ it('should update when messages are added to store', () => {
202
+ render(
203
+ <TestApp>
204
+ <ChatRoot bridge={bridge} tt={mockI18n} />
205
+ </TestApp>
206
+ );
207
+
208
+ // Add a message to the store
209
+ messagesStore.addMessage({
210
+ id: 'test-1',
211
+ role: ChatMessageRole.USER,
212
+ content: 'Test message',
213
+ loading: false,
214
+ startTime: Date.now()
215
+ });
216
+
217
+ // Verify message is in store
218
+ const state = messagesStore.state;
219
+ expect(state.messages.length).toBe(1);
220
+ expect(state.messages[0]?.content).toBe('Test message');
221
+ });
222
+ });
223
+
224
+ describe('Button States', () => {
225
+ it('should have disabled send button when no content', () => {
226
+ render(
227
+ <TestApp>
228
+ <ChatRoot bridge={bridge} tt={mockI18n} />
229
+ </TestApp>
230
+ );
231
+
232
+ const sendButton = screen.getByTestId('FocusBar-Button-Send');
233
+ expect(sendButton).toHaveProperty('disabled', true);
234
+ });
235
+ });
236
+
237
+ describe('Component Structure', () => {
238
+ it('should have correct CSS classes for layout', () => {
239
+ render(
240
+ <TestApp>
241
+ <ChatRoot bridge={bridge} tt={mockI18n} />
242
+ </TestApp>
243
+ );
244
+
245
+ const chatRoot = screen.getByTestId('ChatRoot');
246
+ expect(chatRoot.className).toContain('flex');
247
+ expect(chatRoot.className).toContain('flex-col');
248
+ });
249
+
250
+ it('should render MessagesList with correct data-testid', () => {
251
+ render(
252
+ <TestApp>
253
+ <ChatRoot bridge={bridge} tt={mockI18n} />
254
+ </TestApp>
255
+ );
256
+
257
+ const messagesList = screen.getByTestId('MessagesList');
258
+ expect(messagesList).toBeDefined();
259
+ expect(messagesList.className).toContain('overflow-y-auto');
260
+ });
261
+
262
+ it('should render FocusBar with correct data-testid', () => {
263
+ render(
264
+ <TestApp>
265
+ <ChatRoot bridge={bridge} tt={mockI18n} />
266
+ </TestApp>
267
+ );
268
+
269
+ const focusBar = screen.getByTestId('FocusBar');
270
+ expect(focusBar).toBeDefined();
271
+ expect(focusBar.className).toContain('border-t');
272
+ });
273
+ });
274
+ });
@@ -6,6 +6,7 @@ import type { ExecutorPageBridgeInterface } from '@/base/port/ExecutorPageBridge
6
6
  import type { JSONStoragePageBridgeInterface } from '@/base/port/JSONStoragePageBridgeInterface';
7
7
  import type { RequestPageBridgeInterface } from '@/base/port/RequestPageBridgeInterface';
8
8
  import type { I18nService } from '@/base/services/I18nService';
9
+ import type { MessageService } from '@/base/services/MessageService';
9
10
  import type { ProcesserExecutor } from '@/base/services/ProcesserExecutor';
10
11
  import type { RouteService } from '@/base/services/RouteService';
11
12
  import type { UserService } from '@/base/services/UserService';
@@ -32,6 +33,7 @@ export const IOCIdentifier = Object.freeze({
32
33
  ProcesserExecutorInterface: 'ProcesserExecutorInterface',
33
34
  RouteServiceInterface: 'RouteServiceInterface',
34
35
  UserServiceInterface: 'UserServiceInterface',
36
+ MessageServiceInterface: 'MessageServiceInterface',
35
37
  I18nKeyErrorPlugin: 'I18nKeyErrorPlugin',
36
38
  FeApiCommonPlugin: 'FeApiCommonPlugin',
37
39
  ApiMockPlugin: 'ApiMockPlugin',
@@ -74,6 +76,7 @@ export interface IOCIdentifierMap {
74
76
  [IOCIdentifier.ProcesserExecutorInterface]: ProcesserExecutor;
75
77
  [IOCIdentifier.RouteServiceInterface]: RouteService;
76
78
  [IOCIdentifier.UserServiceInterface]: UserService;
79
+ [IOCIdentifier.MessageServiceInterface]: MessageService;
77
80
  [IOCIdentifier.I18nKeyErrorPlugin]: I18nKeyErrorPlugin;
78
81
  [IOCIdentifier.FeApiCommonPlugin]: CorekitBridge.RequestCommonPlugin;
79
82
  [IOCIdentifier.ApiMockPlugin]: CorekitBridge.ApiMockPlugin;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @description Chat send button text
3
+ * @localZh 发送
4
+ * @localEn Send
5
+ */
6
+ export const COMPONENT_CHAT_SEND = 'component_chat:send';
7
+
8
+ /**
9
+ * @description Chat stop button text
10
+ * @localZh 停止
11
+ * @localEn Stop
12
+ */
13
+ export const COMPONENT_CHAT_STOP = 'component_chat:stop';
14
+
15
+ /**
16
+ * @description Chat loading text
17
+ * @localZh 加载中...
18
+ * @localEn Loading...
19
+ */
20
+ export const COMPONENT_CHAT_LOADING = 'component_chat:loading';
21
+
22
+ /**
23
+ * @description Chat input placeholder
24
+ * @localZh 输入消息... (Ctrl+Enter 发送)
25
+ * @localEn Type a message... (Ctrl+Enter to send)
26
+ */
27
+ export const COMPONENT_CHAT_INPUT_PLACEHOLDER =
28
+ 'component_chat:input_placeholder';
29
+
30
+ /**
31
+ * @description Chat empty messages
32
+ * @localZh 暂无对话
33
+ * @localEn No messages yet
34
+ */
35
+ export const COMPONENT_CHAT_EMPTY = 'component_chat:empty';
36
+
37
+ /**
38
+ * @description Chat start conversation
39
+ * @localZh 开始对话
40
+ * @localEn Start a conversation
41
+ */
42
+ export const COMPONENT_CHAT_START = 'component_chat:start';
43
+
44
+ /**
45
+ * @description Chat retry button
46
+ * @localZh 重试
47
+ * @localEn Retry
48
+ */
49
+ export const COMPONENT_CHAT_RETRY = 'component_chat:retry';
50
+
51
+ /**
52
+ * @description Chat duration label
53
+ * @localZh 耗时
54
+ * @localEn Duration
55
+ */
56
+ export const COMPONENT_CHAT_DURATION = 'component_chat:duration';
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @description Message gateway test title
3
+ * @localZh 消息网关测试
4
+ * @localEn Message Gateway Test
5
+ */
6
+ export const COMPONENT_MESSAGE_BASE_LIST_TITLE =
7
+ 'component_message_base_list:title';
8
+
9
+ /**
10
+ * @description Message gateway test description
11
+ * @localZh 发送消息并等待网关响应
12
+ * @localEn Send messages and wait for gateway response
13
+ */
14
+ export const COMPONENT_MESSAGE_BASE_LIST_DESCRIPTION =
15
+ 'component_message_base_list:description';
16
+
17
+ /**
18
+ * @description No messages yet
19
+ * @localZh 暂无消息
20
+ * @localEn No messages yet
21
+ */
22
+ export const COMPONENT_MESSAGE_BASE_LIST_NO_MESSAGES =
23
+ 'component_message_base_list:no_messages';
24
+
25
+ /**
26
+ * @description Send your first message to get started
27
+ * @localZh 发送第一条消息开始使用
28
+ * @localEn Send your first message to get started
29
+ */
30
+ export const COMPONENT_MESSAGE_BASE_LIST_GET_STARTED =
31
+ 'component_message_base_list:get_started';
32
+
33
+ /**
34
+ * @description User label
35
+ * @localZh 你
36
+ * @localEn You
37
+ */
38
+ export const COMPONENT_MESSAGE_BASE_LIST_USER =
39
+ 'component_message_base_list:user';
40
+
41
+ /**
42
+ * @description Gateway label
43
+ * @localZh 网关
44
+ * @localEn Gateway
45
+ */
46
+ export const COMPONENT_MESSAGE_BASE_LIST_GATEWAY =
47
+ 'component_message_base_list:gateway';
48
+
49
+ /**
50
+ * @description Processing status
51
+ * @localZh 处理中...
52
+ * @localEn Processing...
53
+ */
54
+ export const COMPONENT_MESSAGE_BASE_LIST_PROCESSING =
55
+ 'component_message_base_list:processing';
56
+
57
+ /**
58
+ * @description Gateway failed label
59
+ * @localZh 网关(失败)
60
+ * @localEn Gateway (Failed)
61
+ */
62
+ export const COMPONENT_MESSAGE_BASE_LIST_GATEWAY_FAILED =
63
+ 'component_message_base_list:gateway_failed';
64
+
65
+ /**
66
+ * @description Failed to send message
67
+ * @localZh 发送消息失败
68
+ * @localEn Failed to send message
69
+ */
70
+ export const COMPONENT_MESSAGE_BASE_LIST_SEND_FAILED =
71
+ 'component_message_base_list:send_failed';
72
+
73
+ /**
74
+ * @description Gateway response label
75
+ * @localZh 网关响应
76
+ * @localEn Gateway Response
77
+ */
78
+ export const COMPONENT_MESSAGE_BASE_LIST_GATEWAY_RESPONSE =
79
+ 'component_message_base_list:gateway_response';
80
+
81
+ /**
82
+ * @description Input placeholder
83
+ * @localZh 在此输入您的消息...
84
+ * @localEn Type your message here...
85
+ */
86
+ export const COMPONENT_MESSAGE_BASE_LIST_INPUT_PLACEHOLDER =
87
+ 'component_message_base_list:input_placeholder';
88
+
89
+ /**
90
+ * @description Send button
91
+ * @localZh 发送
92
+ * @localEn Send
93
+ */
94
+ export const COMPONENT_MESSAGE_BASE_LIST_SEND_BUTTON =
95
+ 'component_message_base_list:send_button';
96
+
97
+ /**
98
+ * @description Error handling tip
99
+ * @localZh 提示:尝试发送 "error" 或 "Failed" 来测试错误处理
100
+ * @localEn Tip: Try sending "error" or "Failed" to test error handling
101
+ */
102
+ export const COMPONENT_MESSAGE_BASE_LIST_ERROR_TIP =
103
+ 'component_message_base_list:error_tip';
@@ -4,5 +4,6 @@ export * from './page.home';
4
4
  export * from './page.identifiter';
5
5
  export * from './page.jsonStorage';
6
6
  export * from './page.login';
7
+ export * from './page.message';
7
8
  export * from './page.register';
8
9
  export * from './page.request';
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @description Message page title
3
+ * @localZh 消息测试
4
+ * @localEn Message Test
5
+ */
6
+ export const PAGE_MESSAGE_TITLE = 'page_message:title';
7
+
8
+ /**
9
+ * @description Message page description
10
+ * @localZh 消息网关测试页面
11
+ * @localEn Message gateway test page
12
+ */
13
+ export const PAGE_MESSAGE_DESCRIPTION = 'page_message:description';
14
+
15
+ /**
16
+ * @description Message page keywords
17
+ * @localZh 消息, 网关, 测试
18
+ * @localEn Message, Gateway, Test
19
+ */
20
+ export const PAGE_MESSAGE_KEYWORDS = 'page_message:keywords';
@@ -5,6 +5,7 @@ import { homeI18n } from './i18n/homeI18n';
5
5
  import { identifiter18n } from './i18n/identifiter18n';
6
6
  import { jsonStorage18n } from './i18n/jsonStorage18n';
7
7
  import { login18n } from './i18n/login18n';
8
+ import { messageI18n } from './i18n/messageI18n';
8
9
  import { notFound18n, serverError18n } from './i18n/notFoundI18n';
9
10
  import { register18n } from './i18n/register18n';
10
11
  import { request18n } from './i18n/request18n';
@@ -88,6 +89,17 @@ export const baseRoutes: RouteConfigValue[] = [
88
89
  i18nInterface: identifiter18n
89
90
  }
90
91
  },
92
+ {
93
+ path: 'message',
94
+ element: 'base/MessagePage',
95
+ meta: {
96
+ title: identifier.PAGE_MESSAGE_TITLE,
97
+ description: identifier.PAGE_MESSAGE_DESCRIPTION,
98
+ icon: 'message',
99
+ localNamespace: 'common',
100
+ i18nInterface: messageI18n
101
+ }
102
+ },
91
103
  {
92
104
  path: '*',
93
105
  element: 'NoRouteFound',
@@ -249,6 +261,17 @@ export const baseNoLocaleRoutes: RouteConfigValue[] = [
249
261
  localNamespace: 'common',
250
262
  i18nInterface: identifiter18n
251
263
  }
264
+ },
265
+ {
266
+ path: 'message',
267
+ element: 'base/MessagePage',
268
+ meta: {
269
+ title: identifier.PAGE_MESSAGE_TITLE,
270
+ description: identifier.PAGE_MESSAGE_DESCRIPTION,
271
+ icon: 'message',
272
+ localNamespace: 'common',
273
+ i18nInterface: messageI18n
274
+ }
252
275
  }
253
276
  ]
254
277
  },