Repository: maioria/chatgpt-talkieai
Branch: main
Commit: 1702a894bb50
Files: 413
Total size: 1.3 MB
Directory structure:
gitextract_o3btxkwl/
├── .gitignore
├── LICENSE
├── README.md
├── talkieai-server/
│ ├── app/
│ │ ├── __init__.py
│ │ ├── ai/
│ │ │ ├── __init__.py
│ │ │ ├── impl/
│ │ │ │ ├── __init__.py
│ │ │ │ ├── chat_gpt_ai.py
│ │ │ │ └── zhipu_ai.py
│ │ │ ├── interfaces.py
│ │ │ └── models.py
│ │ ├── api/
│ │ │ ├── __init__.py
│ │ │ ├── account_routes.py
│ │ │ ├── message_routes.py
│ │ │ ├── session_routes.py
│ │ │ ├── sys_routes.py
│ │ │ └── topics_route.py
│ │ ├── config.py
│ │ ├── core/
│ │ │ ├── __init__.py
│ │ │ ├── auth.py
│ │ │ ├── azure_voice.py
│ │ │ ├── db_cache.py
│ │ │ ├── exceptions.py
│ │ │ ├── language.py
│ │ │ ├── logging.py
│ │ │ └── utils.py
│ │ ├── db/
│ │ │ ├── __init__.py
│ │ │ ├── account_entities.py
│ │ │ ├── chat_entities.py
│ │ │ ├── sys_entities.py
│ │ │ └── topic_entities.py
│ │ ├── main.py
│ │ ├── models/
│ │ │ ├── __init__.py
│ │ │ ├── account_models.py
│ │ │ ├── chat_models.py
│ │ │ ├── response.py
│ │ │ ├── sys_models.py
│ │ │ └── topic_models.py
│ │ └── services/
│ │ ├── __init__.py
│ │ ├── account_service.py
│ │ ├── chat_service.py
│ │ ├── sys_service.py
│ │ └── topic_service.py
│ ├── data/
│ │ ├── azure.json
│ │ ├── azure_style_label.json
│ │ ├── default_topic_data.json
│ │ ├── language_demo_map.json
│ │ └── sys_language.json
│ ├── requirements.txt
│ └── start.sh
└── talkieai-uniapp/
├── index.html
├── package.json
├── src/
│ ├── App.vue
│ ├── api/
│ │ ├── account.ts
│ │ ├── chat.ts
│ │ ├── sys.ts
│ │ └── topic.ts
│ ├── axios/
│ │ ├── api.ts
│ │ ├── axiosServer.ts
│ │ └── axiosService.ts
│ ├── components/
│ │ ├── AudioPlayer.vue
│ │ ├── Checkbox.vue
│ │ ├── Collect.vue
│ │ ├── CommonHeader.vue
│ │ ├── FunctionalText.vue
│ │ ├── GithubLink.vue
│ │ ├── Loading.vue
│ │ ├── LoadingRound.vue
│ │ ├── Rare2.vue
│ │ ├── Rate.vue
│ │ ├── Speech.vue
│ │ ├── WordAnalysisPopup.vue
│ │ ├── audioPlayerExecuter.ts
│ │ └── speechExecuter.ts
│ ├── config/
│ │ └── env.ts
│ ├── env.d.ts
│ ├── global/
│ │ └── globalCount.hooks.ts
│ ├── less/
│ │ └── global.less
│ ├── main.ts
│ ├── manifest.json
│ ├── models/
│ │ ├── chat.ts
│ │ ├── models.ts
│ │ └── sys.ts
│ ├── pages/
│ │ ├── chat/
│ │ │ ├── components/
│ │ │ │ ├── CommonAudioPlayer.vue
│ │ │ │ ├── MessageContent.vue
│ │ │ │ ├── MessageGrammar.vue
│ │ │ │ ├── MessageGrammarPopup.vue
│ │ │ │ ├── MessagePronunciation.vue
│ │ │ │ ├── MessageSpeech.vue
│ │ │ │ ├── PhonemeBox.vue
│ │ │ │ ├── Prompt.vue
│ │ │ │ ├── PromptPopup.vue
│ │ │ │ ├── TextPronunciation.vue
│ │ │ │ ├── TranslationPopup.vue
│ │ │ │ └── WordDetail.vue
│ │ │ ├── index.vue
│ │ │ └── settings.vue
│ │ ├── contact/
│ │ │ ├── index.vue
│ │ │ └── less/
│ │ │ └── index.less
│ │ ├── feedback/
│ │ │ ├── index.vue
│ │ │ └── less/
│ │ │ └── index.less
│ │ ├── index/
│ │ │ ├── components/
│ │ │ │ └── Topics.vue
│ │ │ ├── index.vue
│ │ │ └── switchRole.vue
│ │ ├── login/
│ │ │ ├── index.vue
│ │ │ └── service.ts
│ │ ├── my/
│ │ │ ├── index.vue
│ │ │ ├── learnLanguage.vue
│ │ │ └── less/
│ │ │ └── index.less
│ │ ├── practice/
│ │ │ ├── components/
│ │ │ │ ├── Single.vue
│ │ │ │ └── Statement.vue
│ │ │ └── index.vue
│ │ └── topic/
│ │ ├── completion.vue
│ │ ├── history.vue
│ │ ├── index.vue
│ │ ├── phrase.vue
│ │ └── topicCreate.vue
│ ├── pages.json
│ ├── shime-uni.d.ts
│ ├── uni.scss
│ ├── uni_modules/
│ │ ├── uni-badge/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-badge/
│ │ │ │ └── uni-badge.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-calendar/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-calendar/
│ │ │ │ ├── calendar.js
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ ├── uni-calendar-item.vue
│ │ │ │ ├── uni-calendar.vue
│ │ │ │ └── util.js
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-card/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-card/
│ │ │ │ └── uni-card.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-collapse/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-collapse/
│ │ │ │ │ └── uni-collapse.vue
│ │ │ │ └── uni-collapse-item/
│ │ │ │ └── uni-collapse-item.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-combox/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-combox/
│ │ │ │ └── uni-combox.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-countdown/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-countdown/
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ └── uni-countdown.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-data-checkbox/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-data-checkbox/
│ │ │ │ └── uni-data-checkbox.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-data-picker/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-data-picker/
│ │ │ │ │ ├── keypress.js
│ │ │ │ │ └── uni-data-picker.vue
│ │ │ │ └── uni-data-pickerview/
│ │ │ │ ├── uni-data-picker.js
│ │ │ │ └── uni-data-pickerview.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-data-select/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-data-select/
│ │ │ │ └── uni-data-select.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-dateformat/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-dateformat/
│ │ │ │ ├── date-format.js
│ │ │ │ └── uni-dateformat.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-datetime-picker/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-datetime-picker/
│ │ │ │ ├── calendar-item.vue
│ │ │ │ ├── calendar.vue
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ ├── time-picker.vue
│ │ │ │ ├── uni-datetime-picker.vue
│ │ │ │ └── util.js
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-drawer/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-drawer/
│ │ │ │ ├── keypress.js
│ │ │ │ └── uni-drawer.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-easyinput/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-easyinput/
│ │ │ │ ├── common.js
│ │ │ │ └── uni-easyinput.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-fab/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-fab/
│ │ │ │ └── uni-fab.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-fav/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-fav/
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ └── uni-fav.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-file-picker/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-file-picker/
│ │ │ │ ├── choose-and-upload-file.js
│ │ │ │ ├── uni-file-picker.vue
│ │ │ │ ├── upload-file.vue
│ │ │ │ ├── upload-image.vue
│ │ │ │ └── utils.js
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-forms/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-forms/
│ │ │ │ │ ├── uni-forms.vue
│ │ │ │ │ ├── utils.js
│ │ │ │ │ └── validate.js
│ │ │ │ └── uni-forms-item/
│ │ │ │ └── uni-forms-item.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-goods-nav/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-goods-nav/
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ └── uni-goods-nav.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-grid/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-grid/
│ │ │ │ │ └── uni-grid.vue
│ │ │ │ └── uni-grid-item/
│ │ │ │ └── uni-grid-item.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-group/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-group/
│ │ │ │ └── uni-group.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-icons/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-icons/
│ │ │ │ ├── icons.js
│ │ │ │ ├── uni-icons.vue
│ │ │ │ └── uniicons.css
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-indexed-list/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-indexed-list/
│ │ │ │ ├── uni-indexed-list-item.vue
│ │ │ │ └── uni-indexed-list.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-link/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-link/
│ │ │ │ └── uni-link.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-list/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-list/
│ │ │ │ │ ├── uni-list.vue
│ │ │ │ │ ├── uni-refresh.vue
│ │ │ │ │ └── uni-refresh.wxs
│ │ │ │ ├── uni-list-ad/
│ │ │ │ │ └── uni-list-ad.vue
│ │ │ │ ├── uni-list-chat/
│ │ │ │ │ ├── uni-list-chat.scss
│ │ │ │ │ └── uni-list-chat.vue
│ │ │ │ └── uni-list-item/
│ │ │ │ └── uni-list-item.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-load-more/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-load-more/
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ └── uni-load-more.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-nav-bar/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-nav-bar/
│ │ │ │ ├── uni-nav-bar.vue
│ │ │ │ └── uni-status-bar.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-notice-bar/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-notice-bar/
│ │ │ │ └── uni-notice-bar.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-number-box/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-number-box/
│ │ │ │ └── uni-number-box.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-pagination/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-pagination/
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── es.json
│ │ │ │ │ ├── fr.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ └── uni-pagination.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-popup/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-popup/
│ │ │ │ │ ├── i18n/
│ │ │ │ │ │ ├── en.json
│ │ │ │ │ │ ├── index.js
│ │ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ │ └── zh-Hant.json
│ │ │ │ │ ├── keypress.js
│ │ │ │ │ ├── popup.js
│ │ │ │ │ └── uni-popup.vue
│ │ │ │ ├── uni-popup-dialog/
│ │ │ │ │ ├── keypress.js
│ │ │ │ │ └── uni-popup-dialog.vue
│ │ │ │ ├── uni-popup-message/
│ │ │ │ │ └── uni-popup-message.vue
│ │ │ │ └── uni-popup-share/
│ │ │ │ └── uni-popup-share.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-rate/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-rate/
│ │ │ │ └── uni-rate.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-row/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-col/
│ │ │ │ │ └── uni-col.vue
│ │ │ │ └── uni-row/
│ │ │ │ └── uni-row.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-scss/
│ │ │ ├── changelog.md
│ │ │ ├── index.scss
│ │ │ ├── package.json
│ │ │ ├── readme.md
│ │ │ ├── styles/
│ │ │ │ ├── index.scss
│ │ │ │ ├── setting/
│ │ │ │ │ ├── _border.scss
│ │ │ │ │ ├── _color.scss
│ │ │ │ │ ├── _radius.scss
│ │ │ │ │ ├── _space.scss
│ │ │ │ │ ├── _styles.scss
│ │ │ │ │ ├── _text.scss
│ │ │ │ │ └── _variables.scss
│ │ │ │ └── tools/
│ │ │ │ └── functions.scss
│ │ │ ├── theme.scss
│ │ │ └── variables.scss
│ │ ├── uni-search-bar/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-search-bar/
│ │ │ │ ├── i18n/
│ │ │ │ │ ├── en.json
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── zh-Hans.json
│ │ │ │ │ └── zh-Hant.json
│ │ │ │ └── uni-search-bar.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-section/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-section/
│ │ │ │ └── uni-section.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-segmented-control/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-segmented-control/
│ │ │ │ └── uni-segmented-control.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-steps/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-steps/
│ │ │ │ └── uni-steps.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-swipe-action/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-swipe-action/
│ │ │ │ │ └── uni-swipe-action.vue
│ │ │ │ └── uni-swipe-action-item/
│ │ │ │ ├── bindingx.js
│ │ │ │ ├── isPC.js
│ │ │ │ ├── mpalipay.js
│ │ │ │ ├── mpother.js
│ │ │ │ ├── mpwxs.js
│ │ │ │ ├── render.js
│ │ │ │ ├── uni-swipe-action-item.vue
│ │ │ │ └── wx.wxs
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-swiper-dot/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-swiper-dot/
│ │ │ │ └── uni-swiper-dot.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-table/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ ├── uni-table/
│ │ │ │ │ └── uni-table.vue
│ │ │ │ ├── uni-tbody/
│ │ │ │ │ └── uni-tbody.vue
│ │ │ │ ├── uni-td/
│ │ │ │ │ └── uni-td.vue
│ │ │ │ ├── uni-th/
│ │ │ │ │ ├── filter-dropdown.vue
│ │ │ │ │ └── uni-th.vue
│ │ │ │ ├── uni-thead/
│ │ │ │ │ └── uni-thead.vue
│ │ │ │ └── uni-tr/
│ │ │ │ ├── table-checkbox.vue
│ │ │ │ └── uni-tr.vue
│ │ │ ├── i18n/
│ │ │ │ ├── en.json
│ │ │ │ ├── es.json
│ │ │ │ ├── fr.json
│ │ │ │ ├── index.js
│ │ │ │ ├── zh-Hans.json
│ │ │ │ └── zh-Hant.json
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-tag/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-tag/
│ │ │ │ └── uni-tag.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-title/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-title/
│ │ │ │ └── uni-title.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-tooltip/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-tooltip/
│ │ │ │ └── uni-tooltip.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ ├── uni-transition/
│ │ │ ├── changelog.md
│ │ │ ├── components/
│ │ │ │ └── uni-transition/
│ │ │ │ ├── createAnimation.js
│ │ │ │ └── uni-transition.vue
│ │ │ ├── package.json
│ │ │ └── readme.md
│ │ └── uni-ui/
│ │ ├── changelog.md
│ │ ├── components/
│ │ │ └── uni-ui/
│ │ │ └── uni-ui.vue
│ │ ├── package.json
│ │ └── readme.md
│ └── utils/
│ ├── bus.ts
│ └── utils.ts
├── tsconfig.json
└── vite.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/talkieai-server/.env
/talkieai-uniapp/package-lock.json
/talkieai-uniapp/node_modules/
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Copyright (C)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
Copyright (C)
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
.
================================================
FILE: README.md
================================================
# TalkieAI
## 简介
[TalkieAI](https://github.com/maioria/chatgpt-talkieai) 是一个基于AI的外语学习应用,可通过语音进行聊天,语法分析,翻译。
AI可以基于CHAT-GPT, 国内可以配置chat-gpt代理或者使用[智谱开放平台](https://open.bigmodel.cn/)
## 微信小程序

## 后端
- 使用python语言开发,开发使用的版本为3.11,web框架为fastAPI,数据层框架为SQLAlchemy,语音使用azure。
## 前端
- 前端使用uniapp开发,基于vue3,可发布到网页与小程序与APP
## 项目示例图




## 本地启动
```bash
# 数据库,创建一个空的数据库,.env文件配置好数据库后启动服务,服务会自动生成相应的表,并且加载默认数据
# 1.克隆本仓库;
git clone git@github.com:maioria/chatgpt-talkieai.git
cd talkieai-server
# 2.安装依赖;
pip3 install -r requirements.txt
# 3. 启动服务(需要新建.env文件并设置变量,参考.env.default)
nohup uvicorn app.main:app --host 0.0.0.0 --port 8097 &
#前端使用HBuilder直接web或者小程序运行
# 1. 安装依赖(前端只用了俩个依赖fingerprintjs2 与 recorder)
npm install
```
## nginx配置(Web)
```bash
# uniapp可以直接跨域请求服务端地址,也可通过nginx来配置反向代理
server {
listen 80;
listen [::]:80;
server_name {server_name};
rewrite ^(.*) https://$server_name$1 permanent;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {server_name};
root {前端编译完后的路径};
ssl_certificate "{crt}";
ssl_certificate_key "{key}";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
location ^~ /api/ {
proxy_pass http://localhost:8000/api/;
proxy_set_header Host $http_host;
proxy_connect_timeout 15s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
try_files $uri $uri/ /index.html;
}
}
```
## 贡献
如果您有任何建议或意见,欢迎提出 [Issues](https://github.com/maioria/chatgpt-talkieai/issues) 或 [ Pull Request](https://github.com/maioria/chatgpt-talkieai/pulls)。
================================================
FILE: talkieai-server/app/__init__.py
================================================
================================================
FILE: talkieai-server/app/ai/__init__.py
================================================
from app.config import Config
from app.ai.impl.zhipu_ai import ZhipuAIComponent
from app.ai.impl.chat_gpt_ai import ChatGPTAI
if Config.AI_SERVER=='CHAT_GPT':
chat_ai = ChatGPTAI(api_key=Config.CHAT_GPT_KEY, base_url=Config.CHAT_GPT_PROXY, model=Config.CHAT_GPT_MODEL)
elif Config.AI_SERVER=='ZHIPU':
chat_ai = ZhipuAIComponent(api_key=Config.ZHIPU_AI_API_KEY, model=Config.ZHIPU_AI_MODEL)
else:
raise Exception('AI_SERVER配置错误,只能配置为CHAT_GPT或ZHIPU')
================================================
FILE: talkieai-server/app/ai/impl/__init__.py
================================================
================================================
FILE: talkieai-server/app/ai/impl/chat_gpt_ai.py
================================================
from typing import List, Dict
import json
from dataclasses import dataclass
from app.ai.interfaces import *
from app.ai.models import *
from app.core.logging import logging
class ChatGPTAI(ChatAI):
"""本地直接调用openai的接口"""
def __init__(self, api_key: str, base_url: str = None, model: str = None):
from openai import OpenAI
self.api_key = api_key
self.client = OpenAI(api_key=api_key)
self.model = model
if base_url:
self.client.base_url = base_url
def invoke_greet(self, params: GreetParams) -> str:
messages = [
{"role": "system", "content": f"你需要使用标识为 {params.language} 的语言来打个招呼,10字左右."}
]
invoke_dto = MessageInvokeDTO(messages=messages)
return self._original_invoke_chat(invoke_dto)
def topic_invoke_greet(self, params: TopicGreetParams) -> str:
messages = [
{
"role": "system",
"content": f"场景:{params.prompt}. 现在你需要使用标识为 {params.language} 的语言来打个招呼,20字左右.",
}
]
invoke_dto = MessageInvokeDTO(messages=messages)
return self._original_invoke_chat(invoke_dto)
def invoke_message(self, dto: MessageParams) -> AIMessageResult:
"""与AI自由聊天"""
language = dto.language
system_message = (
'The reply must be json, and format of json is {"message":"result of message","message_style":"must be one of the options '
+ f"{json.dumps(dto.styles, ensure_ascii=False)}"
+ '"}, '
+ f"The 'message_style' within the square brackets . "
+ f"I want you to act as an {language} speaking partner and improver, your name is {dto.name}. "
+ f"No matter what language I speak to you, you need to reply me in {language}. "
+ f"I hope you will ask me a question from time to time in your reply "
)
messages = [{"role": "system", "content": system_message}]
for message in dto.messages:
messages.append(message)
resp = self._original_invoke_chat_json(MessageInvokeDTO(messages=messages))
result = AIMessageResult(
message=resp["message"], message_style=resp["message_style"]
)
return result
def topic_invoke_message(self, dto: AITopicMessageParams) -> AITopicMessageResult:
"""与AI自由聊天"""
language = dto.language
system_message = (
f"Topic:{dto.prompt}.Please chat with me in this topic. If this conversation can be concluded or if the user wishes to end it, please return topic_completed=true."
+ 'The reply must be json, and format of json is {"message":"result of message","topic_completed":"Whether this topic has been completd.","message_style":"must be one of the options '
+ f"{json.dumps(dto.styles, ensure_ascii=False)}"
+ '"}, '
+ f"The 'message_style' within the square brackets . "
+ f"I want you to act as an {language} speaking partner and improver, your name is {dto.name}. "
+ f"No matter what language I speak to you, you need to reply me in {language}. "
+ f"I hope you will ask me a question from time to time in your reply "
)
messages = [{"role": "system", "content": system_message}]
for message in dto.messages:
messages.append(message)
resp = self._original_invoke_chat_json(MessageInvokeDTO(messages=messages))
message_style = None
# resp是否有message_style
if "message_style" in resp:
message_style = resp["message_style"]
completed = False
# resp是否有topic_completed
if "topic_completed" in resp:
completed = resp["topic_completed"] == "true"
result = AITopicMessageResult(
message=resp["message"], message_style=message_style, completed=completed
)
return result
def topic_invoke_complete(
self, dto: AITopicCompleteParams
) -> AITopicCompleteResult:
"""场景 结束"""
system_content = "下面是一场对话\n"
for message in dto.messages:
if message.role.lower() == "system":
system_content = system_content + f"AI: {message.content}\n"
elif message.role.lower() == "account":
system_content = system_content + f"用户: {message.content}\n"
system_content = system_content + "下面是用户对话中需要实现的目标\n"
for target in dto.targets:
system_content = system_content + f"{target}\n"
system_content = (
system_content
+ "现在你需要计算出 <用户:> 所说的所有话中使用了多少单词数量(仅需要数字结果,重复单词不需要计算),对应后面的目标实现了多少个(仅需要数字结果),对用户的表达给出评分(满分100分,仅需要数字结果),还要给出300字以内的建议(包含中文讲解与英文示例),返回结果只需要有json格式,使用单词量放在words字段,目标实现数量放在targets字段,评分放在score字段,建议放在suggestion字段,不需要再额外的任何信息,记住,只需要统计<用户:>下的内容\n"
)
json_result = self._original_invoke_chat_json(
MessageInvokeDTO(messages=[{"role": "system", "content": system_content}])
)
# 组装成AITopicCompleteResult返回
return AITopicCompleteResult(
targets=json_result["targets"],
score=json_result["score"],
words=json_result["words"],
suggestion=json_result["suggestion"],
)
def invoke_translate(self, dto: TranslateParams) -> str:
"""翻译"""
system_message = f"下面是段文本:'{dto.content}' 仅输出翻译成 {dto.target_language} 后的内容"
invoke_dto = MessageInvokeDTO(
messages=[{"role": "system", "content": system_message}]
)
resp = self._original_invoke_chat(invoke_dto)
return resp
def invoke_grammar_analysis(
self, params: GrammarAnalysisParams
) -> AIGrammarAnalysisResult:
messages = [
{
"role": "user",
"content": f"检查内容是否存在语法错误(不需要检查符号的使用),如果存在就用中文返回这段内容中的语法错误,再提供一句推荐示例,要求数据格式为json,无任何转义字符,可直接被程序正常序列化,语法是否错误放在属性isCorrect中,错误原因放在errorReason中,修正后的正确示例放在correctContent中,推荐示例放在better中,正确示例与推荐示例的语言要使用{params.language},错误原因使用中文. 提供内容是:{params.content}",
}
]
invoke_dto = MessageInvokeDTO(messages=messages)
result_data = self._original_invoke_chat(invoke_dto)
result_json = json.loads(result_data)
return AIGrammarAnalysisResult(
is_correct=result_json["isCorrect"],
error_reason=result_json["errorReason"],
correct_content=result_json["correctContent"],
better=result_json["better"],
)
def invoke_prompt_sentence(self, params: PromptSentenceParams) -> str:
""" """
logging.info(f"request_params:{params}")
system_content = "下面是一场对话\n"
for message in reversed(params.messages):
if message["role"].lower() == "user":
system_content = system_content + f"用户: {message['content']}\n"
else:
system_content = system_content + f"AI: {message['content']}\n"
system_content = (
system_content
+ "现在你需要做为一个用户来回答下一句话,不可以有提供帮助与提问问题的意思,语言使用"
+ params.language
+ ", 直接输出内容前面不可以加 User:"
)
invoke_dto = MessageInvokeDTO(
messages=[{"role": "user", "content": system_content}]
)
resp = self._original_invoke_chat(invoke_dto)
return resp
def invoke_word_detail(self, params: WordDetailParams) -> AIWordDetailResult:
logging.info(f"request_dto:{params}")
messages = [
{
"role": "user",
"content": f'提供一个单词,只需要简洁快速的用中文返回这个单词的音标与翻译,要求数据格式为json,音标放在属性phonetic中,音标的前后要加上"/",翻译放在translation中, 这个单词是"{params.word}"',
}
]
invoke_dto = MessageInvokeDTO(messages=messages)
result_data = self._original_invoke_chat(invoke_dto)
result_json = json.loads(result_data)
return AIWordDetailResult(
phonetic=result_json["phonetic"], translation=result_json["translation"]
)
def _original_invoke_chat_json(self, dto: MessageInvokeDTO):
logging.info(f"request_dto:{dto}")
resp = self.client.chat.completions.create(
model=self.model,
temperature=dto.temperature,
messages=dto.messages,
max_tokens=dto.max_tokens,
response_format={"type": "json_object"},
)
logging.info(f"response:{resp}")
result = resp.choices[0].message.content
return json.loads(result)
def _original_invoke_chat(self, dto: MessageInvokeDTO):
logging.info(f"dto:{dto}")
resp = self.client.chat.completions.create(
model=self.model,
temperature=dto.temperature,
messages=dto.messages,
max_tokens=dto.max_tokens,
)
logging.info(f"response:{resp}")
result = resp.choices[0].message.content
# 去掉俩边的 “” ''
result = result.strip('"')
result = result.strip("'")
# 去掉json的转义字符
result = result.replace('\\"', '"').replace("\\n", "\n").replace("\\", "")
return result
================================================
FILE: talkieai-server/app/ai/impl/zhipu_ai.py
================================================
from typing import List, Dict
import json
from pydantic import BaseModel
from app.ai.interfaces import *
from app.ai.models import *
from app.core.language import *
from app.core.logging import logging
class ZhipuInvokeDTO(BaseModel):
messages: List[Dict]
model: str
temperature: int = 0.1
class ZhipuAIComponent(ChatAI):
def __init__(self, api_key: str, model: str):
from zhipuai import ZhipuAI
self.client = ZhipuAI(api_key=api_key)
self.model = model
def invoke_greet(self, params: GreetParams) -> str:
messages = [
{"role": "user", "content": f"你需要使用标识为 {params.language} 的语言来打个招呼,10字左右."}
]
invoke_dto = MessageInvokeDTO(messages=messages)
return self._original_invoke_chat(invoke_dto)
def topic_invoke_greet(self, params: TopicGreetParams) -> str:
messages = [
{
"role": "user",
"content": f"场景:{params.prompt}. 现在你需要打个招呼,20字左右.记住语言必须使用使用 {get_language_label_by_value(params.language)},不可以使用其他语言 ",
}
]
invoke_dto = MessageInvokeDTO(messages=messages)
return self._original_invoke_chat(invoke_dto)
def invoke_message(self, dto: MessageParams) -> AIMessageResult:
"""与AI自由聊天"""
language = dto.language
system_message = (
'The reply must be json, and format of json is {"message":"result of message","message_style":"must be one of the options '
+ f"{json.dumps(dto.styles, ensure_ascii=False)}"
+ '"}, '
+ f"The 'message_style' within the square brackets . "
+ f"I want you to act as an {language} speaking partner and improver, your name is {dto.name}. "
+ f"No matter what language I speak to you, you need to reply me in {language}. "
+ f"I hope you will ask me a question from time to time in your reply "
)
messages = [{"role": "system", "content": system_message}]
for message in dto.messages:
messages.append(message)
resp = self._original_invoke_chat(MessageInvokeDTO(messages=messages))
# 检查resp是否是json格式,如果不json格式,就返回错误
try:
resp = json.loads(resp)
result = AIMessageResult(
message=resp["message"], message_style=resp["message_style"]
)
except Exception as e:
logging.warn(f"resp不是json格式:{resp},request_params:{system_message}")
result = AIMessageResult(message=resp, message_style=None)
return result
def topic_invoke_message(self, dto: AITopicMessageParams) -> AITopicMessageResult:
"""与AI自由聊天"""
language = dto.language
system_message = (
f"Topic:{dto.prompt}.Please chat with me in this topic. If this conversation can be concluded or if the user wishes to end it, please return topic_completed=true."
+ 'The reply must be json, and format of json is {"message":"result of message","topic_completed":"Whether this topic has been completd.","message_style":"must be one of the options '
+ f"{json.dumps(dto.styles, ensure_ascii=False)}"
+ '"}, '
+ f"The 'message_style' within the square brackets . "
+ f"I want you to act as an {language} speaking partner and improver, your name is {dto.name}. "
+ f"No matter what language I speak to you, you need to reply me in {language}. "
+ f"I hope you will ask me a question from time to time in your reply "
)
messages = [{"role": "system", "content": system_message}]
for message in dto.messages:
messages.append(message)
resp = self._original_invoke_chat(MessageInvokeDTO(messages=messages))
# 检查resp是否是json格式,如果不json格式,就返回错误
try:
resp = json.loads(resp)
message_style = None
# resp是否有message_style
if "message_style" in resp:
message_style = resp["message_style"]
completed = False
# resp是否有topic_completed
if "topic_completed" in resp:
completed = resp["topic_completed"] == "true"
result = AITopicMessageResult(
message=resp["message"],
message_style=message_style,
completed=completed,
)
except Exception as e:
logging.warn(f"resp不是json格式:{resp},request_params:{system_message}")
result = AITopicMessageResult(
message=resp, completed=False, message_style=None
)
return result
def topic_invoke_complete(
self, dto: AITopicCompleteParams
) -> AITopicCompleteResult:
"""场景 结束"""
system_content = "下面是一场对话\n"
for message in dto.messages:
if message.role.lower() == "system":
system_content = system_content + f"AI: {message.content}\n"
elif message.role.lower() == "account":
system_content = system_content + f"用户: {message.content}\n"
system_content = system_content + "下面是用户对话中需要实现的目标\n"
for target in dto.targets:
system_content = system_content + f"{target}\n"
system_content = (
system_content
+ "现在你需要计算出 <用户:> 所说的所有话中使用了多少单词数量(仅需要数字结果,重复单词不需要计算),对应后面的目标实现了多少个(仅需要数字结果),对用户的表达给出评分(满分100分,仅需要数字结果),还要给出300字以内的建议(包含中文讲解与英文示例),返回结果只需要有json格式,使用单词量放在words字段,目标实现数量放在targets字段,评分放在score字段,建议放在suggestion字段,不需要再额外的任何信息,记住,只需要统计<用户:>下的内容\n"
)
json_result = self._original_invoke_chat_json(
MessageInvokeDTO(messages=[{"role": "user", "content": system_content}])
)
# 组装成AITopicCompleteResult返回
return AITopicCompleteResult(
targets=json_result["targets"],
score=json_result["score"],
words=json_result["words"],
suggestion=json_result["suggestion"],
)
def invoke_translate(self, dto: TranslateParams) -> str:
"""翻译"""
system_message = f"下面是段文本:'{dto.content}' 仅输出翻译成 {dto.target_language} 后的内容,不可以有其他介绍内容"
invoke_dto = MessageInvokeDTO(
messages=[{"role": "user", "content": system_message}]
)
resp = self._original_invoke_chat(invoke_dto)
return resp
def invoke_grammar_analysis(
self, params: GrammarAnalysisParams
) -> AIGrammarAnalysisResult:
messages = [
{
"role": "user",
"content": f"检查内容是否存在语法错误(不需要检查符号的使用),如果存在就用中文返回这段内容中的语法错误,再提供一句推荐示例,要求数据格式为json,无任何转义字符,可直接被程序正常序列化,语法是否错误放在属性isCorrect中,错误原因放在errorReason中,修正后的正确示例放在correctContent中,推荐示例放在better中,正确示例与推荐示例的语言要使用{params.language},错误原因使用中文. 提供内容是:{params.content}",
}
]
invoke_dto = MessageInvokeDTO(messages=messages)
result_json = self._original_invoke_chat_json(invoke_dto)
return AIGrammarAnalysisResult(
is_correct=result_json["isCorrect"],
error_reason=result_json["errorReason"],
correct_content=result_json["correctContent"],
better=result_json["better"],
)
def invoke_prompt_sentence(self, params: PromptSentenceParams) -> str:
""" """
logging.info(f"request_params:{params}")
system_content = "下面是一场对话\n"
for message in reversed(params.messages):
if message["role"].lower() == "user":
system_content = system_content + f"用户: {message['content']}\n"
else:
system_content = system_content + f"AI: {message['content']}\n"
system_content = (
system_content
+ "现在你需要做为一个用户来回答下一句话,不可以有提供帮助与提问问题的意思,返回内容不得包含 用户: 等其他介绍字眼,语言使用"
+ params.language
)
invoke_dto = MessageInvokeDTO(
messages=[{"role": "user", "content": system_content}]
)
resp = self._original_invoke_chat(invoke_dto)
return resp
def invoke_word_detail(self, params: WordDetailParams) -> AIWordDetailResult:
logging.info(f"request_dto:{params}")
messages = [
{
"role": "user",
"content": f'提供一个单词,只需要简洁快速的用中文返回这个单词的音标与翻译,要求数据格式为json,音标放在属性phonetic中,音标的前后要加上"/",翻译放在translation中, 这个单词是"{params.word}"',
}
]
invoke_dto = MessageInvokeDTO(messages=messages)
result_json = self._original_invoke_chat_json(invoke_dto)
return AIWordDetailResult(
phonetic=result_json["phonetic"], translation=result_json["translation"]
)
def _original_invoke_chat(self, dto: MessageInvokeDTO):
logging.info(f"request_params:{dto.__dict__}")
# invoke_dto = ZhipuInvokeDTO(messages=dto.messages, model=self.model)
resp = self.client.chat.completions.create(
model=self.model, messages=dto.messages, stream=False
)
logging.info(f"response:{resp}")
result = resp.choices[0].message.content
# 去掉俩边的 “”
result = result.strip('"')
# 去掉json的转义字符
result = result.replace('\\"', '"').replace("\\n", "\n").replace("\\", "")
return result
def _original_invoke_chat_json(self, dto: MessageInvokeDTO):
logging.info(f"request_params:{dto.__dict__}")
invoke_dto = ZhipuInvokeDTO(messages=dto.messages, model=self.model)
resp = self.client.chat.completions.create(**invoke_dto.__dict__)
logging.info(f"response:{resp}")
result = resp.choices[0].message.content
# 如果格式为类似markdown的 ```json\n{}\n```,就去掉前后的 ```json\n 与 \n```
if result.startswith("```json\n") and result.endswith("\n```"):
result = result.replace("```json\n", "").replace("\n```", "")
# 去掉俩边的 “”
result = result.strip('"')
# 去掉json的转义字符
result = result.replace('\\"', '"').replace("\\n", "\n").replace("\\", "")
return json.loads(result)
================================================
FILE: talkieai-server/app/ai/interfaces.py
================================================
from app.ai.models import *
from abc import ABC, abstractmethod
from typing import List, Dict
from abc import ABC, abstractmethod
from dataclasses import dataclass
from app.ai.models import *
@dataclass
class MessageInvokeDTO:
messages: List[Dict]
temperature: float = 0.5
max_tokens: int = 300
@dataclass
class FunctionInvokeDTO:
function: Dict
messages: List[Dict]
temperature: float = 0.5
max_tokens: int = 300
class ChatAI(ABC):
@abstractmethod
def invoke_message(self, dto: MessageParams) -> AIMessageResult:
"""聊天"""
pass
@abstractmethod
def invoke_translate(self, dto: TranslateParams) -> str:
"""翻译"""
pass
@abstractmethod
def invoke_greet(self, dto: GreetParams) -> str:
"""打招呼"""
pass
@abstractmethod
def invoke_grammar_analysis(
self, dto: GrammarAnalysisParams
) -> AIGrammarAnalysisResult:
"""语法分析"""
pass
@abstractmethod
def invoke_prompt_sentence(self, dto: PromptSentenceParams) -> str:
"""为用户提示句子"""
pass
@abstractmethod
def invoke_word_detail(self, dto: WordDetailParams) -> AIWordDetailResult:
"""单词详情"""
pass
@abstractmethod
def topic_invoke_greet(self, dto: TopicGreetParams) -> str:
"""场景 打招呼"""
pass
@abstractmethod
def topic_invoke_message(self, dto: AITopicMessageParams) -> AITopicMessageResult:
"""场景 聊天"""
pass
@abstractmethod
def topic_invoke_complete(self, dto: AITopicCompleteParams) -> AITopicCompleteResult:
"""场景 结束"""
pass
================================================
FILE: talkieai-server/app/ai/models.py
================================================
from typing import List, Dict
from dataclasses import dataclass, field
@dataclass
class MessageItemParams:
role: str
content: str
@dataclass
class MessageParams:
language: str
name: str
messages: List[Dict]
styles: List[str]
temperature: float = 0.5
max_tokens: int = 300
@dataclass
class AITopicMessageParams:
language: str
speech_role_name: str
prompt: str
name: str
messages: List[Dict] = field(default_factory=list)
styles: List[str] = field(default_factory=list)
temperature: float = 0.5
max_tokens: int = 300
@dataclass
class AITopicCompleteParams:
language: str
targets: List[str] = field(default_factory=list)
messages: List[MessageItemParams] = field(default_factory=list)
@dataclass
class AITopicCompleteResult:
targets: str
score: str
words: int
suggestion: str
@dataclass
class AIMessageResult:
message: str
message_style: str | None
@dataclass
class AITopicMessageResult:
message: str
message_style: str | None
completed: bool
@dataclass
class TranslateParams:
target_language: str
content: str
@dataclass
class GreetParams:
language: str
@dataclass
class GrammarAnalysisParams:
language: str
content: str
@dataclass
class AIGrammarAnalysisResult:
is_correct: bool
error_reason: str
correct_content: str
better: str
@dataclass
class PromptSentenceParams:
language: str
messages: List[Dict]
@dataclass
class WordDetailParams:
word: str
@dataclass
class AIWordDetailResult:
phonetic: str
translation: str
@dataclass
class TopicGreetParams:
language: str
prompt: str
================================================
FILE: talkieai-server/app/api/__init__.py
================================================
================================================
FILE: talkieai-server/app/api/account_routes.py
================================================
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from app.core import get_current_account
from app.db import get_db
from app.models.account_models import *
from app.models.response import ApiResponse
from app.services.account_service import AccountService
from app.services.chat_service import ChatService
router = APIRouter()
@router.post("/account/visitor-login", name="Visitor login")
def visitor_login(
request: Request, dto: VisitorLoginDTO, db: Session = Depends(get_db)
):
"""用户访客登录,一个IP只能有一个访客,如果ip已经生成了访客"""
client_host = request.client.host
# client_host 不能为空
if not client_host:
return ApiResponse(code="400", status="FAILED", message="client_host 不能为空")
# dto.fingerprint 不能为空
if not dto.fingerprint:
return ApiResponse(code="400", status="FAILED", message="dto.fingerprint 不能为空")
user_agent = request.headers["User-Agent"]
account_service = AccountService(db)
return ApiResponse(
data=account_service.visitor_login(dto.fingerprint, client_host, user_agent)
)
@router.get("/account/info", name="Get User info")
def get_account_info(
db: Session = Depends(get_db), account_id: str = Depends(get_current_account)
):
"""获取用户信息"""
account_service = AccountService(db)
return ApiResponse(data=account_service.get_account_info(account_id))
@router.post("/account/settings")
def account_settings_api(
dto: AccountSettingsDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""用户保存设置"""
account_service = AccountService(db)
return ApiResponse(data=account_service.save_settings(dto, account_id))
@router.get('/account/settings')
def get_account_settings_api(
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取用户设置"""
account_service = AccountService(db)
return ApiResponse(data=account_service.get_settings(account_id))
@router.post("/account/role", name="Update User role")
def update_role(
dto: UpdateRoleDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""选择角色"""
account_service = AccountService(db)
return ApiResponse(data=account_service.update_role_setting(dto, account_id))
@router.get("/account/role", name="Get User role")
def get_account_role(
db: Session = Depends(get_db), account_id: str = Depends(get_current_account)
):
"""获取选择的角色"""
account_service = AccountService(db)
return ApiResponse(data=account_service.get_role_setting(account_id))
@router.get("/account/collect")
def get_account_collect_api(
type: str,
message_id: str = None,
content: str = None,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取用户收藏状态"""
account_service = AccountService(db)
return ApiResponse(
data=account_service.get_collect(
CollectDTO(type=type, message_id=message_id, content=content), account_id
)
)
@router.post("/account/collect")
def account_collect_api(
dto: CollectDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""用户保存单词与句子的接口"""
account_service = AccountService(db)
return ApiResponse(data=account_service.collect(dto, account_id))
@router.delete("/account/collect")
def account_collect_api(
dto: CollectDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""取消用户保存的单词或者句子"""
account_service = AccountService(db)
return ApiResponse(data=account_service.cancel_collect(dto, account_id))
@router.get("/account/collects")
def get_account_collects_api(
type: str,
page: int = 1,
page_size: int = 10,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取用户收藏的列表信息,包含分页效果"""
account_service = AccountService(db)
return ApiResponse(
data=account_service.get_collects(type, page, page_size, account_id)
)
================================================
FILE: talkieai-server/app/api/message_routes.py
================================================
from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm import Session
from app.core import get_current_account
from app.core.utils import *
from app.db import get_db
from app.models.account_models import *
from app.models.chat_models import *
from app.models.response import ApiResponse
from app.services.account_service import AccountService
from app.services.chat_service import ChatService
router = APIRouter()
@router.post("/messages/{message_id}/practice")
def message_practice_api(
message_id: str,
dto: MessagePracticeDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""发送消息"""
chat_service = ChatService(db)
return ApiResponse(
data=chat_service.message_practice(message_id, dto, account_id)
)
@router.post("/messages/{message_id}/translate")
def translate_api(
message_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""翻译成用户的源语言"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.translate_message(message_id, account_id))
@router.get("/message/speech")
def speech_api(
message_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""消息转语音"""
chat_service = ChatService(db)
speech_result = chat_service.message_speech(message_id, account_id)
"""获取文件"""
file_path = voice_file_get_path(speech_result["file"])
# 判断文件是否存在
with open(file_path, "rb") as file:
contents = file.read()
headers = {
"Content-Type": "audio/wav",
"Content-Disposition": "attachment",
"filename": speech_result["file"],
}
return Response(
content=contents, media_type="application/octet-stream", headers=headers
)
@router.post("/message/translate-source-language")
def translate_source_language(
dto: TranslateTextDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""翻译成用户本身的语言"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.translate_source_language(dto, account_id))
@router.post("/message/translate-setting-language")
def translate_setting_language(
dto: TranslateTextDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""翻译成用户学习的语言"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.translate_setting_language(dto, account_id))
@router.get("/message/speech-content")
def speech_content_api(
content: str,
session_id: str = None,
speech_role_name: str = None,
speech_role_style: str = None,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""消息转语音"""
chat_service = ChatService(db)
speech_result = chat_service.message_speech_content(
TransformContentSpeechDTO(
content=content, speech_role_name=speech_role_name, speech_role_style=speech_role_style, session_id=session_id
),
account_id,
)
"""获取文件"""
file_path = voice_file_get_path(speech_result["file"])
# 判断文件是否存在
with open(file_path, "rb") as file:
contents = file.read()
headers = {
"Content-Type": "audio/wav",
"Content-Disposition": "attachment",
"filename": speech_result["file"],
}
return Response(
content=contents, media_type="application/octet-stream", headers=headers
)
@router.post("/message/grammar")
def grammar_api(
dto: GrammarDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""分析语法错误"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.grammar_analysis(dto, account_id))
# 进行发音评估
@router.post("/message/pronunciation")
def pronunciation_api(
dto: PronunciationDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""进行发单评估"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.pronunciation(dto, account_id))
# 进行音素级别的发音评估
# 获取单词的音标与翻译
@router.post("/message/word/detail")
def get_word_api(
dto: WordDetailDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取单词的音标与翻译"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.get_word(dto, account_id))
# 单词练习
@router.post("/message/word/practice")
def word_practice_api(
dto: WordPracticeDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""单词练习"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.word_practice(dto, account_id))
# 帮助用户生成提示句
@router.post("/message/prompt")
def prompt_api(
dto: PromptDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""帮助用户生成提示句"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.prompt_sentence(dto, account_id))
================================================
FILE: talkieai-server/app/api/session_routes.py
================================================
from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm import Session
from app.core import get_current_account
from app.core.utils import *
from app.db import get_db
from app.models.account_models import *
from app.models.response import ApiResponse
from app.services.account_service import AccountService
from app.services.chat_service import ChatService
router = APIRouter()
@router.get("/sessions/default")
def get_default_session(
db: Session = Depends(get_db), account_id: str = Depends(get_current_account)
):
"""获取默认会话"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.get_default_session(account_id))
@router.get("/sessions/{session_id}")
def get_session(
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取会话详情"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.get_session(session_id, account_id))
@router.post("/sessions/{session_id}/voice-translate")
def voice_upload_api(
session_id: str,
dto: VoiceTranslateDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""语音解析成文字"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.transform_text(session_id, dto, account_id))
# 获取ai的第一句问候语
@router.get("/sessions/{session_id}/greeting")
def get_session_greeting(
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取会话消息"""
chat_service = ChatService(db)
return ApiResponse(data=chat_service.get_session_greeting(session_id, account_id))
@router.post("/sessions/{session_id}/chat")
def chat_api(
session_id: str,
dto: ChatDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""发送消息"""
chat_service = ChatService(db)
return ApiResponse(
data=chat_service.send_session_message(session_id, dto, account_id)
)
# 删除最近俩次的对话
@router.delete("/sessions/{session_id}/messages/latest")
def delete_latest_session_messages(
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""删除最近一次的对话"""
chat_service = ChatService(db)
return ApiResponse(
data=chat_service.delete_latest_session_messages(session_id, account_id)
)
# 删除session下所有的对话
@router.delete("/sessions/{session_id}/messages")
def delete_all_session_messages(
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""删除最近一次的对话"""
chat_service = ChatService(db)
return ApiResponse(
data=chat_service.delete_all_session_messages(session_id, account_id)
)
================================================
FILE: talkieai-server/app/api/sys_routes.py
================================================
import os
from fastapi import APIRouter, Depends, Request, UploadFile, File, Response
from sqlalchemy.orm import Session
from app.core import get_current_account
from app.db import get_db
from app.models.sys_models import *
from app.models.response import ApiResponse
from app.services.sys_service import SysService
from app.config import Config
from app.core.utils import *
router = APIRouter()
@router.get("/languages/example")
def get_settings_languages_example(
language: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取支持的语言"""
sys_service = SysService(db)
return ApiResponse(
data=sys_service.get_settings_languages_example(language, account_id)
)
# 获取语言下支持的角色
@router.get("/sys/roles")
def get_settings_roles(
locale: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取支持的角色"""
sys_service = SysService(db)
return ApiResponse(data=sys_service.get_settings_roles(locale, account_id))
@router.get("/sys/languages")
def get_settings_languages(
db: Session = Depends(get_db), account_id: str = Depends(get_current_account)
):
"""获取支持的语言"""
sys_service = SysService(db)
return ApiResponse(data=sys_service.get_settings_languages(account_id))
@router.post("/voices/upload")
def voice_upload_api(
db: Session = Depends(get_db),
file: UploadFile = File(...),
account_id: str = Depends(get_current_account),
):
"""上传语音文件"""
sys_service = SysService(db)
return ApiResponse(data=sys_service.voice_upload(file, account_id))
@router.get("/voices/{file_name}")
def get_file(file_name: str, response: Response):
"""获取文件"""
file_path = voice_file_get_path(file_name)
# 判断文件是否存在
if os.path.isfile(file_path):
with open(file_path, "rb") as file:
contents = file.read()
response.headers["Content-Type"] = "application/octet-stream"
response.headers[
"Content-Disposition"
] = f"attachment; filename={os.path.basename(file_path)}"
return Response(content=contents, media_type="application/octet-stream")
else:
return {"error": f"File {file_name} not found."}
@router.post("/sys/feedback")
def add_feedback(
dto: FeedbackDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""用户反馈"""
sys_service = SysService(db)
sys_service.add_feedback(dto, account_id)
return ApiResponse(data="SUCCESS")
================================================
FILE: talkieai-server/app/api/topics_route.py
================================================
from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm import Session
from app.core import get_current_account
from app.db import get_db
from app.models.topic_models import *
from app.models.response import ApiResponse
from app.core.logging import logging
from app.services.topic_service import TopicService
router = APIRouter()
# 用户创建自定义话题
@router.post("/topics/custom", name="Create custom topic")
def create_custom_topic(
topic: TopicCreateDTO,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""用户创建自定义话题"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.create_custom_topic(topic, account_id))
# 获取用户创建的自定义话题
@router.get("/topics/custom", name="Get custom topic")
def get_custom_topic(
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取用户创建的自定义话题"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.get_custom_topic(account_id))
# 获取所有话题组与话题
@router.get("/topics", name="Get all chat topic group")
def get_all_chat_topics(
type: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取所有话题组与话题"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.get_all_topics(type, account_id))
# 获取自定义话题的示例
@router.get("/topics/random", name="Get custom topic example")
def get_custom_topic_example(
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取自定义话题的示例"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.get_custom_topic_example(account_id))
# 获取话题详情
@router.get("/topics/{topic_id}", name="Get topic detail")
def get_topic_detail(
topic_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取话题详情"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.get_topic_detail(topic_id, account_id))
# 获取话题历史记录,topic_id做为可选参数,为空时查询所有历史记录
@router.get("/topics/{topic_id}/history", name="Get chat topic history")
def get_chat_topic_history(
topic_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取话题历史记录,topic_id做为可选参数,为空时查询所有历史记录"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.get_topic_history(topic_id, account_id))
# 获取话题短语记录
@router.get("/topics/{topic_id}/phrases", name="Get chat topic phrases")
def get_chat_topic_phrases(
topic_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取话题短语记录"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.get_topic_phrases(topic_id, account_id))
# 基于主题创建一个session
@router.post("/topics/{topic_id}/session", name="Create chat topic session")
def create_chat_topic_session(
topic_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""基于主题创建一个session"""
topic_service = TopicService(db)
return ApiResponse(data=topic_service.create_topic_session(topic_id, account_id))
# 结束当前会话并进行评分
@router.post("/topics/sessions/{session_id}/complete", name="Compete Session")
def complete_session(
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""结束话题下的session"""
topic_service = TopicService(db)
return ApiResponse(
data=topic_service.complete_topic_session(session_id, account_id)
)
# 删除当前会话
@router.delete("/topics/{topic_id}/session/{session_id}", name="Delete Session")
def delete_session(
topic_id: str,
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""删除话题下的session"""
topic_service = TopicService(db)
return ApiResponse(
data=topic_service.delete_topic_session(topic_id, session_id, account_id)
)
# 获取话题下的session结果
@router.get(
"/topics/{topic_id}/session/{session_id}/completion", name="Get Session Result"
)
def get_session_result(
topic_id: str,
session_id: str,
db: Session = Depends(get_db),
account_id: str = Depends(get_current_account),
):
"""获取话题下的session结果"""
topic_service = TopicService(db)
return ApiResponse(
data=topic_service.get_session_result(topic_id, session_id, account_id)
)
================================================
FILE: talkieai-server/app/config.py
================================================
import os
from dotenv import load_dotenv
load_dotenv()
class Config:
DEFAULT_SOURCE_LANGUAGE = 'zh-CN'
DEFAULT_TARGET_LANGUAGE = 'en-US'
AI_NAME = os.getenv('AI_NAME')
# 数据库连接信息,需要判断不能为空
SQLALCHEMY_DATABASE_URL: str = os.getenv('DATABASE_URL')
# 文件上传路径
TEMP_SAVE_FILE_PATH = os.getenv('TEMP_SAVE_FILE_PATH')
# 微软语音
AZURE_KEY = os.getenv('AZURE_KEY')
# AI
AI_SERVER = os.getenv('AI_SERVER')
# ChatGPT Key
CHAT_GPT_PROXY = os.getenv('CHAT_GPT_PROXY')
CHAT_GPT_KEY = os.getenv('CHAT_GPT_KEY')
CHAT_GPT_MODEL = os.getenv('CHAT_GPT_MODEL')
# 智谱AI配置
ZHIPU_AI_API_KEY = os.getenv('ZHIPU_AI_API_KEY')
ZHIPU_AI_MODEL = os.getenv('ZHIPU_AI_MODEL')
# WeChat
WECHAT_APP_ID = os.getenv('WECHAT_APP_ID')
WECHAT_APP_SECRET = os.getenv('WECHAT_APP_SECRET')
WE_CHAT_SERVER_URL = os.getenv('WE_CHAT_SERVER_URL')
# 是否开启SQL语句打印
SQL_ECHO: bool = os.getenv('SQL_ECHO').lower() == 'true'
# JWT配置
TOKEN_SECRET = os.getenv('TOKEN_SECRET')
ALGORITHM = 'HS256'
DECODED_TOKEN_USER_KEY = "sub"
DECODED_TOKEN_IAT_KEY = "iat"
TOKEN_EXPIRE_TIME = int(os.getenv("TOKEN_EXPIRE_TIME"))
# API前缀
API_PREFIX = os.getenv('API_PREFIX', '/api')
================================================
FILE: talkieai-server/app/core/__init__.py
================================================
from fastapi import Header
from app.config import Config
from app.core.auth import Auth
auth = Auth(Config.TOKEN_SECRET, Config.ALGORITHM, Config.DECODED_TOKEN_IAT_KEY, Config.TOKEN_EXPIRE_TIME,
Config.DECODED_TOKEN_USER_KEY)
def get_current_account(x_token: str = Header(None), x_token_query: str = None):
if x_token_query:
return auth.get_current_account(x_token_query)
return auth.get_current_account(x_token)
================================================
FILE: talkieai-server/app/core/auth.py
================================================
import time
import jwt
from fastapi import HTTPException
class Auth:
def __init__(self, token_secret: str, algorithm: str, decoded_token_iat_key: str, expire_time: int,
decoded_token_user_key: str):
self.token_secret = token_secret
self.algorithm = algorithm
self.expire_time = expire_time
self.decoded_token_iat_key = decoded_token_iat_key
self.decoded_token_user_key = decoded_token_user_key
def init_token(self, name: str, id: str) -> str:
return jwt.encode({
'sub': id,
'iat': int(time.time()),
'name': name
}, self.token_secret, algorithm=self.algorithm)
def get_current_account(self, x_token: str) -> str:
"""Get user info from x_token"""
if not x_token:
raise HTTPException(status_code=401, detail="X-Token header is missing")
try:
decoded_token = jwt.decode(x_token, self.token_secret, algorithms=[self.algorithm])
except jwt.PyJWTError:
print(jwt.PyJWTError)
raise HTTPException(status_code=401, detail="Invalid token")
# Check Whether the token expired
iat = decoded_token.get(self.decoded_token_iat_key)
"""Check whether the token is expired"""
delta = int((time.time() - iat) / 60)
if delta > self.expire_time:
raise HTTPException(status_code=401, detail="Token has expired")
account_id = decoded_token.get(self.decoded_token_user_key)
if not account_id:
raise HTTPException(status_code=401, detail="User not found in token")
return account_id
================================================
FILE: talkieai-server/app/core/azure_voice.py
================================================
import json
import azure.cognitiveservices.speech as speechsdk
from app.config import Config
from app.core.logging import logging
from app.core.language import *
key = Config.AZURE_KEY
region = "eastasia"
speech_config = speechsdk.SpeechConfig(subscription=key, region=region)
speech_synthesizer = speechsdk.SpeechSynthesizer(
speech_config=speech_config, audio_config=None
)
def speech_default(content: str, output_path_str: str, language: str, voice_name: str|None = None):
"""默认语音合成 还是用不了,因为每次还要实例化 speech_synthesizer"""
speech_config.speech_recognition_language = language
speech_config.speech_synthesis_language = language
# 如果voice_name是空,则设置对应语言的默认角色
if not voice_name:
voice_name = get_azure_language_default_role(language)
speech_config.speech_synthesis_voice_name = voice_name
speech_synthesis_result = speech_synthesizer.speak_text_async(content).get()
audio_data_stream = speechsdk.AudioDataStream(speech_synthesis_result)
if (
speech_synthesis_result.reason
== speechsdk.ResultReason.SynthesizingAudioCompleted
):
audio_data_stream.save_to_wav_file(output_path_str)
elif (
speech_synthesis_result.reason
== speechsdk.ResultReason.Canceled
):
cancellation_details = speech_synthesis_result.cancellation_details
logging.error(
"Speech synthesis canceled: {}".format(cancellation_details.reason)
)
if cancellation_details.reason == speechsdk.CancellationReason.Error:
if cancellation_details.error_details:
logging.error(
"Error details: {}".format(cancellation_details.error_details)
)
logging.error(
"Did you set the speech resource key and region values?"
)
raise Exception("语音合成失败")
else:
logging.error(
"Speech synthesis failed: {}".format(speech_synthesis_result.reason)
)
raise Exception("语音合成失败")
def speech_by_ssml(
content: str,
output_path_str: str,
voice_name: str,
speech_rate: str,
feel: str,
targetLang: str,
):
"""可定制的文本转语音"""
# 如果voice_name是空,则设置对应语言的默认角色
if not voice_name:
voice_name = get_azure_language_default_role(targetLang)
ssml = f"""
{content}
"""
logging.info(ssml)
speech_synthesis_result = speech_synthesizer.start_speaking_ssml_async(
ssml
).get() # Get the audio data stream
audio_data_stream = speechsdk.AudioDataStream(speech_synthesis_result)
if (
speech_synthesis_result.reason
== speechsdk.ResultReason.SynthesizingAudioStarted
):
logging.info("init 1")
audio_data_stream.save_to_wav_file(output_path_str)
logging.info("init 2")
else:
logging.error(
"Speech synthesis failed: {}".format(speech_synthesis_result.reason)
)
if speech_synthesis_result.reason == speechsdk.ResultReason.Canceled:
cancellation_details = speech_synthesis_result.cancellation_details
logging.error(
"Speech synthesis canceled: {}".format(cancellation_details.reason)
)
if cancellation_details.reason == speechsdk.CancellationReason.Error:
if cancellation_details.error_details:
logging.error(
"Error details: {}".format(cancellation_details.error_details)
)
logging.error(
"Did you set the speech resource key and region values?"
)
raise Exception("语音合成失败")
def speech_pronunciation(content: str, speech_path: str, language: str = "en-US"):
"""发音评估"""
audio_config = speechsdk.audio.AudioConfig(filename=speech_path)
speech_config.speech_recognition_language = language
speech_recognizer = speechsdk.SpeechRecognizer(
speech_config=speech_config, audio_config=audio_config
)
# "{\"referenceText\":\"good morning\",\"gradingSystem\":\"HundredMark\",\"granularity\":\"Phoneme\",\"EnableMiscue\":true}" 通过dict生成json
json_param = {
"referenceText": content,
"gradingSystem": "HundredMark",
"granularity": "Word",
"EnableMiscue": True,
}
pronunciation_assessment_config = speechsdk.PronunciationAssessmentConfig(
json_string=json.dumps(json_param)
)
pronunciation_assessment_config.apply_to(speech_recognizer)
speech_recognition_result = speech_recognizer.recognize_once()
pronunciation_assessment_result = speechsdk.PronunciationAssessmentResult(
speech_recognition_result
)
result = {
"accuracy_score": pronunciation_assessment_result.accuracy_score,
"fluency_score": pronunciation_assessment_result.fluency_score,
"completeness_score": pronunciation_assessment_result.completeness_score,
"pronunciation_score": pronunciation_assessment_result.pronunciation_score,
}
original_words = pronunciation_assessment_result.words
result_words = []
# 循环words,获取每个单词的发音评估结果
for word in original_words:
result_words.append(
{
"word": word.word,
"accuracy_score": word.accuracy_score,
"error_type": word.error_type,
}
)
result["words"] = result_words
return result
# 单词发单评估,可以精确到每一个音素
def word_speech_pronunciation(word: str, speech_path: str, language: str = "en-US"):
audio_config = speechsdk.audio.AudioConfig(filename=speech_path)
speech_config.speech_recognition_language = language
speech_recognizer = speechsdk.SpeechRecognizer(
speech_config=speech_config, audio_config=audio_config
)
# "{\"referenceText\":\"good morning\",\"gradingSystem\":\"HundredMark\",\"granularity\":\"Phoneme\",\"EnableMiscue\":true}" 通过dict生成json
json_param = {
"referenceText": word,
"gradingSystem": "HundredMark",
"granularity": "Phoneme",
"EnableMiscue": True,
"phonemeAlphabet": "IPA",
}
pronunciation_assessment_config = speechsdk.PronunciationAssessmentConfig(
json_string=json.dumps(json_param)
)
pronunciation_assessment_config.apply_to(speech_recognizer)
speech_recognition_result = speech_recognizer.recognize_once()
pronunciation_assessment_result = speechsdk.PronunciationAssessmentResult(
speech_recognition_result
)
result = {
"accuracy_score": pronunciation_assessment_result.accuracy_score,
"fluency_score": pronunciation_assessment_result.fluency_score,
"completeness_score": pronunciation_assessment_result.completeness_score,
"pronunciation_score": pronunciation_assessment_result.pronunciation_score,
}
original_words = pronunciation_assessment_result.words
result_words = []
# 循环words,获取每个单词的发音评估结果
for word in original_words:
# 获取音素评估结果
phonemes = word.phonemes
phonemes_list = []
for phoneme in phonemes:
phonemes_list.append(
{
"phoneme": phoneme.phoneme,
"accuracy_score": phoneme.accuracy_score
}
)
result_words.append(
{
"word": word.word,
"accuracy_score": word.accuracy_score,
"error_type": word.error_type,
"phonemes": phonemes_list,
}
)
result["words"] = result_words
return result
# 语音转文字
def speech_translate_text(speech_path: str, language: str) -> str:
# languages = ["zh-CN", "en-US"]
languages = []
# 如果languages已经包含了language,就不需要再添加了,不包含需要添加,并且放在第一位
if language not in languages:
languages.insert(0, language)
auto_detect_source_language_config = (
speechsdk.languageconfig.AutoDetectSourceLanguageConfig(languages=languages)
)
audio_config = speechsdk.audio.AudioConfig(filename=speech_path)
speech_recognizer = speechsdk.SpeechRecognizer(
speech_config=speech_config,
auto_detect_source_language_config=auto_detect_source_language_config,
audio_config=audio_config,
)
speech_recognition_result = speech_recognizer.recognize_once_async().get()
if speech_recognition_result.reason == speechsdk.ResultReason.RecognizedSpeech:
print("Recognized: {}".format(speech_recognition_result.text))
return speech_recognition_result.text
elif speech_recognition_result.reason == speechsdk.ResultReason.NoMatch:
print(
"No speech could be recognized: {}".format(
speech_recognition_result.no_match_details
)
)
elif speech_recognition_result.reason == speechsdk.ResultReason.Canceled:
cancellation_details = speech_recognition_result.cancellation_details
print("Speech Recognition canceled: {}".format(cancellation_details.reason))
if cancellation_details.reason == speechsdk.CancellationReason.Error:
print("Error details: {}".format(cancellation_details.error_details))
print("Did you set the speech resource key and region values?")
# 获取支持的语音列表,组装成对象数组进行返回
def get_voice_list():
"""通过synthesizer.getVoicesAsync()方法来获取所有支持的语音列表"""
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config)
voice_list = speech_synthesizer.get_voices_async().get()
# 迭代list,组装成对象数组进行返回
voice_vo_list = []
for voice in voice_list.voices:
voice_vo_list.append(
{
"gender": voice.gender.value,
"locale": voice.locale,
"local_name": voice.local_name,
"name": voice.name,
"short_name": voice.short_name,
"voice_type": {
"name": voice.voice_type.name,
"value": voice.voice_type.value,
},
"style_list": voice.style_list,
}
)
return voice_vo_list
# 获取支持的语音列表,组装成对象数组进行返回
def get_voice_list():
"""通过synthesizer.getVoicesAsync()方法来获取所有支持的语音列表"""
speech_synthesizer = speechsdk.SpeechSynthesizer(speech_config=speech_config)
voice_list = speech_synthesizer.get_voices_async().get()
# 迭代list,组装成对象数组进行返回
voice_vo_list = []
for voice in voice_list.voices:
voice_vo_list.append(
{
"gender": voice.gender.value,
"locale": voice.locale,
"local_name": voice.local_name,
"name": voice.name,
"short_name": voice.short_name,
"voice_type": {
"name": voice.voice_type.name,
"value": voice.voice_type.value,
},
"style_list": voice.style_list,
}
)
return voice_vo_list
voice_vo_list = get_voice_list()
# 获取azure语音配置,并且按 locale 分组
azure_voice_configs = voice_vo_list
azure_voice_configs_group = {}
for azure_voice_config in azure_voice_configs:
if azure_voice_config["locale"] not in azure_voice_configs_group:
azure_voice_configs_group[azure_voice_config["locale"]] = []
azure_voice_configs_group[azure_voice_config["locale"]].append(azure_voice_config)
def get_azure_voice_role_by_short_name(short_name: str):
"""根据short_name获取语音配置"""
local = short_name.rsplit('-', 1)[0]
azure_voice_configs = azure_voice_configs_group[local]
# 迭代azure_voice_configs,找到item中short_name与settings.speech_role_name相同的item,取local_name
result = None
for item in azure_voice_configs:
if item["short_name"] == short_name:
return item
# 抛出异常
raise Exception("未找到对应的语音配置")
================================================
FILE: talkieai-server/app/core/db_cache.py
================================================
================================================
FILE: talkieai-server/app/core/exceptions.py
================================================
# 用户资源访问受限exception
class UserAccessDeniedException(Exception):
pass
# 用户密码不正确
class UserPasswordIncorrectException(Exception):
pass
# 参数不正确
class ParameterIncorrectException(Exception):
pass
================================================
FILE: talkieai-server/app/core/language.py
================================================
import json
from app.core.logging import logging
language_data = []
azure_data = {}
with open("data/azure.json", "r") as f:
azure_data = json.load(f)
azure_style_label_data = []
with open("data/azure_style_label.json", "r") as f:
azure_style_label_data = json.load(f)
azure_style_label_map = {}
for item in azure_style_label_data:
azure_style_label_map[item["value"]] = item["label"]
sys_language_data = {}
with open("data/sys_language.json", "r") as f:
sys_language_data = json.load(f)
def get_label_by_language(language: str) -> str:
"""根据语言获取对应的label"""
for item in sys_language_data:
if item["value"] == language:
return item["label"]
raise Exception("没有找到对应的语言:{language}")
def get_azure_style_label(style: str):
"""根据style获取对应的label"""
# 检查azure_style_label_map是否包含style
if style in azure_style_label_map:
return azure_style_label_map[style]
logging.warning(f"没有找到对应的style:{style}")
return ""
def get_azure_language_default_role(language: str):
"""根据语言获取默认的角色"""
for item in sys_language_data:
if item["value"] == language:
return item["default_voice_role_name"]
raise Exception(f"没有找到对应的语言:{language}")
def get_role_info_by_short_name(short_name: str):
"""根据short_name获取角色信息"""
for item in sys_language_data:
if item["short_name"] == short_name:
return item
raise Exception(f"没有找到对应的角色:{short_name}")
================================================
FILE: talkieai-server/app/core/logging.py
================================================
import logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
================================================
FILE: talkieai-server/app/core/utils.py
================================================
import hashlib
import os
import shutil
import string
import time
from datetime import datetime, timedelta
from uuid import UUID
import random
import jwt
from fastapi import UploadFile
from app.config import Config
def short_uuid() -> str:
"""64-bit characters reduced to 8-bit characters.
link https://blog.csdn.net/dqchouyang/article/details/70230863
"""
uuidChars = ("a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z")
uuid = str(uuid4()).replace('-', '')
result = ''
for i in range(0, 8):
sub = uuid[i * 4: i * 4 + 4]
x = int(sub, 16)
result += uuidChars[x % 0x3E]
return result
def uuid4():
"""Generate a random UUID."""
return UUID(bytes=os.urandom(16), version=4)
def digest_password(password: str):
"""Sha256 digest password"""
return hashlib.sha256(password.encode('utf-8')).hexdigest()
def generate_code():
"""Generate a 4 random letter verification code"""
code = ''.join(random.sample(string.digits, 6))
return code
# 日期转换成 年-月-日 时:分:秒
def date_to_str(date):
return date.strftime("%Y-%m-%d %H:%M:%S")
def day_to_str(date):
return date.strftime("%Y-%m-%d 00:00:00")
def friendly_time(dt):
"""
将日期时间字符串转换为友好的时间差表达方式。
参数:
dt -- 日期时间字符串,格式为YYYY-MM-DD HH:MM:SS
返回值:
友好的时间差表达方式,例如:1分钟前、1小时前、1天前等等。
"""
now = datetime.now()
then = datetime.strptime(dt, '%Y-%m-%d %H:%M:%S')
diff = now - then
if diff < timedelta(seconds=60):
return '刚刚'
elif diff < timedelta(minutes=1):
return f'{diff.seconds}秒前'
elif diff < timedelta(hours=1):
return f'{diff.seconds // 60}分钟前'
elif diff < timedelta(days=1):
return f'{diff.seconds // 3600}小时前'
elif diff < timedelta(days=30):
return f'{diff.days}天前'
elif diff < timedelta(days=365):
return f'{diff.days // 30}个月前'
else:
return f'{diff.days // 365}年前'
def save_file(upload_file: UploadFile) -> str:
# 获取upload_file文件后缀名
file_ext = get_file_ext(upload_file.filename)
filename = f'{short_uuid()}{file_ext}'
file_path = f'{Config.TEMP_SAVE_FILE_PATH}/{filename}'
if not os.path.exists(Config.TEMP_SAVE_FILE_PATH):
os.makedirs(Config.TEMP_SAVE_FILE_PATH)
with open(file_path, 'wb') as buffer:
shutil.copyfileobj(upload_file.file, buffer)
return filename
# def save_voice_file(upload_file: UploadFile) -> str:
# # 获取upload_file文件后缀名
# file_ext = get_file_ext(upload_file.filename)
# filename = f'{short_uuid()}{file_ext}'
# file_path = voice_file_get_path(filename)
# if not os.path.exists(Config.TEMP_SAVE_FILE_PATH):
# os.makedirs(Config.TEMP_SAVE_FILE_PATH)
# with open(file_path, 'wb') as buffer:
# shutil.copyfileobj(upload_file.file, buffer)
# return filename
def file_get_path(filename: str) -> str:
return f'{Config.TEMP_SAVE_FILE_PATH}/{filename}'
# 获取文件的后缀名
def get_file_ext(filename: str) -> str:
return os.path.splitext(filename)[1]
# 获取年月日 yyyymmdd格式
def get_date_str():
return time.strftime("%Y%m%d", time.localtime())
def save_image_file(upload_file: UploadFile) -> str:
# 获取upload_file文件后缀名
file_ext = get_file_ext(upload_file.filename)
filename = f'{short_uuid()}{file_ext}'
file_full_path = image_file_get_path(filename)
# 检查文件的目录是否存在,如果不存在就创建
if not os.path.exists(os.path.dirname(file_full_path)):
os.makedirs(os.path.dirname(file_full_path))
with open(file_full_path, "wb") as buffer:
shutil.copyfileobj(upload_file.file, buffer)
return filename
def save_voice_file(upload_file: UploadFile, prefix='') -> str:
# 获取upload_file文件后缀名
file_ext = get_file_ext(upload_file.filename)
filename = f'{prefix}_{short_uuid()}{file_ext}'
file_full_path = voice_file_get_path(filename)
# 检查文件的目录是否存在,如果不存在就创建
if not os.path.exists(os.path.dirname(file_full_path)):
os.makedirs(os.path.dirname(file_full_path))
with open(file_full_path, "wb") as buffer:
shutil.copyfileobj(upload_file.file, buffer)
return filename
def voice_file_get_path(filename: str) -> str:
result = f"{Config.TEMP_SAVE_FILE_PATH}/voices/{filename}"
# 检查full_file_name文件的所属目录是否存在,不存在则创建新的目录
if not os.path.exists(os.path.dirname(result)):
os.makedirs(os.path.dirname(result))
return result
def image_file_get_path(filename: str) -> str:
return f"{Config.TEMP_SAVE_FILE_PATH}/images/{filename}"
# 获取文件的后缀名
def get_file_ext(filename: str) -> str:
return os.path.splitext(filename)[1]
================================================
FILE: talkieai-server/app/db/__init__.py
================================================
from sqlalchemy import create_engine, event
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.config import Config
from sqlalchemy.exc import DisconnectionError
def checkout_listener(dbapi_con, con_record, con_proxy):
try:
try:
dbapi_con.ping(False)
except TypeError:
dbapi_con.ping()
except dbapi_con.OperationalError as exc:
if exc.args[0] in (2006, 2013, 2014, 2045, 2055):
raise DisconnectionError()
else:
raise
# 创建数据库连接, SQLALCHEMY_DATABASE_URL不能为空
if not Config.SQLALCHEMY_DATABASE_URL:
raise Exception('SQLALCHEMY_DATABASE_URL不能为空')
engine = create_engine(Config.SQLALCHEMY_DATABASE_URL, echo=Config.SQL_ECHO, pool_pre_ping=True, pool_size=100, pool_recycle=360)
event.listen(engine, 'checkout', checkout_listener)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# 创建基类
Base = declarative_base()
# 数据库会话
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
================================================
FILE: talkieai-server/app/db/account_entities.py
================================================
import datetime
from sqlalchemy import Column, String, DateTime, Integer, Index, Text
from app.db import Base, engine
class AccountEntity(Base):
"""访客表"""
__tablename__ = "account"
id = Column("id", String(80), primary_key=True)
client_host = Column("client_host", String(50), nullable=False)
user_agent = Column("user_agent", String(512), nullable=True)
fingerprint = Column("fingerprint", String(64), nullable=True)
status = Column("status", String(50), default="ACTIVE")
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class AccountSettingsEntity(Base):
"""用户设置表"""
__tablename__ = "account_settings"
id = Column("id", Integer, primary_key=True, autoincrement=True)
account_id = Column("account_id", String(80), nullable=False)
source_language = Column("source_language", String(80), nullable=False)
target_language = Column("target_language", String(80), nullable=False)
speech_role_name = Column("speech_role_name", String(80), nullable=True)
auto_playing_voice = Column("auto_playing_voice", Integer, default=1)
playing_voice_speed = Column("playing_voice_speed", String(50), default='1.0')
auto_text_shadow = Column("auto_text_shadow", Integer, default=1)
auto_pronunciation = Column("auto_pronunciation", Integer, default=1)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class AccountCollectEntity(Base):
"""用户收藏表"""
__tablename__ = "account_collect"
id = Column("id", Integer, primary_key=True, autoincrement=True)
message_id = Column("message_id", String(80), nullable=True)
account_id = Column("account_id", String(80), nullable=False)
type = Column("type", String(80), nullable=False)
content = Column("content", String(2500), nullable=True)
translation = Column("translation", String(2500), nullable=True)
deleted = Column("deleted", Integer, default="0")
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# 数据库未创建表的话自动创建表
Base.metadata.create_all(engine)
================================================
FILE: talkieai-server/app/db/chat_entities.py
================================================
import datetime
from sqlalchemy import Column, String, DateTime, Integer, Index, Text
from app.db import Base, engine
class MessageSessionEntity(Base):
"""消息会话表"""
__tablename__ = "message_session"
id = Column("id", String(80), primary_key=True)
account_id = Column("account_id", String(80), nullable=False)
# CHAT TOPIC
type = Column("type", String(50), nullable=False, default="CHAT")
message_count = Column("message_count", Integer, default="0")
is_default = Column("is_default", Integer, default="0")
completed = Column("completed", Integer, default="0")
deleted = Column("deleted", Integer, default="0")
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class MessageEntity(Base):
"""消息表"""
__tablename__ = "message"
id = Column("id", String(80), primary_key=True)
session_id = Column("session_id", String(80), nullable=False)
account_id = Column("account_id", String(80), nullable=False)
sender = Column("sender", String(80), nullable=False)
receiver = Column("receiver", String(80), nullable=False)
type = Column("type", String(50), nullable=False)
content = Column("content", String(2500), nullable=False)
style = Column("style", String(80), nullable=True)
length = Column("length", Integer, nullable=False)
file_name = Column("file_name", String(80), nullable=True)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
deleted = Column("deleted", Integer, default="0")
# 设置一个自增的sequence 排序使用
sequence = Column("sequence", Integer, nullable=False)
# session_id需要加索引
Index("idx_session_id", "session_id")
# account_id需要加索引
Index("idx_account_id", "account_id")
# sequence 需要加索引
Index("idx_sequence", "sequence")
class MessageTranslateEntity(Base):
"""用户翻译记录表, 使用自增id"""
__tablename__ = "message_translate"
id = Column("id", Integer, primary_key=True, autoincrement=True)
session_id = Column("session_id", String(80), nullable=False)
message_id = Column("message_id", String(80), nullable=False)
account_id = Column("account_id", String(80), nullable=False)
source_language = Column("source_language", String(80), nullable=False)
target_language = Column("target_language", String(80), nullable=False)
source_text = Column("source_text", String(512), nullable=False)
target_text = Column("target_text", String(512), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
class MessageGrammarEntity(Base):
"""用户语法与语音分析表,结果使用json保存"""
__tablename__ = "message_grammar"
id = Column("id", Integer, primary_key=True, autoincrement=True)
session_id = Column("session_id", String(80), nullable=False)
message_id = Column("message_id", String(80), nullable=False)
file_name = Column("file_name", String(80), nullable=True)
account_id = Column("account_id", String(80), nullable=False)
# GRAMMAR PRONUNCIATION
type = Column("type", String(80), nullable=False)
result = Column("result", Text, nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
# 数据库未创建表的话自动创建表
Base.metadata.create_all(engine)
================================================
FILE: talkieai-server/app/db/sys_entities.py
================================================
import datetime
from sqlalchemy import Column, String, DateTime, Integer, Index, Text
from app.db import Base, engine
class SettingsLanguageEntity(Base):
"""会话语言配置表"""
__tablename__ = "settings_language"
id = Column("id", String(80), primary_key=True)
language = Column("language", String(80), nullable=False)
full_language = Column("full_language", String(80), nullable=False)
label = Column("label", String(80), nullable=False)
full_label = Column("full_label", String(80), nullable=False)
description = Column("description", String(250), nullable=True)
sequence = Column("sequence", Integer, default="1")
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class SettingsLanguageExampleEntity(Base):
"""会话示例配置表"""
__tablename__ = "settings_language_example"
id = Column("id", String(80), primary_key=True)
language = Column("language", String(80), nullable=False)
example = Column("example", String(250), nullable=True)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# 会话角色配置表
class SettingsRoleEntity(Base):
"""会话角色配置表"""
__tablename__ = "settings_role"
id = Column("id", Integer, primary_key=True, autoincrement=True)
locale = Column("locale", String(80), nullable=False)
local_name = Column("local_name", String(80), nullable=False)
name = Column("name", String(255), nullable=False)
short_name = Column("short_name", String(80), nullable=False)
gender = Column("gender", Integer, nullable=False, default=1)
# 头像地址
avatar = Column("avatar", String(350), nullable=True)
# 试听音频地址
audio = Column("audio", String(350), nullable=True)
styles = Column("styles", String(350), nullable=True)
status = Column("status", String(80), nullable=False, default="ACTIVE")
# 排序
sequence = Column("sequence", Integer, nullable=False, default=0)
# 创建时间
create_time = Column(
"create_time", DateTime, nullable=False, default=datetime.datetime.now
)
# 更新时间
update_time = Column(
"update_time", DateTime, nullable=False, default=datetime.datetime.now
)
deleted = Column("deleted", Integer, nullable=False, default=0)
class FileDetail(Base):
"""文件表"""
__tablename__ = "file_detail"
id = Column("id", String(80), primary_key=True)
file_path = Column("file_path", String(150), nullable=False)
module = Column("module", String(80), nullable=True)
module_id = Column("module_id", String(80), nullable=True)
file_name = Column("file_name", String(150), nullable=True)
file_ext = Column("file_ext", String(20), nullable=True)
deleted = Column("deleted", Integer, default="0")
created_by = Column("created_by", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
class SysCacheEntity(Base):
"""系统缓存表"""
__tablename__ = "sys_cache"
id = Column("id", Integer, primary_key=True, autoincrement=True)
key = Column("key", String(80), nullable=False)
value = Column("value", String(512), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class FeedbackEntity(Base):
"""用户反馈表"""
__tablename__ = "sys_feedback"
id = Column("id", Integer, autoincrement=True, primary_key=True)
account_id = Column("account_id", String(80), nullable=False)
content = Column("content", String(2500), nullable=False)
contact = Column("contact", String(250), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
class SysDictTypeEntity(Base):
"""系统字典类型表"""
__tablename__ = "sys_dict_type"
id = Column("id", Integer, autoincrement=True, primary_key=True)
dict_type = Column("dict_type", String(80), nullable=False)
dict_name = Column("dict_name", String(80), nullable=False)
status = Column("status", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class SysDictDataEntity(Base):
"""系统字典数据表"""
__tablename__ = "sys_dict_data"
id = Column("id", Integer, autoincrement=True, primary_key=True)
dict_type = Column("dict_type", String(80), nullable=False)
dict_label = Column("dict_label", String(80), nullable=False)
dict_value = Column("dict_value", String(80), nullable=False)
status = Column("status", String(80), nullable=False, default="1")
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# 数据库未创建表的话自动创建表
Base.metadata.create_all(engine)
================================================
FILE: talkieai-server/app/db/topic_entities.py
================================================
import datetime
from sqlalchemy import Column, String, DateTime, Integer, Index, Text
from app.db import Base, engine
# 聊天话题组表
class TopicGroupEntity(Base):
"""聊天话题组表"""
__tablename__ = "topic_group"
id = Column("id", String(80), primary_key=True)
# ROLE_PLAY CHAT_TOPIC
type = Column("type", String(80), nullable=False)
name = Column("name", String(80), nullable=False)
description = Column("description", String(80), nullable=False)
status = Column("status", String(80), nullable=False, default="ACTIVE")
sequence = Column("sequence", Integer, nullable=False, default=1)
created_by = Column("created_by", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
class TopicEntity(Base):
"""聊天话题表"""
__tablename__ = "topic"
id = Column("id", String(80), primary_key=True)
# 所属组
group_id = Column("group_id", String(80), nullable=False)
language = Column("language", String(80), nullable=False)
name = Column("name", String(80), nullable=False)
level = Column("level", Integer, nullable=False)
# 所属角色
role_short_name = Column("role_short_name", String(80), nullable=False)
# 角色语音默认速度
role_speech_rate = Column("speech_rate", String(80), nullable=False, default="1")
topic_bot_name = Column("topic_bot_name", String(580), nullable=False)
topic_user_name = Column("topic_user_name", String(580), nullable=False)
prompt = Column("prompt", String(2500), nullable=False)
description = Column("description", String(580), nullable=False)
status = Column("status", String(80), nullable=False, default="ACTIVE")
sequence = Column("sequence", Integer, nullable=False, default=1)
image_url = Column("image_url", String(500), nullable=True)
created_by = Column("created_by", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# created_by 增加搜索索引
created_by_index = Index("created_by_index", created_by)
# group_id 增加搜索索引
group_id_index = Index("group_id_index", group_id)
class TopicSessionRelation(Base):
"""话题与session关系表"""
__tablename__ = "topic_session_relation"
id = Column("id", Integer, primary_key=True, autoincrement=True)
# 所属话题
topic_id = Column("topic_id", String(80), nullable=False)
# 所属session_id
session_id = Column("session_id", String(80), nullable=False)
account_id = Column("account_id", String(80), nullable=False)
# 创建时间
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
# 更新时间
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# topic_id 增加搜索索引
topic_id_index = Index("topic_id_index", topic_id)
# session_id 增加搜索索引
session_id_index = Index("session_id_index", session_id)
class AccountTopicEntity(Base):
""" 用户创建的话题表 """
__tablename__ = "account_topic"
id = Column("id", String(80), primary_key=True)
language = Column("language", String(80), nullable=False)
ai_role = Column("ai_role", String(280), nullable=False)
my_role = Column("my_role", String(280), nullable=False)
topic = Column("topic", String(2500), nullable=False)
account_id = Column("account_id", String(80), nullable=False)
created_by = Column("created_by", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
sequence = Column("sequence", Integer, nullable=False, default=1)
# 话题目标表
class TopicTargetEntity(Base):
"""话题目标表"""
__tablename__ = "topic_target"
id = Column("id", Integer, primary_key=True, autoincrement=True)
# 所属话题
topic_id = Column("topic_id", String(80), nullable=False)
# 目标类型 MAIN TRIAL
type = Column("type", String(80), nullable=False)
# 目标描述
description = Column("description", String(500), nullable=False)
# 目标描述的翻译
description_translation = Column("description_translation", String(500), nullable=True)
# 目标状态
status = Column("status", String(80), nullable=False, default="ACTIVE")
sequence = Column("sequence", Integer, nullable=False, default=1)
created_by = Column("created_by", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# topic_id 增加搜索索引
topic_id_index = Index("topic_id_index", topic_id)
# 话题短语
class TopicPhraseEntity(Base):
"""话题短语"""
__tablename__ = "topic_phrase"
id = Column("id", Integer, primary_key=True, autoincrement=True)
# 所属话题
topic_id = Column("topic_id", String(80), nullable=False)
# 短语
phrase = Column("phrase", String(500), nullable=False)
# 短语翻译
phrase_translation = Column("phrase_translation", String(500), nullable=True)
# 短语类型
type = Column("type", String(80), nullable=False)
# 短语状态
status = Column("status", String(80), nullable=False, default="ACTIVE")
sequence = Column("sequence", Integer, nullable=False, default=1)
created_by = Column("created_by", String(80), nullable=False)
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
update_time = Column("update_time", DateTime, default=datetime.datetime.now)
# topic_id 增加搜索索引
topic_id_index = Index("topic_id_index", topic_id)
class TopicHistoryEntity(Base):
"""话题历史记录表"""
__tablename__ = "topic_history"
id = Column("id", Integer, primary_key=True, autoincrement=True)
account_id = Column("account_id", String(80), nullable=False)
# 所属话题
topic_id = Column("topic_id", String(80), nullable=False)
# 话题类型
topic_type = Column("topic_type", String(80), nullable=False)
# 话题名
topic_name = Column("topic_name", String(80), nullable=False)
# 主目标数量
main_target_count = Column("main_target_count", Integer, nullable=False, default=0)
# 试验目标数量
trial_target_count = Column(
"trial_target_count", Integer, nullable=False, default=0
)
main_target_completed_count = Column(
"main_target_completed_count", Integer, nullable=False, default=0
)
trial_target_completed_count = Column(
"trial_target_completed_count", Integer, nullable=False, default=0
)
# 完成度
completion = Column("completion", Integer, nullable=False, default=0)
# 语音评分
audio_score = Column("audio_score", Integer, nullable=True, default=0)
# 内容评分
content_score = Column("content_score", Integer, nullable=True, default=0)
# 建议
suggestion = Column("suggestion", String(2080), nullable=True)
word_count = Column("word_count", Integer, nullable=True, default=0)
# 所属session_id
session_id = Column("session_id", String(80), nullable=False)
completed = Column("completed", String(80), nullable=False, default="0")
status = Column("status", String(80), nullable=False, default="ACTIVE")
# 创建时间
create_time = Column("create_time", DateTime, default=datetime.datetime.now)
# 数据库未创建表的话自动创建表
Base.metadata.create_all(engine)
================================================
FILE: talkieai-server/app/main.py
================================================
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from starlette.responses import JSONResponse
from app.config import Config
from app.core.exceptions import UserAccessDeniedException
from app.core.logging import logging
from app.models.response import ApiResponse
from app.api.sys_routes import router as sys_routes
from app.api.account_routes import router as account_routes
from app.api.message_routes import router as message_routes
from app.api.session_routes import router as session_routes
from app.api.topics_route import router as topic_routes
app = FastAPI()
# Enables CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(account_routes, prefix=f"{Config.API_PREFIX}/v1")
app.include_router(topic_routes, prefix=f"{Config.API_PREFIX}/v1")
app.include_router(sys_routes, prefix=f"{Config.API_PREFIX}/v1")
app.include_router(session_routes, prefix=f"{Config.API_PREFIX}/v1")
app.include_router(message_routes, prefix=f"{Config.API_PREFIX}/v1")
@app.exception_handler(Exception)
async def conflict_error_handler(_, exc: Exception):
"""全局异常处理"""
logging.error(exc)
# 返回状态码仍为200,exc的错误信息放到ApiResponse中以json格式方式返回并且可以跨域访问
return JSONResponse(
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*",
},
content=ApiResponse(code="500", status="FAILED", message=str(exc)).__dict__,
)
# UserAccessDeniedException异常处理状态码为403
@app.exception_handler(UserAccessDeniedException)
async def user_access_denied_error_handler(_, exc: UserAccessDeniedException):
"""全局异常处理"""
logging.error(exc)
# 返回状态码仍为200,exc的错误信息放到ApiResponse中以json格式方式返回并且可以跨域访问
return JSONResponse(
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*",
},
content=ApiResponse(code="403", status="FAILED", message=str(exc)).__dict__,
)
================================================
FILE: talkieai-server/app/models/__init__.py
================================================
================================================
FILE: talkieai-server/app/models/account_models.py
================================================
from enum import Enum
from typing import List, Dict
from pydantic import BaseModel, constr
class MessageType(Enum):
"""消息类型"""
ACCOUNT = "ACCOUNT"
SYSTEM = "SYSTEM"
class WechatLoginDTO(BaseModel):
code: str = None
state: str = None
class VisitorLoginDTO(BaseModel):
fingerprint: constr(min_length=15)
class ChatDTO(BaseModel):
"""聊天"""
message: str | None = None
file_name: str | None = None
class MessagePracticeDTO(BaseModel):
"""句子练习"""
file_name: str = None
class TransformSpeechDTO(BaseModel):
"""消息转语音"""
message_id: constr(min_length=5)
class VoiceTranslateDTO(BaseModel):
"""语音转文字"""
file_name: constr(min_length=1)
class TranslateDTO(BaseModel):
"""翻译"""
message_id: constr(min_length=1)
class TranslateTextDTO(BaseModel):
"""翻译"""
text: constr(min_length=1)
session_id: str = None
class GrammarDTO(BaseModel):
"""分析英文的语法错误"""
message_id: constr(min_length=1)
class WordDetailDTO(BaseModel):
"""单词详情"""
word: constr(min_length=1)
class WordPracticeDTO(BaseModel):
"""单词练习"""
session_id: str = None
word: constr(min_length=1)
file_name: constr(min_length=1)
class CollectDTO(BaseModel):
"""收藏单词或者句子"""
type: constr(min_length=1)
message_id: str = None
content: str = None
class PromptDTO(BaseModel):
"""帮助用户生成提示句"""
session_id: constr(min_length=1)
class AccountSettingsDTO(BaseModel):
auto_playing_voice: bool = True
playing_voice_speed: str = "1.0"
auto_text_shadow: bool = True
auto_pronunciation: bool = True
class CreateSessionDTO(BaseModel):
role_name: str
class UpdateRoleDTO(BaseModel):
language: str
role_name: str
style: str = None
avatar: str
local_name: str
class UpdateLanguageDTO(BaseModel):
language: constr(min_length=1)
class AccountSettingsDTO(BaseModel):
target_language: str | None = None
speech_role_name: str | None = None
auto_playing_voice: int = 1
playing_voice_speed: str = "1.0"
auto_text_shadow: int = 1
auto_pronunciation: int = 1
================================================
FILE: talkieai-server/app/models/chat_models.py
================================================
from enum import Enum
from typing import List, Dict
from pydantic import BaseModel, constr
class MessageType(Enum):
"""消息类型"""
ACCOUNT = "ACCOUNT"
SYSTEM = "SYSTEM"
# 智谱AI的第一句提示词
PROMPT = "PROMPT"
class CreateTalkSessionDTO(BaseModel):
language: str
short_name: str
style: str = ""
class CreateSessionDTO(BaseModel):
topic_id: str
class ChatDTO(BaseModel):
"""聊天"""
message: str = None
file_name: str = None
class MessagePracticeDTO(BaseModel):
"""句子练习"""
file_name: str = None
class TransformSpeechDTO(BaseModel):
"""消息转语音"""
message_id: constr(min_length=5)
class VoiceTranslateDTO(BaseModel):
"""语音转文字"""
file_name: constr(min_length=1)
class TranslateDTO(BaseModel):
"""翻译"""
message_id: constr(min_length=1)
class TranslateTextDTO(BaseModel):
"""翻译"""
text: constr(min_length=1)
class TransformContentSpeechDTO(BaseModel):
"""内容转语音"""
session_id: str | None = None
content: constr(max_length=500)
speech_role_name: str | None = None
speech_role_style: str | None = None
speech_style: str = ""
speech_rate: str = "1.0"
class GrammarDTO(BaseModel):
"""分析英文的语法错误"""
message_id: constr(min_length=1)
class PronunciationDTO(BaseModel):
"""语音评估"""
message_id: constr(min_length=1)
class WordDetailDTO(BaseModel):
"""单词详情"""
language: str = "en-US"
session_id: str = None
word: constr(min_length=1)
class WordPracticeDTO(BaseModel):
"""单词练习"""
session_id: str = None
word: constr(min_length=1)
file_name: constr(min_length=1)
class PromptDTO(BaseModel):
"""帮助用户生成提示句"""
session_id: constr(min_length=1)
================================================
FILE: talkieai-server/app/models/response.py
================================================
class ApiResponse:
def __init__(self, code: str = '200', status: str = 'SUCCESS', data=None, message: str = 'success'):
self.code = code
self.status = status
self.data = data
self.message = message
================================================
FILE: talkieai-server/app/models/sys_models.py
================================================
from enum import Enum
from typing import List, Dict
from pydantic import BaseModel, constr
class UpdateLanguageDTO(BaseModel):
language: constr(min_length=1)
class FeedbackDTO(BaseModel):
content: constr(min_length=1)
contact: str = None
================================================
FILE: talkieai-server/app/models/topic_models.py
================================================
from enum import Enum
from typing import List, Dict
from pydantic import BaseModel, constr
class CreateSessionDTO(BaseModel):
topic_id: str
class TopicCreateDTO(BaseModel):
ai_role: str
my_role: str
topic: str
================================================
FILE: talkieai-server/app/services/__init__.py
================================================
from app.services.sys_service import SysService
from app.services.account_service import AccountService
from app.services.chat_service import ChatService
from app.services.topic_service import TopicService
from app.db import SessionLocal
# 检查初始化数据
db = SessionLocal()
sys_service = SysService(db)
account_service = AccountService(db)
topic_service = TopicService(db)
chat_service = ChatService(db)
db.close()
================================================
FILE: talkieai-server/app/services/account_service.py
================================================
import json
import os
import re
import datetime
from sqlalchemy.orm import Session
from app.config import Config
from app.core import auth, azure_voice
from app.core.azure_voice import *
from app.core.exceptions import UserAccessDeniedException
from app.core.utils import *
from app.db.sys_entities import *
from app.db.account_entities import *
from app.db.chat_entities import *
from app.models.account_models import *
from app.core.logging import logging
from app.ai import chat_ai
from app.ai.models import *
from app.core.logging import logging
from app.core.language import *
from app.core.language import *
MESSAGE_SYSTEM = "SYSTEM"
class AccountService:
def __init__(self, db: Session):
self.db = db
def visitor_login(self, fingerprint: str, client_host: str, user_agent: str = None):
"""先检查此ip下是否有用户,如果有,直接返回ip下的用户,如果没有,就生成新的访客"""
visitor = (
self.db.query(AccountEntity).filter_by(fingerprint=fingerprint).first()
)
if not visitor:
visitor = AccountEntity(
id=f"visitor_{short_uuid()}",
fingerprint=fingerprint,
client_host=client_host,
user_agent=user_agent,
)
self.db.add(visitor)
self.db.commit()
self.__check_and_init_default_settings(visitor.id)
return auth.init_token(visitor.id, visitor.id)
def collect(self, dto: CollectDTO, account_id: str):
"""用户收藏,根据类型保存到AccountCollectEntity,保存前先做检查,如果已经存在,则不需要再进行保存"""
# 先检查是否已经存在,如果已经存在,就不需要再进行保存
if dto.message_id:
collect = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, message_id=dto.message_id)
.first()
)
else:
collect = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, type=dto.type, content=dto.content)
.first()
)
if collect:
if collect.deleted == 1:
collect.deleted = 0
collect.update_time = datetime.datetime.now()
self.db.commit()
return
# 查询出session
if dto.message_id:
message = (
self.db.query(MessageEntity)
.filter_by(id=dto.message_id, account_id=account_id)
.first()
)
content = message.content
else:
content = dto.content
# 获得翻译
source_language = self.get_account_source_language(account_id)
translation = chat_ai.invoke_translate(
TranslateParams(target_language=source_language, content=content)
)
# 如果没有任何符号且只有单独一个单词,则type为WORD,否则为 SENTENCE
if re.match(r"^[a-zA-Z]+$", content) and len(content.split(" ")) == 1:
type = "WORD"
else:
type = "SENTENCE"
account_collect = AccountCollectEntity(
account_id=account_id,
type=type,
message_id=dto.message_id,
content=content,
translation=translation,
)
self.db.add(account_collect)
self.db.commit()
return
def get_account_info(self, account_id: str):
"""获取用户的今日聊天次数与总次数返回"""
# 如果是访客,就返回访客的信息
if account_id.startswith("visitor_"):
account = self.db.query(AccountEntity).filter_by(id=account_id).first()
else:
# 不再支持account
raise Exception("不再支持account")
if not account:
raise Exception("User not found")
result = {
"account_id": account_id,
"today_chat_count": self.get_user_current_day_system_message_count(
account_id
),
"total_chat_count": self.get_user_system_message_count(account_id),
}
account_settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
target_language = account_settings.target_language
result["target_language"] = target_language
result["target_language_label"] = get_label_by_language(target_language)
return result
def get_collect(self, dto: CollectDTO, account_id: str):
"""获取用户是否已经收藏的数据"""
if dto.message_id:
collect = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, message_id=dto.message_id)
.first()
)
else:
collect = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, type=dto.type, content=dto.content)
.first()
)
if collect and collect.deleted == 0:
return {"is_collect": True}
else:
return {"is_collect": False}
def cancel_collect(self, dto: CollectDTO, account_id: str):
"""取消收藏"""
if dto.message_id:
collect = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, message_id=dto.message_id)
.first()
)
else:
collect = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, type=dto.type, content=dto.content)
.first()
)
if collect:
collect.deleted = 1
collect.update_time = datetime.datetime.now()
self.db.commit()
return
def get_collects(self, type: str, page: int, page_size: int, account_id: str):
"""获取用户收藏的列表信息"""
query = (
self.db.query(AccountCollectEntity)
.filter_by(account_id=account_id, type=type, deleted=0)
.order_by(AccountCollectEntity.create_time.desc())
)
collects = query.offset((page - 1) * page_size).limit(page_size).all()
# 获取总数
total = query.count()
result = []
for collect in collects:
result.append(
{
"id": collect.id,
"type": collect.type,
"content": collect.content,
"translation": collect.translation,
"message_id": collect.message_id,
"create_time": date_to_str(collect.create_time),
}
)
return {"total": total, "list": result}
def get_settings(self, account_id: str):
"""获取AccountSettingsEntity中key 为 auto_playing_voice, playing_voice_speed, auto_text_shadow, auto_pronunciation的配置"""
settings = (
self.db.query(AccountSettingsEntity)
.filter(AccountSettingsEntity.account_id == account_id)
.first()
)
# 设置 vo dict,里面的值与settings中的值一致
vo = {
"auto_playing_voice": settings.auto_playing_voice,
"playing_voice_speed": settings.playing_voice_speed,
"auto_text_shadow": settings.auto_text_shadow,
"auto_pronunciation": settings.auto_pronunciation,
"speech_role_name": settings.speech_role_name,
"target_language": settings.target_language,
}
# 如果存在 speech_role_name,则从azure_voice_configs_group获取对应值,取local_name
if settings.speech_role_name:
voice_role_config = get_azure_voice_role_by_short_name(
settings.speech_role_name
)
vo["speech_role_name_label"] = voice_role_config["local_name"]
return vo
def save_settings(self, dto: AccountSettingsDTO, account_id: str):
"""保存用户设置"""
account_settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
if dto.auto_playing_voice is not None:
account_settings.auto_playing_voice = dto.auto_playing_voice
if dto.playing_voice_speed is not None:
account_settings.playing_voice_speed = dto.playing_voice_speed
if dto.auto_text_shadow is not None:
account_settings.auto_text_shadow = dto.auto_text_shadow
if dto.auto_pronunciation is not None:
account_settings.auto_pronunciation = dto.auto_pronunciation
if dto.speech_role_name is not None:
account_settings.speech_role_name = dto.speech_role_name
if dto.target_language is not None:
if dto.target_language != account_settings.target_language:
# 获取语言对应的语音角色
speech_role_name = get_azure_language_default_role(
dto.target_language
)
account_settings.speech_role_name = speech_role_name
account_settings.target_language = dto.target_language
self.db.commit()
def update_role_setting(self, dto: UpdateRoleDTO, account_id: str):
"""选择角色"""
# 先删除 account_settings 中的数据
account_settings_entity = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
# dto 转json格式保存
account_settings_entity.role_setting = json.dumps(dto.model_dump())
self.db.commit()
return {
"account_id": account_id,
"role_name": dto.role_name,
"role_style": dto.style,
}
def get_role_setting(self, account_id: str):
"""获取用户当前设置的角色"""
# 先删除 account_settings 中的数据
account_settings_entity = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
role_setting = get_azure_voice_role_by_short_name(account_settings_entity.speech_role_name)
# 补充头像,根据性别补充头像
if role_setting['gender'] == 1:
role_setting['role_image'] = 'http://qiniu.prejade.com/1597936949107363840/talkie/images/en-US_JennyNeural.png'
else:
role_setting['role_image'] = 'http://qiniu.prejade.com/1597936949107363840/talkie/images/en-US_Guy.png'
return {
"role_setting": role_setting
}
def get_account_source_language(self, account_id: str):
"""获取用户的学习语言"""
settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
if settings:
return settings.source_language
else:
return Config.DEFAULT_SOURCE_LANGUAGE
def get_account_target_language(self, account_id: str):
"""获取用户的目标语言"""
settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
if settings:
return settings.target_language
else:
return Config.DEFAULT_TARGET_LANGUAGE
def get_user_current_day_system_message_count(self, account_id: str):
"""获取用户当天系统消息次数"""
# 获取当天0点的时间进行筛选
today = day_to_str(datetime.datetime.now())
return (
self.db.query(MessageEntity)
.filter_by(account_id=account_id, type=MessageType.SYSTEM.value)
.filter(MessageEntity.create_time >= today)
.count()
)
def get_user_system_message_count(self, account_id: str):
"""获取用户当天系统消息次数"""
return (
self.db.query(MessageEntity)
.filter_by(account_id=account_id, type=MessageType.SYSTEM.value)
.count()
)
def __check_and_init_default_settings(self, account_id: str):
"""检查并初始化用户的默认设置"""
# 先检查是否已经存在,如果已经存在,就不需要再进行保存
settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
if not settings:
speech_role_name = get_azure_language_default_role(
Config.DEFAULT_TARGET_LANGUAGE
)
settings = AccountSettingsEntity(
account_id=account_id,
target_language=Config.DEFAULT_TARGET_LANGUAGE,
source_language=Config.DEFAULT_SOURCE_LANGUAGE,
speech_role_name=speech_role_name,
)
self.db.add(settings)
self.db.commit()
================================================
FILE: talkieai-server/app/services/chat_service.py
================================================
from sqlalchemy.orm import Session
from app.core.utils import *
from app.db.account_entities import *
from app.db.chat_entities import *
from app.db.sys_entities import *
from app.db.topic_entities import *
from app.models.account_models import *
from app.models.chat_models import *
from app.services.account_service import AccountService
from app.services.topic_service import TopicService
from app.ai.models import *
from app.ai import chat_ai
from app.core.azure_voice import *
from app.core.exceptions import *
MESSAGE_SYSTEM = "SYSTEM"
# 读取data下 language_demo_map.json 生成对应字典
language_demo_map = {}
with open("data/language_demo_map.json", "r") as f:
language_demo_map = json.load(f)
class ChatService:
"""聊天核心类,会调用account_service与topic_service, 反向不可以引用"""
def __init__(self, db: Session):
self.db = db
self.account_service = AccountService(db)
self.topic_service = TopicService(db)
def get_settings_languages_example(self, language: str, account_id: str):
"""获取语言下的示例"""
# 获取语言下的示例
# 语言没有国家 所以去掉后面的国家后缀
language = language.split("-")[0]
return language_demo_map[language]
def get_default_session(self, account_id: str):
"""获取用户的默认会话, 如果没有默认会话,就创建一个"""
session = (
self.db.query(MessageSessionEntity)
.filter_by(
account_id=account_id,
is_default=1,
)
.order_by(MessageSessionEntity.create_time.desc())
.first()
)
if not session:
# 为用户创建一个默认的session
return self.create_session(
account_id,
)
return self.__convert_session_model(session)
def get_session(self, session_id: str, account_id: str):
"""获取会话详情"""
session = self.__get_and_check_session(session_id, account_id)
result = self.__convert_session_model(session)
# 获取会话下的消息
result["messages"] = self.get_session_messages(session_id, account_id, 1, 100)
return result
def get_session_greeting(self, session_id: str, account_id: str):
"""需要会话没有任何消息时,需要返回的问候语"""
# 检查session是否存在
session = self.__get_and_check_session(session_id, account_id)
# 检查会话下是否已经有了消息
self.__check_has_messages(session_id, account_id)
# 区分自由聊天与话题聊天
if session.type == "CHAT":
language = self.account_service.get_account_target_language(account_id)
result = chat_ai.invoke_greet(GreetParams(language=language))
elif session.type == "TOPIC":
topic_greet_params = self.topic_service.get_topic_greet_params(session.id)
result = chat_ai.topic_invoke_greet(topic_greet_params)
sequence = self.__get_message_sequence()
add_message = self.__add_system_message(session_id, account_id, result, "", sequence + 1)
self.db.add(add_message)
self.db.commit()
self.db.flush()
self.__refresh_session_message_count(session_id)
return self.initMessageResult(add_message)
def send_session_message(self, session_id: str, dto: ChatDTO, account_id: str):
"""发送消息"""
# 如果有file_name却没有message,需要解析出message
if not dto.file_name and not dto.message:
raise Exception("Message or file_name is required")
session = self.__get_and_check_session(session_id, account_id)
account_settins = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
target_language = account_settins.target_language
if dto.message:
send_message_content = dto.message
else:
send_message_content = speech_translate_text(
voice_file_get_path(dto.file_name), target_language
)
# 获取前面的sequence
sequence = self.__get_message_sequence()
add_account_message = self.__add_account_message(
account_id, session_id, send_message_content, sequence + 1, dto.file_name
)
send_message_id = add_account_message.id
message_history = (
self.db.query(MessageEntity)
.filter(MessageEntity.session_id == session_id)
.order_by(MessageEntity.create_time.desc())
.slice(0, 5)
.all()
)
messages = []
for message in reversed(message_history):
if message.type == MessageType.SYSTEM.value:
messages.append({"role": "assistant", "content": message.content})
else:
messages.append({"role": "user", "content": message.content})
# 补充上用户新加的这个
messages.append({"role": "user", "content": send_message_content})
completed = False
if session.type == 'CHAT':
speech_role_name = account_settins.speech_role_name
styles = []
if speech_role_name:
voice_role_config = get_azure_voice_role_by_short_name(speech_role_name)
styles = voice_role_config["style_list"]
message_params = MessageParams(
language=target_language, name=Config.AI_NAME, messages=messages, styles=styles
)
invoke_result = chat_ai.invoke_message(message_params)
elif session.type == 'TOPIC':
topic_message_params = self.topic_service.get_topic_message_params(session.id)
topic_message_params.messages = messages
invoke_result = chat_ai.topic_invoke_message(topic_message_params)
completed = invoke_result.completed
add_system_message = self.__add_system_message(
session_id,
account_id,
invoke_result.message,
invoke_result.message_style,
sequence + 2,
)
self.db.add(add_account_message)
self.db.add(add_system_message)
self.db.commit()
self.db.flush()
self.__refresh_session_message_count(session_id)
return {
"data": invoke_result.message,
"id": add_system_message.id,
"session_id": session_id,
"send_message_id": send_message_id,
"send_message_content": send_message_content,
"create_time": date_to_str(add_system_message.create_time),
"completed": completed,
}
def message_practice(
self, message_id: str, dto: MessagePracticeDTO, account_id: str
):
"""用户发送过的消息进行练习"""
message = self.db.query(MessageEntity).filter_by(id=message_id).first()
if not message:
raise Exception("Message not found")
target_language = self.account_service.get_account_target_language(account_id)
return word_speech_pronunciation(
message.content, voice_file_get_path(dto.file_name), target_language
)
def get_word(self, dto: WordDetailDTO, account_id: str):
"""通过AI获取单词的音标与翻译"""
# 先查询数据库中是否有数据,如果有数据就直接返回
word = self.db.query(SysCacheEntity).filter_by(key=f"word_{dto.word}").first()
if word:
return json.loads(word.value)
invoke_result = chat_ai.invoke_word_detail(WordDetailParams(word=dto.word))
result = invoke_result.__dict__
result["original"] = dto.word
# result 转换成字符串进行保存
sys_cache = SysCacheEntity(key=f"word_{dto.word}", value=json.dumps(result))
self.db.add(sys_cache)
self.db.commit()
return result
def grammar_analysis(self, dto: GrammarDTO, account_id: str):
message = self.db.query(MessageEntity).filter_by(id=dto.message_id).first()
# 检查AccountGrammarEntity是否已经存在数据,如果存在就直接返回已经保存的数据
message_grammar = (
self.db.query(MessageGrammarEntity)
.filter_by(
message_id=dto.message_id, file_name=message.file_name, type="GRAMMAR"
)
.first()
)
if message_grammar:
return json.loads(message_grammar.result)
content = message.content
target_language = self.account_service.get_account_target_language(account_id)
result = chat_ai.invoke_grammar_analysis(
GrammarAnalysisParams(language=target_language, content=content)
).__dict__
result["original"] = content
# result是json格式的字符串,把result 解析成json返回
# 结果以字符串方式保存到数据库中
message_grammar = MessageGrammarEntity(
account_id=account_id,
session_id=message.session_id,
message_id=dto.message_id,
file_name=message.file_name,
type="GRAMMAR",
result=json.dumps(result),
)
self.db.add(message_grammar)
self.db.commit()
return result
def word_practice(self, dto: WordPracticeDTO, account_id: str):
"""单词发音练习"""
target_language = self.account_service.get_account_target_language(account_id)
return word_speech_pronunciation(
dto.word, voice_file_get_path(dto.file_name), language=target_language
)
def pronunciation(self, dto: PronunciationDTO, account_id: str):
"""发单评估"""
# 先根据message_id查询出message
message = self.db.query(MessageEntity).filter_by(id=dto.message_id).first()
if not message:
raise UserAccessDeniedException("message不存在")
file_name = message.file_name
if not file_name:
raise UserAccessDeniedException("message中没有语音文件")
# 检查AccountGrammarEntity是否已经存在数据,如果存在就直接返回已经保存的数据
grammar = (
self.db.query(MessageGrammarEntity)
.filter_by(
message_id=dto.message_id,
file_name=message.file_name,
type="PRONUNCIATION",
)
.first()
)
if grammar:
return json.loads(grammar.result)
file_full_path = voice_file_get_path(file_name)
# 检查文件是否存在
if not os.path.exists(file_full_path):
raise UserAccessDeniedException("语音文件不存在")
target_language = self.account_service.get_account_target_language(account_id)
# 进行评分
try:
session = (
self.db.query(MessageSessionEntity)
.filter_by(id=message.session_id)
.first()
)
pronunciation_result = word_speech_pronunciation(
message.content, file_full_path, language=target_language
)
logging.info("end")
except Exception as e:
# 输出错误信息
logging.exception(
f"file_full_path:{file_full_path}\n content:{message.content}", e
)
raise UserAccessDeniedException("语音评估失败")
# 结果以字符串方式保存到数据库中
message_grammar = MessageGrammarEntity(
account_id=account_id,
session_id=message.session_id,
message_id=dto.message_id,
file_name=message.file_name,
type="PRONUNCIATION",
result=json.dumps(pronunciation_result),
)
self.db.add(message_grammar)
self.db.commit()
return pronunciation_result
def message_speech_content(self, dto: TransformContentSpeechDTO, account_id: str):
"""如果file表中已经存在文件的保存,则直接返回,如果不存在,生成一份并保存"""
# 根据convert_language与speech_role_name,speech_rate,speech_style来生成唯一标识,用于生成缓存的key
# 获取用户语言
account_settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
target_language = account_settings.target_language
set_speech_role_name = None
set_speech_role_style = ""
if dto.speech_role_name:
set_speech_role_name = dto.speech_role_name
if dto.speech_role_style:
set_speech_role_style = dto.speech_role_style
elif account_settings.speech_role_name:
set_speech_role_name = account_settings.speech_role_name
set_speech_rate = "1.0"
if dto.speech_rate:
set_speech_rate = dto.speech_rate
elif account_settings.speech_role_speed:
set_speech_rate = account_settings.speech_role_speed
content_md5 = hashlib.md5(dto.content.encode("utf-8")).hexdigest()
key = f"content_{set_speech_role_name}_{set_speech_role_style}_{set_speech_rate}_{content_md5}"
file_module = "SPEECH_CONTENT_VOICE"
# 对key进行md5加密
file_detail = (
self.db.query(FileDetail)
.filter_by(module=file_module, module_id=key, deleted=0)
.first()
)
if file_detail:
# 检查文件是否存在,只有文件存在情况下才进行返回
if os.path.exists(voice_file_get_path(file_detail.file_name)):
return {"file": file_detail.file_name}
else:
# 如果文件不存在,就删除数据库中的记录,重新生成
file_detail.deleted = 1
self.db.commit()
# 调用speech组件,将speech_content转换成语音文件
filename = f"{key}.wav"
full_file_name = voice_file_get_path(filename)
if set_speech_rate != "1.0" or set_speech_role_style:
speech_by_ssml(
dto.content,
full_file_name,
voice_name=set_speech_role_name,
speech_rate=set_speech_rate,
feel=set_speech_role_style,
targetLang=target_language,
)
else:
speech_default(
dto.content, full_file_name, target_language, set_speech_role_name
)
file_detail = FileDetail(
id=short_uuid(),
file_path=filename,
module=file_module,
file_name=filename,
module_id=key,
file_ext="wav",
created_by=account_id,
)
self.db.add(file_detail)
self.db.commit()
self.db.flush()
return {"file": file_detail.file_name}
def message_speech(self, message_id: str, account_id: str):
"""文字转语音"""
# 如果没有,就生成一个
message = self.db.query(MessageEntity).filter_by(id=message_id).first()
# 获取用户的语音配置
account_settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
target_language = account_settings.target_language
voice_name = account_settings.speech_role_name
speech_speed = account_settings.playing_voice_speed
filename = f"message_{message.id}_{voice_name}_{speech_speed}.wav"
full_file_name = voice_file_get_path(filename)
voice_role_style = ""
if message.style:
voice_role_style = message.style
speech_by_ssml(
message.content,
full_file_name,
voice_name=voice_name,
speech_rate=speech_speed,
feel=voice_role_style,
targetLang=target_language,
)
file_detail = FileDetail(
id=short_uuid(),
file_path=filename,
module="SPEECH_VOICE",
file_name=filename,
module_id=message_id,
file_ext="wav",
created_by=account_id,
)
self.db.add(file_detail)
message.file_name = filename
self.db.commit()
return {"file": file_detail.file_name}
def create_session(self, account_id: str):
"""为用户创建新的session,并且设置成默认的session"""
session = MessageSessionEntity(
id=f"session_{short_uuid()}", account_id=account_id, is_default=1
)
self.db.add(session)
self.db.commit()
return self.__convert_session_model(session)
def get_session_messages(
self, session_id: str, account_id: str, page: int, page_size: int
):
query = (
self.db.query(MessageEntity)
.filter_by(session_id=session_id, account_id=account_id, deleted=0)
.filter(
MessageEntity.type.in_(
[MessageType.ACCOUNT.value, MessageType.SYSTEM.value]
)
)
)
messages = (
query.order_by(MessageEntity.sequence.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
# 获取总数
total = query.count()
result = []
for message in reversed(messages):
result.append(self.initMessageResult(message))
# 如果是我的消息,则检查是否进行过语音分析,如果进行过就加载分析结果
self.initOwnerMessagePronunciation(result)
return {"total": total, "list": result}
def prompt_sentence(self, dto: PromptDTO, account_id: str):
"""提示用户下一句话"""
# 查询出session中最后5条消息
messageEntities = (
self.db.query(MessageEntity)
.filter_by(session_id=dto.session_id)
.order_by(MessageEntity.create_time.desc())
.limit(5)
.all()
)
messages = []
for message in messageEntities:
messages.append(self.initMessageResult(message))
target_language = self.account_service.get_account_target_language(account_id)
return chat_ai.invoke_prompt_sentence(
PromptSentenceParams(language=target_language, messages=messages)
)
def delete_all_session_messages(self, session_id: str, account_id: str):
"""把所有的消息都调整为deleted=1"""
messages = (
self.db.query(MessageEntity)
.filter_by(session_id=session_id, account_id=account_id, deleted=0)
.all()
)
for message in messages:
message.deleted = 1
self.db.commit()
return True
def transform_text(self, session_id: str, dto: VoiceTranslateDTO, account_id: str):
"""语音解析成文字"""
result = speech_translate_text(
voice_file_get_path(dto.file_name),
self.account_service.get_account_target_language(account_id),
)
return result
def delete_latest_session_messages(self, session_id: str, account_id: str):
"""查出最近的一条type为ACCOUNT的数据,并且把create_time之后的数据全部调整为deleted=1,删除成功后需要返回所有删除成功的message的id"""
message = (
self.db.query(MessageEntity)
.filter_by(
session_id=session_id,
account_id=account_id,
type=MessageType.ACCOUNT.value,
deleted=0,
)
.order_by(MessageEntity.create_time.desc())
.first()
)
if message:
# 获取所有需要删除的数据
messages = (
self.db.query(MessageEntity)
.filter_by(session_id=session_id, deleted=0)
.filter(MessageEntity.create_time >= message.create_time)
.all()
)
for message in messages:
message.deleted = 1
self.db.commit()
return [message.id for message in messages]
return []
def initOwnerMessagePronunciation(self, result):
# 过滤出所有role为USER的id列表,然后根据id列表获取所有的message_pronunciation,再组装到item中,不存在则组装None
user_message_ids = [item["id"] for item in result if item["role"] == "USER"]
message_pronunciations = (
self.db.query(MessageGrammarEntity)
.filter(
MessageGrammarEntity.message_id.in_(user_message_ids),
MessageGrammarEntity.type == "PRONUNCIATION",
)
.all()
)
for item in result:
if item["role"] == "USER":
item["pronunciation"] = None
for message_pronunciation in message_pronunciations:
if message_pronunciation.message_id == item["id"]:
item["pronunciation"] = json.loads(message_pronunciation.result)
break
def translate_source_language(self, dto: TranslateTextDTO, account_id: str):
"""翻译成源语言"""
source_language = self.account_service.get_account_source_language(account_id)
result = self.translate_language(dto.text, source_language)
return result
def translate_setting_language(self, dto: TranslateTextDTO, account_id: str):
"""翻译成目标语言,也就是用户学习的语言"""
# 获取用户配置language
account_settings = (
self.db.query(AccountSettingsEntity)
.filter_by(account_id=account_id)
.first()
)
result = self.translate_language(dto.text, account_settings.target_language)
return result
def translate_language(self, content: str, language: str):
"""翻译成参数中配置的语言"""
result = chat_ai.invoke_translate(
TranslateParams(target_language=language, content=content)
)
return result
def translate_message(self, message_id: str, account_id: str):
# 检查是否已经生成了对应翻译,生成的话直接返回
message_translate = (
self.db.query(MessageTranslateEntity)
.filter_by(message_id=message_id)
.first()
)
if message_translate:
return message_translate.target_text
message = self.db.query(MessageEntity).filter_by(id=message_id).first()
content = message.content
source_language = self.account_service.get_account_source_language(account_id)
target_language = self.account_service.get_account_target_language(account_id)
result = self.translate_source_language(
TranslateTextDTO(text=content), account_id
)
account_translate = MessageTranslateEntity(
account_id=account_id,
session_id=message.session_id,
message_id=message_id,
target_language=target_language,
source_language=source_language,
source_text=content,
target_text=result,
)
self.db.add(account_translate)
self.db.commit()
return result
def initMessageResult(self, message: MessageEntity):
return {
"role": "ASSISTANT" if message.type == MessageType.SYSTEM.value else "USER",
"content": message.content,
"file_name": message.file_name,
"id": message.id,
"create_time": date_to_str(message.create_time),
"session_id": message.session_id,
}
def __convert_session_model(self, session: MessageSessionEntity):
return {
"id": session.id,
"type": session.type,
"message_count": session.message_count,
"create_time": date_to_str(session.create_time),
"friendly_time": friendly_time(date_to_str(session.create_time)),
}
def __add_account_message(
self, account_id: str, session_id: str, content: str, sequence: int, file_name: str = None
):
"""添加用户消息"""
message = MessageEntity(
id=short_uuid(),
account_id=account_id,
sender=account_id,
session_id=session_id,
receiver=MESSAGE_SYSTEM,
type=MessageType.ACCOUNT.value,
content=content,
file_name=file_name,
length=len(content),
sequence=sequence,
)
return message
def __add_system_message(
self, session_id, account_id: str, content: str, style: str, sequence: int
) -> MessageEntity:
"""添加系统消息"""
add_message = MessageEntity(
id=short_uuid(),
account_id=account_id,
sender=MESSAGE_SYSTEM,
session_id=session_id,
receiver=account_id,
type=MessageType.SYSTEM.value,
content=content,
style=style,
length=len(content),
sequence=sequence,
)
return add_message
def __refresh_session_message_count(self, session_id: str):
"""刷新session的消息数量, 需要排除deleted为1的数据"""
count = (
self.db.query(MessageEntity)
.filter(MessageEntity.session_id == session_id, MessageEntity.deleted == 0)
.count()
)
self.db.query(MessageSessionEntity).filter(
MessageSessionEntity.id == session_id
).update({"message_count": count})
self.db.commit()
self.db.flush()
def __get_and_check_session(
self, session_id: str, account_id: str
) -> MessageSessionEntity:
"""检查会话是否存在"""
session = (
self.db.query(MessageSessionEntity)
.filter_by(id=session_id, account_id=account_id)
.first()
)
if not session:
raise Exception("Session not found")
return session
def __check_has_messages(self, session_id: str, account_id: str):
"""检查会话下是否已经有了消息"""
messages = (
self.db.query(MessageEntity)
.filter_by(session_id=session_id, account_id=account_id, deleted=0)
.order_by(MessageEntity.create_time.desc())
.slice(0, 1)
.all()
)
if len(messages) == 1:
raise Exception("Session has messages")
def __get_message_sequence(self):
"""获取当前最大的sequence"""
sequence = (
self.db.query(MessageEntity)
.filter_by(deleted=0)
.order_by(MessageEntity.sequence.desc())
.first()
)
if sequence:
return sequence.sequence
return 0
================================================
FILE: talkieai-server/app/services/sys_service.py
================================================
import json
import os
from pydub import AudioSegment
from sqlalchemy.orm import Session
from app.core.utils import *
from app.core.exceptions import *
from app.core.utils import *
from app.db.account_entities import *
from app.db.sys_entities import *
from app.core.logging import logging
from app.models.sys_models import *
from app.core.language import *
# 读取data下 language_demo_map.json 生成对应字典
LANGUAGE_DICT_KEY = "learn_language"
language_demo_map = {}
with open("data/language_demo_map.json", "r") as f:
language_demo_map = json.load(f)
class SysService:
def __init__(self, db: Session):
self.db = db
# 查出所有系统配置的语言,并且生成默认角色与语气的信息
self._check_and_init_default_learn_languages()
def get_settings_roles(self, locale: str, account_id: str):
"""根据语言获取语言下所有支持的角色"""
roles = (
self.db.query(SettingsRoleEntity)
.filter_by(locale=locale, deleted=0)
.order_by(SettingsRoleEntity.name.asc())
.all()
)
result = []
for role in roles:
# 通过roles批量获取所有的style,并且在迭代中进行组装
styles = json.loads(role.styles)
# 转换成value、label格式
styles_data = []
for style in styles:
if style:
styles_data.append(
{"value": style, "label": get_azure_style_label(style)}
)
else:
styles_data.append({"value": "", "label": ""})
result.append(
{
"id": role.id,
"name": role.name,
"locale": role.locale,
"local_name": role.local_name,
"short_name": role.short_name,
"avatar": role.avatar,
"audio": role.audio,
"speech_content": "",
"gender": role.gender,
"styles": styles_data,
}
)
return result
def get_settings_languages_example(self, language: str, account_id: str):
"""获取语言下的示例"""
# 获取语言下的示例
# 语言没有国家 所以去掉后面的国家后缀
language = language.split("-")[0]
languages = (
self.db.query(SettingsLanguageExampleEntity)
.filter_by(language=language)
.first()
)
return languages.example
def get_settings_languages(self, account_id: str):
"""获取用户支持的所有语种"""
# sys_dict_data 获取 dict_type 为 learn_language 的数据
sys_dict_data_entities = (
self.db.query(SysDictDataEntity)
.filter(SysDictDataEntity.dict_type == LANGUAGE_DICT_KEY)
.all()
)
# 取出dict_label 与 dict_value 返回
result = []
for sys_dict_data_entity in sys_dict_data_entities:
result.append(
{
"label": sys_dict_data_entity.dict_label,
"value": sys_dict_data_entity.dict_value,
}
)
return result
def get_settings_languages_example(self, language: str, account_id: str):
"""获取语言下的示例"""
# 获取语言下的示例
# 语言没有国家 所以去掉后面的国家后缀
language = language.split("-")[0]
return language_demo_map[language]
def voice_upload(self, file: UploadFile, account_id: str):
"""用户上传语音文件"""
file_name = save_voice_file(file, account_id)
# 如果上传的是mp3格式(暂时只有android手机只能用mp3格式), 就转换成wav返回, 为了后续azure服务解析音频(mp3会解析失败), 因为chat接口本身比较慢,所以在这里进行转换
if file.filename.endswith(".mp3"):
mp3_file_path = file_get_path(file_name)
wav_file_name = file_name.replace(".mp3", ".wav")
wav_file_path = file_get_path(wav_file_name)
sound = AudioSegment.from_mp3(mp3_file_path)
sound.export(wav_file_path, format="wav")
# mp3文件需要删除
os.remove(mp3_file_path)
file_name = wav_file_name
return {"file": file_name}
def add_feedback(self, dto: FeedbackDTO, account_id: str):
add_feedback = FeedbackEntity(
account_id=account_id, content=dto.content, contact=dto.contact
)
self.db.add(add_feedback)
self.db.commit()
self.db.refresh(add_feedback)
def _check_and_init_default_learn_languages(self):
# 查出所有系统配置的语言
sys_dict_data_entities = (
self.db.query(SysDictDataEntity)
.filter(SysDictDataEntity.dict_type == LANGUAGE_DICT_KEY)
.all()
)
# sys_dict_data_entities 为空的话加载默认的语言
if sys_dict_data_entities:
return
# 加载 data下 sys_language.json文件,生成初始值
with open("data/sys_language.json", "r") as f:
sys_language = json.load(f)
# 保存到数据库中
for language in sys_language:
add_language = SysDictDataEntity(
dict_type=LANGUAGE_DICT_KEY,
dict_label=language["label"],
dict_value=language["value"],
)
self.db.add(add_language)
# 继续检查角色的配置
self._check_and_init_default_roles(add_language.dict_value)
self.db.commit()
def _check_and_init_default_roles(self, locale: str):
"""检查是否初始化了数据,如果未初始化,从azure获取所有的角色,并且保存到数据库中"""
# 查出所有系统配置的角色
roles = (
self.db.query(SettingsRoleEntity).filter_by(locale=locale, deleted=0).all()
)
if roles:
return
roles = [role for role in azure_data if role["locale"] == locale]
# 保存到数据库中
for role in roles:
add_role = SettingsRoleEntity(
name=role["name"],
locale=role["locale"],
local_name=role["local_name"],
short_name=role["short_name"],
gender=role["gender"],
styles=json.dumps(role["style_list"]),
)
self.db.add(add_role)
self.db.commit()
================================================
FILE: talkieai-server/app/services/topic_service.py
================================================
import json
from sqlalchemy.orm import Session
from app.core.utils import *
from app.models.topic_models import *
from app.db.topic_entities import *
from app.db.chat_entities import *
from app.db.account_entities import *
from app.ai.models import *
from app.core.logging import logging
from app.ai import chat_ai
from app.core.azure_voice import *
from app.models.chat_models import *
class TopicService:
def __init__(self, db: Session):
self.db = db
self.__check_and_init_topics()
def get_topic_greet_params(self, session_id: str) -> TopicGreetParams:
"""获取话题的prompt"""
# 根据关联关系取到topic_id
topic_session_relation = (
self.db.query(TopicSessionRelation).filter_by(session_id=session_id).first()
)
topic_entity = (
self.db.query(TopicEntity)
.filter(TopicEntity.id == topic_session_relation.topic_id)
.first()
)
topic_greet_params = TopicGreetParams(
language=topic_entity.language,
prompt=topic_entity.prompt,
)
return topic_greet_params
def get_topic_message_params(self, session_id: str) -> AITopicMessageParams:
"""获取话题的prompt"""
# 根据关联关系取到topic_id
topic_session_relation = (
self.db.query(TopicSessionRelation).filter_by(session_id=session_id).first()
)
topic_entity = (
self.db.query(TopicEntity)
.filter(TopicEntity.id == topic_session_relation.topic_id)
.first()
)
styles = []
if topic_entity.role_short_name:
voice_role_config = get_azure_voice_role_by_short_name(
topic_entity.role_short_name
)
styles = voice_role_config["style_list"]
topic_message_params = AITopicMessageParams(
name=topic_entity.topic_bot_name,
language=topic_entity.language,
prompt=topic_entity.prompt,
speech_role_name=topic_entity.role_short_name,
styles=styles,
)
return topic_message_params
def get_all_topics(self, type: str, account_id: str):
"""获取所有话题组与话题"""
# 获取用户配置,通过用户配置获取language
account_settins = (
self.db.query(AccountSettingsEntity)
.filter(AccountSettingsEntity.account_id == account_id)
.first()
)
# 如果用户没有配置,则使用默认的en-US
if account_settins is None:
language = "en-US"
else:
language = account_settins.target_language
if language == "en-GB":
language = "en-US"
result = []
topic_group_entities = (
self.db.query(TopicGroupEntity)
.filter(
TopicGroupEntity.type == type,
TopicEntity.language == language,
)
.order_by(TopicGroupEntity.sequence.desc())
.all()
)
# 迭代话题组,获取话题组下的话题
for topic_group_entity in topic_group_entities:
topic_entities = (
self.db.query(TopicEntity)
.filter(TopicEntity.group_id == topic_group_entity.id)
.order_by(TopicEntity.sequence.desc())
.all()
)
# 批量查询话题是否已经完成的状态,在后续迭代中补充此属性
topic_ids = []
for topic_entity in topic_entities:
topic_ids.append(topic_entity.id)
topic_history_entities = (
self.db.query(TopicHistoryEntity)
.filter(TopicHistoryEntity.topic_id.in_(topic_ids))
.filter(TopicHistoryEntity.account_id == account_id)
.filter(TopicHistoryEntity.completed == "1")
.all()
)
topic_history_map = {}
for topic_history_entity in topic_history_entities:
topic_history_map[topic_history_entity.topic_id] = topic_history_entity
# 迭代话题数据进行补充
topics = []
for topic_entitity in topic_entities:
topic = {
"id": topic_entitity.id,
"name": topic_entitity.name,
"description": topic_entitity.description,
"prompt": topic_entitity.prompt,
"level": topic_entitity.level,
"image_url": topic_entitity.image_url,
}
# 补充是否已经完成的状态
if topic_entitity.id in topic_history_map:
topic["completed"] = topic_history_map[topic_entitity.id].completed
else:
topic["completed"] = "0"
topics.append(topic)
group = {
"id": topic_group_entity.id,
"name": topic_group_entity.name,
"topics": topics,
}
result.append(group)
return result
def get_topic_detail(self, topic_id: str, account_id: str):
"""获取话题详情"""
topic_entity = (
self.db.query(TopicEntity).filter(TopicEntity.id == topic_id).first()
)
# 获取话题目标
topic_target_entities = (
self.db.query(TopicTargetEntity)
.filter(TopicTargetEntity.topic_id == topic_id)
.order_by(TopicTargetEntity.type)
.all()
)
# 迭代话题目标数据进行补充
# TopicTargetEntity按 type为 MAIN TRIAL 的不同值进行组装
main_targets = []
trial_targets = []
for topic_target_entity in topic_target_entities:
target = {
"id": topic_target_entity.id,
"type": topic_target_entity.type,
"description": topic_target_entity.description,
}
if topic_target_entity.type == "MAIN":
main_targets.append(target)
elif topic_target_entity.type == "TRIAL":
trial_targets.append(target)
result = {
"id": topic_entity.id,
"name": topic_entity.name,
"description": topic_entity.description,
"prompt": topic_entity.prompt,
"image_url": topic_entity.image_url,
"main_targets": main_targets,
"trial_targets": trial_targets,
}
return result
def create_topic_session(self, topic_id: str, account_id: str):
"""基于主题创建一个会话"""
# 获取Topic详情
topic_entity = (
self.db.query(TopicEntity).filter(TopicEntity.id == topic_id).first()
)
# 创建session
session = MessageSessionEntity(
id=f"session_{short_uuid()}",
account_id=account_id,
type="TOPIC",
)
self.db.add(session)
# 创建session与topic的关系
session_topic_relation = TopicSessionRelation(
session_id=session.id,
topic_id=topic_id,
account_id=account_id,
)
self.db.add(session_topic_relation)
# 保存话题历史记录
topic_entity = (
self.db.query(TopicEntity).filter(TopicEntity.id == topic_id).first()
)
topic_history_entity = TopicHistoryEntity(
account_id=account_id,
topic_id=topic_id,
topic_type="TOPIC",
topic_name=topic_entity.name,
completion=0,
session_id=session.id,
)
self.db.add(topic_history_entity)
self.db.commit()
return {"id": session.id}
def complete_topic_session(self, session_id: str, account_id: str):
"""结束话题下的session"""
# 获取话题与session数据
topic_session_relation = (
self.db.query(TopicSessionRelation).filter_by(session_id=session_id).first()
)
topic_id = topic_session_relation.topic_id
topic_entity = (
self.db.query(TopicEntity).filter(TopicEntity.id == topic_id).first()
)
session_entity = (
self.db.query(MessageSessionEntity)
.filter(MessageSessionEntity.id == session_id)
.first()
)
# 取出所有的聊天内容,通过AI进行完成度与评分计算,并计算出所使用的单词数量
message_entities = (
self.db.query(MessageEntity)
.filter(MessageEntity.session_id == session_id)
.order_by(MessageEntity.create_time.asc())
.all()
)
messages = []
for message in message_entities:
if message.type == MessageType.SYSTEM.value:
messages.append(MessageItemParams(role='assistant', content=message.content))
else:
messages.append(MessageItemParams(role='user', content=message.content))
# 计算完成度,取出topic下所有的target
topic_target_entities = (
self.db.query(TopicTargetEntity)
.filter(TopicTargetEntity.topic_id == topic_id)
.all()
)
targets = []
for target in topic_target_entities:
targets.append(target.description)
invoke_result = chat_ai.topic_invoke_complete(
AITopicCompleteParams(
language=topic_entity.language, messages=messages, targets=targets
)
)
# 获取history 并进行记录
topic_history_entity = (
self.db.query(TopicHistoryEntity)
.filter(TopicHistoryEntity.session_id == session_id)
.first()
)
topic_history_entity.completion = invoke_result.targets
topic_history_entity.content_score = invoke_result.score
topic_history_entity.word_count = invoke_result.words
topic_history_entity.suggestion = invoke_result.suggestion
topic_history_entity.completed = "1"
session_entity.completed = "1"
self.db.commit()
def delete_topic_session(self, topic_id: str, session_id: str, account_id: str):
"""把话题下的history的status设置为DELETED"""
topic_history_entity = (
self.db.query(TopicHistoryEntity)
.filter(TopicHistoryEntity.session_id == session_id)
.first()
)
topic_history_entity.status = "DELETED"
self.db.commit()
def get_custom_topic_example(self, account_id: str):
"""获取随机话题"""
return {"my_role": "Jack", "ai_role": "小厨师", "topic": "关于厨房的那些事儿"}
def create_custom_topic(self, dto: TopicCreateDTO, account_id: str):
"""用户创建自己的话题"""
# 获取用户的语言配置信息
account_settins = (
self.db.query(AccountSettingsEntity)
.filter(AccountSettingsEntity.account_id == account_id)
.first()
)
language = account_settins.target_language
if language == "en-GB":
language = "en-US"
account_topic = AccountTopicEntity(
id=f"account_topic_{short_uuid()}",
account_id=account_id,
language=language,
ai_role=dto.ai_role,
my_role=dto.my_role,
topic=dto.topic,
)
return account_topic.id
def get_custom_topic(self, account_id: str):
"""获取用户创建的自定义话题"""
account_topics = (
self.db.query(AccountTopicEntity)
.filter(AccountTopicEntity.account_id == account_id)
.all()
)
result = []
for account_topic in account_topics:
result.append(
{
"id": account_topic.id,
"ai_role": account_topic.ai_role,
"my_role": account_topic.my_role,
"topic": account_topic.topic,
}
)
return result
def get_session_result(self, topic_id: str, session_id: str, account_id: str):
"""获取主题聊天下的结果"""
topic_history_entity = (
self.db.query(TopicHistoryEntity)
.filter(TopicHistoryEntity.session_id == session_id)
.first()
)
return {
"topic_name": topic_history_entity.topic_name,
"main_target_count": topic_history_entity.main_target_count,
"trial_target_count": topic_history_entity.trial_target_count,
"main_target_completed_count": topic_history_entity.main_target_completed_count,
"trial_target_completed_count": topic_history_entity.trial_target_completed_count,
"completion": topic_history_entity.completion,
"audio_score": topic_history_entity.audio_score,
"content_score": topic_history_entity.content_score,
"suggestion": topic_history_entity.suggestion,
"word_count": topic_history_entity.word_count,
}
def get_topic_history(self, topic_id: str, account_id: str):
"""获取话题历史记录,topic_id做为可选参数,为空时查询所有历史记录"""
result = []
if topic_id is None:
topic_history_entities = (
self.db.query(TopicHistoryEntity)
.filter(
TopicHistoryEntity.account_id == account_id,
TopicHistoryEntity.status == "ACTIVE",
)
.order_by(TopicHistoryEntity.create_time.desc())
.all()
)
else:
topic_history_entities = (
self.db.query(TopicHistoryEntity)
.filter(TopicHistoryEntity.account_id == account_id)
.filter(
TopicHistoryEntity.topic_id == topic_id,
TopicHistoryEntity.status == "ACTIVE",
)
.order_by(TopicHistoryEntity.create_time.desc())
.all()
)
# 查出来所有历史记录涉及的Topic, 后面迭代进行补充
topic_ids = []
for topic_history_entity in topic_history_entities:
topic_ids.append(topic_history_entity.topic_id)
topic_entities = (
self.db.query(TopicEntity).filter(TopicEntity.id.in_(topic_ids)).all()
)
# 迭代话题历史记录数据进行补充
for topic_history_entity in topic_history_entities:
topic_id = topic_history_entity.topic_id
# 通过topic_entities取出对应的topic
topic_entity = next(filter(lambda x: x.id == topic_id, topic_entities))
create_time_str = date_to_str(topic_history_entity.create_time)
history = {
"id": topic_history_entity.id,
"topic_id": topic_history_entity.topic_id,
"topic_type": topic_history_entity.topic_type,
"topic_name": topic_history_entity.topic_name,
"completion": topic_history_entity.completion,
"session_id": topic_history_entity.session_id,
"create_time": create_time_str,
"create_time_friendly": friendly_time(create_time_str),
"completed": topic_history_entity.completed,
"topic": {
"id": topic_entity.id,
"topic": topic_entity.name,
"description": topic_entity.description,
"prompt": topic_entity.prompt,
"image_url": topic_entity.image_url,
},
}
result.append(history)
return result
def get_topic_phrases(self, topic_id: str, account_id: str):
"""获取话题短语记录"""
result = []
topic_phrase_entities = (
self.db.query(TopicPhraseEntity)
.filter(TopicPhraseEntity.topic_id == topic_id)
.order_by(TopicPhraseEntity.sequence)
.all()
)
for topic_phrase_entity in topic_phrase_entities:
phrase = {
"id": topic_phrase_entity.id,
"phrase": topic_phrase_entity.phrase,
"phrase_translation": topic_phrase_entity.phrase_translation,
"type": topic_phrase_entity.type,
"sequence": topic_phrase_entity.sequence,
}
result.append(phrase)
return result
def __check_and_init_topics(self):
"""检查与生成默认的 topics"""
# 检查topic_group是否有数据
topic_group_entities = self.db.query(TopicGroupEntity).all()
if len(topic_group_entities) != 0:
return
# 根据配置文件生成默认数据
with open("data/default_topic_data.json", "r") as f:
topic_data = json.load(f)
default_account = "system_init"
for topic_group in topic_data["groups"]:
topic_group_entity = TopicGroupEntity(
id=topic_group["id"],
type=topic_group["type"],
name=topic_group["name"],
sequence=topic_group["sequence"],
description=topic_group["description"],
created_by=default_account,
)
self.db.add(topic_group_entity)
for topic in topic_group["topics"]:
topic_entity = TopicEntity(
id=topic["id"],
group_id=topic_group_entity.id,
name=topic["name"],
description=topic["description"],
level=topic["level"],
image_url=topic["image_url"],
language=topic["language"],
role_short_name=topic["role_short_name"],
role_speech_rate=topic["role_speech_rate"],
sequence=topic["sequence"],
topic_user_name=topic["topic_user_name"],
topic_bot_name=topic["topic_bot_name"],
prompt=topic["prompt"],
created_by=default_account,
)
self.db.add(topic_entity)
for target in topic["targets"]:
topic_target_entity = TopicTargetEntity(
topic_id=topic_entity.id,
type=target["type"],
description=target["description"],
sequence=target["sequence"],
description_translation=target["description_translation"],
created_by=default_account,
)
self.db.add(topic_target_entity)
for phrase in topic["phrases"]:
topic_phrase_entity = TopicPhraseEntity(
topic_id=topic_entity.id,
phrase=phrase["phrase"],
phrase_translation=phrase["phrase_translation"],
type=phrase["type"],
sequence=phrase["sequence"],
created_by=default_account,
)
self.db.add(topic_phrase_entity)
self.db.commit()
================================================
FILE: talkieai-server/data/azure.json
================================================
[{
"gender": 1,
"locale": "af-ZA",
"local_name": "Adri",
"name": "Microsoft Server Speech Text to Speech Voice (af-ZA, AdriNeural)",
"short_name": "af-ZA-AdriNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "af-ZA",
"local_name": "Willem",
"name": "Microsoft Server Speech Text to Speech Voice (af-ZA, WillemNeural)",
"short_name": "af-ZA-WillemNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "am-ET",
"local_name": "መቅደስ",
"name": "Microsoft Server Speech Text to Speech Voice (am-ET, MekdesNeural)",
"short_name": "am-ET-MekdesNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "am-ET",
"local_name": "አምሀ",
"name": "Microsoft Server Speech Text to Speech Voice (am-ET, AmehaNeural)",
"short_name": "am-ET-AmehaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-AE",
"local_name": "فاطمة",
"name": "Microsoft Server Speech Text to Speech Voice (ar-AE, FatimaNeural)",
"short_name": "ar-AE-FatimaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-AE",
"local_name": "حمدان",
"name": "Microsoft Server Speech Text to Speech Voice (ar-AE, HamdanNeural)",
"short_name": "ar-AE-HamdanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-BH",
"local_name": "ليلى",
"name": "Microsoft Server Speech Text to Speech Voice (ar-BH, LailaNeural)",
"short_name": "ar-BH-LailaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-BH",
"local_name": "علي",
"name": "Microsoft Server Speech Text to Speech Voice (ar-BH, AliNeural)",
"short_name": "ar-BH-AliNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-DZ",
"local_name": "أمينة",
"name": "Microsoft Server Speech Text to Speech Voice (ar-DZ, AminaNeural)",
"short_name": "ar-DZ-AminaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-DZ",
"local_name": "إسماعيل",
"name": "Microsoft Server Speech Text to Speech Voice (ar-DZ, IsmaelNeural)",
"short_name": "ar-DZ-IsmaelNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-EG",
"local_name": "سلمى",
"name": "Microsoft Server Speech Text to Speech Voice (ar-EG, SalmaNeural)",
"short_name": "ar-EG-SalmaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-EG",
"local_name": "شاكر",
"name": "Microsoft Server Speech Text to Speech Voice (ar-EG, ShakirNeural)",
"short_name": "ar-EG-ShakirNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-IQ",
"local_name": "رنا",
"name": "Microsoft Server Speech Text to Speech Voice (ar-IQ, RanaNeural)",
"short_name": "ar-IQ-RanaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-IQ",
"local_name": "باسل",
"name": "Microsoft Server Speech Text to Speech Voice (ar-IQ, BasselNeural)",
"short_name": "ar-IQ-BasselNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-JO",
"local_name": "سناء",
"name": "Microsoft Server Speech Text to Speech Voice (ar-JO, SanaNeural)",
"short_name": "ar-JO-SanaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-JO",
"local_name": "تيم",
"name": "Microsoft Server Speech Text to Speech Voice (ar-JO, TaimNeural)",
"short_name": "ar-JO-TaimNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-KW",
"local_name": "نورا",
"name": "Microsoft Server Speech Text to Speech Voice (ar-KW, NouraNeural)",
"short_name": "ar-KW-NouraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-KW",
"local_name": "فهد",
"name": "Microsoft Server Speech Text to Speech Voice (ar-KW, FahedNeural)",
"short_name": "ar-KW-FahedNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-LB",
"local_name": "ليلى",
"name": "Microsoft Server Speech Text to Speech Voice (ar-LB, LaylaNeural)",
"short_name": "ar-LB-LaylaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-LB",
"local_name": "رامي",
"name": "Microsoft Server Speech Text to Speech Voice (ar-LB, RamiNeural)",
"short_name": "ar-LB-RamiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-LY",
"local_name": "إيمان",
"name": "Microsoft Server Speech Text to Speech Voice (ar-LY, ImanNeural)",
"short_name": "ar-LY-ImanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-LY",
"local_name": "أحمد",
"name": "Microsoft Server Speech Text to Speech Voice (ar-LY, OmarNeural)",
"short_name": "ar-LY-OmarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-MA",
"local_name": "منى",
"name": "Microsoft Server Speech Text to Speech Voice (ar-MA, MounaNeural)",
"short_name": "ar-MA-MounaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-MA",
"local_name": "جمال",
"name": "Microsoft Server Speech Text to Speech Voice (ar-MA, JamalNeural)",
"short_name": "ar-MA-JamalNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-OM",
"local_name": "عائشة",
"name": "Microsoft Server Speech Text to Speech Voice (ar-OM, AyshaNeural)",
"short_name": "ar-OM-AyshaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-OM",
"local_name": "عبدالله",
"name": "Microsoft Server Speech Text to Speech Voice (ar-OM, AbdullahNeural)",
"short_name": "ar-OM-AbdullahNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-QA",
"local_name": "أمل",
"name": "Microsoft Server Speech Text to Speech Voice (ar-QA, AmalNeural)",
"short_name": "ar-QA-AmalNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-QA",
"local_name": "معاذ",
"name": "Microsoft Server Speech Text to Speech Voice (ar-QA, MoazNeural)",
"short_name": "ar-QA-MoazNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-SA",
"local_name": "زارية",
"name": "Microsoft Server Speech Text to Speech Voice (ar-SA, ZariyahNeural)",
"short_name": "ar-SA-ZariyahNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-SA",
"local_name": "حامد",
"name": "Microsoft Server Speech Text to Speech Voice (ar-SA, HamedNeural)",
"short_name": "ar-SA-HamedNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-SY",
"local_name": "أماني",
"name": "Microsoft Server Speech Text to Speech Voice (ar-SY, AmanyNeural)",
"short_name": "ar-SY-AmanyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-SY",
"local_name": "ليث",
"name": "Microsoft Server Speech Text to Speech Voice (ar-SY, LaithNeural)",
"short_name": "ar-SY-LaithNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-TN",
"local_name": "ريم",
"name": "Microsoft Server Speech Text to Speech Voice (ar-TN, ReemNeural)",
"short_name": "ar-TN-ReemNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-TN",
"local_name": "هادي",
"name": "Microsoft Server Speech Text to Speech Voice (ar-TN, HediNeural)",
"short_name": "ar-TN-HediNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ar-YE",
"local_name": "مريم",
"name": "Microsoft Server Speech Text to Speech Voice (ar-YE, MaryamNeural)",
"short_name": "ar-YE-MaryamNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ar-YE",
"local_name": "صالح",
"name": "Microsoft Server Speech Text to Speech Voice (ar-YE, SalehNeural)",
"short_name": "ar-YE-SalehNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "az-AZ",
"local_name": "Banu",
"name": "Microsoft Server Speech Text to Speech Voice (az-AZ, BanuNeural)",
"short_name": "az-AZ-BanuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "az-AZ",
"local_name": "Babək",
"name": "Microsoft Server Speech Text to Speech Voice (az-AZ, BabekNeural)",
"short_name": "az-AZ-BabekNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "bg-BG",
"local_name": "Калина",
"name": "Microsoft Server Speech Text to Speech Voice (bg-BG, KalinaNeural)",
"short_name": "bg-BG-KalinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "bg-BG",
"local_name": "Борислав",
"name": "Microsoft Server Speech Text to Speech Voice (bg-BG, BorislavNeural)",
"short_name": "bg-BG-BorislavNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "bn-BD",
"local_name": "নবনীতা",
"name": "Microsoft Server Speech Text to Speech Voice (bn-BD, NabanitaNeural)",
"short_name": "bn-BD-NabanitaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "bn-BD",
"local_name": "প্রদ্বীপ",
"name": "Microsoft Server Speech Text to Speech Voice (bn-BD, PradeepNeural)",
"short_name": "bn-BD-PradeepNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "bn-IN",
"local_name": "তানিশা",
"name": "Microsoft Server Speech Text to Speech Voice (bn-IN, TanishaaNeural)",
"short_name": "bn-IN-TanishaaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "bn-IN",
"local_name": "ভাস্কর",
"name": "Microsoft Server Speech Text to Speech Voice (bn-IN, BashkarNeural)",
"short_name": "bn-IN-BashkarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "bs-BA",
"local_name": "Vesna",
"name": "Microsoft Server Speech Text to Speech Voice (bs-BA, VesnaNeural)",
"short_name": "bs-BA-VesnaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "bs-BA",
"local_name": "Goran",
"name": "Microsoft Server Speech Text to Speech Voice (bs-BA, GoranNeural)",
"short_name": "bs-BA-GoranNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ca-ES",
"local_name": "Joana",
"name": "Microsoft Server Speech Text to Speech Voice (ca-ES, JoanaNeural)",
"short_name": "ca-ES-JoanaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ca-ES",
"local_name": "Enric",
"name": "Microsoft Server Speech Text to Speech Voice (ca-ES, EnricNeural)",
"short_name": "ca-ES-EnricNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ca-ES",
"local_name": "Alba",
"name": "Microsoft Server Speech Text to Speech Voice (ca-ES, AlbaNeural)",
"short_name": "ca-ES-AlbaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "cs-CZ",
"local_name": "Vlasta",
"name": "Microsoft Server Speech Text to Speech Voice (cs-CZ, VlastaNeural)",
"short_name": "cs-CZ-VlastaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "cs-CZ",
"local_name": "Antonín",
"name": "Microsoft Server Speech Text to Speech Voice (cs-CZ, AntoninNeural)",
"short_name": "cs-CZ-AntoninNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "cy-GB",
"local_name": "Nia",
"name": "Microsoft Server Speech Text to Speech Voice (cy-GB, NiaNeural)",
"short_name": "cy-GB-NiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "cy-GB",
"local_name": "Aled",
"name": "Microsoft Server Speech Text to Speech Voice (cy-GB, AledNeural)",
"short_name": "cy-GB-AledNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "da-DK",
"local_name": "Christel",
"name": "Microsoft Server Speech Text to Speech Voice (da-DK, ChristelNeural)",
"short_name": "da-DK-ChristelNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "da-DK",
"local_name": "Jeppe",
"name": "Microsoft Server Speech Text to Speech Voice (da-DK, JeppeNeural)",
"short_name": "da-DK-JeppeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-AT",
"local_name": "Ingrid",
"name": "Microsoft Server Speech Text to Speech Voice (de-AT, IngridNeural)",
"short_name": "de-AT-IngridNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-AT",
"local_name": "Jonas",
"name": "Microsoft Server Speech Text to Speech Voice (de-AT, JonasNeural)",
"short_name": "de-AT-JonasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-CH",
"local_name": "Leni",
"name": "Microsoft Server Speech Text to Speech Voice (de-CH, LeniNeural)",
"short_name": "de-CH-LeniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-CH",
"local_name": "Jan",
"name": "Microsoft Server Speech Text to Speech Voice (de-CH, JanNeural)",
"short_name": "de-CH-JanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Katja",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, KatjaNeural)",
"short_name": "de-DE-KatjaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Conrad",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, ConradNeural)",
"short_name": "de-DE-ConradNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful"]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Amala",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, AmalaNeural)",
"short_name": "de-DE-AmalaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Bernd",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, BerndNeural)",
"short_name": "de-DE-BerndNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Christoph",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, ChristophNeural)",
"short_name": "de-DE-ChristophNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Elke",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, ElkeNeural)",
"short_name": "de-DE-ElkeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Gisela",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, GiselaNeural)",
"short_name": "de-DE-GiselaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Kasper",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, KasperNeural)",
"short_name": "de-DE-KasperNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Killian",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, KillianNeural)",
"short_name": "de-DE-KillianNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Klarissa",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, KlarissaNeural)",
"short_name": "de-DE-KlarissaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Klaus",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, KlausNeural)",
"short_name": "de-DE-KlausNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Louisa",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, LouisaNeural)",
"short_name": "de-DE-LouisaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Maja",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, MajaNeural)",
"short_name": "de-DE-MajaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "de-DE",
"local_name": "Ralf",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, RalfNeural)",
"short_name": "de-DE-RalfNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "de-DE",
"local_name": "Tanja",
"name": "Microsoft Server Speech Text to Speech Voice (de-DE, TanjaNeural)",
"short_name": "de-DE-TanjaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "el-GR",
"local_name": "Αθηνά",
"name": "Microsoft Server Speech Text to Speech Voice (el-GR, AthinaNeural)",
"short_name": "el-GR-AthinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "el-GR",
"local_name": "Νέστορας",
"name": "Microsoft Server Speech Text to Speech Voice (el-GR, NestorasNeural)",
"short_name": "el-GR-NestorasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Natasha",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, NatashaNeural)",
"short_name": "en-AU-NatashaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-AU",
"local_name": "William",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, WilliamNeural)",
"short_name": "en-AU-WilliamNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Annette",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, AnnetteNeural)",
"short_name": "en-AU-AnnetteNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Carly",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, CarlyNeural)",
"short_name": "en-AU-CarlyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-AU",
"local_name": "Darren",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, DarrenNeural)",
"short_name": "en-AU-DarrenNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-AU",
"local_name": "Duncan",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, DuncanNeural)",
"short_name": "en-AU-DuncanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Elsie",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, ElsieNeural)",
"short_name": "en-AU-ElsieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Freya",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, FreyaNeural)",
"short_name": "en-AU-FreyaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Joanne",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, JoanneNeural)",
"short_name": "en-AU-JoanneNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-AU",
"local_name": "Ken",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, KenNeural)",
"short_name": "en-AU-KenNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Kim",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, KimNeural)",
"short_name": "en-AU-KimNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-AU",
"local_name": "Neil",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, NeilNeural)",
"short_name": "en-AU-NeilNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-AU",
"local_name": "Tim",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, TimNeural)",
"short_name": "en-AU-TimNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-AU",
"local_name": "Tina",
"name": "Microsoft Server Speech Text to Speech Voice (en-AU, TinaNeural)",
"short_name": "en-AU-TinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-CA",
"local_name": "Clara",
"name": "Microsoft Server Speech Text to Speech Voice (en-CA, ClaraNeural)",
"short_name": "en-CA-ClaraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-CA",
"local_name": "Liam",
"name": "Microsoft Server Speech Text to Speech Voice (en-CA, LiamNeural)",
"short_name": "en-CA-LiamNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Sonia",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, SoniaNeural)",
"short_name": "en-GB-SoniaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful", "sad"]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Ryan",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, RyanNeural)",
"short_name": "en-GB-RyanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful", "chat"]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Libby",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, LibbyNeural)",
"short_name": "en-GB-LibbyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Abbi",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, AbbiNeural)",
"short_name": "en-GB-AbbiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Alfie",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, AlfieNeural)",
"short_name": "en-GB-AlfieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Bella",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, BellaNeural)",
"short_name": "en-GB-BellaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Elliot",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, ElliotNeural)",
"short_name": "en-GB-ElliotNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Ethan",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, EthanNeural)",
"short_name": "en-GB-EthanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Hollie",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, HollieNeural)",
"short_name": "en-GB-HollieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Maisie",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, MaisieNeural)",
"short_name": "en-GB-MaisieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Noah",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, NoahNeural)",
"short_name": "en-GB-NoahNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Oliver",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, OliverNeural)",
"short_name": "en-GB-OliverNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Olivia",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, OliviaNeural)",
"short_name": "en-GB-OliviaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-GB",
"local_name": "Thomas",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, ThomasNeural)",
"short_name": "en-GB-ThomasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-GB",
"local_name": "Mia",
"name": "Microsoft Server Speech Text to Speech Voice (en-GB, MiaNeural)",
"short_name": "en-GB-MiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-HK",
"local_name": "Yan",
"name": "Microsoft Server Speech Text to Speech Voice (en-HK, YanNeural)",
"short_name": "en-HK-YanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-HK",
"local_name": "Sam",
"name": "Microsoft Server Speech Text to Speech Voice (en-HK, SamNeural)",
"short_name": "en-HK-SamNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-IE",
"local_name": "Emily",
"name": "Microsoft Server Speech Text to Speech Voice (en-IE, EmilyNeural)",
"short_name": "en-IE-EmilyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-IE",
"local_name": "Connor",
"name": "Microsoft Server Speech Text to Speech Voice (en-IE, ConnorNeural)",
"short_name": "en-IE-ConnorNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-IN",
"local_name": "Neerja",
"name": "Microsoft Server Speech Text to Speech Voice (en-IN, NeerjaNeural)",
"short_name": "en-IN-NeerjaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-IN",
"local_name": "Prabhat",
"name": "Microsoft Server Speech Text to Speech Voice (en-IN, PrabhatNeural)",
"short_name": "en-IN-PrabhatNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-KE",
"local_name": "Asilia",
"name": "Microsoft Server Speech Text to Speech Voice (en-KE, AsiliaNeural)",
"short_name": "en-KE-AsiliaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-KE",
"local_name": "Chilemba",
"name": "Microsoft Server Speech Text to Speech Voice (en-KE, ChilembaNeural)",
"short_name": "en-KE-ChilembaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-NG",
"local_name": "Ezinne",
"name": "Microsoft Server Speech Text to Speech Voice (en-NG, EzinneNeural)",
"short_name": "en-NG-EzinneNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-NG",
"local_name": "Abeo",
"name": "Microsoft Server Speech Text to Speech Voice (en-NG, AbeoNeural)",
"short_name": "en-NG-AbeoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-NZ",
"local_name": "Molly",
"name": "Microsoft Server Speech Text to Speech Voice (en-NZ, MollyNeural)",
"short_name": "en-NZ-MollyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-NZ",
"local_name": "Mitchell",
"name": "Microsoft Server Speech Text to Speech Voice (en-NZ, MitchellNeural)",
"short_name": "en-NZ-MitchellNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-PH",
"local_name": "Rosa",
"name": "Microsoft Server Speech Text to Speech Voice (en-PH, RosaNeural)",
"short_name": "en-PH-RosaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-PH",
"local_name": "James",
"name": "Microsoft Server Speech Text to Speech Voice (en-PH, JamesNeural)",
"short_name": "en-PH-JamesNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-SG",
"local_name": "Luna",
"name": "Microsoft Server Speech Text to Speech Voice (en-SG, LunaNeural)",
"short_name": "en-SG-LunaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-SG",
"local_name": "Wayne",
"name": "Microsoft Server Speech Text to Speech Voice (en-SG, WayneNeural)",
"short_name": "en-SG-WayneNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-TZ",
"local_name": "Imani",
"name": "Microsoft Server Speech Text to Speech Voice (en-TZ, ImaniNeural)",
"short_name": "en-TZ-ImaniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-TZ",
"local_name": "Elimu",
"name": "Microsoft Server Speech Text to Speech Voice (en-TZ, ElimuNeural)",
"short_name": "en-TZ-ElimuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Jenny Multilingual",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, JennyMultilingualNeural)",
"short_name": "en-US-JennyMultilingualNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Jenny",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, JennyNeural)",
"short_name": "en-US-JennyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["assistant", "chat", "customerservice", "newscast", "angry", "cheerful", "sad", "excited",
"friendly", "terrified", "shouting", "unfriendly", "whispering", "hopeful"
]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Guy",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, GuyNeural)",
"short_name": "en-US-GuyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["newscast", "angry", "cheerful", "sad", "excited", "friendly", "terrified", "shouting",
"unfriendly", "whispering", "hopeful"
]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Aria",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, AriaNeural)",
"short_name": "en-US-AriaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["chat", "customerservice", "narration-professional", "newscast-casual", "newscast-formal",
"cheerful", "empathetic", "angry", "sad", "excited", "friendly", "terrified", "shouting",
"unfriendly", "whispering", "hopeful"
]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Davis",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, DavisNeural)",
"short_name": "en-US-DavisNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["chat", "angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting",
"terrified", "unfriendly", "whispering"
]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Amber",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, AmberNeural)",
"short_name": "en-US-AmberNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Ana",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, AnaNeural)",
"short_name": "en-US-AnaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Ashley",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, AshleyNeural)",
"short_name": "en-US-AshleyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Brandon",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, BrandonNeural)",
"short_name": "en-US-BrandonNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Christopher",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, ChristopherNeural)",
"short_name": "en-US-ChristopherNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Cora",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, CoraNeural)",
"short_name": "en-US-CoraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Elizabeth",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, ElizabethNeural)",
"short_name": "en-US-ElizabethNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Eric",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, EricNeural)",
"short_name": "en-US-EricNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Jacob",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, JacobNeural)",
"short_name": "en-US-JacobNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Jane",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, JaneNeural)",
"short_name": "en-US-JaneNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified",
"unfriendly", "whispering"
]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Jason",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, JasonNeural)",
"short_name": "en-US-JasonNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified",
"unfriendly", "whispering"
]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Michelle",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, MichelleNeural)",
"short_name": "en-US-MichelleNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Monica",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, MonicaNeural)",
"short_name": "en-US-MonicaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Nancy",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, NancyNeural)",
"short_name": "en-US-NancyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified",
"unfriendly", "whispering"
]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Roger",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, RogerNeural)",
"short_name": "en-US-RogerNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Sara",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, SaraNeural)",
"short_name": "en-US-SaraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified",
"unfriendly", "whispering"
]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Steffan",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, SteffanNeural)",
"short_name": "en-US-SteffanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "Tony",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, TonyNeural)",
"short_name": "en-US-TonyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "cheerful", "excited", "friendly", "hopeful", "sad", "shouting", "terrified",
"unfriendly", "whispering"
]
}, {
"gender": 2,
"locale": "en-US",
"local_name": "AIGenerate1",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, AIGenerate1Neural)",
"short_name": "en-US-AIGenerate1Neural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "AIGenerate2",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, AIGenerate2Neural)",
"short_name": "en-US-AIGenerate2Neural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-US",
"local_name": "Blue",
"name": "Microsoft Server Speech Text to Speech Voice (en-US, BlueNeural)",
"short_name": "en-US-BlueNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "en-ZA",
"local_name": "Leah",
"name": "Microsoft Server Speech Text to Speech Voice (en-ZA, LeahNeural)",
"short_name": "en-ZA-LeahNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "en-ZA",
"local_name": "Luke",
"name": "Microsoft Server Speech Text to Speech Voice (en-ZA, LukeNeural)",
"short_name": "en-ZA-LukeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-AR",
"local_name": "Elena",
"name": "Microsoft Server Speech Text to Speech Voice (es-AR, ElenaNeural)",
"short_name": "es-AR-ElenaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-AR",
"local_name": "Tomas",
"name": "Microsoft Server Speech Text to Speech Voice (es-AR, TomasNeural)",
"short_name": "es-AR-TomasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-BO",
"local_name": "Sofia",
"name": "Microsoft Server Speech Text to Speech Voice (es-BO, SofiaNeural)",
"short_name": "es-BO-SofiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-BO",
"local_name": "Marcelo",
"name": "Microsoft Server Speech Text to Speech Voice (es-BO, MarceloNeural)",
"short_name": "es-BO-MarceloNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-CL",
"local_name": "Catalina",
"name": "Microsoft Server Speech Text to Speech Voice (es-CL, CatalinaNeural)",
"short_name": "es-CL-CatalinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-CL",
"local_name": "Lorenzo",
"name": "Microsoft Server Speech Text to Speech Voice (es-CL, LorenzoNeural)",
"short_name": "es-CL-LorenzoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-CO",
"local_name": "Salome",
"name": "Microsoft Server Speech Text to Speech Voice (es-CO, SalomeNeural)",
"short_name": "es-CO-SalomeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-CO",
"local_name": "Gonzalo",
"name": "Microsoft Server Speech Text to Speech Voice (es-CO, GonzaloNeural)",
"short_name": "es-CO-GonzaloNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-CR",
"local_name": "María",
"name": "Microsoft Server Speech Text to Speech Voice (es-CR, MariaNeural)",
"short_name": "es-CR-MariaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-CR",
"local_name": "Juan",
"name": "Microsoft Server Speech Text to Speech Voice (es-CR, JuanNeural)",
"short_name": "es-CR-JuanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-CU",
"local_name": "Belkys",
"name": "Microsoft Server Speech Text to Speech Voice (es-CU, BelkysNeural)",
"short_name": "es-CU-BelkysNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-CU",
"local_name": "Manuel",
"name": "Microsoft Server Speech Text to Speech Voice (es-CU, ManuelNeural)",
"short_name": "es-CU-ManuelNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-DO",
"local_name": "Ramona",
"name": "Microsoft Server Speech Text to Speech Voice (es-DO, RamonaNeural)",
"short_name": "es-DO-RamonaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-DO",
"local_name": "Emilio",
"name": "Microsoft Server Speech Text to Speech Voice (es-DO, EmilioNeural)",
"short_name": "es-DO-EmilioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-EC",
"local_name": "Andrea",
"name": "Microsoft Server Speech Text to Speech Voice (es-EC, AndreaNeural)",
"short_name": "es-EC-AndreaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-EC",
"local_name": "Luis",
"name": "Microsoft Server Speech Text to Speech Voice (es-EC, LuisNeural)",
"short_name": "es-EC-LuisNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Elvira",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, ElviraNeural)",
"short_name": "es-ES-ElviraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Álvaro",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, AlvaroNeural)",
"short_name": "es-ES-AlvaroNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Abril",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, AbrilNeural)",
"short_name": "es-ES-AbrilNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Arnau",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, ArnauNeural)",
"short_name": "es-ES-ArnauNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Dario",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, DarioNeural)",
"short_name": "es-ES-DarioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Elias",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, EliasNeural)",
"short_name": "es-ES-EliasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Estrella",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, EstrellaNeural)",
"short_name": "es-ES-EstrellaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Irene",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, IreneNeural)",
"short_name": "es-ES-IreneNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Laia",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, LaiaNeural)",
"short_name": "es-ES-LaiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Lia",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, LiaNeural)",
"short_name": "es-ES-LiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Nil",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, NilNeural)",
"short_name": "es-ES-NilNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Saul",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, SaulNeural)",
"short_name": "es-ES-SaulNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-ES",
"local_name": "Teo",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, TeoNeural)",
"short_name": "es-ES-TeoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Triana",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, TrianaNeural)",
"short_name": "es-ES-TrianaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-ES",
"local_name": "Vera",
"name": "Microsoft Server Speech Text to Speech Voice (es-ES, VeraNeural)",
"short_name": "es-ES-VeraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-GQ",
"local_name": "Teresa",
"name": "Microsoft Server Speech Text to Speech Voice (es-GQ, TeresaNeural)",
"short_name": "es-GQ-TeresaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-GQ",
"local_name": "Javier",
"name": "Microsoft Server Speech Text to Speech Voice (es-GQ, JavierNeural)",
"short_name": "es-GQ-JavierNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-GT",
"local_name": "Marta",
"name": "Microsoft Server Speech Text to Speech Voice (es-GT, MartaNeural)",
"short_name": "es-GT-MartaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-GT",
"local_name": "Andrés",
"name": "Microsoft Server Speech Text to Speech Voice (es-GT, AndresNeural)",
"short_name": "es-GT-AndresNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-HN",
"local_name": "Karla",
"name": "Microsoft Server Speech Text to Speech Voice (es-HN, KarlaNeural)",
"short_name": "es-HN-KarlaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-HN",
"local_name": "Carlos",
"name": "Microsoft Server Speech Text to Speech Voice (es-HN, CarlosNeural)",
"short_name": "es-HN-CarlosNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Dalia",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, DaliaNeural)",
"short_name": "es-MX-DaliaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Jorge",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, JorgeNeural)",
"short_name": "es-MX-JorgeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful", "chat"]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Beatriz",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, BeatrizNeural)",
"short_name": "es-MX-BeatrizNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Candela",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, CandelaNeural)",
"short_name": "es-MX-CandelaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Carlota",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, CarlotaNeural)",
"short_name": "es-MX-CarlotaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Cecilio",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, CecilioNeural)",
"short_name": "es-MX-CecilioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Gerardo",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, GerardoNeural)",
"short_name": "es-MX-GerardoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Larissa",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, LarissaNeural)",
"short_name": "es-MX-LarissaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Liberto",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, LibertoNeural)",
"short_name": "es-MX-LibertoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Luciano",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, LucianoNeural)",
"short_name": "es-MX-LucianoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Marina",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, MarinaNeural)",
"short_name": "es-MX-MarinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Nuria",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, NuriaNeural)",
"short_name": "es-MX-NuriaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Pelayo",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, PelayoNeural)",
"short_name": "es-MX-PelayoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-MX",
"local_name": "Renata",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, RenataNeural)",
"short_name": "es-MX-RenataNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-MX",
"local_name": "Yago",
"name": "Microsoft Server Speech Text to Speech Voice (es-MX, YagoNeural)",
"short_name": "es-MX-YagoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-NI",
"local_name": "Yolanda",
"name": "Microsoft Server Speech Text to Speech Voice (es-NI, YolandaNeural)",
"short_name": "es-NI-YolandaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-NI",
"local_name": "Federico",
"name": "Microsoft Server Speech Text to Speech Voice (es-NI, FedericoNeural)",
"short_name": "es-NI-FedericoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-PA",
"local_name": "Margarita",
"name": "Microsoft Server Speech Text to Speech Voice (es-PA, MargaritaNeural)",
"short_name": "es-PA-MargaritaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-PA",
"local_name": "Roberto",
"name": "Microsoft Server Speech Text to Speech Voice (es-PA, RobertoNeural)",
"short_name": "es-PA-RobertoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-PE",
"local_name": "Camila",
"name": "Microsoft Server Speech Text to Speech Voice (es-PE, CamilaNeural)",
"short_name": "es-PE-CamilaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-PE",
"local_name": "Alex",
"name": "Microsoft Server Speech Text to Speech Voice (es-PE, AlexNeural)",
"short_name": "es-PE-AlexNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-PR",
"local_name": "Karina",
"name": "Microsoft Server Speech Text to Speech Voice (es-PR, KarinaNeural)",
"short_name": "es-PR-KarinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-PR",
"local_name": "Víctor",
"name": "Microsoft Server Speech Text to Speech Voice (es-PR, VictorNeural)",
"short_name": "es-PR-VictorNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-PY",
"local_name": "Tania",
"name": "Microsoft Server Speech Text to Speech Voice (es-PY, TaniaNeural)",
"short_name": "es-PY-TaniaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-PY",
"local_name": "Mario",
"name": "Microsoft Server Speech Text to Speech Voice (es-PY, MarioNeural)",
"short_name": "es-PY-MarioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-SV",
"local_name": "Lorena",
"name": "Microsoft Server Speech Text to Speech Voice (es-SV, LorenaNeural)",
"short_name": "es-SV-LorenaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-SV",
"local_name": "Rodrigo",
"name": "Microsoft Server Speech Text to Speech Voice (es-SV, RodrigoNeural)",
"short_name": "es-SV-RodrigoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-US",
"local_name": "Paloma",
"name": "Microsoft Server Speech Text to Speech Voice (es-US, PalomaNeural)",
"short_name": "es-US-PalomaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-US",
"local_name": "Alonso",
"name": "Microsoft Server Speech Text to Speech Voice (es-US, AlonsoNeural)",
"short_name": "es-US-AlonsoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-UY",
"local_name": "Valentina",
"name": "Microsoft Server Speech Text to Speech Voice (es-UY, ValentinaNeural)",
"short_name": "es-UY-ValentinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-UY",
"local_name": "Mateo",
"name": "Microsoft Server Speech Text to Speech Voice (es-UY, MateoNeural)",
"short_name": "es-UY-MateoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "es-VE",
"local_name": "Paola",
"name": "Microsoft Server Speech Text to Speech Voice (es-VE, PaolaNeural)",
"short_name": "es-VE-PaolaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "es-VE",
"local_name": "Sebastián",
"name": "Microsoft Server Speech Text to Speech Voice (es-VE, SebastianNeural)",
"short_name": "es-VE-SebastianNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "et-EE",
"local_name": "Anu",
"name": "Microsoft Server Speech Text to Speech Voice (et-EE, AnuNeural)",
"short_name": "et-EE-AnuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "et-EE",
"local_name": "Kert",
"name": "Microsoft Server Speech Text to Speech Voice (et-EE, KertNeural)",
"short_name": "et-EE-KertNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "eu-ES",
"local_name": "Ainhoa",
"name": "Microsoft Server Speech Text to Speech Voice (eu-ES, AinhoaNeural)",
"short_name": "eu-ES-AinhoaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "eu-ES",
"local_name": "Ander",
"name": "Microsoft Server Speech Text to Speech Voice (eu-ES, AnderNeural)",
"short_name": "eu-ES-AnderNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fa-IR",
"local_name": "دلارا",
"name": "Microsoft Server Speech Text to Speech Voice (fa-IR, DilaraNeural)",
"short_name": "fa-IR-DilaraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fa-IR",
"local_name": "فرید",
"name": "Microsoft Server Speech Text to Speech Voice (fa-IR, FaridNeural)",
"short_name": "fa-IR-FaridNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fi-FI",
"local_name": "Selma",
"name": "Microsoft Server Speech Text to Speech Voice (fi-FI, SelmaNeural)",
"short_name": "fi-FI-SelmaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fi-FI",
"local_name": "Harri",
"name": "Microsoft Server Speech Text to Speech Voice (fi-FI, HarriNeural)",
"short_name": "fi-FI-HarriNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fi-FI",
"local_name": "Noora",
"name": "Microsoft Server Speech Text to Speech Voice (fi-FI, NooraNeural)",
"short_name": "fi-FI-NooraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fil-PH",
"local_name": "Blessica",
"name": "Microsoft Server Speech Text to Speech Voice (fil-PH, BlessicaNeural)",
"short_name": "fil-PH-BlessicaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fil-PH",
"local_name": "Angelo",
"name": "Microsoft Server Speech Text to Speech Voice (fil-PH, AngeloNeural)",
"short_name": "fil-PH-AngeloNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-BE",
"local_name": "Charline",
"name": "Microsoft Server Speech Text to Speech Voice (fr-BE, CharlineNeural)",
"short_name": "fr-BE-CharlineNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-BE",
"local_name": "Gerard",
"name": "Microsoft Server Speech Text to Speech Voice (fr-BE, GerardNeural)",
"short_name": "fr-BE-GerardNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-CA",
"local_name": "Sylvie",
"name": "Microsoft Server Speech Text to Speech Voice (fr-CA, SylvieNeural)",
"short_name": "fr-CA-SylvieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-CA",
"local_name": "Jean",
"name": "Microsoft Server Speech Text to Speech Voice (fr-CA, JeanNeural)",
"short_name": "fr-CA-JeanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-CA",
"local_name": "Antoine",
"name": "Microsoft Server Speech Text to Speech Voice (fr-CA, AntoineNeural)",
"short_name": "fr-CA-AntoineNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-CH",
"local_name": "Ariane",
"name": "Microsoft Server Speech Text to Speech Voice (fr-CH, ArianeNeural)",
"short_name": "fr-CH-ArianeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-CH",
"local_name": "Fabrice",
"name": "Microsoft Server Speech Text to Speech Voice (fr-CH, FabriceNeural)",
"short_name": "fr-CH-FabriceNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Denise",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, DeniseNeural)",
"short_name": "fr-FR-DeniseNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful", "sad"]
}, {
"gender": 2,
"locale": "fr-FR",
"local_name": "Henri",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, HenriNeural)",
"short_name": "fr-FR-HenriNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful", "sad"]
}, {
"gender": 2,
"locale": "fr-FR",
"local_name": "Alain",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, AlainNeural)",
"short_name": "fr-FR-AlainNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Brigitte",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, BrigitteNeural)",
"short_name": "fr-FR-BrigitteNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Celeste",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, CelesteNeural)",
"short_name": "fr-FR-CelesteNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-FR",
"local_name": "Claude",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, ClaudeNeural)",
"short_name": "fr-FR-ClaudeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Coralie",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, CoralieNeural)",
"short_name": "fr-FR-CoralieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Eloise",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, EloiseNeural)",
"short_name": "fr-FR-EloiseNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Jacqueline",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, JacquelineNeural)",
"short_name": "fr-FR-JacquelineNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-FR",
"local_name": "Jerome",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, JeromeNeural)",
"short_name": "fr-FR-JeromeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Josephine",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, JosephineNeural)",
"short_name": "fr-FR-JosephineNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-FR",
"local_name": "Maurice",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, MauriceNeural)",
"short_name": "fr-FR-MauriceNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "fr-FR",
"local_name": "Yves",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, YvesNeural)",
"short_name": "fr-FR-YvesNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "fr-FR",
"local_name": "Yvette",
"name": "Microsoft Server Speech Text to Speech Voice (fr-FR, YvetteNeural)",
"short_name": "fr-FR-YvetteNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ga-IE",
"local_name": "Orla",
"name": "Microsoft Server Speech Text to Speech Voice (ga-IE, OrlaNeural)",
"short_name": "ga-IE-OrlaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ga-IE",
"local_name": "Colm",
"name": "Microsoft Server Speech Text to Speech Voice (ga-IE, ColmNeural)",
"short_name": "ga-IE-ColmNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "gl-ES",
"local_name": "Sabela",
"name": "Microsoft Server Speech Text to Speech Voice (gl-ES, SabelaNeural)",
"short_name": "gl-ES-SabelaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "gl-ES",
"local_name": "Roi",
"name": "Microsoft Server Speech Text to Speech Voice (gl-ES, RoiNeural)",
"short_name": "gl-ES-RoiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "gu-IN",
"local_name": "ધ્વની",
"name": "Microsoft Server Speech Text to Speech Voice (gu-IN, DhwaniNeural)",
"short_name": "gu-IN-DhwaniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "gu-IN",
"local_name": "નિરંજન",
"name": "Microsoft Server Speech Text to Speech Voice (gu-IN, NiranjanNeural)",
"short_name": "gu-IN-NiranjanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "he-IL",
"local_name": "הילה",
"name": "Microsoft Server Speech Text to Speech Voice (he-IL, HilaNeural)",
"short_name": "he-IL-HilaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "he-IL",
"local_name": "אברי",
"name": "Microsoft Server Speech Text to Speech Voice (he-IL, AvriNeural)",
"short_name": "he-IL-AvriNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "hi-IN",
"local_name": "स्वरा",
"name": "Microsoft Server Speech Text to Speech Voice (hi-IN, SwaraNeural)",
"short_name": "hi-IN-SwaraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "hi-IN",
"local_name": "मधुर",
"name": "Microsoft Server Speech Text to Speech Voice (hi-IN, MadhurNeural)",
"short_name": "hi-IN-MadhurNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "hr-HR",
"local_name": "Gabrijela",
"name": "Microsoft Server Speech Text to Speech Voice (hr-HR, GabrijelaNeural)",
"short_name": "hr-HR-GabrijelaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "hr-HR",
"local_name": "Srećko",
"name": "Microsoft Server Speech Text to Speech Voice (hr-HR, SreckoNeural)",
"short_name": "hr-HR-SreckoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "hu-HU",
"local_name": "Noémi",
"name": "Microsoft Server Speech Text to Speech Voice (hu-HU, NoemiNeural)",
"short_name": "hu-HU-NoemiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "hu-HU",
"local_name": "Tamás",
"name": "Microsoft Server Speech Text to Speech Voice (hu-HU, TamasNeural)",
"short_name": "hu-HU-TamasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "hy-AM",
"local_name": "Անահիտ",
"name": "Microsoft Server Speech Text to Speech Voice (hy-AM, AnahitNeural)",
"short_name": "hy-AM-AnahitNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "hy-AM",
"local_name": "Հայկ",
"name": "Microsoft Server Speech Text to Speech Voice (hy-AM, HaykNeural)",
"short_name": "hy-AM-HaykNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "id-ID",
"local_name": "Gadis",
"name": "Microsoft Server Speech Text to Speech Voice (id-ID, GadisNeural)",
"short_name": "id-ID-GadisNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "id-ID",
"local_name": "Ardi",
"name": "Microsoft Server Speech Text to Speech Voice (id-ID, ArdiNeural)",
"short_name": "id-ID-ArdiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "is-IS",
"local_name": "Guðrún",
"name": "Microsoft Server Speech Text to Speech Voice (is-IS, GudrunNeural)",
"short_name": "is-IS-GudrunNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "is-IS",
"local_name": "Gunnar",
"name": "Microsoft Server Speech Text to Speech Voice (is-IS, GunnarNeural)",
"short_name": "is-IS-GunnarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Elsa",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, ElsaNeural)",
"short_name": "it-IT-ElsaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Isabella",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, IsabellaNeural)",
"short_name": "it-IT-IsabellaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["cheerful", "chat"]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Diego",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, DiegoNeural)",
"short_name": "it-IT-DiegoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Benigno",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, BenignoNeural)",
"short_name": "it-IT-BenignoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Calimero",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, CalimeroNeural)",
"short_name": "it-IT-CalimeroNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Cataldo",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, CataldoNeural)",
"short_name": "it-IT-CataldoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Fabiola",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, FabiolaNeural)",
"short_name": "it-IT-FabiolaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Fiamma",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, FiammaNeural)",
"short_name": "it-IT-FiammaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Gianni",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, GianniNeural)",
"short_name": "it-IT-GianniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Imelda",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, ImeldaNeural)",
"short_name": "it-IT-ImeldaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Irma",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, IrmaNeural)",
"short_name": "it-IT-IrmaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Lisandro",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, LisandroNeural)",
"short_name": "it-IT-LisandroNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Palmira",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, PalmiraNeural)",
"short_name": "it-IT-PalmiraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "it-IT",
"local_name": "Pierina",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, PierinaNeural)",
"short_name": "it-IT-PierinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "it-IT",
"local_name": "Rinaldo",
"name": "Microsoft Server Speech Text to Speech Voice (it-IT, RinaldoNeural)",
"short_name": "it-IT-RinaldoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ja-JP",
"local_name": "七海",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, NanamiNeural)",
"short_name": "ja-JP-NanamiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["chat", "customerservice", "cheerful"]
}, {
"gender": 2,
"locale": "ja-JP",
"local_name": "圭太",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, KeitaNeural)",
"short_name": "ja-JP-KeitaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ja-JP",
"local_name": "碧衣",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, AoiNeural)",
"short_name": "ja-JP-AoiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ja-JP",
"local_name": "大智",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, DaichiNeural)",
"short_name": "ja-JP-DaichiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ja-JP",
"local_name": "真夕",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, MayuNeural)",
"short_name": "ja-JP-MayuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ja-JP",
"local_name": "直紀",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, NaokiNeural)",
"short_name": "ja-JP-NaokiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ja-JP",
"local_name": "志織",
"name": "Microsoft Server Speech Text to Speech Voice (ja-JP, ShioriNeural)",
"short_name": "ja-JP-ShioriNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "jv-ID",
"local_name": "Siti",
"name": "Microsoft Server Speech Text to Speech Voice (jv-ID, SitiNeural)",
"short_name": "jv-ID-SitiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "jv-ID",
"local_name": "Dimas",
"name": "Microsoft Server Speech Text to Speech Voice (jv-ID, DimasNeural)",
"short_name": "jv-ID-DimasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ka-GE",
"local_name": "ეკა",
"name": "Microsoft Server Speech Text to Speech Voice (ka-GE, EkaNeural)",
"short_name": "ka-GE-EkaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ka-GE",
"local_name": "გიორგი",
"name": "Microsoft Server Speech Text to Speech Voice (ka-GE, GiorgiNeural)",
"short_name": "ka-GE-GiorgiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "kk-KZ",
"local_name": "Айгүл",
"name": "Microsoft Server Speech Text to Speech Voice (kk-KZ, AigulNeural)",
"short_name": "kk-KZ-AigulNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "kk-KZ",
"local_name": "Дәулет",
"name": "Microsoft Server Speech Text to Speech Voice (kk-KZ, DauletNeural)",
"short_name": "kk-KZ-DauletNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "km-KH",
"local_name": "ស្រីមុំ",
"name": "Microsoft Server Speech Text to Speech Voice (km-KH, SreymomNeural)",
"short_name": "km-KH-SreymomNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "km-KH",
"local_name": "ពិសិដ្ឋ",
"name": "Microsoft Server Speech Text to Speech Voice (km-KH, PisethNeural)",
"short_name": "km-KH-PisethNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "kn-IN",
"local_name": "ಸಪ್ನಾ",
"name": "Microsoft Server Speech Text to Speech Voice (kn-IN, SapnaNeural)",
"short_name": "kn-IN-SapnaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "kn-IN",
"local_name": "ಗಗನ್",
"name": "Microsoft Server Speech Text to Speech Voice (kn-IN, GaganNeural)",
"short_name": "kn-IN-GaganNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ko-KR",
"local_name": "선히",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, SunHiNeural)",
"short_name": "ko-KR-SunHiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ko-KR",
"local_name": "인준",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, InJoonNeural)",
"short_name": "ko-KR-InJoonNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ko-KR",
"local_name": "봉진",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, BongJinNeural)",
"short_name": "ko-KR-BongJinNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ko-KR",
"local_name": "국민",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, GookMinNeural)",
"short_name": "ko-KR-GookMinNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ko-KR",
"local_name": "지민",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, JiMinNeural)",
"short_name": "ko-KR-JiMinNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ko-KR",
"local_name": "서현",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, SeoHyeonNeural)",
"short_name": "ko-KR-SeoHyeonNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ko-KR",
"local_name": "순복",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, SoonBokNeural)",
"short_name": "ko-KR-SoonBokNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ko-KR",
"local_name": "유진",
"name": "Microsoft Server Speech Text to Speech Voice (ko-KR, YuJinNeural)",
"short_name": "ko-KR-YuJinNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "lo-LA",
"local_name": "ແກ້ວມະນີ",
"name": "Microsoft Server Speech Text to Speech Voice (lo-LA, KeomanyNeural)",
"short_name": "lo-LA-KeomanyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "lo-LA",
"local_name": "ຈັນທະວົງ",
"name": "Microsoft Server Speech Text to Speech Voice (lo-LA, ChanthavongNeural)",
"short_name": "lo-LA-ChanthavongNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "lt-LT",
"local_name": "Ona",
"name": "Microsoft Server Speech Text to Speech Voice (lt-LT, OnaNeural)",
"short_name": "lt-LT-OnaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "lt-LT",
"local_name": "Leonas",
"name": "Microsoft Server Speech Text to Speech Voice (lt-LT, LeonasNeural)",
"short_name": "lt-LT-LeonasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "lv-LV",
"local_name": "Everita",
"name": "Microsoft Server Speech Text to Speech Voice (lv-LV, EveritaNeural)",
"short_name": "lv-LV-EveritaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "lv-LV",
"local_name": "Nils",
"name": "Microsoft Server Speech Text to Speech Voice (lv-LV, NilsNeural)",
"short_name": "lv-LV-NilsNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "mk-MK",
"local_name": "Марија",
"name": "Microsoft Server Speech Text to Speech Voice (mk-MK, MarijaNeural)",
"short_name": "mk-MK-MarijaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "mk-MK",
"local_name": "Александар",
"name": "Microsoft Server Speech Text to Speech Voice (mk-MK, AleksandarNeural)",
"short_name": "mk-MK-AleksandarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ml-IN",
"local_name": "ശോഭന",
"name": "Microsoft Server Speech Text to Speech Voice (ml-IN, SobhanaNeural)",
"short_name": "ml-IN-SobhanaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ml-IN",
"local_name": "മിഥുൻ",
"name": "Microsoft Server Speech Text to Speech Voice (ml-IN, MidhunNeural)",
"short_name": "ml-IN-MidhunNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "mn-MN",
"local_name": "Есүй",
"name": "Microsoft Server Speech Text to Speech Voice (mn-MN, YesuiNeural)",
"short_name": "mn-MN-YesuiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "mn-MN",
"local_name": "Батаа",
"name": "Microsoft Server Speech Text to Speech Voice (mn-MN, BataaNeural)",
"short_name": "mn-MN-BataaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "mr-IN",
"local_name": "आरोही",
"name": "Microsoft Server Speech Text to Speech Voice (mr-IN, AarohiNeural)",
"short_name": "mr-IN-AarohiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "mr-IN",
"local_name": "मनोहर",
"name": "Microsoft Server Speech Text to Speech Voice (mr-IN, ManoharNeural)",
"short_name": "mr-IN-ManoharNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ms-MY",
"local_name": "Yasmin",
"name": "Microsoft Server Speech Text to Speech Voice (ms-MY, YasminNeural)",
"short_name": "ms-MY-YasminNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ms-MY",
"local_name": "Osman",
"name": "Microsoft Server Speech Text to Speech Voice (ms-MY, OsmanNeural)",
"short_name": "ms-MY-OsmanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "mt-MT",
"local_name": "Grace",
"name": "Microsoft Server Speech Text to Speech Voice (mt-MT, GraceNeural)",
"short_name": "mt-MT-GraceNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "mt-MT",
"local_name": "Joseph",
"name": "Microsoft Server Speech Text to Speech Voice (mt-MT, JosephNeural)",
"short_name": "mt-MT-JosephNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "my-MM",
"local_name": "နီလာ",
"name": "Microsoft Server Speech Text to Speech Voice (my-MM, NilarNeural)",
"short_name": "my-MM-NilarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "my-MM",
"local_name": "သီဟ",
"name": "Microsoft Server Speech Text to Speech Voice (my-MM, ThihaNeural)",
"short_name": "my-MM-ThihaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "nb-NO",
"local_name": "Pernille",
"name": "Microsoft Server Speech Text to Speech Voice (nb-NO, PernilleNeural)",
"short_name": "nb-NO-PernilleNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "nb-NO",
"local_name": "Finn",
"name": "Microsoft Server Speech Text to Speech Voice (nb-NO, FinnNeural)",
"short_name": "nb-NO-FinnNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "nb-NO",
"local_name": "Iselin",
"name": "Microsoft Server Speech Text to Speech Voice (nb-NO, IselinNeural)",
"short_name": "nb-NO-IselinNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ne-NP",
"local_name": "हेमकला",
"name": "Microsoft Server Speech Text to Speech Voice (ne-NP, HemkalaNeural)",
"short_name": "ne-NP-HemkalaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ne-NP",
"local_name": "सागर",
"name": "Microsoft Server Speech Text to Speech Voice (ne-NP, SagarNeural)",
"short_name": "ne-NP-SagarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "nl-BE",
"local_name": "Dena",
"name": "Microsoft Server Speech Text to Speech Voice (nl-BE, DenaNeural)",
"short_name": "nl-BE-DenaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "nl-BE",
"local_name": "Arnaud",
"name": "Microsoft Server Speech Text to Speech Voice (nl-BE, ArnaudNeural)",
"short_name": "nl-BE-ArnaudNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "nl-NL",
"local_name": "Fenna",
"name": "Microsoft Server Speech Text to Speech Voice (nl-NL, FennaNeural)",
"short_name": "nl-NL-FennaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "nl-NL",
"local_name": "Maarten",
"name": "Microsoft Server Speech Text to Speech Voice (nl-NL, MaartenNeural)",
"short_name": "nl-NL-MaartenNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "nl-NL",
"local_name": "Colette",
"name": "Microsoft Server Speech Text to Speech Voice (nl-NL, ColetteNeural)",
"short_name": "nl-NL-ColetteNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pl-PL",
"local_name": "Agnieszka",
"name": "Microsoft Server Speech Text to Speech Voice (pl-PL, AgnieszkaNeural)",
"short_name": "pl-PL-AgnieszkaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pl-PL",
"local_name": "Marek",
"name": "Microsoft Server Speech Text to Speech Voice (pl-PL, MarekNeural)",
"short_name": "pl-PL-MarekNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pl-PL",
"local_name": "Zofia",
"name": "Microsoft Server Speech Text to Speech Voice (pl-PL, ZofiaNeural)",
"short_name": "pl-PL-ZofiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ps-AF",
"local_name": "لطيفه",
"name": "Microsoft Server Speech Text to Speech Voice (ps-AF, LatifaNeural)",
"short_name": "ps-AF-LatifaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ps-AF",
"local_name": " ګل نواز",
"name": "Microsoft Server Speech Text to Speech Voice (ps-AF, GulNawazNeural)",
"short_name": "ps-AF-GulNawazNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Francisca",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, FranciscaNeural)",
"short_name": "pt-BR-FranciscaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["calm"]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Antônio",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, AntonioNeural)",
"short_name": "pt-BR-AntonioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Brenda",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, BrendaNeural)",
"short_name": "pt-BR-BrendaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Donato",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, DonatoNeural)",
"short_name": "pt-BR-DonatoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Elza",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, ElzaNeural)",
"short_name": "pt-BR-ElzaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Fabio",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, FabioNeural)",
"short_name": "pt-BR-FabioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Giovanna",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, GiovannaNeural)",
"short_name": "pt-BR-GiovannaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Humberto",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, HumbertoNeural)",
"short_name": "pt-BR-HumbertoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Julio",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, JulioNeural)",
"short_name": "pt-BR-JulioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Leila",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, LeilaNeural)",
"short_name": "pt-BR-LeilaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Leticia",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, LeticiaNeural)",
"short_name": "pt-BR-LeticiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Manuela",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, ManuelaNeural)",
"short_name": "pt-BR-ManuelaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Nicolau",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, NicolauNeural)",
"short_name": "pt-BR-NicolauNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-BR",
"local_name": "Valerio",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, ValerioNeural)",
"short_name": "pt-BR-ValerioNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-BR",
"local_name": "Yara",
"name": "Microsoft Server Speech Text to Speech Voice (pt-BR, YaraNeural)",
"short_name": "pt-BR-YaraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-PT",
"local_name": "Raquel",
"name": "Microsoft Server Speech Text to Speech Voice (pt-PT, RaquelNeural)",
"short_name": "pt-PT-RaquelNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "pt-PT",
"local_name": "Duarte",
"name": "Microsoft Server Speech Text to Speech Voice (pt-PT, DuarteNeural)",
"short_name": "pt-PT-DuarteNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "pt-PT",
"local_name": "Fernanda",
"name": "Microsoft Server Speech Text to Speech Voice (pt-PT, FernandaNeural)",
"short_name": "pt-PT-FernandaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ro-RO",
"local_name": "Alina",
"name": "Microsoft Server Speech Text to Speech Voice (ro-RO, AlinaNeural)",
"short_name": "ro-RO-AlinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ro-RO",
"local_name": "Emil",
"name": "Microsoft Server Speech Text to Speech Voice (ro-RO, EmilNeural)",
"short_name": "ro-RO-EmilNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ru-RU",
"local_name": "Светлана",
"name": "Microsoft Server Speech Text to Speech Voice (ru-RU, SvetlanaNeural)",
"short_name": "ru-RU-SvetlanaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ru-RU",
"local_name": "Дмитрий",
"name": "Microsoft Server Speech Text to Speech Voice (ru-RU, DmitryNeural)",
"short_name": "ru-RU-DmitryNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ru-RU",
"local_name": "Дария",
"name": "Microsoft Server Speech Text to Speech Voice (ru-RU, DariyaNeural)",
"short_name": "ru-RU-DariyaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "si-LK",
"local_name": "තිළිණි",
"name": "Microsoft Server Speech Text to Speech Voice (si-LK, ThiliniNeural)",
"short_name": "si-LK-ThiliniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "si-LK",
"local_name": "සමීර",
"name": "Microsoft Server Speech Text to Speech Voice (si-LK, SameeraNeural)",
"short_name": "si-LK-SameeraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sk-SK",
"local_name": "Viktória",
"name": "Microsoft Server Speech Text to Speech Voice (sk-SK, ViktoriaNeural)",
"short_name": "sk-SK-ViktoriaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sk-SK",
"local_name": "Lukáš",
"name": "Microsoft Server Speech Text to Speech Voice (sk-SK, LukasNeural)",
"short_name": "sk-SK-LukasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sl-SI",
"local_name": "Petra",
"name": "Microsoft Server Speech Text to Speech Voice (sl-SI, PetraNeural)",
"short_name": "sl-SI-PetraNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sl-SI",
"local_name": "Rok",
"name": "Microsoft Server Speech Text to Speech Voice (sl-SI, RokNeural)",
"short_name": "sl-SI-RokNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "so-SO",
"local_name": "Ubax",
"name": "Microsoft Server Speech Text to Speech Voice (so-SO, UbaxNeural)",
"short_name": "so-SO-UbaxNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "so-SO",
"local_name": "Muuse",
"name": "Microsoft Server Speech Text to Speech Voice (so-SO, MuuseNeural)",
"short_name": "so-SO-MuuseNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sq-AL",
"local_name": "Anila",
"name": "Microsoft Server Speech Text to Speech Voice (sq-AL, AnilaNeural)",
"short_name": "sq-AL-AnilaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sq-AL",
"local_name": "Ilir",
"name": "Microsoft Server Speech Text to Speech Voice (sq-AL, IlirNeural)",
"short_name": "sq-AL-IlirNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sr-Latn-RS",
"local_name": "Nicholas",
"name": "Microsoft Server Speech Text to Speech Voice (sr-Latn-RS, NicholasNeural)",
"short_name": "sr-Latn-RS-NicholasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sr-Latn-RS",
"local_name": "Sophie",
"name": "Microsoft Server Speech Text to Speech Voice (sr-Latn-RS, SophieNeural)",
"short_name": "sr-Latn-RS-SophieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sr-RS",
"local_name": "Софија",
"name": "Microsoft Server Speech Text to Speech Voice (sr-RS, SophieNeural)",
"short_name": "sr-RS-SophieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sr-RS",
"local_name": "Никола",
"name": "Microsoft Server Speech Text to Speech Voice (sr-RS, NicholasNeural)",
"short_name": "sr-RS-NicholasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "su-ID",
"local_name": "Tuti",
"name": "Microsoft Server Speech Text to Speech Voice (su-ID, TutiNeural)",
"short_name": "su-ID-TutiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "su-ID",
"local_name": "Jajang",
"name": "Microsoft Server Speech Text to Speech Voice (su-ID, JajangNeural)",
"short_name": "su-ID-JajangNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sv-SE",
"local_name": "Sofie",
"name": "Microsoft Server Speech Text to Speech Voice (sv-SE, SofieNeural)",
"short_name": "sv-SE-SofieNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sv-SE",
"local_name": "Mattias",
"name": "Microsoft Server Speech Text to Speech Voice (sv-SE, MattiasNeural)",
"short_name": "sv-SE-MattiasNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sv-SE",
"local_name": "Hillevi",
"name": "Microsoft Server Speech Text to Speech Voice (sv-SE, HilleviNeural)",
"short_name": "sv-SE-HilleviNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sw-KE",
"local_name": "Zuri",
"name": "Microsoft Server Speech Text to Speech Voice (sw-KE, ZuriNeural)",
"short_name": "sw-KE-ZuriNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sw-KE",
"local_name": "Rafiki",
"name": "Microsoft Server Speech Text to Speech Voice (sw-KE, RafikiNeural)",
"short_name": "sw-KE-RafikiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "sw-TZ",
"local_name": "Rehema",
"name": "Microsoft Server Speech Text to Speech Voice (sw-TZ, RehemaNeural)",
"short_name": "sw-TZ-RehemaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "sw-TZ",
"local_name": "Daudi",
"name": "Microsoft Server Speech Text to Speech Voice (sw-TZ, DaudiNeural)",
"short_name": "sw-TZ-DaudiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ta-IN",
"local_name": "பல்லவி",
"name": "Microsoft Server Speech Text to Speech Voice (ta-IN, PallaviNeural)",
"short_name": "ta-IN-PallaviNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ta-IN",
"local_name": "வள்ளுவர்",
"name": "Microsoft Server Speech Text to Speech Voice (ta-IN, ValluvarNeural)",
"short_name": "ta-IN-ValluvarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ta-LK",
"local_name": "சரண்யா",
"name": "Microsoft Server Speech Text to Speech Voice (ta-LK, SaranyaNeural)",
"short_name": "ta-LK-SaranyaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ta-LK",
"local_name": "குமார்",
"name": "Microsoft Server Speech Text to Speech Voice (ta-LK, KumarNeural)",
"short_name": "ta-LK-KumarNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ta-MY",
"local_name": "கனி",
"name": "Microsoft Server Speech Text to Speech Voice (ta-MY, KaniNeural)",
"short_name": "ta-MY-KaniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ta-MY",
"local_name": "சூர்யா",
"name": "Microsoft Server Speech Text to Speech Voice (ta-MY, SuryaNeural)",
"short_name": "ta-MY-SuryaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ta-SG",
"local_name": "வெண்பா",
"name": "Microsoft Server Speech Text to Speech Voice (ta-SG, VenbaNeural)",
"short_name": "ta-SG-VenbaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ta-SG",
"local_name": "அன்பு",
"name": "Microsoft Server Speech Text to Speech Voice (ta-SG, AnbuNeural)",
"short_name": "ta-SG-AnbuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "te-IN",
"local_name": "శ్రుతి",
"name": "Microsoft Server Speech Text to Speech Voice (te-IN, ShrutiNeural)",
"short_name": "te-IN-ShrutiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "te-IN",
"local_name": "మోహన్",
"name": "Microsoft Server Speech Text to Speech Voice (te-IN, MohanNeural)",
"short_name": "te-IN-MohanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "th-TH",
"local_name": "เปรมวดี",
"name": "Microsoft Server Speech Text to Speech Voice (th-TH, PremwadeeNeural)",
"short_name": "th-TH-PremwadeeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "th-TH",
"local_name": "นิวัฒน์",
"name": "Microsoft Server Speech Text to Speech Voice (th-TH, NiwatNeural)",
"short_name": "th-TH-NiwatNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "th-TH",
"local_name": "อัจฉรา",
"name": "Microsoft Server Speech Text to Speech Voice (th-TH, AcharaNeural)",
"short_name": "th-TH-AcharaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "tr-TR",
"local_name": "Emel",
"name": "Microsoft Server Speech Text to Speech Voice (tr-TR, EmelNeural)",
"short_name": "tr-TR-EmelNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "tr-TR",
"local_name": "Ahmet",
"name": "Microsoft Server Speech Text to Speech Voice (tr-TR, AhmetNeural)",
"short_name": "tr-TR-AhmetNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "uk-UA",
"local_name": "Поліна",
"name": "Microsoft Server Speech Text to Speech Voice (uk-UA, PolinaNeural)",
"short_name": "uk-UA-PolinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "uk-UA",
"local_name": "Остап",
"name": "Microsoft Server Speech Text to Speech Voice (uk-UA, OstapNeural)",
"short_name": "uk-UA-OstapNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ur-IN",
"local_name": "گل",
"name": "Microsoft Server Speech Text to Speech Voice (ur-IN, GulNeural)",
"short_name": "ur-IN-GulNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ur-IN",
"local_name": "سلمان",
"name": "Microsoft Server Speech Text to Speech Voice (ur-IN, SalmanNeural)",
"short_name": "ur-IN-SalmanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "ur-PK",
"local_name": "عظمیٰ",
"name": "Microsoft Server Speech Text to Speech Voice (ur-PK, UzmaNeural)",
"short_name": "ur-PK-UzmaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "ur-PK",
"local_name": "اسد",
"name": "Microsoft Server Speech Text to Speech Voice (ur-PK, AsadNeural)",
"short_name": "ur-PK-AsadNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "uz-UZ",
"local_name": "Madina",
"name": "Microsoft Server Speech Text to Speech Voice (uz-UZ, MadinaNeural)",
"short_name": "uz-UZ-MadinaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "uz-UZ",
"local_name": "Sardor",
"name": "Microsoft Server Speech Text to Speech Voice (uz-UZ, SardorNeural)",
"short_name": "uz-UZ-SardorNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "vi-VN",
"local_name": "Hoài My",
"name": "Microsoft Server Speech Text to Speech Voice (vi-VN, HoaiMyNeural)",
"short_name": "vi-VN-HoaiMyNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "vi-VN",
"local_name": "Nam Minh",
"name": "Microsoft Server Speech Text to Speech Voice (vi-VN, NamMinhNeural)",
"short_name": "vi-VN-NamMinhNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "wuu-CN",
"local_name": "晓彤",
"name": "Microsoft Server Speech Text to Speech Voice (wuu-CN, XiaotongNeural)",
"short_name": "wuu-CN-XiaotongNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "wuu-CN",
"local_name": "云哲",
"name": "Microsoft Server Speech Text to Speech Voice (wuu-CN, YunzheNeural)",
"short_name": "wuu-CN-YunzheNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "yue-CN",
"local_name": "晓敏",
"name": "Microsoft Server Speech Text to Speech Voice (yue-CN, XiaoMinNeural)",
"short_name": "yue-CN-XiaoMinNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "yue-CN",
"local_name": "云松",
"name": "Microsoft Server Speech Text to Speech Voice (yue-CN, YunSongNeural)",
"short_name": "yue-CN-YunSongNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓晓",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxiaoNeural)",
"short_name": "zh-CN-XiaoxiaoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["assistant", "chat", "customerservice", "newscast", "affectionate", "angry", "calm",
"cheerful", "disgruntled", "fearful", "gentle", "lyrical", "sad", "serious", "poetry-reading",
"friendly"
]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云希",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiNeural)",
"short_name": "zh-CN-YunxiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["narration-relaxed", "embarrassed", "fearful", "cheerful", "disgruntled", "serious", "angry",
"sad", "depressed", "chat", "assistant", "newscast"
]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云健",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunjianNeural)",
"short_name": "zh-CN-YunjianNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["narration-relaxed", "sports-commentary", "sports-commentary-excited"]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓伊",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoyiNeural)",
"short_name": "zh-CN-XiaoyiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "disgruntled", "affectionate", "cheerful", "fearful", "sad", "embarrassed",
"serious", "gentle"
]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云扬",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunyangNeural)",
"short_name": "zh-CN-YunyangNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["customerservice", "narration-professional", "newscast-casual"]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓辰",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaochenNeural)",
"short_name": "zh-CN-XiaochenNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓涵",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaohanNeural)",
"short_name": "zh-CN-XiaohanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["calm", "fearful", "cheerful", "disgruntled", "serious", "angry", "sad", "gentle",
"affectionate", "embarrassed"
]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓梦",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaomengNeural)",
"short_name": "zh-CN-XiaomengNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["chat"]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓墨",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaomoNeural)",
"short_name": "zh-CN-XiaomoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["embarrassed", "calm", "fearful", "cheerful", "disgruntled", "serious", "angry", "sad",
"depressed", "affectionate", "gentle", "envious"
]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓秋",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoqiuNeural)",
"short_name": "zh-CN-XiaoqiuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓睿",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoruiNeural)",
"short_name": "zh-CN-XiaoruiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["calm", "fearful", "angry", "sad"]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓双",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoshuangNeural)",
"short_name": "zh-CN-XiaoshuangNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["chat"]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓萱",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoxuanNeural)",
"short_name": "zh-CN-XiaoxuanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["calm", "fearful", "cheerful", "disgruntled", "serious", "angry", "gentle", "depressed"]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓颜",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoyanNeural)",
"short_name": "zh-CN-XiaoyanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓悠",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaoyouNeural)",
"short_name": "zh-CN-XiaoyouNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN",
"local_name": "晓甄",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, XiaozhenNeural)",
"short_name": "zh-CN-XiaozhenNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "disgruntled", "cheerful", "fearful", "sad", "serious"]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云枫",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunfengNeural)",
"short_name": "zh-CN-YunfengNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["angry", "disgruntled", "cheerful", "fearful", "sad", "serious", "depressed"]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云皓",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunhaoNeural)",
"short_name": "zh-CN-YunhaoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["advertisement-upbeat"]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云夏",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunxiaNeural)",
"short_name": "zh-CN-YunxiaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["calm", "fearful", "cheerful", "angry", "sad"]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云野",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunyeNeural)",
"short_name": "zh-CN-YunyeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["embarrassed", "calm", "fearful", "cheerful", "disgruntled", "serious", "angry", "sad"]
}, {
"gender": 2,
"locale": "zh-CN",
"local_name": "云泽",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN, YunzeNeural)",
"short_name": "zh-CN-YunzeNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": ["calm", "fearful", "cheerful", "disgruntled", "serious", "angry", "sad", "depressed",
"documentary-narration"
]
}, {
"gender": 2,
"locale": "zh-CN-henan",
"local_name": "云登",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN-henan, YundengNeural)",
"short_name": "zh-CN-henan-YundengNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN-liaoning",
"local_name": "晓北",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN-liaoning, XiaobeiNeural)",
"short_name": "zh-CN-liaoning-XiaobeiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-CN-shaanxi",
"local_name": "晓妮",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN-shaanxi, XiaoniNeural)",
"short_name": "zh-CN-shaanxi-XiaoniNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "zh-CN-shandong",
"local_name": "云翔",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN-shandong, YunxiangNeural)",
"short_name": "zh-CN-shandong-YunxiangNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "zh-CN-sichuan",
"local_name": "云希",
"name": "Microsoft Server Speech Text to Speech Voice (zh-CN-sichuan, YunxiNeural)",
"short_name": "zh-CN-sichuan-YunxiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-HK",
"local_name": "曉曼",
"name": "Microsoft Server Speech Text to Speech Voice (zh-HK, HiuMaanNeural)",
"short_name": "zh-HK-HiuMaanNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "zh-HK",
"local_name": "雲龍",
"name": "Microsoft Server Speech Text to Speech Voice (zh-HK, WanLungNeural)",
"short_name": "zh-HK-WanLungNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-HK",
"local_name": "曉佳",
"name": "Microsoft Server Speech Text to Speech Voice (zh-HK, HiuGaaiNeural)",
"short_name": "zh-HK-HiuGaaiNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-TW",
"local_name": "曉臻",
"name": "Microsoft Server Speech Text to Speech Voice (zh-TW, HsiaoChenNeural)",
"short_name": "zh-TW-HsiaoChenNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "zh-TW",
"local_name": "雲哲",
"name": "Microsoft Server Speech Text to Speech Voice (zh-TW, YunJheNeural)",
"short_name": "zh-TW-YunJheNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zh-TW",
"local_name": "曉雨",
"name": "Microsoft Server Speech Text to Speech Voice (zh-TW, HsiaoYuNeural)",
"short_name": "zh-TW-HsiaoYuNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 1,
"locale": "zu-ZA",
"local_name": "Thando",
"name": "Microsoft Server Speech Text to Speech Voice (zu-ZA, ThandoNeural)",
"short_name": "zu-ZA-ThandoNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}, {
"gender": 2,
"locale": "zu-ZA",
"local_name": "Themba",
"name": "Microsoft Server Speech Text to Speech Voice (zu-ZA, ThembaNeural)",
"short_name": "zu-ZA-ThembaNeural",
"voice_type": {
"name": "OnlineNeural",
"value": 1
},
"style_list": [""]
}]
================================================
FILE: talkieai-server/data/azure_style_label.json
================================================
[
{ "value": "chat", "label": "聊天" },
{ "value": "customerservice", "label": "服务" },
{ "value": "narration-professional", "label": "专业" },
{ "value": "newscast-casual", "label": "随意" },
{ "value": "newscast-formal", "label": "正式" },
{ "value": "cheerful", "label": "愉快" },
{ "value": "empathetic", "label": "同情" },
{ "value": "angry", "label": "生气" },
{ "value": "sad", "label": "悲伤" },
{ "value": "excited", "label": "兴奋" },
{ "value": "friendly", "label": "友好" },
{ "value": "terrified", "label": "害怕" },
{ "value": "shouting", "label": "喊叫" },
{ "value": "unfriendly", "label": "不友好" },
{ "value": "whispering", "label": "低语" },
{ "value": "hopeful", "label": "希望" },
{ "value": "calm", "label": "平静" },
{ "value": "fearful", "label": "害怕" },
{ "value": "disgruntled", "label": "不满" },
{ "value": "serious", "label": "认真" },
{ "value": "gentle", "label": "温和" },
{ "value": "affectionate", "label": "深情" },
{ "value": "embarrassed", "label": "尴尬" },
{ "value": "depressed", "label": "沮丧" },
{ "value": "envious", "label": "嫉妒" },
{ "value": "assistant", "label": "助手" },
{ "value": "newscast", "label": "播报" },
{ "value": "lyrical", "label": "抒情" },
{ "value": "poetry-reading", "label": "朗诵" },
{ "value": "advertisement-upbeat", "label": "广告" },
{ "value": "narration-relaxed", "label": "叙述" },
{ "value": "sports-commentary", "label": "体育评论" },
{ "value": "sports-commentary-excited", "label": "体育评论" },
{ "value": "documentary-narration", "label": "纪录片" }
]
================================================
FILE: talkieai-server/data/default_topic_data.json
================================================
{
"groups": [
{
"id": "group_1",
"name": "自我介绍",
"type": "ROLE_PLAY",
"description": "做一个基本的自我介绍吧",
"sequence": 10,
"topics": [
{
"id": "topic_1",
"language": "en-US",
"name": "自我介绍",
"level": 2,
"role_short_name": "en-US-JaneNeural",
"role_speech_rate": "1.0",
"topic_user_name": "Jack",
"topic_bot_name": "Jane",
"prompt": "你是用户的一位很好的朋友,现在开始引导用户来做一次简单的自我介绍吧,一步步的引导用户来介绍姓名、地址、爱好、身边朋友",
"description": "做一个基本的自我介绍吧",
"image_url": "https://qiniu.prejade.com/1597936949107363840/aitake/images/be8d50e5-17ba-4941-8960-af3ee9be6711.png",
"sequence": 1,
"phrases":[
{"phrase":"Hello, my name is Jack", "phrase_translation":"你好,我的名子叫Jack", "type":"PHRASE", "sequence": 1}
],
"targets": [
{
"type": "MAIN",
"description": "自我介绍并进行轻松随意的交谈",
"description_translation": "",
"sequence": 100
},
{
"type": "TRIAL",
"description": "问新朋友俩个关于他/她自己的问题",
"description_translation": "",
"sequence": 3
},
{
"type": "TRIAL",
"description": "展开聊聊你的家乡和工作",
"description_translation": "",
"sequence": 2
},
{
"type": "TRIAL",
"description": "展开聊聊你的家乡和工作",
"description_translation": "",
"sequence": 1
}
]
}
]
},
{
"id": "group_2",
"name": "外贸业务",
"type": "ROLE_PLAY",
"description": "外贸业务的交流",
"sequence": 1,
"topics": [
{
"id": "group_2_topic_1",
"language": "en-US",
"name": "外贸介绍",
"level": 4,
"role_short_name": "en-US-JaneNeural",
"role_speech_rate": "1.0",
"topic_user_name": "Jack",
"topic_bot_name": "Jane",
"prompt": "你是用户的一位外贸客户,现在开始引导用户来做一次简单的外贸介绍吧,一步步的引导用户来介绍产品、效果、使用方式",
"description": "做一个基本的自我介绍吧",
"image_url": "https://qiniu.prejade.com/1597936949107363840/aitake/images/be8d50e5-17ba-4941-8960-af3ee9be6711.png",
"sequence": 1,
"phrases":[
{"phrase":"Hello, my name is Jack", "phrase_translation":"你好,我的名子叫Jack", "type":"PHRASE", "sequence": 1}
],
"targets": [
{
"type": "MAIN",
"description": "自我介绍并进行轻松随意的交谈",
"description_translation": "",
"sequence": 100
},
{
"type": "TRIAL",
"description": "讲清楚自己的产品",
"description_translation": "",
"sequence": 3
},
{
"type": "TRIAL",
"description": "讲清楚产品的使用方式",
"description_translation": "",
"sequence": 2
}
]
}
]
}
]
}
================================================
FILE: talkieai-server/data/language_demo_map.json
================================================
{
"en": "Hello, welcome to Talkie. We hope you have a good learning experience.",
"ga": "Dia duit, fáilte chuig talkie, tá súil agam go mbeidh taithí foghlama maith agaibh.",
"fr": "Bonjour, bienvenue sur TalkieAI. Nous espérons que vous passerez une bonne expérience d'apprentissage.",
"ja": "こんにちは、TalkieAIをご利用いただきありがとうございます。良い学習体験をお楽しみください。",
"ko": "안녕하세요, TalkieAI를 사용해 주셔서 감사합니다. 좋은 학습 경험을 가지시길 바랍니다.",
"es": "Hola, bienvenido/a a TalkieAI. Espero que tengas una buena experiencia de aprendizaje.",
"bn": "নমস্কার, TalkieAI ব্যবহারে স্বাগতম। আমি আশা করি আপনি একটি ভাল শিক্ষার অভিজ্ঞতা অর্জন করতে পারবেন।",
"cs": "Dobrý den, vítejte v aplikaci TalkieAI. Doufáme, že budete mít dobrý zážitek při učení.",
"ca": "Hola, benvingut a TalkieAI. Esperem que tinguis una bona experiència d'aprenentatge.",
"bg": "Здравейте, добре дошли в TalkieAI. Надявам се да имате приятно учебно изживяване.",
"da": "Hej, velkommen til at bruge TalkieAI. Vi håber, at du får en god læringsoplevelse.",
"hr": "Dobar dan, dobrodošli na TalkieAI. Nadamo se da ćete imati dobro iskustvo u učenju.",
"nl": "Hallo, welkom bij TalkieAI. We hopen dat je een goede leerervaring hebt.",
"el": "Γεια σας, καλωσορίσατε στο TalkieAI, ελπίζουμε να έχετε μια καλή εμπειρία μάθησης.",
"he": "שלום, ברוכים הבאים לשימוש ב-TalkieAI, מקווים שתהיה לך חווית למידה טובה.",
"fi": "Hei, tervetuloa käyttämään talkiea, toivottavasti sinulla on hyvä oppimiskokemus.",
"de": "Hallo, willkommen bei Talkie. Ich hoffe, Sie haben eine gute Lernerfahrung.",
"hi": "नमस्ते, टॉकी का स्वागत करते हैं। हमें आशा है कि आपको एक अच्छा सीखने का अनुभव मिलेगा।",
"hu": "Szia, üdvözöllek a TalkieAI-n. Reméljük, hogy jó tanulási élményben lesz részed.",
"id": "Halo, selamat datang di TalkieAI. Kami harap Anda memiliki pengalaman belajar yang baik.",
"it": "Ciao, benvenuto/a su TalkieAI. Spero che tu abbia una buona esperienza di apprendimento.",
"ms": "Halo, selamat datang ke TalkieAI. Kami harap anda akan mempunyai pengalaman pembelajaran yang baik.",
"nb": "Hei, velkommen til TalkieAI. Vi håper du har en god læringsopplevelse.",
"pl": "Cześć, witaj w TalkieAI. Mamy nadzieję, że będziesz miał/a dobrą przygodę z nauką.",
"pt": "Olá, bem-vindo ao TalkieAI. Espero que tenha uma boa experiência de aprendizagem.",
"ro": "Salut, bun venit la TalkieAI. Sper că vei avea o experiență plăcută de învățare.",
"ru": "Привет, добро пожаловать в TalkieAI. Желаем вам хорошего образовательного опыта.",
"sk": "Ahoj, vitajte v TalkieAI. Dúfame, že budete mať dobrý zážitok pri učení.",
"sl": "Pozdravljeni, dobrodošli na TalkieAI. Upamo, da boste imeli dobro izkušnjo pri učenju.",
"sv": "Hej, välkommen till TalkieAI. Vi hoppas att du får en bra lärandeupplevelse.",
"ta": "வணக்கம், தாக்கி என்ற இணையத்திற்கு வரவேற்கிறோம். நலமான கற்றல் அனுபவத்தை அடைய நமது ஆர்வம்.",
"te": "హలో, TalkieAI కు స్వాగతం. మీకు ఒక మంచి అభ్యాస అనుభవం కావాలని ఆశిస్తున్నాం.",
"th": "สวัสดีครับ ยินดีต้อนรับสู่ TalkieAI หวังว่าคุณจะมีประสบการณ์การเรียนรู้ที่ดี",
"tr": "Merhaba, TalkieAI'yi kullanmaya hoş geldiniz. Umarız iyi bir öğrenme deneyimi yaşarsınız.",
"uk": "Привіт, ласкаво просимо до TalkieAI. Бажаємо вам гарного досвіду навчання.",
"vi": "Xin chào, chào mừng bạn đến với TalkieAI. Hy vọng bạn có trải nghiệm học tập tốt.",
"zh": "你好,欢迎使用 TalkieAI。希望您有一个好的学习体验。"
}
================================================
FILE: talkieai-server/data/sys_language.json
================================================
[
{ "label": "英语(美国)", "value": "en-US", "default_voice_role_name":"en-US-JennyNeural" },
{ "label": "英语(英国)", "value": "en-GB", "default_voice_role_name":"en-GB-SoniaNeural" },
{ "label": "中文(普通话)", "value": "zh-CN", "default_voice_role_name":"zh-CN-XiaoxiaoNeural" },
{ "label": "中文(粤语)", "value": "zh-HK", "default_voice_role_name":"" },
{ "label": "德语", "value": "de-DE", "default_voice_role_name":"zh-HK-HiuMaanNeural" },
{ "label": "日语", "value": "ja-JPR", "default_voice_role_name":"ja-JP-NanamiNeural" },
{ "label": "韩语", "value": "ko-KR", "default_voice_role_name":"ko-KR-SunHiNeural" },
{ "label": "俄语", "value": "ru-RU", "default_voice_role_name":"ru-RU-SvetlanaNeural" },
{ "label": "法语", "value": "fr-FR", "default_voice_role_name":"fr-FR-DeniseNeural" }
]
================================================
FILE: talkieai-server/requirements.txt
================================================
uvicorn==0.20.0
azure_storage==0.37.0
fastapi==0.109.0
openai==1.9.0
pydantic==1.10.5
pydub==0.25.1
PyJWT==2.8.0
python-dotenv==1.0.1
SQLAlchemy==2.0.10
starlette==0.36.1
zhipuai==1.0.7
pymysql~=1.0.2
azure-cognitiveservices-speech~=1.34.1
requests~=2.28.2
python-multipart
================================================
FILE: talkieai-server/start.sh
================================================
nohup uvicorn app.main:app --host 0.0.0.0 --port 8097 &
================================================
FILE: talkieai-uniapp/index.html
================================================
TalkieAI
================================================
FILE: talkieai-uniapp/package.json
================================================
{
"name": "uni-preset-vue",
"version": "0.0.0",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union",
"type-check": "vue-tsc --noEmit"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-app-plus": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-components": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-h5": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-alipay": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-baidu": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-jd": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-lark": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-qq": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-toutiao": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-mp-weixin": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-quickapp-webview": "3.0.0-alpha-3070720230316001",
"axios": "^0.21.4",
"fingerprintjs2": "^2.1.4",
"js-cookie": "^2.2.1",
"less": "^4.1.3",
"less-loader": "^11.1.2",
"query-string": "^8.1.0",
"recorder-core": "1.2.23070100",
"sass": "^1.62.1",
"sass-loader": "^13.3.1",
"uview-ui": "^1.8.8",
"vue": "^3.2.45",
"vue-i18n": "^9.1.9"
},
"devDependencies": {
"@dcloudio/types": "^3.3.2",
"@dcloudio/uni-automator": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-cli-shared": "3.0.0-alpha-3070720230316001",
"@dcloudio/uni-stacktracey": "3.0.0-alpha-3070720230316001",
"@dcloudio/vite-plugin-uni": "3.0.0-alpha-3070720230316001",
"@vue/tsconfig": "^0.1.3",
"typescript": "^4.9.4",
"vite": "4.0.4",
"vue-tsc": "^1.0.24"
}
}
================================================
FILE: talkieai-uniapp/src/App.vue
================================================
================================================
FILE: talkieai-uniapp/src/api/account.ts
================================================
import request from "@/axios/api";
export default {
visitorLogin: (data: any) => {
return request("/account/visitor-login", "POST", data, true);
},
accountInfoGet: () => {
return request("/account/info", "GET");
},
setSettings: (data: any) => {
return request("/account/settings", "POST", data);
},
getSettings: () => {
return request("/account/settings", "GET");
},
setRole: (data: any) => {
return request("/account/role", "POST", data);
},
getRole: () => {
return request("/account/role", "GET", null);
},
setLearningLanguage: (data: any) => {
return request("/account/language", "POST", data);
},
getLearningLanguage: () => {
return request("/account/language", "GET", null);
},
collectGet: (data: any) => {
return request("/account/collect", "GET", data, false);
},
collect: (data: any) => {
return request("/account/collect", "POST", data, false);
},
cancelCollect: (data: any) => {
return request("/account/collect", "DELETE", data, false);
},
collectsGet: (data: any) => {
return request("/account/collects", "GET", data, false);
}
};
================================================
FILE: talkieai-uniapp/src/api/chat.ts
================================================
import request from "@/axios/api";
export default {
sessionCreate: (data: any) => {
return request("/sessions", "POST", data, true);
},
sessionDefaultGet: (data: any) => {
return request("/sessions/default", "GET", data, true);
},
sessionDetailsGet: (data: any) => {
return request("/sessions/" + data.sessionId, "GET", data, true);
},
sessionInitGreeting: (sessionId: string) => {
return request("/sessions/" + sessionId + "/greeting", "GET", {}, false);
},
sessionChatInvoke: (data: any) => {
return request(`/sessions/${data.sessionId}/chat`, "POST", data, false);
},
transformText: (data: any) => {
return request(
`/sessions/${data.sessionId}/voice-translate`,
"POST",
data,
false
);
},
messagesLatestDelete: (sessionId: string) => {
return request(
`/sessions/${sessionId}/messages/latest`,
"DELETE",
null,
false
);
},
messagesAllDelete: (sessionId: string) => {
return request(`/sessions/${sessionId}/messages`, "DELETE", null, false);
},
translateInvoke: (data: any) => {
return request(
`/messages/${data.message_id}/translate`,
"POST",
data,
false
);
},
messagePractice: (data: any) => {
return request(
`/messages/${data.message_id}/practice`,
"POST",
data,
false
);
},
speechContent: (data: any) => {
return request("/message/speech-content", "POST", data, false);
},
speechDemo: (data: any) => {
return request("/message/speech-demo", "POST", data, false);
},
grammarInvoke: (data: any) => {
return request("/message/grammar", "POST", data, false);
},
pronunciationInvoke: (data: any) => {
return request("/message/pronunciation", "POST", data, false);
},
translateSettingLanguage: (data: any) => {
return request("/message/translate-setting-language", "POST", data, false);
},
translateSourceLanguage: (data: any) => {
return request("/message/translate-source-language", "POST", data, false);
},
transferSpeech: (data: any) => {
return request("/message/speech", "POST", data, false);
},
wordDetail: (data: any) => {
return request("/message/word/detail", "POST", data, false);
},
wordPractice: (data: any) => {
return request("/message/word/practice", "POST", data, false);
},
promptInvoke: (data: any) => {
return request("/message/prompt", "POST", data, false);
},
languageExampleGet: (data?: any) => {
return request("/languages/example", "GET", data, false);
},
rolesGet: (data?: any) => {
return request("/roles", "GET", data, false);
},
};
================================================
FILE: talkieai-uniapp/src/api/sys.ts
================================================
import request from "@/axios/api";
export default {
feedbackAdd: (data: any) => {
return request("/sys/feedback", "POST", data, false);
},
getLanguages: () => {
return request("/sys/languages", "GET", null);
},
getRoles: (data: any) => {
return request("/sys/roles", "GET", data);
},
setLearningLanguage: (data: any) => {
return request("/sys/language", "POST", data);
},
settingsPost: (data: any) => {
return request("/sys/settings", "POST", data);
},
settingsGet: () => {
return request("/sys/settings", "GET");
},
};
================================================
FILE: talkieai-uniapp/src/api/topic.ts
================================================
import request from "@/axios/api";
export default {
getTopicData: (params: any) => {
return request("/topics", "GET", params, false);
},
getTopicDetail: (id: string) => {
return request(`/topics/${id}`, "GET", null, false);
},
getTopicHistory: (id: string) => {
return request(`/topics/${id}/history`, "GET", null, false);
},
createSession: (data: any) => {
return request(`/topics/${data.topic_id}/session`, "POST", data, true);
},
completeTopic: (data: any) => {
return request(`/topics/sessions/${data.session_id}/complete`, "POST", data, true);
},
getTopicCompletation: (data: any) => {
return request(`/topics/${data.topic_id}/session/${data.session_id}/completion`, "GET", null, false);
},
getPhrase: (data: any) => {
return request(`/topics/${data.topic_id}/phrases`, "GET", null, false);
},
deleteTopicHistory: (data: any) => {
return request(`/topics/${data.topic_id}/session/${data.session_id}`, "DELETE", null, false);
}
};
================================================
FILE: talkieai-uniapp/src/axios/api.ts
================================================
import __config from "@/config/env";
const request = (
url: string,
method?:
| "OPTIONS"
| "GET"
| "HEAD"
| "POST"
| "PUT"
| "DELETE"
| "TRACE"
| "CONNECT",
data?: any,
showLoading?: boolean
): Promise => {
let _url = __config.basePath + url;
return new Promise((resolve, reject) => {
if (showLoading) {
uni.showLoading();
}
uni.request({
url: _url,
method: method,
data: data,
header: {
"Content-Type": "application/json",
"X-Token": uni.getStorageSync("x-token")
? uni.getStorageSync("x-token")
: "",
},
success(res) {
if (res.statusCode == 200) {
resolve(res.data);
} else if (res.statusCode == 401) {
uni.showToast({
title: "登录过期,重新登录",
icon: "none",
duration: 2000,
});
uni.removeStorageSync("x-token");
uni.navigateTo({
url: "/pages/login/index",
});
} else {
reject(res.data);
}
},
fail(error) {
console.error(error);
reject(error);
},
complete(res) {
// 判断是否在loading中
if (showLoading) {
uni?.hideLoading();
}
},
});
});
};
export default request;
================================================
FILE: talkieai-uniapp/src/axios/axiosServer.ts
================================================
// import axios from "./axiosService";
// import qs from "query-string";
// export const PostJson = (url: string, params: any) => {
// return axios.post(url, params);
// };
// export const GetUrl = (url: string, params: any = {}) => {
// return axios.get(`${url}?${qs.stringify(params)}`, params);
// };
================================================
FILE: talkieai-uniapp/src/axios/axiosService.ts
================================================
// import axios from "axios";
// import Cookies from "js-cookie";
// axios.interceptors.request.use(
// function (config) {
// // 在发送请求之前做些什么
// let configCp = { ...config };
// const token = Cookies.get("token");
// const authHeader = {
// Authorization: `${token}`,
// headers: {
// "Content-Type": "application/json",
// },
// };
// configCp.headers = {
// ...configCp.headers,
// ...authHeader,
// } as any;
// return configCp;
// },
// function (error) {
// // 对请求错误做些什么
// return Promise.reject(error);
// }
// );
// // 添加响应拦截器
// axios.interceptors.response.use(
// function (response) {
// // 2xx 范围内的状态码都会触发该函数。
// // 对响应数据做点什么
// console.log("response", response);
// if (response.status == 200) {
// if (response.data.code === 401) {
// Cookies.remove("token");
// window.location.href = "./login";
// // Toast.show(response.data.message || "Server Internal Error");
// } else if (response.data.code !== 200) {
// // Toast.show(response.data.message || "Server Internal Error");
// }
// } else if (response.status == 200) {
// // Toast.show("Server Internal Error");
// } else if (response.status != 200) {
// // Toast.show("Server Internal Error");
// }
// return response;
// },
// function (response) {
// // 超出 2xx 范围的状态码都会触发该函数。
// // 对响应错误做点什么
// if (response.status == 200 && response.data.code !== 200) {
// // Toast.show(response.data.message || "Server Internal Error");
// } else if (response.status != 200) {
// // Toast.show("Server Internal Error");
// }
// return response;
// }
// );
// export default axios;
================================================
FILE: talkieai-uniapp/src/components/AudioPlayer.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/Checkbox.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/Collect.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/CommonHeader.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/FunctionalText.vue
================================================
{{ text }}
{{ text }}
{{ word }}
{{ translateText }}
================================================
FILE: talkieai-uniapp/src/components/GithubLink.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/Loading.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/LoadingRound.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/Rare2.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/Rate.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/Speech.vue
================================================
================================================
FILE: talkieai-uniapp/src/components/WordAnalysisPopup.vue
================================================
{{ word }}
{{ wordPhoneticSymbol }}
{{ wordExplain }}
================================================
FILE: talkieai-uniapp/src/components/audioPlayerExecuter.ts
================================================
import __config from "@/config/env";
import { ref } from "vue";
interface Listener {
playing?: () => void;
success?: () => void;
error?: () => void;
}
class AudioPlayer {
private audioContext: any = null;
constructor() {}
/**
* 录音的时候使用,录音前要先关闭所有音频播放
*/
stopAudio() {
if (this.audioContext) {
this.audioContext.stop();
}
}
playAudio({ audioUrl, listener }: { audioUrl: string; listener: Listener }) {
let audioSrc = audioUrl;
if (this.audioContext) {
}
if (this.audioContext) {
const oldSrc = this.audioContext.src;
this.audioContext.stop();
if (oldSrc === audioSrc) {
this.audioContext = null;
return;
}
}
let innerAudioContext = this.createInnerAudioContext(audioUrl, listener);
this.audioContext = innerAudioContext;
innerAudioContext.play(); // 播放
}
createInnerAudioContext(src: string, listener: Listener) {
let innerAudioContext: any = null;
// #ifdef MP-WEIXIN
innerAudioContext = wx.createInnerAudioContext({
useWebAudioImplement: true,
});
// #endif
// #ifndef MP-WEIXIN
innerAudioContext = uni.createInnerAudioContext();
// #endif
innerAudioContext.src = src;
innerAudioContext.onPlay(() => {
if (listener.playing) {
listener.playing();
}
});
innerAudioContext.onStop(() => {
if (listener.success) {
listener.success();
}
innerAudioContext.destory && innerAudioContext.destory();
this.audioContext = null;
});
innerAudioContext.onEnded(() => {
if (listener.success) {
listener.success();
}
innerAudioContext.destory && innerAudioContext.destory();
this.audioContext = null;
});
innerAudioContext.onError((res: any) => {
if (listener.error) {
listener.error();
}
});
return innerAudioContext;
}
}
const audioPlayer = new AudioPlayer();
// export default audioPlayer;
export default audioPlayer;
================================================
FILE: talkieai-uniapp/src/components/speechExecuter.ts
================================================
import __config from "@/config/env";
// #ifdef H5
import Recorder from "recorder-core";
import "recorder-core/src/engine/wav";
// #endif
/**
* 录音后自动上传文件,成功后回调文件名
*/
const MAXIMUM_RECORDING_TIME = 60;
class Speech {
private recorder = {
start: false,
processing: false,
remainingTime: 0,
rec: null as any | null,
wxRecorderManager: null,
};
private intervalId: any = null;
private listener = {
success: null as Function | null,
cancel: null as Function | null,
error: null as Function | null,
interval: null as Function | null,
processing: null as Function | null,
};
constructor() {
// ... Constructor logic ...
}
handleVoiceStart({
success,
cancel,
error,
interval,
processing,
}: {
success: Function;
cancel: Function;
error: Function;
interval: Function;
processing: Function;
}) {
let self = this;
self.listener.success = success;
self.listener.cancel = cancel;
self.listener.error = error;
self.listener.interval = interval;
self.listener.processing = processing;
// #ifndef H5
self.mpWeixinVoiceStart();
// #endif
// #ifdef H5
self.h5VoiceStart();
// #endif
}
mpWeixinVoiceStart() {
let self = this;
let recorderManager = uni.getRecorderManager();
console.log(recorderManager);
// 如果是andoird手机使用,则须要设置mp3格式,否则无法播放
let format = "wav";
// #ifdef APP-PLUS
if (uni.getSystemInfoSync().platform === "android") {
format = "mp3";
}
// #endif
self.recorder.wxRecorderManager = recorderManager;
recorderManager.start({
duration: MAXIMUM_RECORDING_TIME * 1000,
sampleRate: 44100,
encodeBitRate: 192000,
format: format,
});
console.log("speech start..");
self.recorder.start = true;
self.recorder.remainingTime = MAXIMUM_RECORDING_TIME;
self.intervalId = setInterval(() => {
if (self.recorder.remainingTime === 0) {
self.handleEndVoice();
} else {
if (self.listener.interval) {
self.listener.interval(self.recorder.remainingTime);
}
self.recorder.remainingTime--;
}
}, 1000);
recorderManager.onStop((res: any) => {
console.log("speech on stop.." + res.tempFilePath);
self.handleProcessWxEndVoice({
filePath: res.tempFilePath,
});
});
}
clearInterval() {
const self = this;
if (self.intervalId) {
clearInterval(self.intervalId);
}
}
h5VoiceStart() {
let self = this;
self.recorder.rec = Recorder({
type: "wav",
bitRate: 32,
sampleRate: 32000,
});
self.recorder.rec.open(
() => {
self.recorder.start = true;
self.recorder.rec.start();
self.recorder.remainingTime = MAXIMUM_RECORDING_TIME;
self.intervalId = setInterval(() => {
if (self.listener.interval) {
self.listener.interval(self.recorder.remainingTime);
}
if (self.recorder.remainingTime === 0) {
clearInterval(self.intervalId);
self.handleEndVoice();
} else {
self.recorder.remainingTime--;
}
}, 1000);
},
(msg: string, isUserNotAllow: any) => {
uni.showToast({
title: "请开启录音权限",
icon: "none",
});
if (self.listener.error) {
self.listener.error(msg);
}
}
);
}
handleCancleVoice() {
let self = this;
self.clearInterval();
// #ifndef H5
if (self.recorder.wxRecorderManager) {
self.recorder.wxRecorderManager.stop();
self.recorder.start = false;
self.recorder.processing = false;
self.recorder.wxRecorderManager = null;
}
// #endif
// #ifdef H5
if (self.recorder.rec) {
self.recorder.rec.stop(() => {
self.recorder.start = false;
self.recorder.processing = false;
self.recorder.rec = null;
});
}
// #endif
if (self.listener.cancel) {
self.listener.cancel();
}
}
handleEndVoice() {
let self = this;
self.clearInterval();
if (self.recorder.processing) {
return;
}
// #ifndef H5
console.log("speech trigger end..");
self.handleWxEndVoice();
// #endif
// #ifdef H5
self.handleH5EndVoice();
// #endif
}
handleWxEndVoice() {
let self = this;
console.log("execute stop1");
console.log(self.recorder);
self.recorder.wxRecorderManager.stop();
console.log("execute stop");
}
handleProcessWxEndVoice({ filePath }: { filePath: string }) {
console.log("speech end...");
let self = this;
if (self.listener.processing) {
self.listener.processing();
}
uni.uploadFile({
url: __config.basePath + "/voice/upload",
filePath: filePath,
header: {
"X-Token": uni.getStorageSync("x-token"),
},
name: "file",
success: (res: any) => {
var resData = res;
self.handleUploadResult({
resData,
});
},
fail(e: any) {
console.error(e, "失败原因");
uni.showToast({
title: "上传失败",
icon: "none",
});
if (self.listener.error) {
self.listener.error(e);
}
},
complete: () => {
self.recorder.start = false;
self.recorder.processing = false;
self.recorder.rec = null;
},
});
}
handleH5EndVoice() {
let self = this;
if (self.listener.processing) {
self.listener.processing();
}
self.recorder.rec.stop(
(blob: any, duration: any) => {
self.recorder.processing = true;
var reader = new FileReader();
reader.addEventListener("load", function () {}, false);
reader.readAsDataURL(blob);
let blobURL = window.URL.createObjectURL(blob);
uni.uploadFile({
file: blob,
header: {
"X-Token": uni.getStorageSync("x-token"),
},
name: "file",
formData: {
file: blob,
},
url: __config.basePath + "/voices/upload",
success: (res) => {
var resData = res;
self.handleUploadResult({
resData,
});
},
fail(e) {
console.error(e, "失败原因");
uni.showToast({
title: "上传失败",
icon: "none",
});
},
complete: () => {
self.recorder.start = false;
self.recorder.processing = false;
self.recorder.rec = null;
},
});
},
function (s: any) {
if (self.listener.error) {
self.listener.error(s);
}
console.error("结束出错");
},
true
);
}
handleUploadResult({ resData }: { resData: any }) {
const self = this;
if (resData.statusCode == 200) {
let resultJson = JSON.parse(resData.data);
if (resultJson.code != "200") {
uni.showToast({
title: resultJson.message,
icon: "none",
});
if (self.listener.error) {
self.listener.error(resultJson);
}
return;
}
let dataJson = resultJson.data;
if (self.listener.success) {
self.listener.success({
inputValue: dataJson.result,
voiceFileName: dataJson.file,
});
}
}
}
}
const speech = new Speech();
export default speech;
================================================
FILE: talkieai-uniapp/src/config/env.ts
================================================
export default {
// basePath: "http://192.168.0.102:8098/api/v1"
basePath: "http://localhost:8097/api/v1"
// basePath: "https://talkie.prejade.com/api/v1"
};
================================================
FILE: talkieai-uniapp/src/env.d.ts
================================================
///
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
================================================
FILE: talkieai-uniapp/src/global/globalCount.hooks.ts
================================================
import { ref, onMounted } from "vue";
import chatRequest from "@/api/chat";
// 全局状态,创建在模块作用域下
const globalUserInfo = ref(1);
const globalLoading = ref(false);
export function useUserInfo() {
// 局部状态,每个组件都会创建
const localCount = ref(1);
onMounted(() => {
globalLoading.value = true;
chatRequest.sessionDefaultGet({}).then((data) => {
globalUserInfo.value = data.data;
});
globalLoading.value = false;
});
return {
globalUserInfo,
globalLoading,
localCount,
};
}
================================================
FILE: talkieai-uniapp/src/less/global.less
================================================
// 使用 * 的话 微信小程序会报错
// [ WXSS 文件编译错误] ./pages/contact/index.wxss unexpected token *
view {
margin: 0;
padding: 0;
}
@common-color:#6236FF;
@common-bg-gray-color: #F5F5FE;
.common-button{
width: 590rpx;
height: 120rpx;
background: @common-color;
color:#fff;
font-size: 14px;
border-radius: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
button:active{
opacity: .8;
}
view{
box-sizing: border-box;
}
.row-bc {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.row-sc {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.common-button{
width: 590rpx;
height: 120rpx;
background: @common-color;
color:#fff;
font-size: 14px;
border-radius: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.atk-btn-box {
padding: 28rpx 0;
background: @common-color;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
border-radius: 60rpx;
color:#fff;
.atk-btn {
letter-spacing: 4rpx;
}
&.gray {
background: #999;
}
}
.bottom-box {
margin: 32rpx;
width: calc(100vw - 64rpx);
position: fixed;
bottom: 0;
padding-bottom: calc(env(safe-area-inset-bottom) / 2);
}
.icon {
width: 32rpx;
height: 32rpx;
}
================================================
FILE: talkieai-uniapp/src/main.ts
================================================
import { createSSRApp } from "vue";
import App from "./App.vue";
import EventBus from "@/utils/bus";
const getHeight = (global: any) => {
uni.getSystemInfo({
success: (e) => {
global.StatusBar = e.statusBarHeight || 0;
// Platform-specific logic
if (e.platform === "android") {
global.CustomBar = global.StatusBar + 50;
} else {
global.CustomBar = global.StatusBar + 45;
}
// MP-WEIXIN specific logic
// #ifdef MP-WEIXIN
global.Custom = wx.getMenuButtonBoundingClientRect();
let customBar =
global.Custom.bottom + global.Custom.top - global.StatusBar + 4;
global.CustomBar = customBar;
// #endif
// MP-ALIPAY specific logic
// #ifdef MP-ALIPAY
const titleBarHeight = e.titleBarHeight || 0;
global.CustomBar = global.StatusBar + titleBarHeight;
// #endif
},
});
};
export function createApp() {
const $bus = new EventBus();
const app = createSSRApp(App);
app.provide("$bus", $bus);
app.config.globalProperties.$bus = $bus;
getHeight(app.config.globalProperties);
return {
app,
};
}
================================================
FILE: talkieai-uniapp/src/manifest.json
================================================
{
"name" : "talkie",
"appid" : "__UNI__1EE8DB0",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* 模块配置 */
"modules" : {
"Record" : {}
},
/* 应用发布信息 */
"distribute" : {
/* android打包配置 */
"android" : {
"permissions" : [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""
],
"minSdkVersion" : 21
},
/* ios打包配置 */
"ios" : {
"dSYMs" : false
},
/* SDK配置 */
"sdkConfigs" : {
"ad" : {},
"speech" : {}
},
"icons" : {
"android" : {
"hdpi" : "unpackage/res/icons/72x72.png",
"xhdpi" : "unpackage/res/icons/96x96.png",
"xxhdpi" : "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png"
},
"ios" : {
"appstore" : "unpackage/res/icons/1024x1024.png",
"ipad" : {
"app" : "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png"
},
"iphone" : {
"app@2x" : "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png"
}
}
}
}
},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wxd0d975d602551eb6",
"setting" : {
"urlCheck" : false
},
"requiredBackgroundModes" : [ "audio", "location" ],
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3",
"h5" : {
"template" : "index.html",
"title" : "TalkieAI"
}
}
================================================
FILE: talkieai-uniapp/src/models/chat.ts
================================================
================================================
FILE: talkieai-uniapp/src/models/models.ts
================================================
export interface AccountInfo {
account_id: string;
today_chat_count: number;
total_chat_count: number;
target_language_label: string;
}
export interface AccountSettings {
auto_playing_voice:number;
auto_text_shadow:number;
auto_pronunciation:number;
playing_voice_speed:string;
speech_role_name_label:string;
speech_role_name:string;
target_language:string;
}
export interface Collect {
id?: string | null;
type: string;
content: string;
translation: string;
message_id?: string | null;
create_time?: string | null;
}
export interface Message {
id?: string | null;
content?: string | null;
owner: boolean;
file_name?: string | null;
role: string | "USER" | "ASSISTANT";
session_id?: string | null;
auto_play?: boolean | null;
auto_hint?: boolean | null;
auto_pronunciation?: boolean | null;
pronunciation?: Pronunciation | null | undefined;
}
export interface Phoneme {
phoneme: string;
accuracy_score: number;
}
export interface Word {
word: string;
accuracy_score: number;
error_type: string;
phonemes: Phoneme[];
}
export interface Pronunciation {
accuracy_score: number;
fluency_score: number;
completeness_score: number;
pronunciation_score: number;
words: Word[];
}
export interface MessagePage {
list: Message[];
total: number;
}
export interface Session {
id?: string;
type?: string;
messages: MessagePage;
}
export interface Prompt {
text?: string;
translateShow?: boolean;
}
================================================
FILE: talkieai-uniapp/src/models/sys.ts
================================================
export interface Language {
id?: string | null;
language?: string | null;
label: boolean;
description?: string | null;
}
export interface Role {
id?: string | null;
short_name: string;
country: string;
gender: string;
avatar: string;
audio: string;
sequence: number;
}
================================================
FILE: talkieai-uniapp/src/pages/chat/components/CommonAudioPlayer.vue
================================================
================================================
FILE: talkieai-uniapp/src/pages/chat/components/MessageContent.vue
================================================
{{ message.content }}
{{ utils.removeDecimal(message.pronunciation.pronunciation_score) }}
语法
================================================
FILE: talkieai-uniapp/src/pages/chat/components/MessageGrammar.vue
================================================
Well done!
{{ grammarAnalysisResult.original }}
{{ grammarAnalysisResult.correct_content }}
错误点
{{ grammarAnalysisResult.error_reason }}
你也可以说
================================================
FILE: talkieai-uniapp/src/pages/chat/components/MessageGrammarPopup.vue
================================================
语法
发音
================================================
FILE: talkieai-uniapp/src/pages/chat/components/MessagePronunciation.vue
================================================
点击查看单词评分
我
练习
================================================
FILE: talkieai-uniapp/src/pages/chat/components/MessageSpeech.vue
================================================
================================================
FILE: talkieai-uniapp/src/pages/chat/components/PhonemeBox.vue
================================================
{{ phoneme.phoneme }}
================================================
FILE: talkieai-uniapp/src/pages/chat/components/Prompt.vue
================================================
================================================
FILE: talkieai-uniapp/src/pages/chat/components/PromptPopup.vue
================================================
================================================
FILE: talkieai-uniapp/src/pages/chat/components/TextPronunciation.vue
================================================
{{ word.word }}
================================================
FILE: talkieai-uniapp/src/pages/chat/components/TranslationPopup.vue
================================================
输入语句
翻译
{{ translationText }}
发送文本
================================================
FILE: talkieai-uniapp/src/pages/chat/components/WordDetail.vue
================================================
{{ wordDetail.original }}
{{ wordDetail.phonetic }}
{{ wordDetail.translation }}
练习
================================================
FILE: talkieai-uniapp/src/pages/chat/index.vue
================================================
{{ session.name }}
================================================
FILE: talkieai-uniapp/src/pages/chat/settings.vue
================================================
设置
AI角色
{{ settingInfo.speech_role_name_label || '默认角色' }}
自动播放语音
inputCheck('auto_playing_voice', check)"
:checked="settingInfo.auto_playing_voice === 1" />
自动模糊文本
inputCheck('auto_text_shadow', check)"
:checked="settingInfo.auto_text_shadow === 1" />
自动语音评分
inputCheck('auto_pronunciation', check)"
:checked="settingInfo.auto_pronunciation === 1" />
语速
慢速
正常
较快
================================================
FILE: talkieai-uniapp/src/pages/contact/index.vue
================================================
欢迎随时联系我们反馈产品体验
================================================
FILE: talkieai-uniapp/src/pages/contact/less/index.less
================================================
@import url('../../../less/global.less');
.contact{
display: flex;
padding-top: 100rpx;
justify-content: center;
flex-direction: column;
align-items: center;
.contact-text{
font-size: 28rpx;
}
.contact-image{
width: 600rpx;
height: 1075rpx;
margin-top: 40rpx;
}
}
================================================
FILE: talkieai-uniapp/src/pages/feedback/index.vue
================================================
提交成功
================================================
FILE: talkieai-uniapp/src/pages/feedback/less/index.less
================================================
@import url("../../../less/global.less");
.feedback {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin-top: 30rpx;
.feedback-box {
width: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
.feedback-textarea-box {
padding: 0 32rpx;
border-bottom: 1rpx solid #e8e8e8;
width: 750rpx;
position: relative;
.feedback-textarea {
z-index: 99;
width: 100%;
font-size: 28rpx;
white-space: initial;
height: 360rpx;
}
.placeholder-style {
position: absolute;
top: 0;
left: 32rpx;
font-size: 28rpx;
font-weight: 400;
color: #707070;
line-height: 40rpx;
white-space: pre-wrap;
word-break: break-all;
width: 686rpx;
}
}
.feedback-input-box {
padding: 28rpx;
width: 100%;
font-size: 28rpx;
border-bottom: 1rpx solid #e8e8e8;
.feedback-input {
width: 100%;
white-space: initial;
}
}
.feedback-btn-box {
padding: 0 32rpx;
width: 100%;
.feedback-btn {
width: 100%;
border-radius: 30rpx;
height: 108rpx;
margin-top: 100rpx;
font-size: 36rpx;
font-weight: 500;
line-height: 50rpx;
letter-spacing: 1px;
}
}
.feedback-success {
text-align: center;
color: #6236ff;
font-size: 34rpx;
margin-top: 30rpx;
}
.feedback-box {
padding-top: 12rpx;
.feedback-ico {
margin-top: 100rpx;
width: 346rpx;
height: 234rpx;
}
}
.return-btn {
margin-top: 120rpx;
}
}
================================================
FILE: talkieai-uniapp/src/pages/index/components/Topics.vue
================================================
角色扮演
自由畅聊
写下你想要的场景,让你的角色扮演更加自由
{{ group.name }}
{{ topic.name }}
已学习
================================================
FILE: talkieai-uniapp/src/pages/index/index.vue
================================================
Talkie
{{ settingRole.local_name }}
切换角色
进入会话
================================================
FILE: talkieai-uniapp/src/pages/index/switchRole.vue
================================================
选择角色
{{ m.local_name }}
{{ style.label || '默认' }}
================================================
FILE: talkieai-uniapp/src/pages/login/index.vue
================================================
欢迎使用Talkie AI
练习口语、写作的好帮手
随便逛逛
================================================
FILE: talkieai-uniapp/src/pages/login/service.ts
================================================
import request from "@/axios/api";
export default {
wechatLogin: (data: any) => {
return request('/wechat/code-login', "POST", data, false);
},
visitorLogin: (data: any) => {
return request('/visitor-login', "POST", data, false);
}
};
================================================
FILE: talkieai-uniapp/src/pages/my/index.vue
================================================
请登录
{{ accountInfo.account_id }}
今日次数
{{ accountInfo.today_chat_count }}
总次数
{{ accountInfo.total_chat_count }}
学习语言
{{ accountInfo.target_language_label }}
反馈
联系我们
Github
退出登录
================================================
FILE: talkieai-uniapp/src/pages/my/learnLanguage.vue
================================================
我想学...
{{ language.label }}
================================================
FILE: talkieai-uniapp/src/pages/my/less/index.less
================================================
.my-container {
background: linear-gradient(180deg, #F5F5FE 0%, #FFFFFF 100%);
}
.header {
}
.mine-content {
min-height: calc(100vh - 100rpx);
.profile-box {
height: 176rpx;
background: #FFFFFF;
box-shadow: 0rpx 0rpx 8rpx 0rpx rgba(196,196,196,0.5);
border-radius: 30rpx;
padding: 28rpx;
margin: 32rpx;
.profile {
display: flex;
align-items: center;
.profile-avatar {
width: 120rpx;
height: 120rpx
}
.profile-name {
margin-left: 40rpx;
height: 40rpx;
font-size: 28rpx;
font-weight: 500;
color: #000000;
line-height: 40rpx;
}
}
}
.setting{
margin-top: 38rpx;
.setting-card{
background: #ddd;
height: 100rpx;
background: url('../../../static/right.png') no-repeat 706rpx center #fff;
background-size: 16rpx 28rpx;
padding: 0 28rpx;
display: flex;
align-items: center;
.setting-card-logo{
width: 28rpx;
height: 28rpx;
margin-right: 20rpx;
}
.setting-card-title{
}
}
.setting-card:active{
background-color: #ddd;
}
}
.mine-message-box{
padding: 60rpx 60rpx 0;
.logo{
width: 100%;
height: 240rpx;
}
.mine-list-box{
display: flex;
padding-bottom: 40rpx;
.mine-list-item{
background: #fff;
height: 220rpx;
border-radius: 30rpx;
width: 50%;
padding: 38rpx;
}
.mine-list-item:nth-child(2n){
margin-left: 32rpx;
}
.mine-list-item-title{
font-size: 28rpx;
color:#000;
padding-left: 24rpx;
position: relative;
}
.mine-list-item-title::after{
position: absolute;
content: '';
width: 10rpx;
height: 28rpx;
border-radius: 5rpx;
left: 0;
top: 4rpx;
background: #6236FF;
}
.mine-list-item-title-total::after{
background: #FF6B6B;
}
.mine-list-item-num{
font-size: 64rpx;
color:#000;font-weight: 500;
}
}
}
}
.logout-box {
display: flex;
justify-content: center;
align-items: center;
background: #fff;
height: 100rpx;
padding: 0 28rpx;
}
.logout-text {
color: #707070;
}
================================================
FILE: talkieai-uniapp/src/pages/practice/components/Single.vue
================================================
{{ collect.content }}
{{ collect.translation }}
================================================
FILE: talkieai-uniapp/src/pages/practice/components/Statement.vue
================================================
{{ collect.content }}
{{ collect.translation }}
================================================
FILE: talkieai-uniapp/src/pages/practice/index.vue
================================================
练习
单词
句子
================================================
FILE: talkieai-uniapp/src/pages/topic/completion.vue
================================================
完成情况
已完成
{{ topicHistory.main_target_completed_count + topicHistory.trial_target_completed_count }}/{{
topicHistory.main_target_count + topicHistory.trial_target_count }}
已达成目标
{{ topicHistory.content_score }}%
分数
{{ topicHistory.word_count }}
已用单词数
{{ topicHistory.suggestion }}
================================================
FILE: talkieai-uniapp/src/pages/topic/history.vue
================================================
课程历史记录
{{ history.topic.topic }}
{{ history.create_time }}
已完成
未完成
暂时没有任何历史记录
================================================
FILE: talkieai-uniapp/src/pages/topic/index.vue
================================================
{{ topicDetail.name }}
场景
{{ topicDetail.description }}
目标
{{ main_target.description }}
也试试
{{ main_target.description }}
查看短语手册
开始
================================================
FILE: talkieai-uniapp/src/pages/topic/phrase.vue
================================================
短语手册
我保存的短语
场景短语
================================================
FILE: talkieai-uniapp/src/pages/topic/topicCreate.vue
================================================
您想谈些什么?
随机话题
我的角色
AI的角色
情境
创建话题
================================================
FILE: talkieai-uniapp/src/pages.json
================================================
{
"pages": [
{
"path": "pages/login/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/index/switchRole",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/chat/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/chat/settings",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/practice/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/my/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path":"pages/my/learnLanguage",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/contact/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/feedback/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/topic/index",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/topic/history",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/topic/phrase",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/topic/completion",
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/topic/topicCreate",
"style": {
"navigationStyle": "custom"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"tabBar": {
"color": "#000000",
"selectedColor": "#5456EB",
"borderStyle": "white",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"selectedIconPath": "static/home_select.png",
"iconPath": "static/home.png"
},
{
"pagePath": "pages/practice/index",
"text": "练习",
"selectedIconPath": "static/edit_select.png",
"iconPath": "static/edit.png"
},
{
"pagePath": "pages/my/index",
"text": "我的",
"selectedIconPath": "static/mine_select.png",
"iconPath": "static/mine.png"
}
]
}
}
================================================
FILE: talkieai-uniapp/src/shime-uni.d.ts
================================================
export {}
declare module "vue" {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {}
}
================================================
FILE: talkieai-uniapp/src/uni.scss
================================================
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color: #333; // 基本色
$uni-text-color-inverse: #fff; // 反色
$uni-text-color-grey: #999; // 辅助灰色,如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable: #c0c0c0;
/* 背景颜色 */
$uni-bg-color: #fff;
$uni-bg-color-grey: #f8f8f8;
$uni-bg-color-hover: #f1f1f1; // 点击状态颜色
$uni-bg-color-mask: rgba(0, 0, 0, 0.4); // 遮罩颜色
/* 边框颜色 */
$uni-border-color: #c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm: 12px;
$uni-font-size-base: 14px;
$uni-font-size-lg: 16;
/* 图片尺寸 */
$uni-img-size-sm: 20px;
$uni-img-size-base: 26px;
$uni-img-size-lg: 40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2c405a; // 文章标题颜色
$uni-font-size-title: 20px;
$uni-color-subtitle: #555; // 二级标题颜色
$uni-font-size-subtitle: 18px;
$uni-color-paragraph: #3f536e; // 文章段落颜色
$uni-font-size-paragraph: 15px;
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-badge/changelog.md
================================================
## 1.2.2(2023-01-28)
- 修复 运行/打包 控制台警告问题
## 1.2.1(2022-09-05)
- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473)
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-badge](https://uniapp.dcloud.io/component/uniui/uni-badge)
## 1.1.7(2021-11-08)
- 优化 升级ui
- 修改 size 属性默认值调整为 small
- 修改 type 属性,默认值调整为 error,info 替换 default
## 1.1.6(2021-09-22)
- 修复 在字节小程序上样式不生效的 bug
## 1.1.5(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.4(2021-07-29)
- 修复 去掉 nvue 不支持css 的 align-self 属性,nvue 下不暂支持 absolute 属性
## 1.1.3(2021-06-24)
- 优化 示例项目
## 1.1.1(2021-05-12)
- 新增 组件示例地址
## 1.1.0(2021-05-12)
- 新增 uni-badge 的 absolute 属性,支持定位
- 新增 uni-badge 的 offset 属性,支持定位偏移
- 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点
- 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+
- 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式
## 1.0.7(2021-05-07)
- 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug
- 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug
- 新增 uni-badge 属性 custom-style, 支持自定义样式
## 1.0.6(2021-02-04)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-badge/components/uni-badge/uni-badge.vue
================================================
{{displayValue}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-badge/package.json
================================================
{
"id": "uni-badge",
"displayName": "uni-badge 数字角标",
"version": "1.2.2",
"description": "数字角标(徽章)组件,在元素周围展示消息提醒,一般用于列表、九宫格、按钮等地方。",
"keywords": [
"",
"badge",
"uni-ui",
"uniui",
"数字角标",
"徽章"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-badge/readme.md
================================================
## Badge 数字角标
> **组件名:uni-badge**
> 代码块: `uBadge`
数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-badge)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/changelog.md
================================================
## 1.4.10(2023-04-10)
- 修复 某些情况 monthSwitch 未触发的Bug
## 1.4.9(2023-02-02)
- 修复 某些情况切换月份错误的Bug
## 1.4.8(2023-01-30)
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/161964)
## 1.4.7(2022-09-16)
- 优化 支持使用 uni-scss 控制主题色
## 1.4.6(2022-09-08)
- 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件的Bug
## 1.4.5(2022-02-25)
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.4(2022-02-25)
- 修复 条件编译 nvue 不支持的 css 样式的Bug
## 1.4.3(2021-09-22)
- 修复 startDate、 endDate 属性失效的Bug
## 1.4.2(2021-08-24)
- 新增 支持国际化
## 1.4.1(2021-08-05)
- 修复 弹出层被 tabbar 遮盖的Bug
## 1.4.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.3.16(2021-05-12)
- 新增 组件示例地址
## 1.3.15(2021-02-04)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/calendar.js
================================================
/**
* @1900-2100区间内的公历、农历互转
* @charset UTF-8
* @github https://github.com/jjonline/calendar.js
* @Author Jea杨(JJonline@JJonline.Cn)
* @Time 2014-7-21
* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
* @Version 1.0.3
* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
*/
/* eslint-disable */
var calendar = {
/**
* 农历1900-2100的润大小信息表
* @Array Of Property
* @return Hex
*/
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
/** Add By JJonline@JJonline.Cn**/
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
0x0d520], // 2100
/**
* 公历每个月份的天数普通表
* @Array Of Property
* @return Number
*/
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
/**
* 天干地支之天干速查表
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
* @return Cn string
*/
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
/**
* 天干地支之地支速查表
* @Array Of Property
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
* @return Cn string
*/
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
/**
* 天干地支之地支速查表<=>生肖
* @Array Of Property
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
* @return Cn string
*/
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
/**
* 24节气速查表
* @Array Of Property
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
* @return Cn string
*/
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
/**
* 1900-2100各年的24节气日期速查表
* @Array Of Property
* @return 0x string For splice
*/
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
/**
* 数字转中文速查表
* @Array Of Property
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
* @return Cn string
*/
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
/**
* 日期转农历称呼速查表
* @Array Of Property
* @trans ['初','十','廿','卅']
* @return Cn string
*/
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
/**
* 月份转农历称呼速查表
* @Array Of Property
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
* @return Cn string
*/
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
/**
* 返回农历y年一整年的总天数
* @param lunar Year
* @return Number
* @eg:var count = calendar.lYearDays(1987) ;//count=387
*/
lYearDays: function (y) {
var i; var sum = 348
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
return (sum + this.leapDays(y))
},
/**
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
* @param lunar Year
* @return Number (0-12)
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
*/
leapMonth: function (y) { // 闰字编码 \u95f0
return (this.lunarInfo[y - 1900] & 0xf)
},
/**
* 返回农历y年闰月的天数 若该年没有闰月则返回0
* @param lunar Year
* @return Number (0、29、30)
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
*/
leapDays: function (y) {
if (this.leapMonth(y)) {
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
}
return (0)
},
/**
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
* @param lunar Year
* @return Number (-1、29、30)
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
*/
monthDays: function (y, m) {
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
},
/**
* 返回公历(!)y年m月的天数
* @param solar Year
* @return Number (-1、28、29、30、31)
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
*/
solarDays: function (y, m) {
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var ms = m - 1
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
} else {
return (this.solarMonth[ms])
}
},
/**
* 农历年份转换为干支纪年
* @param lYear 农历年的年份数
* @return Cn string
*/
toGanZhiYear: function (lYear) {
var ganKey = (lYear - 3) % 10
var zhiKey = (lYear - 3) % 12
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
},
/**
* 公历月、日判断所属星座
* @param cMonth [description]
* @param cDay [description]
* @return Cn string
*/
toAstro: function (cMonth, cDay) {
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
},
/**
* 传入offset偏移量返回干支
* @param offset 相对甲子的偏移量
* @return Cn string
*/
toGanZhi: function (offset) {
return this.Gan[offset % 10] + this.Zhi[offset % 12]
},
/**
* 传入公历(!)y年获得该年第n个节气的公历日期
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
* @return day Number
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
*/
getTerm: function (y, n) {
if (y < 1900 || y > 2100) { return -1 }
if (n < 1 || n > 24) { return -1 }
var _table = this.sTermInfo[y - 1900]
var _info = [
parseInt('0x' + _table.substr(0, 5)).toString(),
parseInt('0x' + _table.substr(5, 5)).toString(),
parseInt('0x' + _table.substr(10, 5)).toString(),
parseInt('0x' + _table.substr(15, 5)).toString(),
parseInt('0x' + _table.substr(20, 5)).toString(),
parseInt('0x' + _table.substr(25, 5)).toString()
]
var _calday = [
_info[0].substr(0, 1),
_info[0].substr(1, 2),
_info[0].substr(3, 1),
_info[0].substr(4, 2),
_info[1].substr(0, 1),
_info[1].substr(1, 2),
_info[1].substr(3, 1),
_info[1].substr(4, 2),
_info[2].substr(0, 1),
_info[2].substr(1, 2),
_info[2].substr(3, 1),
_info[2].substr(4, 2),
_info[3].substr(0, 1),
_info[3].substr(1, 2),
_info[3].substr(3, 1),
_info[3].substr(4, 2),
_info[4].substr(0, 1),
_info[4].substr(1, 2),
_info[4].substr(3, 1),
_info[4].substr(4, 2),
_info[5].substr(0, 1),
_info[5].substr(1, 2),
_info[5].substr(3, 1),
_info[5].substr(4, 2)
]
return parseInt(_calday[n - 1])
},
/**
* 传入农历数字月份返回汉语通俗表示法
* @param lunar month
* @return Cn string
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
*/
toChinaMonth: function (m) { // 月 => \u6708
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
var s = this.nStr3[m - 1]
s += '\u6708'// 加上月字
return s
},
/**
* 传入农历日期数字返回汉字表示法
* @param lunar day
* @return Cn string
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
*/
toChinaDay: function (d) { // 日 => \u65e5
var s
switch (d) {
case 10:
s = '\u521d\u5341'; break
case 20:
s = '\u4e8c\u5341'; break
break
case 30:
s = '\u4e09\u5341'; break
break
default :
s = this.nStr2[Math.floor(d / 10)]
s += this.nStr1[d % 10]
}
return (s)
},
/**
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
* @param y year
* @return Cn string
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
*/
getAnimal: function (y) {
return this.Animals[(y - 4) % 12]
},
/**
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
* @param y solar year
* @param m solar month
* @param d solar day
* @return JSON object
* @eg:console.log(calendar.solar2lunar(1987,11,01));
*/
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
// 年份限定、上限
if (y < 1900 || y > 2100) {
return -1// undefined转换为数字变为NaN
}
// 公历传参最下限
if (y == 1900 && m == 1 && d < 31) {
return -1
}
// 未传参 获得当天
if (!y) {
var objDate = new Date()
} else {
var objDate = new Date(y, parseInt(m) - 1, d)
}
var i; var leap = 0; var temp = 0
// 修正ymd参数
var y = objDate.getFullYear()
var m = objDate.getMonth() + 1
var d = objDate.getDate()
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
for (i = 1900; i < 2101 && offset > 0; i++) {
temp = this.lYearDays(i)
offset -= temp
}
if (offset < 0) {
offset += temp; i--
}
// 是否今天
var isTodayObj = new Date()
var isToday = false
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
isToday = true
}
// 星期几
var nWeek = objDate.getDay()
var cWeek = this.nStr1[nWeek]
// 数字表示周几顺应天朝周一开始的惯例
if (nWeek == 0) {
nWeek = 7
}
// 农历年
var year = i
var leap = this.leapMonth(i) // 闰哪个月
var isLeap = false
// 效验闰月
for (i = 1; i < 13 && offset > 0; i++) {
// 闰月
if (leap > 0 && i == (leap + 1) && isLeap == false) {
--i
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
} else {
temp = this.monthDays(year, i)// 计算农历普通月天数
}
// 解除闰月
if (isLeap == true && i == (leap + 1)) { isLeap = false }
offset -= temp
}
// 闰月导致数组下标重叠取反
if (offset == 0 && leap > 0 && i == leap + 1) {
if (isLeap) {
isLeap = false
} else {
isLeap = true; --i
}
}
if (offset < 0) {
offset += temp; --i
}
// 农历月
var month = i
// 农历日
var day = offset + 1
// 天干地支处理
var sm = m - 1
var gzY = this.toGanZhiYear(year)
// 当月的两个节气
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
// 依据12节气修正干支月
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
if (d >= firstNode) {
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
}
// 传入的日期的节气与否
var isTerm = false
var Term = null
if (firstNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 2]
}
if (secondNode == d) {
isTerm = true
Term = this.solarTerm[m * 2 - 1]
}
// 日柱 当月一日与 1900/1/1 相差天数
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
var gzD = this.toGanZhi(dayCyclical + d - 1)
// 该日期所属的星座
var astro = this.toAstro(m, d)
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
},
/**
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
* @param y lunar year
* @param m lunar month
* @param d lunar day
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
* @return JSON object
* @eg:console.log(calendar.lunar2solar(1987,9,10));
*/
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
var isLeapMonth = !!isLeapMonth
var leapOffset = 0
var leapMonth = this.leapMonth(y)
var leapDay = this.leapDays(y)
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
var day = this.monthDays(y, m)
var _day = day
// bugFix 2016-9-25
// if month is leap, _day use leapDays method
if (isLeapMonth) {
_day = this.leapDays(y, m)
}
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
// 计算农历的时间差
var offset = 0
for (var i = 1900; i < y; i++) {
offset += this.lYearDays(i)
}
var leap = 0; var isAdd = false
for (var i = 1; i < m; i++) {
leap = this.leapMonth(y)
if (!isAdd) { // 处理闰月
if (leap <= i && leap > 0) {
offset += this.leapDays(y); isAdd = true
}
}
offset += this.monthDays(y, i)
}
// 转换闰月农历 需补充该年闰月的前一个月的时差
if (isLeapMonth) { offset += day }
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
var cY = calObj.getUTCFullYear()
var cM = calObj.getUTCMonth() + 1
var cD = calObj.getUTCDate()
return this.solar2lunar(cY, cM, cD)
}
}
export default calendar
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/en.json
================================================
{
"uni-calender.ok": "ok",
"uni-calender.cancel": "cancel",
"uni-calender.today": "today",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/index.js
================================================
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json
================================================
{
"uni-calender.ok": "确定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json
================================================
{
"uni-calender.ok": "確定",
"uni-calender.cancel": "取消",
"uni-calender.today": "今日",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue
================================================
{{weeks.date}}
{{todayText}}
{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}
{{weeks.extraInfo.info}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue
================================================
{{nowDate.month}}
{{SUNText}}
{{monText}}
{{TUEText}}
{{WEDText}}
{{THUText}}
{{FRIText}}
{{SATText}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/util.js
================================================
import CALENDAR from './calendar.js'
class Calendar {
constructor({
date,
selected,
startDate,
endDate,
range
} = {}) {
// 当前日期
this.date = this.getDate(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 范围开始
this.startDate = startDate
// 范围结束
this.endDate = endDate
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
// this._getWeek(this.date.fullDate)
}
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
this.selectDate = this.getDate(date)
this._getWeek(this.selectDate.fullDate)
}
/**
* 清理多选状态
*/
cleanMultipleStatus() {
this.multipleStatus = {
before: '',
after: '',
data: []
}
}
/**
* 重置开始日期
*/
resetSatrtDate(startDate) {
// 范围开始
this.startDate = startDate
}
/**
* 重置结束日期
*/
resetEndDate(endDate) {
// 范围结束
this.endDate = endDate
}
/**
* 获取任意时间
*/
getDate(date, AddDayCount = 0, str = 'day') {
if (!date) {
date = new Date()
}
if (typeof date !== 'object') {
date = date.replace(/-/g, '/')
}
const dd = new Date(date)
switch (str) {
case 'day':
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
break
case 'month':
if (dd.getDate() === 31 && AddDayCount>0) {
dd.setDate(dd.getDate() + AddDayCount)
} else {
const preMonth = dd.getMonth()
dd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期
const nextMonth = dd.getMonth()
// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount<0 && preMonth!==0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth+(nextMonth-preMonth+AddDayCount))
}
// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
if(AddDayCount>0 && nextMonth-preMonth>AddDayCount){
dd.setMonth(nextMonth-(nextMonth-preMonth-AddDayCount))
}
}
break
case 'year':
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
break
}
const y = dd.getFullYear()
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
return {
fullDate: y + '-' + m + '-' + d,
year: y,
month: m,
date: d,
day: dd.getDay()
}
}
/**
* 获取上月剩余天数
*/
_getLastMonthDays(firstDay, full) {
let dateArr = []
for (let i = firstDay; i > 0; i--) {
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
dateArr.push({
date: beforeDate,
month: full.month - 1,
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
disable: true
})
}
return dateArr
}
/**
* 获取本月天数
*/
_currentMonthDys(dateData, full) {
let dateArr = []
let fullDate = this.date.fullDate
for (let i = 1; i <= dateData; i++) {
let nowDate = full.year + '-' + (full.month < 10 ?
full.month : full.month) + '-' + (i < 10 ?
'0' + i : i)
// 是否今天
let isDay = fullDate === nowDate
// 获取打点信息
let info = this.selected && this.selected.find((item) => {
if (this.dateEqual(nowDate, item.date)) {
return item
}
})
// 日期禁用
let disableBefore = true
let disableAfter = true
if (this.startDate) {
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
disableBefore = this.dateCompare(this.startDate, nowDate)
}
if (this.endDate) {
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
disableAfter = this.dateCompare(nowDate, this.endDate)
}
let multiples = this.multipleStatus.data
let checked = false
let multiplesStatus = -1
if (this.range) {
if (multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, nowDate)
})
}
if (multiplesStatus !== -1) {
checked = true
}
}
let data = {
fullDate: nowDate,
year: full.year,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
month: full.month,
lunar: this.getlunar(full.year, full.month, i),
disable: !(disableBefore && disableAfter),
isDay
}
if (info) {
data.extraInfo = info
}
dateArr.push(data)
}
return dateArr
}
/**
* 获取下月天数
*/
_getNextMonthDays(surplus, full) {
let dateArr = []
for (let i = 1; i < surplus + 1; i++) {
dateArr.push({
date: i,
month: Number(full.month) + 1,
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
disable: true
})
}
return dateArr
}
/**
* 获取当前日期详情
* @param {Object} date
*/
getInfo(date) {
if (!date) {
date = new Date()
}
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
return dateInfo
}
/**
* 比较时间大小
*/
dateCompare(startDate, endDate) {
// 计算截止时间
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
if (startDate <= endDate) {
return true
} else {
return false
}
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
// 计算截止时间
before = new Date(before.replace('-', '/').replace('-', '/'))
// 计算详细项的截止时间
after = new Date(after.replace('-', '/').replace('-', '/'))
if (before.getTime() - after.getTime() === 0) {
return true
} else {
return false
}
}
/**
* 获取日期范围内所有日期
* @param {Object} begin
* @param {Object} end
*/
geDateAll(begin, end) {
var arr = []
var ab = begin.split('-')
var ae = end.split('-')
var db = new Date()
db.setFullYear(ab[0], ab[1] - 1, ab[2])
var de = new Date()
de.setFullYear(ae[0], ae[1] - 1, ae[2])
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
}
return arr
}
/**
* 计算阴历日期显示
*/
getlunar(year, month, date) {
return CALENDAR.solar2lunar(year, month, date)
}
/**
* 设置打点
*/
setSelectInfo(data, value) {
this.selected = value
this._getWeek(data)
}
/**
* 获取多选状态
*/
setMultiple(fullDate) {
let {
before,
after
} = this.multipleStatus
if (!this.range) return
if (before && after) {
this.multipleStatus.before = ''
this.multipleStatus.after = ''
this.multipleStatus.data = []
} else {
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
}
this._getWeek(fullDate)
}
/**
* 获取每周数据
* @param {Object} dateData
*/
_getWeek(dateData) {
const {
year,
month
} = this.getDate(dateData)
let firstDay = new Date(year, month - 1, 1).getDay()
let currentDay = new Date(year, month, 0).getDate()
let dates = {
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
nextMonthDays: [], // 下个月开始几天
weeks: []
}
let canlender = []
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
let weeks = {}
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
for (let i = 0; i < canlender.length; i++) {
if (i % 7 === 0) {
weeks[parseInt(i / 7)] = new Array(7)
}
weeks[parseInt(i / 7)][i % 7] = canlender[i]
}
this.canlender = canlender
this.weeks = weeks
}
//静态方法
// static init(date) {
// if (!this.instance) {
// this.instance = new Calendar(date);
// }
// return this.instance;
// }
}
export default Calendar
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/package.json
================================================
{
"id": "uni-calendar",
"displayName": "uni-calendar 日历",
"version": "1.4.10",
"description": "日历组件",
"keywords": [
"uni-ui",
"uniui",
"日历",
"",
"打卡",
"日历选择"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-calendar/readme.md
================================================
## Calendar 日历
> **组件名:uni-calendar**
> 代码块: `uCalendar`
日历组件
> **注意事项**
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
> - 本组件农历转换使用的js是 [@1900-2100区间内的公历、农历互转](https://github.com/jjonline/calendar.js)
> - 仅支持自定义组件模式
> - `date`属性传入的应该是一个 String ,如: 2019-06-27 ,而不是 new Date()
> - 通过 `insert` 属性来确定当前的事件是 @change 还是 @confirm 。理应合并为一个事件,但是为了区分模式,现使用两个事件,这里需要注意
> - 弹窗模式下无法阻止后面的元素滚动,如有需要阻止,请在弹窗弹出后,手动设置滚动元素为不可滚动
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
### 基本用法
在 ``template`` 中使用组件
```html
```
### 通过方法打开日历
需要设置 `insert` 为 `false`
```html
```
```javascript
export default {
data() {
return {};
},
methods: {
open(){
this.$refs.calendar.open();
},
confirm(e) {
console.log(e);
}
}
};
```
## API
### Calendar Props
| 属性名 | 类型 | 默认值| 说明 |
| - | - | - | - |
| date | String |- | 自定义当前时间,默认为今天 |
| lunar | Boolean | false | 显示农历 |
| startDate | String |- | 日期选择范围-开始日期 |
| endDate | String |- | 日期选择范围-结束日期 |
| range | Boolean | false | 范围选择 |
| insert | Boolean | false | 插入模式,可选值,ture:插入模式;false:弹窗模式;默认为插入模式 |
|clearDate |Boolean |true |弹窗模式是否清空上次选择内容 |
| selected | Array |- | 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|showMonth | Boolean | true | 是否显示月份为背景 |
### Calendar Events
| 事件名 | 说明 |返回值|
| - | - | - |
| open | 弹出日历组件,`insert :false` 时生效|- |
## 组件示例
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar](https://hellouniapp.dcloud.net.cn/pages/extUI/calendar/calendar)
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-card/changelog.md
================================================
## 1.3.1(2021-12-20)
- 修复 在vue页面下略缩图显示不正常的bug
## 1.3.0(2021-11-19)
- 重构插槽的用法 ,header 替换为 title
- 新增 actions 插槽
- 新增 cover 封面图属性和插槽
- 新增 padding 内容默认内边距离
- 新增 margin 卡片默认外边距离
- 新增 spacing 卡片默认内边距
- 新增 shadow 卡片阴影属性
- 取消 mode 属性,可使用组合插槽代替
- 取消 note 属性 ,使用actions插槽代替
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)
## 1.2.1(2021-07-30)
- 优化 vue3下事件警告的问题
## 1.2.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.8(2021-07-01)
- 优化 图文卡片无图片加载时,提供占位图标
- 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持)
- 修复 thumbnail 不存在仍然占位的 bug
## 1.1.7(2021-05-12)
- 新增 组件示例地址
## 1.1.6(2021-02-04)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-card/components/uni-card/uni-card.vue
================================================
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-card/package.json
================================================
{
"id": "uni-card",
"displayName": "uni-card 卡片",
"version": "1.3.1",
"description": "Card 组件,提供常见的卡片样式。",
"keywords": [
"uni-ui",
"uniui",
"card",
"",
"卡片"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-icons",
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-card/readme.md
================================================
## Card 卡片
> **组件名:uni-card**
> 代码块: `uCard`
卡片视图组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-collapse/changelog.md
================================================
## 1.4.3(2022-01-25)
- 修复 初始化的时候 ,open 属性失效的bug
## 1.4.2(2022-01-21)
- 修复 微信小程序resize后组件收起的bug
## 1.4.1(2021-11-22)
- 修复 vue3中个别scss变量无法找到的问题
## 1.4.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-collapse](https://uniapp.dcloud.io/component/uniui/uni-collapse)
## 1.3.3(2021-08-17)
- 优化 show-arrow 属性默认为true
## 1.3.2(2021-08-17)
- 新增 show-arrow 属性,控制是否显示右侧箭头
## 1.3.1(2021-07-30)
- 优化 vue3下小程序事件警告的问题
## 1.3.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.2.2(2021-07-21)
- 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug
## 1.2.1(2021-07-21)
- 优化 组件示例
## 1.2.0(2021-07-21)
- 新增 组件折叠动画
- 新增 value\v-model 属性 ,动态修改面板折叠状态
- 新增 title 插槽 ,可定义面板标题
- 新增 border 属性 ,显示隐藏面板内容分隔线
- 新增 title-border 属性 ,显示隐藏面板标题分隔线
- 修复 resize 方法失效的Bug
- 修复 change 事件返回参数不正确的Bug
- 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法
## 1.1.7(2021-05-12)
- 新增 组件示例地址
## 1.1.6(2021-02-05)
- 优化 组件引用关系,通过uni_modules引用组件
## 1.1.5(2021-02-05)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue
================================================
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue
================================================
{{ title }}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-collapse/package.json
================================================
{
"id": "uni-collapse",
"displayName": "uni-collapse 折叠面板",
"version": "1.4.3",
"description": "Collapse 组件,可以折叠 / 展开的内容区域。",
"keywords": [
"uni-ui",
"折叠",
"折叠面板",
"手风琴"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-collapse/readme.md
================================================
## Collapse 折叠面板
> **组件名:uni-collapse**
> 代码块: `uCollapse`
> 关联组件:`uni-collapse-item`、`uni-icons`。
折叠面板用来折叠/显示过长的内容或者是列表。通常是在多内容分类项使用,折叠不重要的内容,显示重要内容。点击可以展开折叠部分。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-collapse)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-combox/changelog.md
================================================
## 1.0.1(2021-11-23)
- 优化 label、label-width 属性
## 1.0.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-combox](https://uniapp.dcloud.io/component/uniui/uni-combox)
## 0.1.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.0.6(2021-05-12)
- 新增 组件示例地址
## 0.0.5(2021-04-21)
- 优化 添加依赖 uni-icons, 导入后自动下载依赖
## 0.0.4(2021-02-05)
- 优化 组件引用关系,通过uni_modules引用组件
## 0.0.3(2021-02-04)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-combox/components/uni-combox/uni-combox.vue
================================================
{{label}}
{{emptyTips}}
{{item}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-combox/package.json
================================================
{
"id": "uni-combox",
"displayName": "uni-combox 组合框",
"version": "1.0.1",
"description": "可以选择也可以输入的表单项 ",
"keywords": [
"uni-ui",
"uniui",
"combox",
"组合框",
"select"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-combox/readme.md
================================================
## Combox 组合框
> **组件名:uni-combox**
> 代码块: `uCombox`
组合框组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-combox)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/changelog.md
================================================
## 1.2.2(2022-01-19)
- 修复 在微信小程序中样式不生效的bug
## 1.2.1(2022-01-18)
- 新增 update 方法 ,在动态更新时间后,刷新组件
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-countdown](https://uniapp.dcloud.io/component/uniui/uni-countdown)
## 1.1.3(2021-10-18)
- 重构
- 新增 font-size 支持自定义字体大小
## 1.1.2(2021-08-24)
- 新增 支持国际化
## 1.1.1(2021-07-30)
- 优化 vue3下小程序事件警告的问题
## 1.1.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.5(2021-06-18)
- 修复 uni-countdown 重复赋值跳两秒的 bug
## 1.0.4(2021-05-12)
- 新增 组件示例地址
## 1.0.3(2021-05-08)
- 修复 uni-countdown 不能控制倒计时的 bug
## 1.0.2(2021-02-04)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/en.json
================================================
{
"uni-countdown.day": "day",
"uni-countdown.h": "h",
"uni-countdown.m": "m",
"uni-countdown.s": "s"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/index.js
================================================
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json
================================================
{
"uni-countdown.day": "天",
"uni-countdown.h": "时",
"uni-countdown.m": "分",
"uni-countdown.s": "秒"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json
================================================
{
"uni-countdown.day": "天",
"uni-countdown.h": "時",
"uni-countdown.m": "分",
"uni-countdown.s": "秒"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue
================================================
{{ d }}
{{dayText}}
{{ h }}
{{ showColon ? ':' : hourText }}
{{ i }}
{{ showColon ? ':' : minuteText }}
{{ s }}
{{secondText}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/package.json
================================================
{
"id": "uni-countdown",
"displayName": "uni-countdown 倒计时",
"version": "1.2.2",
"description": "CountDown 倒计时组件",
"keywords": [
"uni-ui",
"uniui",
"countdown",
"倒计时"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-countdown/readme.md
================================================
## CountDown 倒计时
> **组件名:uni-countdown**
> 代码块: `uCountDown`
倒计时组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-countdown)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-checkbox/changelog.md
================================================
## 1.0.3(2022-09-16)
- 可以使用 uni-scss 控制主题色
## 1.0.2(2022-06-30)
- 优化 在 uni-forms 中的依赖注入方式
## 1.0.1(2022-02-07)
- 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug
## 1.0.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-checkbox](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
## 0.2.5(2021-08-23)
- 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题
## 0.2.4(2021-08-17)
- 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题
## 0.2.3(2021-08-11)
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.2.2(2021-07-30)
- 优化 在uni-forms组件,与label不对齐的问题
## 0.2.1(2021-07-27)
- 修复 单选默认值为0不能选中的Bug
## 0.2.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.11(2021-07-06)
- 优化 删除无用日志
## 0.1.10(2021-07-05)
- 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题
## 0.1.9(2021-07-05)
- 修复 nvue 黑框样式问题
## 0.1.8(2021-06-28)
- 修复 selectedTextColor 属性不生效的Bug
## 0.1.7(2021-06-02)
- 新增 map 属性,可以方便映射text/value属性
## 0.1.6(2021-05-26)
- 修复 不关联服务空间的情况下组件报错的Bug
## 0.1.5(2021-05-12)
- 新增 组件示例地址
## 0.1.4(2021-04-09)
- 修复 nvue 下无法选中的问题
## 0.1.3(2021-03-22)
- 新增 disabled属性
## 0.1.2(2021-02-24)
- 优化 默认颜色显示
## 0.1.1(2021-02-24)
- 新增 支持nvue
## 0.1.0(2021-02-18)
- “暂无数据”显示居中
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue
================================================
{{mixinDatacomErrorMessage}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-checkbox/package.json
================================================
{
"id": "uni-data-checkbox",
"displayName": "uni-data-checkbox 数据选择器",
"version": "1.0.3",
"description": "通过数据驱动的单选框和复选框",
"keywords": [
"uni-ui",
"checkbox",
"单选",
"多选",
"单选多选"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "^3.1.1"
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-load-more","uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-checkbox/readme.md
================================================
## DataCheckbox 数据驱动的单选复选框
> **组件名:uni-data-checkbox**
> 代码块: `uDataCheckbox`
本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括:
1. 数据绑定型组件:给本组件绑定一个data,会自动渲染一组候选内容。再以往,开发者需要编写不少代码实现类似功能
2. 自动的表单校验:组件绑定了data,且符合[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)组件的表单校验规范,搭配使用会自动实现表单校验
3. 本组件合并了单选多选
4. 本组件有若干风格选择,如普通的单选多选框、并列button风格、tag风格。开发者可以快速选择需要的风格。但作为一个封装组件,样式代码虽然不用自己写了,却会牺牲一定的样式自定义性
在uniCloud开发中,`DB Schema`中配置了enum枚举等类型后,在web控制台的[自动生成表单](https://uniapp.dcloud.io/uniCloud/schema?id=autocode)功能中,会自动生成``uni-data-checkbox``组件并绑定好data
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-checkbox)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/changelog.md
================================================
## 1.1.2(2023-04-11)
- 修复 更改 modelValue 报错的 bug
- 修复 v-for 未使用 key 值控制台 warning
## 1.1.1(2023-02-21)
- 修复代码合并时引发 value 属性为空时不渲染数据的问题
## 1.1.0(2023-02-15)
- 修复 localdata 不支持动态更新的bug
## 1.0.9(2023-02-15)
- 修复 localdata 不支持动态更新的bug
## 1.0.8(2022-09-16)
- 可以使用 uni-scss 控制主题色
## 1.0.7(2022-07-06)
- 优化 pc端图标位置不正确的问题
## 1.0.6(2022-07-05)
- 优化 显示样式
## 1.0.5(2022-07-04)
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
## 1.0.4(2022-04-19)
- 修复 字节小程序 本地数据无法选择下一级的Bug
## 1.0.3(2022-02-25)
- 修复 nvue 不支持的 v-show 的 bug
## 1.0.2(2022-02-25)
- 修复 条件编译 nvue 不支持的 css 样式
## 1.0.1(2021-11-23)
- 修复 由上个版本引发的map、v-model等属性不生效的bug
## 1.0.0(2021-11-19)
- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-picker](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
## 0.4.9(2021-10-28)
- 修复 VUE2 v-model 概率无效的 bug
## 0.4.8(2021-10-27)
- 修复 v-model 概率无效的 bug
## 0.4.7(2021-10-25)
- 新增 属性 spaceInfo 服务空间配置 HBuilderX 3.2.11+
- 修复 树型 uniCloud 数据类型为 int 时报错的 bug
## 0.4.6(2021-10-19)
- 修复 非 VUE3 v-model 为 0 时无法选中的 bug
## 0.4.5(2021-09-26)
- 新增 清除已选项的功能(通过 clearIcon 属性配置是否显示按钮),同时提供 clear 方法以供调用,二者等效
- 修复 readonly 为 true 时报错的 bug
## 0.4.4(2021-09-26)
- 修复 上一版本造成的 map 属性失效的 bug
- 新增 ellipsis 属性,支持配置 tab 选项长度过长时是否自动省略
## 0.4.3(2021-09-24)
- 修复 某些情况下级联未触发的 bug
## 0.4.2(2021-09-23)
- 新增 提供 show 和 hide 方法,开发者可以通过 ref 调用
- 新增 选项内容过长自动添加省略号
## 0.4.1(2021-09-15)
- 新增 map 属性 字段映射,将 text/value 映射到数据中的其他字段
## 0.4.0(2021-07-13)
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.3.5(2021-06-04)
- 修复 无法加载云端数据的问题
## 0.3.4(2021-05-28)
- 修复 v-model 无效问题
- 修复 loaddata 为空数据组时加载时间过长问题
- 修复 上个版本引出的本地数据无法选择带有 children 的 2 级节点
## 0.3.3(2021-05-12)
- 新增 组件示例地址
## 0.3.2(2021-04-22)
- 修复 非树形数据有 where 属性查询报错的问题
## 0.3.1(2021-04-15)
- 修复 本地数据概率无法回显时问题
## 0.3.0(2021-04-07)
- 新增 支持云端非树形表结构数据
- 修复 根节点 parent_field 字段等于 null 时选择界面错乱问题
## 0.2.0(2021-03-15)
- 修复 nodeclick、popupopened、popupclosed 事件无法触发的问题
## 0.1.9(2021-03-09)
- 修复 微信小程序某些情况下无法选择的问题
## 0.1.8(2021-02-05)
- 优化 部分样式在 nvue 上的兼容表现
## 0.1.7(2021-02-05)
- 调整为 uni_modules 目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js
================================================
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
this.$once('hook:beforeDestroy', () => {
document.removeEventListener('keyup', listener)
})
},
render: () => {}
}
// #endif
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue
================================================
{{errorMessage}}
{{item.text}}{{split}}
{{placeholder}}
{{popupTitle}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js
================================================
export default {
props: {
localdata: {
type: [Array, Object],
default () {
return []
}
},
spaceInfo: {
type: Object,
default () {
return {}
}
},
collection: {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 500
},
getcount: {
type: [Boolean, String],
default: false
},
getone: {
type: [Boolean, String],
default: false
},
gettree: {
type: [Boolean, String],
default: false
},
manual: {
type: Boolean,
default: false
},
value: {
type: [Array, String, Number],
default () {
return []
}
},
modelValue: {
type: [Array, String, Number],
default () {
return []
}
},
preload: {
type: Boolean,
default: false
},
stepSearh: {
type: Boolean,
default: true
},
selfField: {
type: String,
default: ''
},
parentField: {
type: String,
default: ''
},
multiple: {
type: Boolean,
default: false
},
map: {
type: Object,
default () {
return {
text: "text",
value: "value"
}
}
}
},
data() {
return {
loading: false,
errorMessage: '',
loadMore: {
contentdown: '',
contentrefresh: '',
contentnomore: ''
},
dataList: [],
selected: [],
selectedIndex: 0,
page: {
current: this.pageCurrent,
size: this.pageSize,
count: 0
}
}
},
computed: {
isLocalData() {
return !this.collection.length;
},
isCloudData() {
return this.collection.length > 0;
},
isCloudDataList() {
return (this.isCloudData && (!this.parentField && !this.selfField));
},
isCloudDataTree() {
return (this.isCloudData && this.parentField && this.selfField);
},
dataValue() {
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null ||
this.modelValue !== undefined);
return isModelValue ? this.modelValue : this.value;
},
hasValue() {
if (typeof this.dataValue === 'number') {
return true
}
return (this.dataValue != null) && (this.dataValue.length > 0)
}
},
created() {
this.$watch(() => {
var al = [];
['pageCurrent',
'pageSize',
'spaceInfo',
'value',
'modelValue',
'localdata',
'collection',
'action',
'field',
'orderby',
'where',
'getont',
'getcount',
'gettree'
].forEach(key => {
al.push(this[key])
});
return al
}, (newValue, oldValue) => {
let needReset = false
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] != oldValue[i]) {
needReset = true
break
}
}
if (newValue[0] != oldValue[0]) {
this.page.current = this.pageCurrent
}
this.page.size = this.pageSize
this.onPropsChange()
})
this._treeData = []
},
methods: {
onPropsChange() {
this._treeData = [];
},
// 填充 pickview 数据
async loadData() {
if (this.isLocalData) {
this.loadLocalData();
} else if (this.isCloudDataList) {
this.loadCloudDataList();
} else if (this.isCloudDataTree) {
this.loadCloudDataTree();
}
},
// 加载本地数据
async loadLocalData() {
this._treeData = [];
this._extractTree(this.localdata, this._treeData);
let inputValue = this.dataValue;
if (inputValue === undefined) {
return;
}
if (Array.isArray(inputValue)) {
inputValue = inputValue[inputValue.length - 1];
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
inputValue = inputValue[this.map.value];
}
}
this.selected = this._findNodePath(inputValue, this.localdata);
},
// 加载 Cloud 数据 (单列)
async loadCloudDataList() {
if (this.loading) {
return;
}
this.loading = true;
try {
let response = await this.getCommand();
let responseData = response.result.data;
this._treeData = responseData;
this._updateBindData();
this._updateSelected();
this.onDataChange();
} catch (e) {
this.errorMessage = e;
} finally {
this.loading = false;
}
},
// 加载 Cloud 数据 (树形)
async loadCloudDataTree() {
if (this.loading) {
return;
}
this.loading = true;
try {
let commandOptions = {
field: this._cloudDataPostField(),
where: this._cloudDataTreeWhere()
};
if (this.gettree) {
commandOptions.startwith = `${this.selfField}=='${this.dataValue}'`;
}
let response = await this.getCommand(commandOptions);
let responseData = response.result.data;
this._treeData = responseData;
this._updateBindData();
this._updateSelected();
this.onDataChange();
} catch (e) {
this.errorMessage = e;
} finally {
this.loading = false;
}
},
// 加载 Cloud 数据 (节点)
async loadCloudDataNode(callback) {
if (this.loading) {
return;
}
this.loading = true;
try {
let commandOptions = {
field: this._cloudDataPostField(),
where: this._cloudDataNodeWhere()
};
let response = await this.getCommand(commandOptions);
let responseData = response.result.data;
callback(responseData);
} catch (e) {
this.errorMessage = e;
} finally {
this.loading = false;
}
},
// 回显 Cloud 数据
getCloudDataValue() {
if (this.isCloudDataList) {
return this.getCloudDataListValue();
}
if (this.isCloudDataTree) {
return this.getCloudDataTreeValue();
}
},
// 回显 Cloud 数据 (单列)
getCloudDataListValue() {
// 根据 field's as value标识匹配 where 条件
let where = [];
let whereField = this._getForeignKeyByField();
if (whereField) {
where.push(`${whereField} == '${this.dataValue}'`)
}
where = where.join(' || ');
if (this.where) {
where = `(${this.where}) && (${where})`
}
return this.getCommand({
field: this._cloudDataPostField(),
where
}).then((res) => {
this.selected = res.result.data;
return res.result.data;
});
},
// 回显 Cloud 数据 (树形)
getCloudDataTreeValue() {
return this.getCommand({
field: this._cloudDataPostField(),
getTreePath: {
startWith: `${this.selfField}=='${this.dataValue}'`
}
}).then((res) => {
let treePath = [];
this._extractTreePath(res.result.data, treePath);
this.selected = treePath;
return treePath;
});
},
getCommand(options = {}) {
/* eslint-disable no-undef */
let db = uniCloud.database(this.spaceInfo)
const action = options.action || this.action
if (action) {
db = db.action(action)
}
const collection = options.collection || this.collection
db = db.collection(collection)
const where = options.where || this.where
if (!(!where || !Object.keys(where).length)) {
db = db.where(where)
}
const field = options.field || this.field
if (field) {
db = db.field(field)
}
const orderby = options.orderby || this.orderby
if (orderby) {
db = db.orderBy(orderby)
}
const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
const size = options.pageSize !== undefined ? options.pageSize : this.page.size
const getCount = options.getcount !== undefined ? options.getcount : this.getcount
const getTree = options.gettree !== undefined ? options.gettree : this.gettree
const getOptions = {
getCount,
getTree
}
if (options.getTreePath) {
getOptions.getTreePath = options.getTreePath
}
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
return db
},
_cloudDataPostField() {
let fields = [this.field];
if (this.parentField) {
fields.push(`${this.parentField} as parent_value`);
}
return fields.join(',');
},
_cloudDataTreeWhere() {
let result = []
let selected = this.selected
let parentField = this.parentField
if (parentField) {
result.push(`${parentField} == null || ${parentField} == ""`)
}
if (selected.length) {
for (var i = 0; i < selected.length - 1; i++) {
result.push(`${parentField} == '${selected[i].value}'`)
}
}
let where = []
if (this.where) {
where.push(`(${this.where})`)
}
if (result.length) {
where.push(`(${result.join(' || ')})`)
}
return where.join(' && ')
},
_cloudDataNodeWhere() {
let where = []
let selected = this.selected;
if (selected.length) {
where.push(`${this.parentField} == '${selected[selected.length - 1].value}'`);
}
where = where.join(' || ');
if (this.where) {
return `(${this.where}) && (${where})`
}
return where
},
_getWhereByForeignKey() {
let result = []
let whereField = this._getForeignKeyByField();
if (whereField) {
result.push(`${whereField} == '${this.dataValue}'`)
}
if (this.where) {
return `(${this.where}) && (${result.join(' || ')})`
}
return result.join(' || ')
},
_getForeignKeyByField() {
let fields = this.field.split(',');
let whereField = null;
for (let i = 0; i < fields.length; i++) {
const items = fields[i].split('as');
if (items.length < 2) {
continue;
}
if (items[1].trim() === 'value') {
whereField = items[0].trim();
break;
}
}
return whereField;
},
_updateBindData(node) {
const {
dataList,
hasNodes
} = this._filterData(this._treeData, this.selected)
let isleaf = this._stepSearh === false && !hasNodes
if (node) {
node.isleaf = isleaf
}
this.dataList = dataList
this.selectedIndex = dataList.length - 1
if (!isleaf && this.selected.length < dataList.length) {
this.selected.push({
value: null,
text: "请选择"
})
}
return {
isleaf,
hasNodes
}
},
_updateSelected() {
let dl = this.dataList
let sl = this.selected
let textField = this.map.text
let valueField = this.map.value
for (let i = 0; i < sl.length; i++) {
let value = sl[i].value
let dl2 = dl[i]
for (let j = 0; j < dl2.length; j++) {
let item2 = dl2[j]
if (item2[valueField] === value) {
sl[i].text = item2[textField]
break
}
}
}
},
_filterData(data, paths) {
let dataList = []
let hasNodes = true
dataList.push(data.filter((item) => {
return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
}))
for (let i = 0; i < paths.length; i++) {
let value = paths[i].value
let nodes = data.filter((item) => {
return item.parent_value === value
})
if (nodes.length) {
dataList.push(nodes)
} else {
hasNodes = false
}
}
return {
dataList,
hasNodes
}
},
_extractTree(nodes, result, parent_value) {
let list = result || []
let valueField = this.map.value
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let child = {}
for (let key in node) {
if (key !== 'children') {
child[key] = node[key]
}
}
if (parent_value !== null && parent_value !== undefined && parent_value !== '') {
child.parent_value = parent_value
}
result.push(child)
let children = node.children
if (children) {
this._extractTree(children, result, node[valueField])
}
}
},
_extractTreePath(nodes, result) {
let list = result || []
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let child = {}
for (let key in node) {
if (key !== 'children') {
child[key] = node[key]
}
}
result.push(child)
let children = node.children
if (children) {
this._extractTreePath(children, result)
}
}
},
_findNodePath(key, nodes, path = []) {
let textField = this.map.text
let valueField = this.map.value
for (let i = 0; i < nodes.length; i++) {
let node = nodes[i]
let children = node.children
let text = node[textField]
let value = node[valueField]
path.push({
value,
text
})
if (value === key) {
return path
}
if (children) {
const p = this._findNodePath(key, children, path)
if (p.length) {
return p
}
}
path.pop()
}
return []
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue
================================================
{{item.text || ''}}
{{item[map.text]}}
{{errorMessage}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/package.json
================================================
{
"id": "uni-data-picker",
"displayName": "uni-data-picker 数据驱动的picker选择器",
"version": "1.1.2",
"description": "单列、多列级联选择器,常用于省市区城市选择、公司部门选择、多级分类等场景",
"keywords": [
"uni-ui",
"uniui",
"picker",
"级联",
"省市区",
""
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-load-more",
"uni-icons",
"uni-scss"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/readme.md
================================================
## DataPicker 级联选择
> **组件名:uni-data-picker**
> 代码块: `uDataPicker`
> 关联组件:`uni-data-pickerview`、`uni-load-more`。
`` 是一个选择类[datacom组件](https://uniapp.dcloud.net.cn/component/datacom)。
支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
候选数据支持一次性加载完毕,也支持懒加载,比如示例图中,选择了“北京”后,动态加载北京的区县数据。
`` 组件尤其适用于地址选择、分类选择等选择类。
`` 支持本地数据、云端静态数据(json),uniCloud云数据库数据。
`` 可以通过JQL直连uniCloud云数据库,配套[DB Schema](https://uniapp.dcloud.net.cn/uniCloud/schema),可在schema2code中自动生成前端页面,还支持服务器端校验。
在uniCloud数据表中新建表“uni-id-address”和“opendb-city-china”,这2个表的schema自带foreignKey关联。在“uni-id-address”表的表结构页面使用schema2code生成前端页面,会自动生成地址管理的维护页面,自动从“opendb-city-china”表包含的中国所有省市区信息里选择地址。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-select/changelog.md
================================================
## 1.0.6(2023-04-12)
- 修复 微信小程序点击时会改变背景颜色的 bug
## 1.0.5(2023-02-03)
- 修复 禁用时会显示清空按钮
## 1.0.4(2023-02-02)
- 优化 查询条件短期内多次变更只查询最后一次变更后的结果
- 调整 内部缓存键名调整为 uni-data-select-lastSelectedValue
## 1.0.3(2023-01-16)
- 修复 不关联服务空间报错的问题
## 1.0.2(2023-01-14)
- 新增 属性 `format` 可用于格式化显示选项内容
## 1.0.1(2022-12-06)
- 修复 当where变化时,数据不会自动更新的问题
## 0.1.9(2022-09-05)
- 修复 微信小程序下拉框出现后选择会点击到蒙板后面的输入框
## 0.1.8(2022-08-29)
- 修复 点击的位置不准确
## 0.1.7(2022-08-12)
- 新增 支持 disabled 属性
## 0.1.6(2022-07-06)
- 修复 pc端宽度异常的bug
## 0.1.5
- 修复 pc端宽度异常的bug
## 0.1.4(2022-07-05)
- 优化 显示样式
## 0.1.3(2022-06-02)
- 修复 localdata 赋值不生效的 bug
- 新增 支持 uni.scss 修改颜色
- 新增 支持选项禁用(数据选项设置 disabled: true 即禁用)
## 0.1.2(2022-05-08)
- 修复 当 value 为 0 时选择不生效的 bug
## 0.1.1(2022-05-07)
- 新增 记住上次的选项(仅 collection 存在时有效)
## 0.1.0(2022-04-22)
- 初始化
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue
================================================
{{label + ':'}}
{{current}}
{{typePlaceholder}}
{{emptyTips}}
{{formatItemName(item)}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-select/package.json
================================================
{
"id": "uni-data-select",
"displayName": "uni-data-select 下拉框选择器",
"version": "1.0.6",
"description": "通过数据驱动的下拉框选择器",
"keywords": [
"uni-ui",
"select",
"uni-data-select",
"下拉框",
"下拉选"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": "^3.1.1"
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-load-more"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "u",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-data-select/readme.md
================================================
## DataSelect 下拉框选择器
> **组件名:uni-data-select**
> 代码块: `uDataSelect`
当选项过多时,使用下拉菜单展示并选择内容
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-select)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-dateformat/changelog.md
================================================
## 1.0.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-dateformat](https://uniapp.dcloud.io/component/uniui/uni-dateformat)
## 0.0.5(2021-07-08)
- 调整 默认时间不再是当前时间,而是显示'-'字符
## 0.0.4(2021-05-12)
- 新增 组件示例地址
## 0.0.3(2021-02-04)
- 调整为uni_modules目录规范
- 修复 iOS 平台日期格式化出错的问题
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js
================================================
// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型
function pad(str, length = 2) {
str += ''
while (str.length < length) {
str = '0' + str
}
return str.slice(-length)
}
const parser = {
yyyy: (dateObj) => {
return pad(dateObj.year, 4)
},
yy: (dateObj) => {
return pad(dateObj.year)
},
MM: (dateObj) => {
return pad(dateObj.month)
},
M: (dateObj) => {
return dateObj.month
},
dd: (dateObj) => {
return pad(dateObj.day)
},
d: (dateObj) => {
return dateObj.day
},
hh: (dateObj) => {
return pad(dateObj.hour)
},
h: (dateObj) => {
return dateObj.hour
},
mm: (dateObj) => {
return pad(dateObj.minute)
},
m: (dateObj) => {
return dateObj.minute
},
ss: (dateObj) => {
return pad(dateObj.second)
},
s: (dateObj) => {
return dateObj.second
},
SSS: (dateObj) => {
return pad(dateObj.millisecond, 3)
},
S: (dateObj) => {
return dateObj.millisecond
},
}
// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12
function getDate(time) {
if (time instanceof Date) {
return time
}
switch (typeof time) {
case 'string':
{
// 2020-12-12T12:12:12.000Z、2020-12-12T12:12:12.000
if (time.indexOf('T') > -1) {
return new Date(time)
}
return new Date(time.replace(/-/g, '/'))
}
default:
return new Date(time)
}
}
export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') {
if (!date && date !== 0) {
return ''
}
date = getDate(date)
const dateObj = {
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
hour: date.getHours(),
minute: date.getMinutes(),
second: date.getSeconds(),
millisecond: date.getMilliseconds()
}
const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/
let flag = true
let result = format
while (flag) {
flag = false
result = result.replace(tokenRegExp, function(matched) {
flag = true
return parser[matched](dateObj)
})
}
return result
}
export function friendlyDate(time, {
locale = 'zh',
threshold = [60000, 3600000],
format = 'yyyy/MM/dd hh:mm:ss'
}) {
if (time === '-') {
return time
}
if (!time && time !== 0) {
return ''
}
const localeText = {
zh: {
year: '年',
month: '月',
day: '天',
hour: '小时',
minute: '分钟',
second: '秒',
ago: '前',
later: '后',
justNow: '刚刚',
soon: '马上',
template: '{num}{unit}{suffix}'
},
en: {
year: 'year',
month: 'month',
day: 'day',
hour: 'hour',
minute: 'minute',
second: 'second',
ago: 'ago',
later: 'later',
justNow: 'just now',
soon: 'soon',
template: '{num} {unit} {suffix}'
}
}
const text = localeText[locale] || localeText.zh
let date = getDate(time)
let ms = date.getTime() - Date.now()
let absMs = Math.abs(ms)
if (absMs < threshold[0]) {
return ms < 0 ? text.justNow : text.soon
}
if (absMs >= threshold[1]) {
return formatDate(date, format)
}
let num
let unit
let suffix = text.later
if (ms < 0) {
suffix = text.ago
ms = -ms
}
const seconds = Math.floor((ms) / 1000)
const minutes = Math.floor(seconds / 60)
const hours = Math.floor(minutes / 60)
const days = Math.floor(hours / 24)
const months = Math.floor(days / 30)
const years = Math.floor(months / 12)
switch (true) {
case years > 0:
num = years
unit = text.year
break
case months > 0:
num = months
unit = text.month
break
case days > 0:
num = days
unit = text.day
break
case hours > 0:
num = hours
unit = text.hour
break
case minutes > 0:
num = minutes
unit = text.minute
break
default:
num = seconds
unit = text.second
break
}
if (locale === 'en') {
if (num === 1) {
num = 'a'
} else {
unit += 's'
}
}
return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g,
suffix)
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue
================================================
{{dateShow}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-dateformat/package.json
================================================
{
"id": "uni-dateformat",
"displayName": "uni-dateformat 日期格式化",
"version": "1.0.0",
"description": "日期格式化组件,可以将日期格式化为1分钟前、刚刚等形式",
"keywords": [
"uni-ui",
"uniui",
"日期格式化",
"时间格式化",
"格式化时间",
""
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-dateformat/readme.md
================================================
### DateFormat 日期格式化
> **组件名:uni-dateformat**
> 代码块: `uDateformat`
日期格式化组件。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-dateformat)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/changelog.md
================================================
## 2.2.24(2023-06-02)
- 修复 部分情况修改时间,开始、结束时间显示异常的Bug [详情](https://ask.dcloud.net.cn/question/171146)
- 优化 当前月可以选择上月、下月的日期
## 2.2.23(2023-05-02)
- 修复 部分情况修改时间,开始时间未更新 [详情](https://github.com/dcloudio/uni-ui/issues/737)
- 修复 部分平台及设备第一次点击无法显示弹框
- 修复 ios 日期格式未补零显示及使用异常 [详情](https://ask.dcloud.net.cn/question/162979)
## 2.2.22(2023-03-30)
- 修复 日历 picker 修改年月后,自动选中当月1日 [详情](https://ask.dcloud.net.cn/question/165937)
- 修复 小程序端 低版本 ios NaN [详情](https://ask.dcloud.net.cn/question/162979)
## 2.2.21(2023-02-20)
- 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362)
## 2.2.20(2023-02-17)
- 优化 值为空依然选中当天问题
- 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间
- 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间
- 优化 字节小程序日期时间范围选择,底部日期换行问题
## 2.2.19(2023-02-09)
- 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686)
## 2.2.18(2023-02-08)
- 修复 移动端范围选择change事件触发异常的Bug [详情](https://github.com/dcloudio/uni-ui/issues/684)
- 优化 PC端输入日期格式错误时返回当前日期时间
- 优化 PC端输入日期时间超出 start、end 限制的Bug
- 优化 移动端日期时间范围用法时间展示不完整问题
## 2.2.17(2023-02-04)
- 修复 小程序端绑定 Date 类型报错的Bug [详情](https://github.com/dcloudio/uni-ui/issues/679)
- 修复 vue3 time-picker 无法显示绑定时分秒的Bug
## 2.2.16(2023-02-02)
- 修复 字节小程序报错的Bug
## 2.2.15(2023-02-02)
- 修复 某些情况切换月份错误的Bug
## 2.2.14(2023-01-30)
- 修复 某些情况切换月份错误的Bug [详情](https://ask.dcloud.net.cn/question/162033)
## 2.2.13(2023-01-10)
- 修复 多次加载组件造成内存占用的Bug
## 2.2.12(2022-12-01)
- 修复 vue3 下 i18n 国际化初始值不正确的Bug
## 2.2.11(2022-09-19)
- 修复 支付宝小程序样式错乱的Bug [详情](https://github.com/dcloudio/uni-app/issues/3861)
## 2.2.10(2022-09-19)
- 修复 反向选择日期范围,日期显示异常的Bug [详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false)
## 2.2.9(2022-09-16)
- 可以使用 uni-scss 控制主题色
## 2.2.8(2022-09-08)
- 修复 close事件无效的Bug
## 2.2.7(2022-09-05)
- 修复 移动端 maskClick 无效的Bug [详情](https://ask.dcloud.net.cn/question/140824)
## 2.2.6(2022-06-30)
- 优化 组件样式,调整了组件图标大小、高度、颜色等,与uni-ui风格保持一致
## 2.2.5(2022-06-24)
- 修复 日历顶部年月及底部确认未国际化的Bug
## 2.2.4(2022-03-31)
- 修复 Vue3 下动态赋值,单选类型未响应的Bug
## 2.2.3(2022-03-28)
- 修复 Vue3 下动态赋值未响应的Bug
## 2.2.2(2021-12-10)
- 修复 clear-icon 属性在小程序平台不生效的Bug
## 2.2.1(2021-12-10)
- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的Bug
## 2.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源 [详情](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移 [https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
## 2.1.5(2021-11-09)
- 新增 提供组件设计资源,组件样式调整
## 2.1.4(2021-09-10)
- 修复 hide-second 在移动端的Bug
- 修复 单选赋默认值时,赋值日期未高亮的Bug
- 修复 赋默认值时,移动端未正确显示时间的Bug
## 2.1.3(2021-09-09)
- 新增 hide-second 属性,支持只使用时分,隐藏秒
## 2.1.2(2021-09-03)
- 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次
- 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法
- 优化 调整字号大小,美化日历界面
- 修复 因国际化导致的 placeholder 失效的Bug
## 2.1.1(2021-08-24)
- 新增 支持国际化
- 优化 范围选择器在 pc 端过宽的问题
## 2.1.0(2021-08-09)
- 新增 适配 vue3
## 2.0.19(2021-08-09)
- 新增 支持作为 uni-forms 子组件相关功能
- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的Bug
## 2.0.18(2021-08-05)
- 修复 type 属性动态赋值无效的Bug
- 修复 ‘确认’按钮被 tabbar 遮盖 bug
- 修复 组件未赋值时范围选左、右日历相同的Bug
## 2.0.17(2021-08-04)
- 修复 范围选未正确显示当前值的Bug
- 修复 h5 平台(移动端)报错 'cale' of undefined 的Bug
## 2.0.16(2021-07-21)
- 新增 return-type 属性支持返回 date 日期对象
## 2.0.15(2021-07-14)
- 修复 单选日期类型,初始赋值后不在当前日历的Bug
- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效)
- 优化 移动端移除显示框的清空按钮,无实际用途
## 2.0.14(2021-07-14)
- 修复 组件赋值为空,界面未更新的Bug
- 修复 start 和 end 不能动态赋值的Bug
- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的Bug
## 2.0.13(2021-07-08)
- 修复 范围选择不能动态赋值的Bug
## 2.0.12(2021-07-08)
- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug
## 2.0.11(2021-07-08)
- 优化 弹出层在超出视窗边缘定位不准确的问题
## 2.0.10(2021-07-08)
- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的Bug
- 优化 弹出层在超出视窗边缘被遮盖的问题
## 2.0.9(2021-07-07)
- 新增 maskClick 事件
- 修复 特殊情况日历 rpx 布局错误的Bug,rpx -> px
- 修复 范围选择时清空返回值不合理的bug,['', ''] -> []
## 2.0.8(2021-07-07)
- 新增 日期时间显示框支持插槽
## 2.0.7(2021-07-01)
- 优化 添加 uni-icons 依赖
## 2.0.6(2021-05-22)
- 修复 图标在小程序上不显示的Bug
- 优化 重命名引用组件,避免潜在组件命名冲突
## 2.0.5(2021-05-20)
- 优化 代码目录扁平化
## 2.0.4(2021-05-12)
- 新增 组件示例地址
## 2.0.3(2021-05-10)
- 修复 ios 下不识别 '-' 日期格式的Bug
- 优化 pc 下弹出层添加边框和阴影
## 2.0.2(2021-05-08)
- 修复 在 admin 中获取弹出层定位错误的bug
## 2.0.1(2021-05-08)
- 修复 type 属性向下兼容,默认值从 date 变更为 datetime
## 2.0.0(2021-04-30)
- 支持日历形式的日期+时间的范围选择
> 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)
## 1.0.6(2021-03-18)
- 新增 hide-second 属性,时间支持仅选择时、分
- 修复 选择跟显示的日期不一样的Bug
- 修复 chang事件触发2次的Bug
- 修复 分、秒 end 范围错误的Bug
- 优化 更好的 nvue 适配
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue
================================================
{{weeks.date}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue
================================================
{{nowDate.month}}
{{SUNText}}
{{MONText}}
{{TUEText}}
{{WEDText}}
{{THUText}}
{{FRIText}}
{{SATText}}
{{tempSingleDate ? tempSingleDate : selectDateText}}
{{tempRange.before ? tempRange.before : startDateText}}
{{tempRange.after ? tempRange.after : endDateText}}
{{confirmText}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json
================================================
{
"uni-datetime-picker.selectDate": "select date",
"uni-datetime-picker.selectTime": "select time",
"uni-datetime-picker.selectDateTime": "select date and time",
"uni-datetime-picker.startDate": "start date",
"uni-datetime-picker.endDate": "end date",
"uni-datetime-picker.startTime": "start time",
"uni-datetime-picker.endTime": "end time",
"uni-datetime-picker.ok": "ok",
"uni-datetime-picker.clear": "clear",
"uni-datetime-picker.cancel": "cancel",
"uni-datetime-picker.year": "-",
"uni-datetime-picker.month": "",
"uni-calender.MON": "MON",
"uni-calender.TUE": "TUE",
"uni-calender.WED": "WED",
"uni-calender.THU": "THU",
"uni-calender.FRI": "FRI",
"uni-calender.SAT": "SAT",
"uni-calender.SUN": "SUN",
"uni-calender.confirm": "confirm"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js
================================================
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json
================================================
{
"uni-datetime-picker.selectDate": "选择日期",
"uni-datetime-picker.selectTime": "选择时间",
"uni-datetime-picker.selectDateTime": "选择日期时间",
"uni-datetime-picker.startDate": "开始日期",
"uni-datetime-picker.endDate": "结束日期",
"uni-datetime-picker.startTime": "开始时间",
"uni-datetime-picker.endTime": "结束时间",
"uni-datetime-picker.ok": "确定",
"uni-datetime-picker.clear": "清除",
"uni-datetime-picker.cancel": "取消",
"uni-datetime-picker.year": "年",
"uni-datetime-picker.month": "月",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六",
"uni-calender.confirm": "确认"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json
================================================
{
"uni-datetime-picker.selectDate": "選擇日期",
"uni-datetime-picker.selectTime": "選擇時間",
"uni-datetime-picker.selectDateTime": "選擇日期時間",
"uni-datetime-picker.startDate": "開始日期",
"uni-datetime-picker.endDate": "結束日期",
"uni-datetime-picker.startTime": "開始时间",
"uni-datetime-picker.endTime": "結束时间",
"uni-datetime-picker.ok": "確定",
"uni-datetime-picker.clear": "清除",
"uni-datetime-picker.cancel": "取消",
"uni-datetime-picker.year": "年",
"uni-datetime-picker.month": "月",
"uni-calender.SUN": "日",
"uni-calender.MON": "一",
"uni-calender.TUE": "二",
"uni-calender.WED": "三",
"uni-calender.THU": "四",
"uni-calender.FRI": "五",
"uni-calender.SAT": "六",
"uni-calender.confirm": "確認"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue
================================================
{{time}}
{{selectTimeText}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue
================================================
{{ displayValue || singlePlaceholderText }}
{{ displayRangeValue.startDate || startPlaceholderText }}
{{rangeSeparator}}
{{ displayRangeValue.endDate || endPlaceholderText }}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js
================================================
class Calendar {
constructor({
selected,
startDate,
endDate,
range,
} = {}) {
// 当前日期
this.date = this.getDateObj(new Date()) // 当前初入日期
// 打点信息
this.selected = selected || [];
// 起始时间
this.startDate = startDate
// 终止时间
this.endDate = endDate
// 是否范围选择
this.range = range
// 多选状态
this.cleanMultipleStatus()
// 每周日期
this.weeks = {}
this.lastHover = false
}
/**
* 设置日期
* @param {Object} date
*/
setDate(date) {
const selectDate = this.getDateObj(date)
this.getWeeks(selectDate.fullDate)
}
/**
* 清理多选状态
*/
cleanMultipleStatus() {
this.multipleStatus = {
before: '',
after: '',
data: []
}
}
setStartDate(startDate) {
this.startDate = startDate
}
setEndDate(endDate) {
this.endDate = endDate
}
getPreMonthObj(date){
date = fixIosDateFormat(date)
date = new Date(date)
const oldMonth = date.getMonth()
date.setMonth(oldMonth - 1)
const newMonth = date.getMonth()
if(oldMonth !== 0 && newMonth - oldMonth === 0){
date.setMonth(newMonth - 1)
}
return this.getDateObj(date)
}
getNextMonthObj(date){
date = fixIosDateFormat(date)
date = new Date(date)
const oldMonth = date.getMonth()
date.setMonth(oldMonth + 1)
const newMonth = date.getMonth()
if(newMonth - oldMonth > 1){
date.setMonth(newMonth - 1)
}
return this.getDateObj(date)
}
/**
* 获取指定格式Date对象
*/
getDateObj(date) {
date = fixIosDateFormat(date)
date = new Date(date)
return {
fullDate: getDate(date),
year: date.getFullYear(),
month: addZero(date.getMonth() + 1),
date: addZero(date.getDate()),
day: date.getDay()
}
}
/**
* 获取上一个月日期集合
*/
getPreMonthDays(amount, dateObj) {
const result = []
for (let i = amount - 1; i >= 0; i--) {
const month = dateObj.month > 1 ? dateObj.month -1 : 12
const year = month === 12 ? dateObj.year - 1 : dateObj.year
const date = new Date(year,month,-i).getDate()
const fullDate = `${year}-${addZero(month)}-${addZero(date)}`
let multiples = this.multipleStatus.data
let multiplesStatus = -1
if (this.range && multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, fullDate)
})
}
const checked = multiplesStatus !== -1
// 获取打点信息
const extraInfo = this.selected && this.selected.find((item) => {
if (this.dateEqual(fullDate, item.date)) {
return item
}
})
result.push({
fullDate,
year,
month,
date,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(fullDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(fullDate, this.multipleStatus.before, this.multipleStatus.after),
disable: (this.startDate && !dateCompare(this.startDate, fullDate)) || (this.endDate && !dateCompare(fullDate,this.endDate)),
isToday: fullDate === this.date.fullDate,
userChecked: false,
extraInfo
})
}
return result
}
/**
* 获取本月日期集合
*/
getCurrentMonthDays(amount, dateObj) {
const result = []
const fullDate = this.date.fullDate
for (let i = 1; i <= amount; i++) {
const currentDate = `${dateObj.year}-${dateObj.month}-${addZero(i)}`
const isToday = fullDate === currentDate
// 获取打点信息
const extraInfo = this.selected && this.selected.find((item) => {
if (this.dateEqual(currentDate, item.date)) {
return item
}
})
// 日期禁用
let disableBefore = true
let disableAfter = true
if (this.startDate) {
disableBefore = dateCompare(this.startDate, currentDate)
}
if (this.endDate) {
disableAfter = dateCompare(currentDate, this.endDate)
}
let multiples = this.multipleStatus.data
let multiplesStatus = -1
if (this.range && multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, currentDate)
})
}
const checked = multiplesStatus !== -1
result.push({
fullDate: currentDate,
year: dateObj.year,
month: dateObj.month,
date: i,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(currentDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(currentDate, this.multipleStatus.before, this.multipleStatus.after),
disable: (this.startDate && !dateCompare(this.startDate, currentDate)) || (this.endDate && !dateCompare(currentDate,this.endDate)),
isToday,
userChecked: false,
extraInfo
})
}
return result
}
/**
* 获取下一个月日期集合
*/
_getNextMonthDays(amount, dateObj) {
const result = []
const month = dateObj.month + 1
for (let i = 1; i <= amount; i++) {
const month = dateObj.month === 12 ? 1 : dateObj.month*1 + 1
const year = month === 1 ? dateObj.year + 1 : dateObj.year
const fullDate = `${year}-${addZero(month)}-${addZero(i)}`
let multiples = this.multipleStatus.data
let multiplesStatus = -1
if (this.range && multiples) {
multiplesStatus = multiples.findIndex((item) => {
return this.dateEqual(item, fullDate)
})
}
const checked = multiplesStatus !== -1
// 获取打点信息
const extraInfo = this.selected && this.selected.find((item) => {
if (this.dateEqual(fullDate, item.date)) {
return item
}
})
result.push({
fullDate,
year,
date: i,
month,
multiple: this.range ? checked : false,
beforeMultiple: this.isLogicBefore(fullDate, this.multipleStatus.before, this.multipleStatus.after),
afterMultiple: this.isLogicAfter(fullDate, this.multipleStatus.before, this.multipleStatus.after),
disable: (this.startDate && !dateCompare(this.startDate, fullDate)) || (this.endDate && !dateCompare(fullDate,this.endDate)),
isToday: fullDate === this.date.fullDate,
userChecked: false,
extraInfo
})
}
return result
}
/**
* 获取当前日期详情
* @param {Object} date
*/
getInfo(date) {
if (!date) {
date = new Date()
}
return this.calendar.find(item => item.fullDate === this.getDateObj(date).fullDate)
}
/**
* 比较时间是否相等
*/
dateEqual(before, after) {
before = new Date(fixIosDateFormat(before))
after = new Date(fixIosDateFormat(after))
return before.valueOf() === after.valueOf()
}
/**
* 比较真实起始日期
*/
isLogicBefore(currentDate, before, after) {
let logicBefore = before
if (before && after) {
logicBefore = dateCompare(before, after) ? before : after
}
return this.dateEqual(logicBefore, currentDate)
}
isLogicAfter(currentDate, before, after) {
let logicAfter = after
if (before && after) {
logicAfter = dateCompare(before, after) ? after : before
}
return this.dateEqual(logicAfter, currentDate)
}
/**
* 获取日期范围内所有日期
* @param {Object} begin
* @param {Object} end
*/
geDateAll(begin, end) {
var arr = []
var ab = begin.split('-')
var ae = end.split('-')
var db = new Date()
db.setFullYear(ab[0], ab[1] - 1, ab[2])
var de = new Date()
de.setFullYear(ae[0], ae[1] - 1, ae[2])
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
for (var k = unixDb; k <= unixDe;) {
k = k + 24 * 60 * 60 * 1000
arr.push(this.getDateObj(new Date(parseInt(k))).fullDate)
}
return arr
}
/**
* 获取多选状态
*/
setMultiple(fullDate) {
if (!this.range) return
let {
before,
after
} = this.multipleStatus
if (before && after) {
if (!this.lastHover) {
this.lastHover = true
return
}
this.multipleStatus.before = fullDate
this.multipleStatus.after = ''
this.multipleStatus.data = []
this.multipleStatus.fulldate = ''
this.lastHover = false
} else {
if (!before) {
this.multipleStatus.before = fullDate
this.lastHover = false
} else {
this.multipleStatus.after = fullDate
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus
.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus
.before);
}
this.lastHover = true
}
}
this.getWeeks(fullDate)
}
/**
* 鼠标 hover 更新多选状态
*/
setHoverMultiple(fullDate) {
if (!this.range || this.lastHover) return
const { before } = this.multipleStatus
if (!before) {
this.multipleStatus.before = fullDate
} else {
this.multipleStatus.after = fullDate
if (dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
} else {
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
}
}
this.getWeeks(fullDate)
}
/**
* 更新默认值多选状态
*/
setDefaultMultiple(before, after) {
this.multipleStatus.before = before
this.multipleStatus.after = after
if (before && after) {
if (dateCompare(before, after)) {
this.multipleStatus.data = this.geDateAll(before, after);
this.getWeeks(after)
} else {
this.multipleStatus.data = this.geDateAll(after, before);
this.getWeeks(before)
}
}
}
/**
* 获取每周数据
* @param {Object} dateData
*/
getWeeks(dateData) {
const {
year,
month,
} = this.getDateObj(dateData)
const preMonthDayAmount = new Date(year, month - 1, 1).getDay()
const preMonthDays = this.getPreMonthDays(preMonthDayAmount, this.getDateObj(dateData))
const currentMonthDayAmount = new Date(year, month, 0).getDate()
const currentMonthDays = this.getCurrentMonthDays(currentMonthDayAmount, this.getDateObj(dateData))
const nextMonthDayAmount = 42 - preMonthDayAmount - currentMonthDayAmount
const nextMonthDays = this._getNextMonthDays(nextMonthDayAmount, this.getDateObj(dateData))
const calendarDays = [...preMonthDays, ...currentMonthDays, ...nextMonthDays]
const weeks = new Array(6)
for (let i = 0; i < calendarDays.length; i++) {
const index = Math.floor(i / 7)
if(!weeks[index]){
weeks[index] = new Array(7)
}
weeks[index][i % 7] = calendarDays[i]
}
this.calendar = calendarDays
this.weeks = weeks
}
}
function getDateTime(date, hideSecond){
return `${getDate(date)} ${getTime(date, hideSecond)}`
}
function getDate(date) {
date = fixIosDateFormat(date)
date = new Date(date)
const year = date.getFullYear()
const month = date.getMonth()+1
const day = date.getDate()
return `${year}-${addZero(month)}-${addZero(day)}`
}
function getTime(date, hideSecond){
date = fixIosDateFormat(date)
date = new Date(date)
const hour = date.getHours()
const minute = date.getMinutes()
const second = date.getSeconds()
return hideSecond ? `${addZero(hour)}:${addZero(minute)}` : `${addZero(hour)}:${addZero(minute)}:${addZero(second)}`
}
function addZero(num) {
if(num < 10){
num = `0${num}`
}
return num
}
function getDefaultSecond(hideSecond) {
return hideSecond ? '00:00' : '00:00:00'
}
function dateCompare(startDate, endDate) {
startDate = new Date(fixIosDateFormat(startDate))
endDate = new Date(fixIosDateFormat(endDate))
return startDate <= endDate
}
function checkDate(date){
const dateReg = /((19|20)\d{2})(-|\/)\d{1,2}(-|\/)\d{1,2}/g
return date.match(dateReg)
}
const dateTimeReg = /^\d{4}-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])( [0-5]?[0-9]:[0-5]?[0-9]:[0-5]?[0-9])?$/
function fixIosDateFormat(value) {
if (typeof value === 'string' && dateTimeReg.test(value)) {
value = value.replace(/-/g, '/')
}
return value
}
export {Calendar, getDateTime, getDate, getTime, addZero, getDefaultSecond, dateCompare, checkDate, fixIosDateFormat}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/package.json
================================================
{
"id": "uni-datetime-picker",
"displayName": "uni-datetime-picker 日期选择器",
"version": "2.2.24",
"description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择",
"keywords": [
"uni-datetime-picker",
"uni-ui",
"uniui",
"日期时间选择器",
"日期时间"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/readme.md
================================================
> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)。若仍需使用旧版本,可在插件市场下载*非uni_modules版本*,旧版本将不再维护`
## DatetimePicker 时间选择器
> **组件名:uni-datetime-picker**
> 代码块: `uDatetimePicker`
该组件的优势是,支持**时间戳**输入和输出(起始时间、终止时间也支持时间戳),可**同时选择**日期和时间。
若只是需要单独选择日期和时间,不需要时间戳输入和输出,可使用原生的 picker 组件。
**_点击 picker 默认值规则:_**
- 若设置初始值 value, 会显示在 picker 显示框中
- 若无初始值 value,则初始值 value 为当前本地时间 Date.now(), 但不会显示在 picker 显示框中
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-drawer/changelog.md
================================================
## 1.2.1(2021-11-22)
- 修复 vue3中个别scss变量无法找到的问题
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-drawer](https://uniapp.dcloud.io/component/uniui/uni-drawer)
## 1.1.1(2021-07-30)
- 优化 vue3下事件警告的问题
## 1.1.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.7(2021-05-12)
- 新增 组件示例地址
## 1.0.6(2021-02-04)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-drawer/components/uni-drawer/keypress.js
================================================
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue
================================================
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-drawer/package.json
================================================
{
"id": "uni-drawer",
"displayName": "uni-drawer 抽屉",
"version": "1.2.1",
"description": "抽屉式导航,用于展示侧滑菜单,侧滑导航。",
"keywords": [
"uni-ui",
"uniui",
"drawer",
"抽屉",
"侧滑导航"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-drawer/readme.md
================================================
## Drawer 抽屉
> **组件名:uni-drawer**
> 代码块: `uDrawer`
抽屉侧滑菜单。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-drawer)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-easyinput/changelog.md
================================================
## 1.1.9(2023-04-11)
- 修复 vue3 下 keyboardheightchange 事件报错的bug
## 1.1.8(2023-03-29)
- 优化 trim 属性默认值
## 1.1.7(2023-03-29)
- 新增 cursor-spacing 属性
## 1.1.6(2023-01-28)
- 新增 keyboardheightchange 事件,可监听键盘高度变化
## 1.1.5(2022-11-29)
- 优化 主题样式
## 1.1.4(2022-10-27)
- 修复 props 中背景颜色无默认值的bug
## 1.1.0(2022-06-30)
- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
- 新增 clear 事件,点击右侧叉号图标触发
- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
## 1.0.5(2022-06-07)
- 优化 clearable 显示策略
## 1.0.4(2022-06-07)
- 优化 clearable 显示策略
## 1.0.3(2022-05-20)
- 修复 关闭图标某些情况下无法取消的 bug
## 1.0.2(2022-04-12)
- 修复 默认值不生效的 bug
## 1.0.1(2022-04-02)
- 修复 value 不能为 0 的 bug
## 1.0.0(2021-11-19)
- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
## 0.1.4(2021-08-20)
- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
## 0.1.3(2021-08-11)
- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
## 0.1.2(2021-07-30)
- 优化 vue3 下事件警告的问题
## 0.1.1
- 优化 errorMessage 属性支持 Boolean 类型
## 0.1.0(2021-07-13)
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.0.16(2021-06-29)
- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
## 0.0.15(2021-06-21)
- 修复 passwordIcon 属性拼写错误的 bug
## 0.0.14(2021-06-18)
- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
- 修复 confirmType 属性不生效的问题
## 0.0.13(2021-06-04)
- 修复 disabled 状态可清出内容的 bug
## 0.0.12(2021-05-12)
- 新增 组件示例地址
## 0.0.11(2021-05-07)
- 修复 input-border 属性不生效的问题
## 0.0.10(2021-04-30)
- 修复 ios 遮挡文字、显示一半的问题
## 0.0.9(2021-02-05)
- 调整为 uni_modules 目录规范
- 优化 兼容 nvue 页面
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-easyinput/components/uni-easyinput/common.js
================================================
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行, false - 延迟执行
*/
export const debounce = function(func, wait = 1000, immediate = true) {
let timer;
console.log(1);
return function() {
console.log(123);
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, wait)
}
}
}
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发
*/
export const throttle = (func, wait = 1000, type = 1) => {
let previous = 0;
let timeout;
return function() {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue
================================================
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-easyinput/package.json
================================================
{
"id": "uni-easyinput",
"displayName": "uni-easyinput 增强输入框",
"version": "1.1.9",
"description": "Easyinput 组件是对原生input组件的增强",
"keywords": [
"uni-ui",
"uniui",
"input",
"uni-easyinput",
"输入框"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-easyinput/readme.md
================================================
### Easyinput 增强输入框
> **组件名:uni-easyinput**
> 代码块: `uEasyinput`
easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fab/changelog.md
================================================
## 1.2.5(2023-03-29)
- 新增 pattern.icon 属性,可自定义图标
## 1.2.4(2022-09-07)
小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false)
## 1.2.3(2022-09-05)
- 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310)
## 1.2.2(2021-12-29)
- 更新 组件依赖
## 1.2.1(2021-11-19)
- 修复 阴影颜色不正确的bug
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fab](https://uniapp.dcloud.io/component/uniui/uni-fab)
## 1.1.1(2021-11-09)
- 新增 提供组件设计资源,组件样式调整
## 1.1.0(2021-07-30)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.7(2021-05-12)
- 新增 组件示例地址
## 1.0.6(2021-02-05)
- 调整为uni_modules目录规范
- 优化 按钮背景色调整
- 优化 兼容pc端
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fab/components/uni-fab/uni-fab.vue
================================================
{{ item.text }}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fab/package.json
================================================
{
"id": "uni-fab",
"displayName": "uni-fab 悬浮按钮",
"version": "1.2.5",
"description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。",
"keywords": [
"uni-ui",
"uniui",
"按钮",
"悬浮按钮",
"fab"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss","uni-icons"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fab/readme.md
================================================
## Fab 悬浮按钮
> **组件名:uni-fab**
> 代码块: `uFab`
点击可展开一个图形按钮菜单
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fab)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/changelog.md
================================================
## 1.2.1(2022-05-30)
- 新增 stat 属性 ,是否开启uni统计功能
## 1.2.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fav](https://uniapp.dcloud.io/component/uniui/uni-fav)
## 1.1.1(2021-08-24)
- 新增 支持国际化
## 1.1.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.0.6(2021-05-12)
- 新增 组件示例地址
## 1.0.5(2021-04-21)
- 优化 添加依赖 uni-icons, 导入后自动下载依赖
## 1.0.4(2021-02-05)
- 优化 组件引用关系,通过uni_modules引用组件
## 1.0.3(2021-02-05)
- 优化 组件引用关系,通过uni_modules引用组件
## 1.0.2(2021-02-05)
- 调整为uni_modules目录规范
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/components/uni-fav/i18n/en.json
================================================
{
"uni-fav.collect": "collect",
"uni-fav.collected": "collected"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/components/uni-fav/i18n/index.js
================================================
import en from './en.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json
================================================
{
"uni-fav.collect": "收藏",
"uni-fav.collected": "已收藏"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json
================================================
{
"uni-fav.collect": "收藏",
"uni-fav.collected": "已收藏"
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/components/uni-fav/uni-fav.vue
================================================
{{ checked ? contentFav : contentDefault }}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/package.json
================================================
{
"id": "uni-fav",
"displayName": "uni-fav 收藏按钮",
"version": "1.2.1",
"description": " Fav 收藏组件,可自定义颜色、大小。",
"keywords": [
"fav",
"uni-ui",
"uniui",
"收藏"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-fav/readme.md
================================================
## Fav 收藏按钮
> **组件名:uni-fav**
> 代码块: `uFav`
用于收藏功能,可点击切换选中、不选中的状态。
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fav)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/changelog.md
================================================
## 1.0.4(2023-03-29)
- 修复 手动上传删除一个文件后不能再上传的bug
## 1.0.3(2022-12-19)
- 新增 sourceType 属性, 可以自定义图片和视频选择的来源
## 1.0.2(2022-07-04)
- 修复 在uni-forms下样式不生效的bug
## 1.0.1(2021-11-23)
- 修复 参数为对象的情况下,url在某些情况显示错误的bug
## 1.0.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
## 0.2.16(2021-11-08)
- 修复 传入空对象 ,显示错误的Bug
## 0.2.15(2021-08-30)
- 修复 return-type="object" 时且存在v-model时,无法删除文件的Bug
## 0.2.14(2021-08-23)
- 新增 参数中返回 fileID 字段
## 0.2.13(2021-08-23)
- 修复 腾讯云传入fileID 不能回显的bug
- 修复 选择图片后,不能放大的问题
## 0.2.12(2021-08-17)
- 修复 由于 0.2.11 版本引起的不能回显图片的Bug
## 0.2.11(2021-08-16)
- 新增 clearFiles(index) 方法,可以手动删除指定文件
- 修复 v-model 值设为 null 报错的Bug
## 0.2.10(2021-08-13)
- 修复 return-type="object" 时,无法删除文件的Bug
## 0.2.9(2021-08-03)
- 修复 auto-upload 属性失效的Bug
## 0.2.8(2021-07-31)
- 修复 fileExtname属性不指定值报错的Bug
## 0.2.7(2021-07-31)
- 修复 在某种场景下图片不回显的Bug
## 0.2.6(2021-07-30)
- 修复 return-type为object下,返回值不正确的Bug
## 0.2.5(2021-07-30)
- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题
## 0.2.3(2021-07-28)
- 优化 调整示例代码
## 0.2.2(2021-07-27)
- 修复 vue3 下赋值错误的Bug
- 优化 h5平台下上传文件导致页面卡死的问题
## 0.2.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.1.1(2021-07-02)
- 修复 sourceType 缺少默认值导致 ios 无法选择文件
## 0.1.0(2021-06-30)
- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改
## 0.0.11(2021-06-30)
- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题
## 0.0.10(2021-06-29)
- 优化 文件上传后进度条消失时机
## 0.0.9(2021-06-29)
- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug
## 0.0.8(2021-06-15)
- 修复 删除文件时无法触发 v-model 的Bug
## 0.0.7(2021-05-12)
- 新增 组件示例地址
## 0.0.6(2021-04-09)
- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug
## 0.0.5(2021-04-09)
- 优化 更新组件示例
## 0.0.4(2021-04-09)
- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔
## 0.0.3(2021-02-05)
- 调整为uni_modules目录规范
- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js
================================================
'use strict';
const ERR_MSG_OK = 'chooseAndUploadFile:ok';
const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
function chooseImage(opts) {
const {
count,
sizeType = ['original', 'compressed'],
sourceType,
extension
} = opts
return new Promise((resolve, reject) => {
uni.chooseImage({
count,
sizeType,
sourceType,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res, 'image'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseVideo(opts) {
const {
camera,
compressed,
maxDuration,
sourceType,
extension
} = opts;
return new Promise((resolve, reject) => {
uni.chooseVideo({
camera,
compressed,
maxDuration,
sourceType,
extension,
success(res) {
const {
tempFilePath,
duration,
size,
height,
width
} = res;
resolve(normalizeChooseAndUploadFileRes({
errMsg: 'chooseVideo:ok',
tempFilePaths: [tempFilePath],
tempFiles: [
{
name: (res.tempFile && res.tempFile.name) || '',
path: tempFilePath,
size,
type: (res.tempFile && res.tempFile.type) || '',
width,
height,
duration,
fileType: 'video',
cloudPath: '',
}, ],
}, 'video'));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL),
});
},
});
});
}
function chooseAll(opts) {
const {
count,
extension
} = opts;
return new Promise((resolve, reject) => {
let chooseFile = uni.chooseFile;
if (typeof wx !== 'undefined' &&
typeof wx.chooseMessageFile === 'function') {
chooseFile = wx.chooseMessageFile;
}
if (typeof chooseFile !== 'function') {
return reject({
errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。',
});
}
chooseFile({
type: 'all',
count,
extension,
success(res) {
resolve(normalizeChooseAndUploadFileRes(res));
},
fail(res) {
reject({
errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL),
});
},
});
});
}
function normalizeChooseAndUploadFileRes(res, fileType) {
res.tempFiles.forEach((item, index) => {
if (!item.name) {
item.name = item.path.substring(item.path.lastIndexOf('/') + 1);
}
if (fileType) {
item.fileType = fileType;
}
item.cloudPath =
Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.'));
});
if (!res.tempFilePaths) {
res.tempFilePaths = res.tempFiles.map((file) => file.path);
}
return res;
}
function uploadCloudFiles(files, max = 5, onUploadProgress) {
files = JSON.parse(JSON.stringify(files))
const len = files.length
let count = 0
let self = this
return new Promise(resolve => {
while (count < max) {
next()
}
function next() {
let cur = count++
if (cur >= len) {
!files.find(item => !item.url && !item.errMsg) && resolve(files)
return
}
const fileItem = files[cur]
const index = self.files.findIndex(v => v.uuid === fileItem.uuid)
fileItem.url = ''
delete fileItem.errMsg
uniCloud
.uploadFile({
filePath: fileItem.path,
cloudPath: fileItem.cloudPath,
fileType: fileItem.fileType,
onUploadProgress: res => {
res.index = index
onUploadProgress && onUploadProgress(res)
}
})
.then(res => {
fileItem.url = res.fileID
fileItem.index = index
if (cur < len) {
next()
}
})
.catch(res => {
fileItem.errMsg = res.errMsg || res.message
fileItem.index = index
if (cur < len) {
next()
}
})
}
})
}
function uploadFiles(choosePromise, {
onChooseFile,
onUploadProgress
}) {
return choosePromise
.then((res) => {
if (onChooseFile) {
const customChooseRes = onChooseFile(res);
if (typeof customChooseRes !== 'undefined') {
return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ?
res : chooseRes);
}
}
return res;
})
.then((res) => {
if (res === false) {
return {
errMsg: ERR_MSG_OK,
tempFilePaths: [],
tempFiles: [],
};
}
return res
})
}
function chooseAndUploadFile(opts = {
type: 'all'
}) {
if (opts.type === 'image') {
return uploadFiles(chooseImage(opts), opts);
}
else if (opts.type === 'video') {
return uploadFiles(chooseVideo(opts), opts);
}
return uploadFiles(chooseAll(opts), opts);
}
export {
chooseAndUploadFile,
uploadCloudFiles
};
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue
================================================
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue
================================================
{{item.name}}
点击重试
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue
================================================
点击重试
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/utils.js
================================================
/**
* 获取文件名和后缀
* @param {String} name
*/
export const get_file_ext = (name) => {
const last_len = name.lastIndexOf('.')
const len = name.length
return {
name: name.substring(0, last_len),
ext: name.substring(last_len + 1, len)
}
}
/**
* 获取扩展名
* @param {Array} fileExtname
*/
export const get_extname = (fileExtname) => {
if (!Array.isArray(fileExtname)) {
let extname = fileExtname.replace(/(\[|\])/g, '')
return extname.split(',')
} else {
return fileExtname
}
return []
}
/**
* 获取文件和检测是否可选
*/
export const get_files_and_is_max = (res, _extname) => {
let filePaths = []
let files = []
if(!_extname || _extname.length === 0){
return {
filePaths,
files
}
}
res.tempFiles.forEach(v => {
let fileFullName = get_file_ext(v.name)
const extname = fileFullName.ext.toLowerCase()
if (_extname.indexOf(extname) !== -1) {
files.push(v)
filePaths.push(v.path)
}
})
if (files.length !== res.tempFiles.length) {
uni.showToast({
title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`,
icon: 'none',
duration: 5000
})
}
return {
filePaths,
files
}
}
/**
* 获取图片信息
* @param {Object} filepath
*/
export const get_file_info = (filepath) => {
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: filepath,
success(res) {
resolve(res)
},
fail(err) {
reject(err)
}
})
})
}
/**
* 获取封装数据
*/
export const get_file_data = async (files, type = 'image') => {
// 最终需要上传数据库的数据
let fileFullName = get_file_ext(files.name)
const extname = fileFullName.ext.toLowerCase()
let filedata = {
name: files.name,
uuid: files.uuid,
extname: extname || '',
cloudPath: files.cloudPath,
fileType: files.fileType,
url: files.path || files.path,
size: files.size, //单位是字节
image: {},
path: files.path,
video: {}
}
if (type === 'image') {
const imageinfo = await get_file_info(files.path)
delete filedata.video
filedata.image.width = imageinfo.width
filedata.image.height = imageinfo.height
filedata.image.location = imageinfo.path
} else {
delete filedata.image
}
return filedata
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/package.json
================================================
{
"id": "uni-file-picker",
"displayName": "uni-file-picker 文件选择上传",
"version": "1.0.4",
"description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间",
"keywords": [
"uni-ui",
"uniui",
"图片上传",
"文件上传"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": ["uni-scss"],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/readme.md
================================================
## FilePicker 文件选择上传
> **组件名:uni-file-picker**
> 代码块: `uFilePicker`
文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker)
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/changelog.md
================================================
## 1.4.9(2023-02-10)
- 修复 required 参数无法动态绑定
## 1.4.8(2022-08-23)
- 优化 根据 rules 自动添加 required 的问题
## 1.4.7(2022-08-22)
- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540)
## 1.4.6(2022-07-13)
- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug
## 1.4.5(2022-07-05)
- 新增 更多表单示例
- 优化 子表单组件过期提示的问题
- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式
## 1.4.4(2022-07-04)
- 更新 删除组件日志
## 1.4.3(2022-07-04)
- 修复 由 1.4.0 引发的 label 插槽不生效的bug
## 1.4.2(2022-07-04)
- 修复 子组件找不到 setValue 报错的bug
## 1.4.1(2022-07-04)
- 修复 uni-data-picker 在 uni-forms-item 中报错的bug
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
## 1.4.0(2022-06-30)
- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题
- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力
- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃
- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效
- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法
- 新增 子表单的 setRules 方法,配合自定义校验函数使用
- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则
- 优化 动态表单校验方式,废弃拼接name的方式
## 1.3.3(2022-06-22)
- 修复 表单校验顺序无序问题
## 1.3.2(2021-12-09)
-
## 1.3.1(2021-11-19)
- 修复 label 插槽不生效的bug
## 1.3.0(2021-11-19)
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms)
## 1.2.7(2021-08-13)
- 修复 没有添加校验规则的字段依然报错的Bug
## 1.2.6(2021-08-11)
- 修复 重置表单错误信息无法清除的问题
## 1.2.5(2021-08-11)
- 优化 组件文档
## 1.2.4(2021-08-11)
- 修复 表单验证只生效一次的问题
## 1.2.3(2021-07-30)
- 优化 vue3下事件警告的问题
## 1.2.2(2021-07-26)
- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug
- 修复 1.2.1 引起的示例在小程序平台报错的Bug
## 1.2.1(2021-07-22)
- 修复 动态校验表单,默认值为空的情况下校验失效的Bug
- 修复 不指定name属性时,运行报错的Bug
- 优化 label默认宽度从65调整至70,使required为true且四字时不换行
- 优化 组件示例,新增动态校验示例代码
- 优化 组件文档,使用方式更清晰
## 1.2.0(2021-07-13)
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 1.1.2(2021-06-25)
- 修复 pattern 属性在微信小程序平台无效的问题
## 1.1.1(2021-06-22)
- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug
## 1.1.0(2021-06-22)
- 修复 只写setRules方法而导致校验不生效的Bug
- 修复 由上个办法引发的错误提示文字错位的Bug
## 1.0.48(2021-06-21)
- 修复 不设置 label 属性 ,无法设置label插槽的问题
## 1.0.47(2021-06-21)
- 修复 不设置label属性,label-width属性不生效的bug
- 修复 setRules 方法与rules属性冲突的问题
## 1.0.46(2021-06-04)
- 修复 动态删减数据导致报错的问题
## 1.0.45(2021-06-04)
- 新增 modelValue 属性 ,value 即将废弃
## 1.0.44(2021-06-02)
- 新增 uni-forms-item 可以设置单独的 rules
- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤
- 优化 submit 事件重命名为 validate
## 1.0.43(2021-05-12)
- 新增 组件示例地址
## 1.0.42(2021-04-30)
- 修复 自定义检验器失效的问题
## 1.0.41(2021-03-05)
- 更新 校验器
- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug
## 1.0.40(2021-03-04)
- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug
## 1.0.39(2021-02-05)
- 调整为uni_modules目录规范
- 修复 校验器传入 int 等类型 ,返回String类型的Bug
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/components/uni-forms/uni-forms.vue
================================================
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/components/uni-forms/utils.js
================================================
/**
* 简单处理对象拷贝
* @param {Obejct} 被拷贝对象
* @@return {Object} 拷贝对象
*/
export const deepCopy = (val) => {
return JSON.parse(JSON.stringify(val))
}
/**
* 过滤数字类型
* @param {String} format 数字类型
* @@return {Boolean} 返回是否为数字类型
*/
export const typeFilter = (format) => {
return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
}
/**
* 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined
* @param {String} key 字段名
* @param {any} value 字段值
* @param {Object} rules 表单校验规则
*/
export const getValue = (key, value, rules) => {
const isRuleNumType = rules.find(val => val.format && typeFilter(val.format));
const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
// 输入类型为 number
if (!!isRuleNumType) {
if (!value && value !== 0) {
value = null
} else {
value = isNumber(Number(value)) ? Number(value) : value
}
}
// 输入类型为 boolean
if (!!isRuleBoolType) {
value = isBoolean(value) ? value : false
}
return value;
}
/**
* 获取表单数据
* @param {String|Array} name 真实名称,需要使用 realName 获取
* @param {Object} data 原始数据
* @param {any} value 需要设置的值
*/
export const setDataValue = (field, formdata, value) => {
formdata[field] = value
return value || ''
}
/**
* 获取表单数据
* @param {String|Array} field 真实名称,需要使用 realName 获取
* @param {Object} data 原始数据
*/
export const getDataValue = (field, data) => {
return objGet(data, field)
}
/**
* 获取表单类型
* @param {String|Array} field 真实名称,需要使用 realName 获取
*/
export const getDataValueType = (field, data) => {
const value = getDataValue(field, data)
return {
type: type(value),
value
}
}
/**
* 获取表单可用的真实name
* @param {String|Array} name 表单name
* @@return {String} 表单可用的真实name
*/
export const realName = (name, data = {}) => {
const base_name = _basePath(name)
if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) {
const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_')
return realname
}
return base_name[0] || name
}
/**
* 判断是否表单可用的真实name
* @param {String|Array} name 表单name
* @@return {String} 表单可用的真实name
*/
export const isRealName = (name) => {
const reg = /^_formdata_#*/
return reg.test(name)
}
/**
* 获取表单数据的原始格式
* @@return {Object|Array} object 需要解析的数据
*/
export const rawData = (object = {}, name) => {
let newData = JSON.parse(JSON.stringify(object))
let formData = {}
for(let i in newData){
let path = name2arr(i)
objSet(formData,path,newData[i])
}
return formData
}
/**
* 真实name还原为 array
* @param {*} name
*/
export const name2arr = (name) => {
let field = name.replace('_formdata_#', '')
field = field.split('#').map(v => (isNumber(v) ? Number(v) : v))
return field
}
/**
* 对象中设置值
* @param {Object|Array} object 源数据
* @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
* @param {String} value 需要设置的值
*/
export const objSet = (object, path, value) => {
if (typeof object !== 'object') return object;
_basePath(path).reduce((o, k, i, _) => {
if (i === _.length - 1) {
// 若遍历结束直接赋值
o[k] = value
return null
} else if (k in o) {
// 若存在对应路径,则返回找到的对象,进行下一次遍历
return o[k]
} else {
// 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象
o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {}
return o[k]
}
}, object)
// 返回object
return object;
}
// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用
function _basePath(path) {
// 若是数组,则直接返回
if (Array.isArray(path)) return path
// 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']'
return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
}
/**
* 从对象中获取值
* @param {Object|Array} object 源数据
* @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
* @param {String} defaultVal 如果无法从调用链中获取值的默认值
*/
export const objGet = (object, path, defaultVal = 'undefined') => {
// 先将path处理成统一格式
let newPath = _basePath(path)
// 递归处理,返回最后结果
let val = newPath.reduce((o, k) => {
return (o || {})[k]
}, object);
return !val || val !== undefined ? val : defaultVal
}
/**
* 是否为 number 类型
* @param {any} num 需要判断的值
* @return {Boolean} 是否为 number
*/
export const isNumber = (num) => {
return !isNaN(Number(num))
}
/**
* 是否为 boolean 类型
* @param {any} bool 需要判断的值
* @return {Boolean} 是否为 boolean
*/
export const isBoolean = (bool) => {
return (typeof bool === 'boolean')
}
/**
* 是否有必填字段
* @param {Object} rules 规则
* @return {Boolean} 是否有必填字段
*/
export const isRequiredField = (rules) => {
let isNoField = false;
for (let i = 0; i < rules.length; i++) {
const ruleData = rules[i];
if (ruleData.required) {
isNoField = true;
break;
}
}
return isNoField;
}
/**
* 获取数据类型
* @param {Any} obj 需要获取数据类型的值
*/
export const type = (obj) => {
var class2type = {};
// 生成class2type映射
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
class2type["[object " + item + "]"] = item.toLowerCase();
})
if (obj == null) {
return obj + "";
}
return typeof obj === "object" || typeof obj === "function" ?
class2type[Object.prototype.toString.call(obj)] || "object" :
typeof obj;
}
/**
* 判断两个值是否相等
* @param {any} a 值
* @param {any} b 值
* @return {Boolean} 是否相等
*/
export const isEqual = (a, b) => {
//如果a和b本来就全等
if (a === b) {
//判断是否为0和-0
return a !== 0 || 1 / a === 1 / b;
}
//判断是否为null和undefined
if (a == null || b == null) {
return a === b;
}
//接下来判断a和b的数据类型
var classNameA = toString.call(a),
classNameB = toString.call(b);
//如果数据类型不相等,则返回false
if (classNameA !== classNameB) {
return false;
}
//如果数据类型相等,再根据不同数据类型分别判断
switch (classNameA) {
case '[object RegExp]':
case '[object String]':
//进行字符串转换比较
return '' + a === '' + b;
case '[object Number]':
//进行数字转换比较,判断是否为NaN
if (+a !== +a) {
return +b !== +b;
}
//判断是否为0或-0
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
case '[object Date]':
case '[object Boolean]':
return +a === +b;
}
//如果是对象类型
if (classNameA == '[object Object]') {
//获取a和b的属性长度
var propsA = Object.getOwnPropertyNames(a),
propsB = Object.getOwnPropertyNames(b);
if (propsA.length != propsB.length) {
return false;
}
for (var i = 0; i < propsA.length; i++) {
var propName = propsA[i];
//如果对应属性对应值不相等,则返回false
if (a[propName] !== b[propName]) {
return false;
}
}
return true;
}
//如果是数组类型
if (classNameA == '[object Array]') {
if (a.toString() == b.toString()) {
return true;
}
return false;
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/components/uni-forms/validate.js
================================================
var pattern = {
email: /^\S+?@\S+?\.\S+?$/,
idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
url: new RegExp(
"^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$",
'i')
};
const FORMAT_MAPPING = {
"int": 'integer',
"bool": 'boolean',
"double": 'number',
"long": 'number',
"password": 'string'
// "fileurls": 'array'
}
function formatMessage(args, resources = '') {
var defaultMessage = ['label']
defaultMessage.forEach((item) => {
if (args[item] === undefined) {
args[item] = ''
}
})
let str = resources
for (let key in args) {
let reg = new RegExp('{' + key + '}')
str = str.replace(reg, args[key])
}
return str
}
function isEmptyValue(value, type) {
if (value === undefined || value === null) {
return true;
}
if (typeof value === 'string' && !value) {
return true;
}
if (Array.isArray(value) && !value.length) {
return true;
}
if (type === 'object' && !Object.keys(value).length) {
return true;
}
return false;
}
const types = {
integer(value) {
return types.number(value) && parseInt(value, 10) === value;
},
string(value) {
return typeof value === 'string';
},
number(value) {
if (isNaN(value)) {
return false;
}
return typeof value === 'number';
},
"boolean": function(value) {
return typeof value === 'boolean';
},
"float": function(value) {
return types.number(value) && !types.integer(value);
},
array(value) {
return Array.isArray(value);
},
object(value) {
return typeof value === 'object' && !types.array(value);
},
date(value) {
return value instanceof Date;
},
timestamp(value) {
if (!this.integer(value) || Math.abs(value).toString().length > 16) {
return false
}
return true;
},
file(value) {
return typeof value.url === 'string';
},
email(value) {
return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
},
url(value) {
return typeof value === 'string' && !!value.match(pattern.url);
},
pattern(reg, value) {
try {
return new RegExp(reg).test(value);
} catch (e) {
return false;
}
},
method(value) {
return typeof value === 'function';
},
idcard(value) {
return typeof value === 'string' && !!value.match(pattern.idcard);
},
'url-https'(value) {
return this.url(value) && value.startsWith('https://');
},
'url-scheme'(value) {
return value.startsWith('://');
},
'url-web'(value) {
return false;
}
}
class RuleValidator {
constructor(message) {
this._message = message
}
async validateRule(fieldKey, fieldValue, value, data, allData) {
var result = null
let rules = fieldValue.rules
let hasRequired = rules.findIndex((item) => {
return item.required
})
if (hasRequired < 0) {
if (value === null || value === undefined) {
return result
}
if (typeof value === 'string' && !value.length) {
return result
}
}
var message = this._message
if (rules === undefined) {
return message['default']
}
for (var i = 0; i < rules.length; i++) {
let rule = rules[i]
let vt = this._getValidateType(rule)
Object.assign(rule, {
label: fieldValue.label || `["${fieldKey}"]`
})
if (RuleValidatorHelper[vt]) {
result = RuleValidatorHelper[vt](rule, value, message)
if (result != null) {
break
}
}
if (rule.validateExpr) {
let now = Date.now()
let resultExpr = rule.validateExpr(value, allData, now)
if (resultExpr === false) {
result = this._getMessage(rule, rule.errorMessage || this._message['default'])
break
}
}
if (rule.validateFunction) {
result = await this.validateFunction(rule, value, data, allData, vt)
if (result !== null) {
break
}
}
}
if (result !== null) {
result = message.TAG + result
}
return result
}
async validateFunction(rule, value, data, allData, vt) {
let result = null
try {
let callbackMessage = null
const res = await rule.validateFunction(rule, value, allData || data, (message) => {
callbackMessage = message
})
if (callbackMessage || (typeof res === 'string' && res) || res === false) {
result = this._getMessage(rule, callbackMessage || res, vt)
}
} catch (e) {
result = this._getMessage(rule, e.message, vt)
}
return result
}
_getMessage(rule, message, vt) {
return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default'])
}
_getValidateType(rule) {
var result = ''
if (rule.required) {
result = 'required'
} else if (rule.format) {
result = 'format'
} else if (rule.arrayType) {
result = 'arrayTypeFormat'
} else if (rule.range) {
result = 'range'
} else if (rule.maximum !== undefined || rule.minimum !== undefined) {
result = 'rangeNumber'
} else if (rule.maxLength !== undefined || rule.minLength !== undefined) {
result = 'rangeLength'
} else if (rule.pattern) {
result = 'pattern'
} else if (rule.validateFunction) {
result = 'validateFunction'
}
return result
}
}
const RuleValidatorHelper = {
required(rule, value, message) {
if (rule.required && isEmptyValue(value, rule.format || typeof value)) {
return formatMessage(rule, rule.errorMessage || message.required);
}
return null
},
range(rule, value, message) {
const {
range,
errorMessage
} = rule;
let list = new Array(range.length);
for (let i = 0; i < range.length; i++) {
const item = range[i];
if (types.object(item) && item.value !== undefined) {
list[i] = item.value;
} else {
list[i] = item;
}
}
let result = false
if (Array.isArray(value)) {
result = (new Set(value.concat(list)).size === list.length);
} else {
if (list.indexOf(value) > -1) {
result = true;
}
}
if (!result) {
return formatMessage(rule, errorMessage || message['enum']);
}
return null
},
rangeNumber(rule, value, message) {
if (!types.number(value)) {
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
}
let {
minimum,
maximum,
exclusiveMinimum,
exclusiveMaximum
} = rule;
let min = exclusiveMinimum ? value <= minimum : value < minimum;
let max = exclusiveMaximum ? value >= maximum : value > maximum;
if (minimum !== undefined && min) {
return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ?
'exclusiveMinimum' : 'minimum'
])
} else if (maximum !== undefined && max) {
return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ?
'exclusiveMaximum' : 'maximum'
])
} else if (minimum !== undefined && maximum !== undefined && (min || max)) {
return formatMessage(rule, rule.errorMessage || message['number'].range)
}
return null
},
rangeLength(rule, value, message) {
if (!types.string(value) && !types.array(value)) {
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
}
let min = rule.minLength;
let max = rule.maxLength;
let val = value.length;
if (min !== undefined && val < min) {
return formatMessage(rule, rule.errorMessage || message['length'].minLength)
} else if (max !== undefined && val > max) {
return formatMessage(rule, rule.errorMessage || message['length'].maxLength)
} else if (min !== undefined && max !== undefined && (val < min || val > max)) {
return formatMessage(rule, rule.errorMessage || message['length'].range)
}
return null
},
pattern(rule, value, message) {
if (!types['pattern'](rule.pattern, value)) {
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
}
return null
},
format(rule, value, message) {
var customTypes = Object.keys(types);
var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType);
if (customTypes.indexOf(format) > -1) {
if (!types[format](value)) {
return formatMessage(rule, rule.errorMessage || message.typeError);
}
}
return null
},
arrayTypeFormat(rule, value, message) {
if (!Array.isArray(value)) {
return formatMessage(rule, rule.errorMessage || message.typeError);
}
for (let i = 0; i < value.length; i++) {
const element = value[i];
let formatResult = this.format(rule, element, message)
if (formatResult !== null) {
return formatResult
}
}
return null
}
}
class SchemaValidator extends RuleValidator {
constructor(schema, options) {
super(SchemaValidator.message);
this._schema = schema
this._options = options || null
}
updateSchema(schema) {
this._schema = schema
}
async validate(data, allData) {
let result = this._checkFieldInSchema(data)
if (!result) {
result = await this.invokeValidate(data, false, allData)
}
return result.length ? result[0] : null
}
async validateAll(data, allData) {
let result = this._checkFieldInSchema(data)
if (!result) {
result = await this.invokeValidate(data, true, allData)
}
return result
}
async validateUpdate(data, allData) {
let result = this._checkFieldInSchema(data)
if (!result) {
result = await this.invokeValidateUpdate(data, false, allData)
}
return result.length ? result[0] : null
}
async invokeValidate(data, all, allData) {
let result = []
let schema = this._schema
for (let key in schema) {
let value = schema[key]
let errorMessage = await this.validateRule(key, value, data[key], data, allData)
if (errorMessage != null) {
result.push({
key,
errorMessage
})
if (!all) break
}
}
return result
}
async invokeValidateUpdate(data, all, allData) {
let result = []
for (let key in data) {
let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData)
if (errorMessage != null) {
result.push({
key,
errorMessage
})
if (!all) break
}
}
return result
}
_checkFieldInSchema(data) {
var keys = Object.keys(data)
var keys2 = Object.keys(this._schema)
if (new Set(keys.concat(keys2)).size === keys2.length) {
return ''
}
var noExistFields = keys.filter((key) => {
return keys2.indexOf(key) < 0;
})
var errorMessage = formatMessage({
field: JSON.stringify(noExistFields)
}, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid'])
return [{
key: 'invalid',
errorMessage
}]
}
}
function Message() {
return {
TAG: "",
default: '验证错误',
defaultInvalid: '提交的字段{field}在数据库中并不存在',
validateFunction: '验证无效',
required: '{label}必填',
'enum': '{label}超出范围',
timestamp: '{label}格式无效',
whitespace: '{label}不能为空',
typeError: '{label}类型无效',
date: {
format: '{label}日期{value}格式无效',
parse: '{label}日期无法解析,{value}无效',
invalid: '{label}日期{value}无效'
},
length: {
minLength: '{label}长度不能少于{minLength}',
maxLength: '{label}长度不能超过{maxLength}',
range: '{label}必须介于{minLength}和{maxLength}之间'
},
number: {
minimum: '{label}不能小于{minimum}',
maximum: '{label}不能大于{maximum}',
exclusiveMinimum: '{label}不能小于等于{minimum}',
exclusiveMaximum: '{label}不能大于等于{maximum}',
range: '{label}必须介于{minimum}and{maximum}之间'
},
pattern: {
mismatch: '{label}格式不匹配'
}
};
}
SchemaValidator.message = new Message();
export default SchemaValidator
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue
================================================
*
{{label}}
{{msg}}
{{msg}}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/package.json
================================================
{
"id": "uni-forms",
"displayName": "uni-forms 表单",
"version": "1.4.9",
"description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据",
"keywords": [
"uni-ui",
"表单",
"校验",
"表单校验",
"表单验证"
],
"repository": "https://github.com/dcloudio/uni-ui",
"engines": {
"HBuilderX": ""
},
"directories": {
"example": "../../temps/example_temps"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
"type": "component-vue"
},
"uni_modules": {
"dependencies": [
"uni-scss",
"uni-icons"
],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
================================================
FILE: talkieai-uniapp/src/uni_modules/uni-forms/readme.md
================================================
## Forms 表单
> **组件名:uni-forms**
> 代码块: `uForms`、`uni-forms-item`
> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。
uni-app的内置组件已经有了 `