Full Code of maioria/chatgpt-talkieai for AI

main 1702a894bb50 cached
413 files
1.3 MB
465.9k tokens
561 symbols
1 requests
Download .txt
Showing preview only (1,613K chars total). Download the full file or copy to clipboard to get everything.
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. <https://fsf.org/>
 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.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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 <https://www.gnu.org/licenses/>.

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:

    <program>  Copyright (C) <year>  <name of author>
    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
<https://www.gnu.org/licenses/>.

  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
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
# TalkieAI

## 简介
[TalkieAI](https://github.com/maioria/chatgpt-talkieai) 是一个基于AI的外语学习应用,可通过语音进行聊天,语法分析,翻译。
AI可以基于CHAT-GPT, 国内可以配置chat-gpt代理或者使用[智谱开放平台](https://open.bigmodel.cn/)

## 微信小程序
![](https://aitake.oss-cn-wulanchabu.aliyuncs.com/9dcce2ab0be473a09e3c4ce28e5d7d05ca848ead92981c14fd1d7601eb7be0f8.jpg)  

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

## 项目示例图
![](https://aitake.oss-cn-wulanchabu.aliyuncs.com/c79b6648bea061d2813773075ba3349807dcaea90c9699c5cef9cfa6b894e9ad.png)
![](https://aitake.oss-cn-wulanchabu.aliyuncs.com/e7a3ad173b55dad7682d843ce1e7a424ef321bf03ac100c81ff07519b05352d0.png)
![](https://aitake.oss-cn-wulanchabu.aliyuncs.com/6a7d7ff41c5f366b43084a41e7268dcdbc32b65fd0dd976b5ec368ac28dca3cf.png)
![](https://aitake.oss-cn-wulanchabu.aliyuncs.com/70d6feeb534e9aa9748d51c8c10acc06bb00224f8a40d96a002dc434977d1524.png)
## 本地启动
```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"""
    <speak version="1.0"  xmlns:mstts="https://www.w3.org/2001/mstts" xmlns="https://www.w3.org/2001/10/synthesis" xml:lang="{targetLang}">
      <voice name="{voice_name}">
        <prosody rate="{speech_rate}">
          <mstts:express-as style="{feel}" styledegree="1.5">
            {content}
          </mstts:express-as>
        </prosody>
      </voice>
    </speak>
    """
    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",
Download .txt
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
Download .txt
SYMBOL INDEX (561 symbols across 60 files)

FILE: talkieai-server/app/ai/impl/chat_gpt_ai.py
  class ChatGPTAI (line 9) | class ChatGPTAI(ChatAI):
    method __init__ (line 12) | def __init__(self, api_key: str, base_url: str = None, model: str = No...
    method invoke_greet (line 21) | def invoke_greet(self, params: GreetParams) -> str:
    method topic_invoke_greet (line 29) | def topic_invoke_greet(self, params: TopicGreetParams) -> str:
    method invoke_message (line 40) | def invoke_message(self, dto: MessageParams) -> AIMessageResult:
    method topic_invoke_message (line 62) | def topic_invoke_message(self, dto: AITopicMessageParams) -> AITopicMe...
    method topic_invoke_complete (line 94) | def topic_invoke_complete(
    method invoke_translate (line 125) | def invoke_translate(self, dto: TranslateParams) -> str:
    method invoke_grammar_analysis (line 134) | def invoke_grammar_analysis(
    method invoke_prompt_sentence (line 153) | def invoke_prompt_sentence(self, params: PromptSentenceParams) -> str:
    method invoke_word_detail (line 174) | def invoke_word_detail(self, params: WordDetailParams) -> AIWordDetail...
    method _original_invoke_chat_json (line 189) | def _original_invoke_chat_json(self, dto: MessageInvokeDTO):
    method _original_invoke_chat (line 202) | def _original_invoke_chat(self, dto: MessageInvokeDTO):

FILE: talkieai-server/app/ai/impl/zhipu_ai.py
  class ZhipuInvokeDTO (line 11) | class ZhipuInvokeDTO(BaseModel):
  class ZhipuAIComponent (line 17) | class ZhipuAIComponent(ChatAI):
    method __init__ (line 18) | def __init__(self, api_key: str, model: str):
    method invoke_greet (line 24) | def invoke_greet(self, params: GreetParams) -> str:
    method topic_invoke_greet (line 32) | def topic_invoke_greet(self, params: TopicGreetParams) -> str:
    method invoke_message (line 43) | def invoke_message(self, dto: MessageParams) -> AIMessageResult:
    method topic_invoke_message (line 72) | def topic_invoke_message(self, dto: AITopicMessageParams) -> AITopicMe...
    method topic_invoke_complete (line 116) | def topic_invoke_complete(
    method invoke_translate (line 146) | def invoke_translate(self, dto: TranslateParams) -> str:
    method invoke_grammar_analysis (line 155) | def invoke_grammar_analysis(
    method invoke_prompt_sentence (line 173) | def invoke_prompt_sentence(self, params: PromptSentenceParams) -> str:
    method invoke_word_detail (line 193) | def invoke_word_detail(self, params: WordDetailParams) -> AIWordDetail...
    method _original_invoke_chat (line 207) | def _original_invoke_chat(self, dto: MessageInvokeDTO):
    method _original_invoke_chat_json (line 222) | def _original_invoke_chat_json(self, dto: MessageInvokeDTO):

FILE: talkieai-server/app/ai/interfaces.py
  class MessageInvokeDTO (line 10) | class MessageInvokeDTO:
  class FunctionInvokeDTO (line 17) | class FunctionInvokeDTO:
  class ChatAI (line 24) | class ChatAI(ABC):
    method invoke_message (line 26) | def invoke_message(self, dto: MessageParams) -> AIMessageResult:
    method invoke_translate (line 31) | def invoke_translate(self, dto: TranslateParams) -> str:
    method invoke_greet (line 36) | def invoke_greet(self, dto: GreetParams) -> str:
    method invoke_grammar_analysis (line 41) | def invoke_grammar_analysis(
    method invoke_prompt_sentence (line 48) | def invoke_prompt_sentence(self, dto: PromptSentenceParams) -> str:
    method invoke_word_detail (line 53) | def invoke_word_detail(self, dto: WordDetailParams) -> AIWordDetailRes...
    method topic_invoke_greet (line 58) | def topic_invoke_greet(self, dto: TopicGreetParams) -> str:
    method topic_invoke_message (line 63) | def topic_invoke_message(self, dto: AITopicMessageParams) -> AITopicMe...
    method topic_invoke_complete (line 68) | def topic_invoke_complete(self, dto: AITopicCompleteParams) -> AITopic...

FILE: talkieai-server/app/ai/models.py
  class MessageItemParams (line 6) | class MessageItemParams:
  class MessageParams (line 11) | class MessageParams:
  class AITopicMessageParams (line 21) | class AITopicMessageParams:
  class AITopicCompleteParams (line 33) | class AITopicCompleteParams:
  class AITopicCompleteResult (line 39) | class AITopicCompleteResult:
  class AIMessageResult (line 46) | class AIMessageResult:
  class AITopicMessageResult (line 52) | class AITopicMessageResult:
  class TranslateParams (line 59) | class TranslateParams:
  class GreetParams (line 65) | class GreetParams:
  class GrammarAnalysisParams (line 70) | class GrammarAnalysisParams:
  class AIGrammarAnalysisResult (line 76) | class AIGrammarAnalysisResult:
  class PromptSentenceParams (line 84) | class PromptSentenceParams:
  class WordDetailParams (line 90) | class WordDetailParams:
  class AIWordDetailResult (line 95) | class AIWordDetailResult:
  class TopicGreetParams (line 101) | class TopicGreetParams:

FILE: talkieai-server/app/api/account_routes.py
  function visitor_login (line 14) | def visitor_login(
  function get_account_info (line 35) | def get_account_info(
  function account_settings_api (line 44) | def account_settings_api(
  function get_account_settings_api (line 54) | def get_account_settings_api(
  function update_role (line 64) | def update_role(
  function get_account_role (line 75) | def get_account_role(
  function get_account_collect_api (line 83) | def get_account_collect_api(
  function account_collect_api (line 100) | def account_collect_api(
  function account_collect_api (line 111) | def account_collect_api(
  function get_account_collects_api (line 122) | def get_account_collects_api(

FILE: talkieai-server/app/api/message_routes.py
  function message_practice_api (line 17) | def message_practice_api(
  function translate_api (line 31) | def translate_api(
  function speech_api (line 42) | def speech_api(
  function translate_source_language (line 66) | def translate_source_language(
  function translate_setting_language (line 77) | def translate_setting_language(
  function speech_content_api (line 88) | def speech_content_api(
  function grammar_api (line 120) | def grammar_api(
  function pronunciation_api (line 132) | def pronunciation_api(
  function get_word_api (line 147) | def get_word_api(
  function word_practice_api (line 159) | def word_practice_api(
  function prompt_api (line 171) | def prompt_api(

FILE: talkieai-server/app/api/session_routes.py
  function get_default_session (line 16) | def get_default_session(
  function get_session (line 25) | def get_session(
  function voice_upload_api (line 36) | def voice_upload_api(
  function get_session_greeting (line 49) | def get_session_greeting(
  function chat_api (line 60) | def chat_api(
  function delete_latest_session_messages (line 74) | def delete_latest_session_messages(
  function delete_all_session_messages (line 87) | def delete_all_session_messages(

FILE: talkieai-server/app/api/sys_routes.py
  function get_settings_languages_example (line 15) | def get_settings_languages_example(
  function get_settings_roles (line 28) | def get_settings_roles(
  function get_settings_languages (line 39) | def get_settings_languages(
  function voice_upload_api (line 48) | def voice_upload_api(
  function get_file (line 59) | def get_file(file_name: str, response: Response):
  function add_feedback (line 75) | def add_feedback(

FILE: talkieai-server/app/api/topics_route.py
  function create_custom_topic (line 16) | def create_custom_topic(
  function get_custom_topic (line 28) | def get_custom_topic(
  function get_all_chat_topics (line 38) | def get_all_chat_topics(
  function get_custom_topic_example (line 50) | def get_custom_topic_example(
  function get_topic_detail (line 61) | def get_topic_detail(
  function get_chat_topic_history (line 73) | def get_chat_topic_history(
  function get_chat_topic_phrases (line 85) | def get_chat_topic_phrases(
  function create_chat_topic_session (line 97) | def create_chat_topic_session(
  function complete_session (line 109) | def complete_session(
  function delete_session (line 123) | def delete_session(
  function get_session_result (line 140) | def get_session_result(

FILE: talkieai-server/app/config.py
  class Config (line 7) | class Config:

FILE: talkieai-server/app/core/__init__.py
  function get_current_account (line 10) | def get_current_account(x_token: str = Header(None), x_token_query: str ...

FILE: talkieai-server/app/core/auth.py
  class Auth (line 7) | class Auth:
    method __init__ (line 8) | def __init__(self, token_secret: str, algorithm: str, decoded_token_ia...
    method init_token (line 16) | def init_token(self, name: str, id: str) -> str:
    method get_current_account (line 23) | def get_current_account(self, x_token: str) -> str:

FILE: talkieai-server/app/core/azure_voice.py
  function speech_default (line 18) | def speech_default(content: str, output_path_str: str, language: str, vo...
  function speech_by_ssml (line 57) | def speech_by_ssml(
  function speech_pronunciation (line 113) | def speech_pronunciation(content: str, speech_path: str, language: str =...
  function word_speech_pronunciation (line 159) | def word_speech_pronunciation(word: str, speech_path: str, language: str...
  function speech_translate_text (line 218) | def speech_translate_text(speech_path: str, language: str) -> str:
  function get_voice_list (line 253) | def get_voice_list():
  function get_voice_list (line 278) | def get_voice_list():
  function get_azure_voice_role_by_short_name (line 311) | def get_azure_voice_role_by_short_name(short_name: str):

FILE: talkieai-server/app/core/exceptions.py
  class UserAccessDeniedException (line 2) | class UserAccessDeniedException(Exception):
  class UserPasswordIncorrectException (line 7) | class UserPasswordIncorrectException(Exception):
  class ParameterIncorrectException (line 12) | class ParameterIncorrectException(Exception):

FILE: talkieai-server/app/core/language.py
  function get_label_by_language (line 22) | def get_label_by_language(language: str) -> str:
  function get_azure_style_label (line 29) | def get_azure_style_label(style: str):
  function get_azure_language_default_role (line 37) | def get_azure_language_default_role(language: str):
  function get_role_info_by_short_name (line 44) | def get_role_info_by_short_name(short_name: str):

FILE: talkieai-server/app/core/utils.py
  function short_uuid (line 16) | def short_uuid() -> str:
  function uuid4 (line 35) | def uuid4():
  function digest_password (line 40) | def digest_password(password: str):
  function generate_code (line 45) | def generate_code():
  function date_to_str (line 52) | def date_to_str(date):
  function day_to_str (line 56) | def day_to_str(date):
  function friendly_time (line 60) | def friendly_time(dt):
  function save_file (line 90) | def save_file(upload_file: UploadFile) -> str:
  function file_get_path (line 115) | def file_get_path(filename: str) -> str:
  function get_file_ext (line 119) | def get_file_ext(filename: str) -> str:
  function get_date_str (line 123) | def get_date_str():
  function save_image_file (line 127) | def save_image_file(upload_file: UploadFile) -> str:
  function save_voice_file (line 143) | def save_voice_file(upload_file: UploadFile, prefix='') -> str:
  function voice_file_get_path (line 158) | def voice_file_get_path(filename: str) -> str:
  function image_file_get_path (line 166) | def image_file_get_path(filename: str) -> str:
  function get_file_ext (line 171) | def get_file_ext(filename: str) -> str:

FILE: talkieai-server/app/db/__init__.py
  function checkout_listener (line 8) | def checkout_listener(dbapi_con, con_record, con_proxy):
  function get_db (line 33) | def get_db():

FILE: talkieai-server/app/db/account_entities.py
  class AccountEntity (line 7) | class AccountEntity(Base):
  class AccountSettingsEntity (line 20) | class AccountSettingsEntity(Base):
  class AccountCollectEntity (line 38) | class AccountCollectEntity(Base):

FILE: talkieai-server/app/db/chat_entities.py
  class MessageSessionEntity (line 6) | class MessageSessionEntity(Base):
  class MessageEntity (line 23) | class MessageEntity(Base):
  class MessageTranslateEntity (line 49) | class MessageTranslateEntity(Base):
  class MessageGrammarEntity (line 63) | class MessageGrammarEntity(Base):

FILE: talkieai-server/app/db/sys_entities.py
  class SettingsLanguageEntity (line 7) | class SettingsLanguageEntity(Base):
  class SettingsLanguageExampleEntity (line 23) | class SettingsLanguageExampleEntity(Base):
  class SettingsRoleEntity (line 36) | class SettingsRoleEntity(Base):
  class FileDetail (line 67) | class FileDetail(Base):
  class SysCacheEntity (line 83) | class SysCacheEntity(Base):
  class FeedbackEntity (line 94) | class FeedbackEntity(Base):
  class SysDictTypeEntity (line 106) | class SysDictTypeEntity(Base):
  class SysDictDataEntity (line 119) | class SysDictDataEntity(Base):

FILE: talkieai-server/app/db/topic_entities.py
  class TopicGroupEntity (line 8) | class TopicGroupEntity(Base):
  class TopicEntity (line 25) | class TopicEntity(Base):
  class TopicSessionRelation (line 56) | class TopicSessionRelation(Base):
  class AccountTopicEntity (line 77) | class AccountTopicEntity(Base):
  class TopicTargetEntity (line 94) | class TopicTargetEntity(Base):
  class TopicPhraseEntity (line 119) | class TopicPhraseEntity(Base):
  class TopicHistoryEntity (line 145) | class TopicHistoryEntity(Base):

FILE: talkieai-server/app/main.py
  function conflict_error_handler (line 35) | async def conflict_error_handler(_, exc: Exception):
  function user_access_denied_error_handler (line 51) | async def user_access_denied_error_handler(_, exc: UserAccessDeniedExcep...

FILE: talkieai-server/app/models/account_models.py
  class MessageType (line 7) | class MessageType(Enum):
  class WechatLoginDTO (line 14) | class WechatLoginDTO(BaseModel):
  class VisitorLoginDTO (line 19) | class VisitorLoginDTO(BaseModel):
  class ChatDTO (line 23) | class ChatDTO(BaseModel):
  class MessagePracticeDTO (line 30) | class MessagePracticeDTO(BaseModel):
  class TransformSpeechDTO (line 36) | class TransformSpeechDTO(BaseModel):
  class VoiceTranslateDTO (line 42) | class VoiceTranslateDTO(BaseModel):
  class TranslateDTO (line 48) | class TranslateDTO(BaseModel):
  class TranslateTextDTO (line 54) | class TranslateTextDTO(BaseModel):
  class GrammarDTO (line 61) | class GrammarDTO(BaseModel):
  class WordDetailDTO (line 67) | class WordDetailDTO(BaseModel):
  class WordPracticeDTO (line 73) | class WordPracticeDTO(BaseModel):
  class CollectDTO (line 81) | class CollectDTO(BaseModel):
  class PromptDTO (line 89) | class PromptDTO(BaseModel):
  class AccountSettingsDTO (line 95) | class AccountSettingsDTO(BaseModel):
  class CreateSessionDTO (line 102) | class CreateSessionDTO(BaseModel):
  class UpdateRoleDTO (line 106) | class UpdateRoleDTO(BaseModel):
  class UpdateLanguageDTO (line 114) | class UpdateLanguageDTO(BaseModel):
  class AccountSettingsDTO (line 118) | class AccountSettingsDTO(BaseModel):

FILE: talkieai-server/app/models/chat_models.py
  class MessageType (line 7) | class MessageType(Enum):
  class CreateTalkSessionDTO (line 16) | class CreateTalkSessionDTO(BaseModel):
  class CreateSessionDTO (line 22) | class CreateSessionDTO(BaseModel):
  class ChatDTO (line 26) | class ChatDTO(BaseModel):
  class MessagePracticeDTO (line 33) | class MessagePracticeDTO(BaseModel):
  class TransformSpeechDTO (line 39) | class TransformSpeechDTO(BaseModel):
  class VoiceTranslateDTO (line 45) | class VoiceTranslateDTO(BaseModel):
  class TranslateDTO (line 51) | class TranslateDTO(BaseModel):
  class TranslateTextDTO (line 57) | class TranslateTextDTO(BaseModel):
  class TransformContentSpeechDTO (line 63) | class TransformContentSpeechDTO(BaseModel):
  class GrammarDTO (line 74) | class GrammarDTO(BaseModel):
  class PronunciationDTO (line 80) | class PronunciationDTO(BaseModel):
  class WordDetailDTO (line 85) | class WordDetailDTO(BaseModel):
  class WordPracticeDTO (line 93) | class WordPracticeDTO(BaseModel):
  class PromptDTO (line 101) | class PromptDTO(BaseModel):

FILE: talkieai-server/app/models/response.py
  class ApiResponse (line 1) | class ApiResponse:
    method __init__ (line 2) | def __init__(self, code: str = '200', status: str = 'SUCCESS', data=No...

FILE: talkieai-server/app/models/sys_models.py
  class UpdateLanguageDTO (line 6) | class UpdateLanguageDTO(BaseModel):
  class FeedbackDTO (line 10) | class FeedbackDTO(BaseModel):

FILE: talkieai-server/app/models/topic_models.py
  class CreateSessionDTO (line 7) | class CreateSessionDTO(BaseModel):
  class TopicCreateDTO (line 10) | class TopicCreateDTO(BaseModel):

FILE: talkieai-server/app/services/account_service.py
  class AccountService (line 27) | class AccountService:
    method __init__ (line 28) | def __init__(self, db: Session):
    method visitor_login (line 31) | def visitor_login(self, fingerprint: str, client_host: str, user_agent...
    method collect (line 49) | def collect(self, dto: CollectDTO, account_id: str):
    method get_account_info (line 109) | def get_account_info(self, account_id: str):
    method get_collect (line 136) | def get_collect(self, dto: CollectDTO, account_id: str):
    method cancel_collect (line 155) | def cancel_collect(self, dto: CollectDTO, account_id: str):
    method get_collects (line 175) | def get_collects(self, type: str, page: int, page_size: int, account_i...
    method get_settings (line 199) | def get_settings(self, account_id: str):
    method save_settings (line 224) | def save_settings(self, dto: AccountSettingsDTO, account_id: str):
    method update_role_setting (line 252) | def update_role_setting(self, dto: UpdateRoleDTO, account_id: str):
    method get_role_setting (line 269) | def get_role_setting(self, account_id: str):
    method get_account_source_language (line 288) | def get_account_source_language(self, account_id: str):
    method get_account_target_language (line 300) | def get_account_target_language(self, account_id: str):
    method get_user_current_day_system_message_count (line 312) | def get_user_current_day_system_message_count(self, account_id: str):
    method get_user_system_message_count (line 323) | def get_user_system_message_count(self, account_id: str):
    method __check_and_init_default_settings (line 331) | def __check_and_init_default_settings(self, account_id: str):

FILE: talkieai-server/app/services/chat_service.py
  class ChatService (line 26) | class ChatService:
    method __init__ (line 29) | def __init__(self, db: Session):
    method get_settings_languages_example (line 34) | def get_settings_languages_example(self, language: str, account_id: str):
    method get_default_session (line 41) | def get_default_session(self, account_id: str):
    method get_session (line 59) | def get_session(self, session_id: str, account_id: str):
    method get_session_greeting (line 68) | def get_session_greeting(self, session_id: str, account_id: str):
    method send_session_message (line 94) | def send_session_message(self, session_id: str, dto: ChatDTO, account_...
    method message_practice (line 179) | def message_practice(
    method get_word (line 191) | def get_word(self, dto: WordDetailDTO, account_id: str):
    method grammar_analysis (line 206) | def grammar_analysis(self, dto: GrammarDTO, account_id: str):
    method word_practice (line 239) | def word_practice(self, dto: WordPracticeDTO, account_id: str):
    method pronunciation (line 246) | def pronunciation(self, dto: PronunciationDTO, account_id: str):
    method message_speech_content (line 304) | def message_speech_content(self, dto: TransformContentSpeechDTO, accou...
    method message_speech (line 379) | def message_speech(self, message_id: str, account_id: str):
    method create_session (line 421) | def create_session(self, account_id: str):
    method get_session_messages (line 430) | def get_session_messages(
    method prompt_sentence (line 458) | def prompt_sentence(self, dto: PromptDTO, account_id: str):
    method delete_all_session_messages (line 477) | def delete_all_session_messages(self, session_id: str, account_id: str):
    method transform_text (line 489) | def transform_text(self, session_id: str, dto: VoiceTranslateDTO, acco...
    method delete_latest_session_messages (line 498) | def delete_latest_session_messages(self, session_id: str, account_id: ...
    method initOwnerMessagePronunciation (line 525) | def initOwnerMessagePronunciation(self, result):
    method translate_source_language (line 544) | def translate_source_language(self, dto: TranslateTextDTO, account_id:...
    method translate_setting_language (line 550) | def translate_setting_language(self, dto: TranslateTextDTO, account_id...
    method translate_language (line 561) | def translate_language(self, content: str, language: str):
    method translate_message (line 568) | def translate_message(self, message_id: str, account_id: str):
    method initMessageResult (line 599) | def initMessageResult(self, message: MessageEntity):
    method __convert_session_model (line 609) | def __convert_session_model(self, session: MessageSessionEntity):
    method __add_account_message (line 618) | def __add_account_message(
    method __add_system_message (line 636) | def __add_system_message(
    method __refresh_session_message_count (line 654) | def __refresh_session_message_count(self, session_id: str):
    method __get_and_check_session (line 667) | def __get_and_check_session(
    method __check_has_messages (line 680) | def __check_has_messages(self, session_id: str, account_id: str):
    method __get_message_sequence (line 692) | def __get_message_sequence(self):

FILE: talkieai-server/app/services/sys_service.py
  class SysService (line 21) | class SysService:
    method __init__ (line 22) | def __init__(self, db: Session):
    method get_settings_roles (line 27) | def get_settings_roles(self, locale: str, account_id: str):
    method get_settings_languages_example (line 65) | def get_settings_languages_example(self, language: str, account_id: str):
    method get_settings_languages (line 77) | def get_settings_languages(self, account_id: str):
    method get_settings_languages_example (line 96) | def get_settings_languages_example(self, language: str, account_id: str):
    method voice_upload (line 103) | def voice_upload(self, file: UploadFile, account_id: str):
    method add_feedback (line 118) | def add_feedback(self, dto: FeedbackDTO, account_id: str):
    method _check_and_init_default_learn_languages (line 126) | def _check_and_init_default_learn_languages(self):
    method _check_and_init_default_roles (line 152) | def _check_and_init_default_roles(self, locale: str):

FILE: talkieai-server/app/services/topic_service.py
  class TopicService (line 16) | class TopicService:
    method __init__ (line 17) | def __init__(self, db: Session):
    method get_topic_greet_params (line 21) | def get_topic_greet_params(self, session_id: str) -> TopicGreetParams:
    method get_topic_message_params (line 38) | def get_topic_message_params(self, session_id: str) -> AITopicMessageP...
    method get_all_topics (line 65) | def get_all_topics(self, type: str, account_id: str):
    method get_topic_detail (line 143) | def get_topic_detail(self, topic_id: str, account_id: str):
    method create_topic_session (line 181) | def create_topic_session(self, topic_id: str, account_id: str):
    method complete_topic_session (line 221) | def complete_topic_session(self, session_id: str, account_id: str):
    method delete_topic_session (line 279) | def delete_topic_session(self, topic_id: str, session_id: str, account...
    method get_custom_topic_example (line 289) | def get_custom_topic_example(self, account_id: str):
    method create_custom_topic (line 293) | def create_custom_topic(self, dto: TopicCreateDTO, account_id: str):
    method get_custom_topic (line 314) | def get_custom_topic(self, account_id: str):
    method get_session_result (line 333) | def get_session_result(self, topic_id: str, session_id: str, account_i...
    method get_topic_history (line 353) | def get_topic_history(self, topic_id: str, account_id: str):
    method get_topic_phrases (line 413) | def get_topic_phrases(self, topic_id: str, account_id: str):
    method __check_and_init_topics (line 433) | def __check_and_init_topics(self):

FILE: talkieai-uniapp/src/axios/api.ts
  method success (line 32) | success(res) {
  method fail (line 49) | fail(error) {
  method complete (line 53) | complete(res) {

FILE: talkieai-uniapp/src/components/audioPlayerExecuter.ts
  type Listener (line 4) | interface Listener {
  class AudioPlayer (line 10) | class AudioPlayer {
    method constructor (line 12) | constructor() {}
    method stopAudio (line 17) | stopAudio() {
    method playAudio (line 23) | playAudio({ audioUrl, listener }: { audioUrl: string; listener: Listen...
    method createInnerAudioContext (line 41) | createInnerAudioContext(src: string, listener: Listener) {

FILE: talkieai-uniapp/src/components/speechExecuter.ts
  constant MAXIMUM_RECORDING_TIME (line 12) | const MAXIMUM_RECORDING_TIME = 60;
  class Speech (line 14) | class Speech {
    method constructor (line 31) | constructor() {
    method handleVoiceStart (line 35) | handleVoiceStart({
    method mpWeixinVoiceStart (line 64) | mpWeixinVoiceStart() {
    method clearInterval (line 106) | clearInterval() {
    method h5VoiceStart (line 113) | h5VoiceStart() {
    method handleCancleVoice (line 149) | handleCancleVoice() {
    method handleEndVoice (line 177) | handleEndVoice() {
    method handleWxEndVoice (line 195) | handleWxEndVoice() {
    method handleProcessWxEndVoice (line 203) | handleProcessWxEndVoice({ filePath }: { filePath: string }) {
    method handleH5EndVoice (line 241) | handleH5EndVoice() {
    method handleUploadResult (line 295) | handleUploadResult({ resData }: { resData: any }) {

FILE: talkieai-uniapp/src/global/globalCount.hooks.ts
  function useUserInfo (line 7) | function useUserInfo() {

FILE: talkieai-uniapp/src/main.ts
  function createApp (line 30) | function createApp() {

FILE: talkieai-uniapp/src/models/models.ts
  type AccountInfo (line 1) | interface AccountInfo {
  type AccountSettings (line 8) | interface AccountSettings {
  type Collect (line 18) | interface Collect {
  type Message (line 26) | interface Message {
  type Phoneme (line 39) | interface Phoneme {
  type Word (line 44) | interface Word {
  type Pronunciation (line 51) | interface Pronunciation {
  type MessagePage (line 59) | interface MessagePage {
  type Session (line 64) | interface Session {
  type Prompt (line 70) | interface Prompt {

FILE: talkieai-uniapp/src/models/sys.ts
  type Language (line 1) | interface Language {
  type Role (line 8) | interface Role {

FILE: talkieai-uniapp/src/shime-uni.d.ts
  type Hooks (line 4) | type Hooks = App.AppInstance & Page.PageInstance;
  type ComponentCustomOptions (line 5) | interface ComponentCustomOptions extends Hooks {}

FILE: talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/util.js
  class Calendar (line 3) | class Calendar {
    method constructor (line 4) | constructor({
    method setDate (line 30) | setDate(date) {
    method cleanMultipleStatus (line 38) | cleanMultipleStatus() {
    method resetSatrtDate (line 49) | resetSatrtDate(startDate) {
    method resetEndDate (line 58) | resetEndDate(endDate) {
    method getDate (line 66) | getDate(date, AddDayCount = 0, str = 'day') {
    method _getLastMonthDays (line 115) | _getLastMonthDays(firstDay, full) {
    method _currentMonthDys (line 131) | _currentMonthDys(dateData, full) {
    method _getNextMonthDays (line 197) | _getNextMonthDays(surplus, full) {
    method getInfo (line 214) | getInfo(date) {
    method dateCompare (line 225) | dateCompare(startDate, endDate) {
    method dateEqual (line 240) | dateEqual(before, after) {
    method geDateAll (line 258) | geDateAll(begin, end) {
    method getlunar (line 277) | getlunar(year, month, date) {
    method setSelectInfo (line 283) | setSelectInfo(data, value) {
    method setMultiple (line 291) | setMultiple(fullDate) {
    method _getWeek (line 321) | _getWeek(dateData) {

FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js
  method mounted (line 10) | mounted () {

FILE: talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js
  method default (line 5) | default () {
  method default (line 11) | default () {
  method default (line 65) | default () {
  method default (line 71) | default () {
  method default (line 97) | default () {
  method data (line 105) | data() {
  method isLocalData (line 125) | isLocalData() {
  method isCloudData (line 128) | isCloudData() {
  method isCloudDataList (line 131) | isCloudDataList() {
  method isCloudDataTree (line 134) | isCloudDataTree() {
  method dataValue (line 137) | dataValue() {
  method hasValue (line 142) | hasValue() {
  method created (line 149) | created() {
  method onPropsChange (line 188) | onPropsChange() {
  method loadData (line 193) | async loadData() {
  method loadLocalData (line 204) | async loadLocalData() {
  method loadCloudDataList (line 224) | async loadCloudDataList() {
  method loadCloudDataTree (line 248) | async loadCloudDataTree() {
  method loadCloudDataNode (line 279) | async loadCloudDataNode(callback) {
  method getCloudDataValue (line 303) | getCloudDataValue() {
  method getCloudDataListValue (line 314) | getCloudDataListValue() {
  method getCloudDataTreeValue (line 338) | getCloudDataTreeValue() {
  method getCommand (line 352) | getCommand(options = {}) {
  method _cloudDataPostField (line 397) | _cloudDataPostField() {
  method _cloudDataTreeWhere (line 405) | _cloudDataTreeWhere() {
  method _cloudDataNodeWhere (line 430) | _cloudDataNodeWhere() {
  method _getWhereByForeignKey (line 446) | _getWhereByForeignKey() {
  method _getForeignKeyByField (line 460) | _getForeignKeyByField() {
  method _updateBindData (line 476) | _updateBindData(node) {
  method _updateSelected (line 504) | _updateSelected() {
  method _filterData (line 522) | _filterData(data, paths) {
  method _extractTree (line 548) | _extractTree(nodes, result, parent_value) {
  method _extractTreePath (line 572) | _extractTreePath(nodes, result) {
  method _findNodePath (line 592) | _findNodePath(key, nodes, path = []) {

FILE: talkieai-uniapp/src/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js
  function pad (line 2) | function pad(str, length = 2) {
  function getDate (line 56) | function getDate(time) {
  function formatDate (line 74) | function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') {
  function friendlyDate (line 101) | function friendlyDate(time, {

FILE: talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js
  class Calendar (line 1) | class Calendar {
    method constructor (line 2) | constructor({
    method setDate (line 28) | setDate(date) {
    method cleanMultipleStatus (line 36) | cleanMultipleStatus() {
    method setStartDate (line 44) | setStartDate(startDate) {
    method setEndDate (line 48) | setEndDate(endDate) {
    method getPreMonthObj (line 52) | getPreMonthObj(date){
    method getNextMonthObj (line 64) | getNextMonthObj(date){
    method getDateObj (line 80) | getDateObj(date) {
    method getPreMonthDays (line 96) | getPreMonthDays(amount, dateObj) {
    method getCurrentMonthDays (line 136) | getCurrentMonthDays(amount, dateObj) {
    method _getNextMonthDays (line 188) | _getNextMonthDays(amount, dateObj) {
    method getInfo (line 230) | getInfo(date) {
    method dateEqual (line 241) | dateEqual(before, after) {
    method isLogicBefore (line 251) | isLogicBefore(currentDate, before, after) {
    method isLogicAfter (line 259) | isLogicAfter(currentDate, before, after) {
    method geDateAll (line 272) | geDateAll(begin, end) {
    method setMultiple (line 292) | setMultiple(fullDate) {
    method setHoverMultiple (line 331) | setHoverMultiple(fullDate) {
    method setDefaultMultiple (line 352) | setDefaultMultiple(before, after) {
    method getWeeks (line 370) | getWeeks(dateData) {
  function getDateTime (line 401) | function getDateTime(date, hideSecond){
  function getDate (line 405) | function getDate(date) {
  function getTime (line 414) | function getTime(date, hideSecond){
  function addZero (line 423) | function addZero(num) {
  function getDefaultSecond (line 430) | function getDefaultSecond(hideSecond) {
  function dateCompare (line 434) | function dateCompare(startDate, endDate) {
  function checkDate (line 440) | function checkDate(date){
  function fixIosDateFormat (line 446) | function fixIosDateFormat(value) {

FILE: talkieai-uniapp/src/uni_modules/uni-drawer/components/uni-drawer/keypress.js
  method mounted (line 10) | mounted () {

FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js
  constant ERR_MSG_OK (line 3) | const ERR_MSG_OK = 'chooseAndUploadFile:ok';
  constant ERR_MSG_FAIL (line 4) | const ERR_MSG_FAIL = 'chooseAndUploadFile:fail';
  function chooseImage (line 6) | function chooseImage(opts) {
  function chooseVideo (line 31) | function chooseVideo(opts) {
  function chooseAll (line 80) | function chooseAll(opts) {
  function normalizeChooseAndUploadFileRes (line 112) | function normalizeChooseAndUploadFileRes(res, fileType) {
  function uploadCloudFiles (line 129) | function uploadCloudFiles(files, max = 5, onUploadProgress) {
  function uploadFiles (line 182) | function uploadFiles(choosePromise, {
  function chooseAndUploadFile (line 209) | function chooseAndUploadFile(opts = {

FILE: talkieai-uniapp/src/uni_modules/uni-file-picker/components/uni-file-picker/utils.js
  method success (line 71) | success(res) {
  method fail (line 74) | fail(err) {

FILE: talkieai-uniapp/src/uni_modules/uni-forms/components/uni-forms/utils.js
  function _basePath (line 151) | function _basePath(path) {

FILE: talkieai-uniapp/src/uni_modules/uni-forms/components/uni-forms/validate.js
  constant FORMAT_MAPPING (line 9) | const FORMAT_MAPPING = {
  function formatMessage (line 18) | function formatMessage(args, resources = '') {
  function isEmptyValue (line 34) | function isEmptyValue(value, type) {
  method integer (line 55) | integer(value) {
  method string (line 58) | string(value) {
  method number (line 61) | number(value) {
  method array (line 73) | array(value) {
  method object (line 76) | object(value) {
  method date (line 79) | date(value) {
  method timestamp (line 82) | timestamp(value) {
  method file (line 88) | file(value) {
  method email (line 91) | email(value) {
  method url (line 94) | url(value) {
  method pattern (line 97) | pattern(reg, value) {
  method method (line 104) | method(value) {
  method idcard (line 107) | idcard(value) {
  method 'url-https' (line 110) | 'url-https'(value) {
  method 'url-scheme' (line 113) | 'url-scheme'(value) {
  method 'url-web' (line 116) | 'url-web'(value) {
  class RuleValidator (line 121) | class RuleValidator {
    method constructor (line 123) | constructor(message) {
    method validateRule (line 127) | async validateRule(fieldKey, fieldValue, value, data, allData) {
    method validateFunction (line 189) | async validateFunction(rule, value, data, allData, vt) {
    method _getMessage (line 205) | _getMessage(rule, message, vt) {
    method _getValidateType (line 209) | _getValidateType(rule) {
  method required (line 233) | required(rule, value, message) {
  method range (line 241) | range(rule, value, message) {
  method rangeNumber (line 273) | rangeNumber(rule, value, message) {
  method rangeLength (line 302) | rangeLength(rule, value, message) {
  method pattern (line 322) | pattern(rule, value, message) {
  method format (line 330) | format(rule, value, message) {
  method arrayTypeFormat (line 343) | arrayTypeFormat(rule, value, message) {
  class SchemaValidator (line 360) | class SchemaValidator extends RuleValidator {
    method constructor (line 362) | constructor(schema, options) {
    method updateSchema (line 369) | updateSchema(schema) {
    method validate (line 373) | async validate(data, allData) {
    method validateAll (line 381) | async validateAll(data, allData) {
    method validateUpdate (line 389) | async validateUpdate(data, allData) {
    method invokeValidate (line 397) | async invokeValidate(data, all, allData) {
    method invokeValidateUpdate (line 414) | async invokeValidateUpdate(data, all, allData) {
    method _checkFieldInSchema (line 429) | _checkFieldInSchema(data) {
  function Message (line 449) | function Message() {

FILE: talkieai-uniapp/src/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js
  method mounted (line 10) | mounted () {

FILE: talkieai-uniapp/src/uni_modules/uni-popup/components/uni-popup/keypress.js
  method mounted (line 10) | mounted () {

FILE: talkieai-uniapp/src/uni_modules/uni-popup/components/uni-popup/popup.js
  method data (line 3) | data() {
  method created (line 8) | created(){
  method getParent (line 15) | getParent(name = 'uniPopup') {

FILE: talkieai-uniapp/src/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js
  method data (line 9) | data() {
  method show (line 14) | show(newVal) {
  method leftOptions (line 24) | leftOptions() {
  method rightOptions (line 28) | rightOptions(newVal) {
  method created (line 32) | created() {
  method mounted (line 38) | mounted() {
  method init (line 53) | init() {
  method onClick (line 64) | onClick(index, item, position) {
  method touchstart (line 71) | touchstart(e) {
  method touchend (line 112) | touchend(e) {
  method bindTiming (line 117) | bindTiming(x) {
  method range (line 153) | range(num, mix, max) {
  method open (line 160) | open(type) {
  method close (line 167) | close() {
  method animation (line 175) | animation(type) {
  method setEmit (line 216) | setEmit(x, type) {
  method move (line 237) | move(ref, value) {
  method getEl (line 258) | getEl(el) {
  method getSelectorQuery (line 264) | getSelectorQuery() {
  method getDom (line 285) | getDom(str) {

FILE: talkieai-uniapp/src/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js
  function isPC (line 1) | function isPC() {

FILE: talkieai-uniapp/src/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js
  method data (line 2) | data() {
  method show (line 12) | show(newVal) {
  method created (line 22) | created() {
  method mounted (line 28) | mounted() {
  method appTouchStart (line 35) | appTouchStart(e) {
  method appTouchEnd (line 42) | appTouchEnd(e, index, item, position) {
  method onChange (line 61) | onChange(e) {
  method touchstart (line 65) | touchstart(e) {
  method touchmove (line 72) | touchmove(e) {}
  method touchend (line 73) | touchend(e) {
  method move (line 88) | move(moveX) {
  method open (line 124) | open(type) {
  method close (line 132) | close() {
  method animation (line 148) | animation(type) {
  method getSlide (line 163) | getSlide(x) {}
  method getQuerySelect (line 164) | getQuerySelect() {

FILE: talkieai-uniapp/src/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js
  constant MIN_DISTANCE (line 4) | const MIN_DISTANCE = 10;
  method data (line 6) | data() {
  method show (line 19) | show(newVal) {
  method left (line 23) | left() {
  method buttonShow (line 26) | buttonShow(newVal) {
  method leftOptions (line 30) | leftOptions() {
  method rightOptions (line 33) | rightOptions() {
  method mounted (line 37) | mounted() {
  method init (line 45) | init() {
  method closeSwipe (line 55) | closeSwipe(e) {
  method appTouchStart (line 60) | appTouchStart(e) {
  method appTouchEnd (line 67) | appTouchEnd(e, index, item, position) {
  method touchstart (line 82) | touchstart(e) {
  method touchmove (line 89) | touchmove(e) {
  method touchend (line 99) | touchend() {
  method move (line 107) | move(value) {
  method range (line 121) | range(num, min, max) {
  method moveDirection (line 129) | moveDirection(left) {
  method openState (line 158) | openState(type) {
  method close (line 188) | close() {
  method getDirection (line 191) | getDirection(x, y) {
  method resetTouchStatus (line 205) | resetTouchStatus() {
  method stopTouchStart (line 217) | stopTouchStart(event) {
  method stopTouchMove (line 228) | stopTouchMove(event) {
  method getSelectorQuery (line 237) | getSelectorQuery() {

FILE: talkieai-uniapp/src/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js
  method data (line 12) | data() {
  method show (line 18) | show(newVal) {
  method created (line 22) | created() {
  method mounted (line 28) | mounted() {
  method closeSwipe (line 33) | closeSwipe(e) {
  method change (line 39) | change(e) {
  method appTouchStart (line 46) | appTouchStart(e) {
  method appTouchEnd (line 54) | appTouchEnd(e, index, item, position) {
  method onClickForPC (line 70) | onClickForPC(index, item, position) {

FILE: talkieai-uniapp/src/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js
  constant MIN_DISTANCE (line 1) | const MIN_DISTANCE = 10;
  method showWatch (line 3) | showWatch(newVal, oldVal, ownerInstance, instance, self) {
  method touchstart (line 24) | touchstart(e, ownerInstance, self) {
  method touchmove (line 49) | touchmove(e, ownerInstance, self) {
  method touchend (line 76) | touchend(e, ownerInstance, self) {
  method move (line 96) | move(value, instance, ownerInstance, self) {
  method getDom (line 117) | getDom(instance, ownerInstance, self) {
  method getDisabledType (line 128) | getDisabledType(value) {
  method range (line 138) | range(num, min, max) {
  method moveDirection (line 150) | moveDirection(left, ins, ownerInstance, self) {
  method openState (line 183) | openState(type, ins, ownerInstance, self) {
  method getDirection (line 219) | getDirection(x, y) {
  method resetTouchStatus (line 233) | resetTouchStatus(instance, self) {
  method stopTouchStart (line 246) | stopTouchStart(event, ownerInstance, self) {
  method stopTouchMove (line 259) | stopTouchMove(event, self) {

FILE: talkieai-uniapp/src/uni_modules/uni-transition/components/uni-transition/createAnimation.js
  class MPAnimation (line 10) | class MPAnimation {
    method constructor (line 11) | constructor(options, _this) {
    method _nvuePushAnimates (line 23) | _nvuePushAnimates(type, args) {
    method _animateRun (line 48) | _animateRun(styles = {}, config = {}) {
    method _nvueNextAnimate (line 61) | _nvueNextAnimate(animates, step = 0, fn) {
    method step (line 79) | step(config = {}) {
    method run (line 91) | run(fn) {
  function createAnimation (line 127) | function createAnimation(option, _this) {

FILE: talkieai-uniapp/src/utils/bus.ts
  class EventBus (line 1) | class EventBus {
    method constructor (line 2) | constructor() {
    method emit (line 5) | emit(eventName: any, data: any) {
    method on (line 12) | on(eventName: any, fn: any) {
    method off (line 17) | off(eventName: any, fn: any) {
Condensed preview — 413 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,726K chars).
[
  {
    "path": ".gitignore",
    "chars": 88,
    "preview": "/talkieai-server/.env\n/talkieai-uniapp/package-lock.json\n/talkieai-uniapp/node_modules/\n"
  },
  {
    "path": "LICENSE",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 2587,
    "preview": "# TalkieAI\n\n## 简介\n[TalkieAI](https://github.com/maioria/chatgpt-talkieai) 是一个基于AI的外语学习应用,可通过语音进行聊天,语法分析,翻译。\nAI可以基于CHAT-G"
  },
  {
    "path": "talkieai-server/app/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "talkieai-server/app/ai/__init__.py",
    "chars": 460,
    "preview": "from app.config import Config\nfrom app.ai.impl.zhipu_ai import ZhipuAIComponent\nfrom app.ai.impl.chat_gpt_ai import Chat"
  },
  {
    "path": "talkieai-server/app/ai/impl/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "talkieai-server/app/ai/impl/chat_gpt_ai.py",
    "chars": 9127,
    "preview": "from typing import List, Dict\nimport json\nfrom dataclasses import dataclass\nfrom app.ai.interfaces import *\nfrom app.ai."
  },
  {
    "path": "talkieai-server/app/ai/impl/zhipu_ai.py",
    "chars": 9998,
    "preview": "from typing import List, Dict\nimport json\nfrom pydantic import BaseModel\n\nfrom app.ai.interfaces import *\nfrom app.ai.mo"
  },
  {
    "path": "talkieai-server/app/ai/interfaces.py",
    "chars": 1628,
    "preview": "from app.ai.models import *\nfrom abc import ABC, abstractmethod\nfrom typing import List, Dict\nfrom abc import ABC, abstr"
  },
  {
    "path": "talkieai-server/app/ai/models.py",
    "chars": 1682,
    "preview": "from typing import List, Dict\nfrom dataclasses import dataclass, field\n\n\n@dataclass\nclass MessageItemParams:\n    role: s"
  },
  {
    "path": "talkieai-server/app/api/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "talkieai-server/app/api/account_routes.py",
    "chars": 4045,
    "preview": "from fastapi import APIRouter, Depends, Request\nfrom sqlalchemy.orm import Session\nfrom app.core import get_current_acco"
  },
  {
    "path": "talkieai-server/app/api/message_routes.py",
    "chars": 5097,
    "preview": "from fastapi import APIRouter, Depends, Response\n\nfrom sqlalchemy.orm import Session\nfrom app.core import get_current_ac"
  },
  {
    "path": "talkieai-server/app/api/session_routes.py",
    "chars": 2761,
    "preview": "from fastapi import APIRouter, Depends, Response\n\nfrom sqlalchemy.orm import Session\nfrom app.core import get_current_ac"
  },
  {
    "path": "talkieai-server/app/api/sys_routes.py",
    "chars": 2557,
    "preview": "import os\nfrom fastapi import APIRouter, Depends, Request, UploadFile, File, Response\nfrom sqlalchemy.orm import Session"
  },
  {
    "path": "talkieai-server/app/api/topics_route.py",
    "chars": 4440,
    "preview": "from fastapi import APIRouter, Depends, Response\nfrom sqlalchemy.orm import Session\n\nfrom app.core import get_current_ac"
  },
  {
    "path": "talkieai-server/app/config.py",
    "chars": 1243,
    "preview": "import os\nfrom dotenv import load_dotenv\n\nload_dotenv()\n\n\nclass Config:\n    DEFAULT_SOURCE_LANGUAGE = 'zh-CN'\n    DEFAUL"
  },
  {
    "path": "talkieai-server/app/core/__init__.py",
    "chars": 445,
    "preview": "from fastapi import Header\n\nfrom app.config import Config\nfrom app.core.auth import Auth\n\nauth = Auth(Config.TOKEN_SECRE"
  },
  {
    "path": "talkieai-server/app/core/auth.py",
    "chars": 1653,
    "preview": "import time\nimport jwt\nfrom fastapi import HTTPException\n\n\n\nclass Auth:\n    def __init__(self, token_secret: str, algori"
  },
  {
    "path": "talkieai-server/app/core/azure_voice.py",
    "chars": 12157,
    "preview": "import json\n\nimport azure.cognitiveservices.speech as speechsdk\n\nfrom app.config import Config\nfrom app.core.logging imp"
  },
  {
    "path": "talkieai-server/app/core/db_cache.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "talkieai-server/app/core/exceptions.py",
    "chars": 208,
    "preview": "# 用户资源访问受限exception\nclass UserAccessDeniedException(Exception):\n    pass\n\n\n# 用户密码不正确\nclass UserPasswordIncorrectExceptio"
  },
  {
    "path": "talkieai-server/app/core/language.py",
    "chars": 1462,
    "preview": "import json\nfrom app.core.logging import logging\n\nlanguage_data = []\n\nazure_data = {}\nwith open(\"data/azure.json\", \"r\") "
  },
  {
    "path": "talkieai-server/app/core/logging.py",
    "chars": 119,
    "preview": "import logging\n\nlogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')\n"
  },
  {
    "path": "talkieai-server/app/core/utils.py",
    "chars": 4938,
    "preview": "import hashlib\nimport os\nimport shutil\nimport string\nimport time\nfrom datetime import datetime, timedelta\nfrom uuid impo"
  },
  {
    "path": "talkieai-server/app/db/__init__.py",
    "chars": 1091,
    "preview": "from sqlalchemy import create_engine, event\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy.orm "
  },
  {
    "path": "talkieai-server/app/db/account_entities.py",
    "chars": 2337,
    "preview": "import datetime\n\nfrom sqlalchemy import Column, String, DateTime, Integer, Index, Text\nfrom app.db import Base, engine\n\n"
  },
  {
    "path": "talkieai-server/app/db/chat_entities.py",
    "chars": 3334,
    "preview": "import datetime\n\nfrom sqlalchemy import Column, String, DateTime, Integer, Index, Text\nfrom app.db import Base, engine\n\n"
  },
  {
    "path": "talkieai-server/app/db/sys_entities.py",
    "chars": 5030,
    "preview": "import datetime\n\nfrom sqlalchemy import Column, String, DateTime, Integer, Index, Text\nfrom app.db import Base, engine\n\n"
  },
  {
    "path": "talkieai-server/app/db/topic_entities.py",
    "chars": 7290,
    "preview": "import datetime\n\nfrom sqlalchemy import Column, String, DateTime, Integer, Index, Text\nfrom app.db import Base, engine\n\n"
  },
  {
    "path": "talkieai-server/app/main.py",
    "chars": 2142,
    "preview": "from fastapi import FastAPI\nfrom starlette.middleware.cors import CORSMiddleware\nfrom starlette.responses import JSONRes"
  },
  {
    "path": "talkieai-server/app/models/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "talkieai-server/app/models/account_models.py",
    "chars": 2124,
    "preview": "from enum import Enum\nfrom typing import List, Dict\n\nfrom pydantic import BaseModel, constr\n\n\nclass MessageType(Enum):\n "
  },
  {
    "path": "talkieai-server/app/models/chat_models.py",
    "chars": 1716,
    "preview": "from enum import Enum\nfrom typing import List, Dict\n\nfrom pydantic import BaseModel, constr\n\n\nclass MessageType(Enum):\n "
  },
  {
    "path": "talkieai-server/app/models/response.py",
    "chars": 234,
    "preview": "class ApiResponse:\n    def __init__(self, code: str = '200', status: str = 'SUCCESS', data=None, message: str = 'success"
  },
  {
    "path": "talkieai-server/app/models/sys_models.py",
    "chars": 253,
    "preview": "from enum import Enum\nfrom typing import List, Dict\n\nfrom pydantic import BaseModel, constr\n\nclass UpdateLanguageDTO(Bas"
  },
  {
    "path": "talkieai-server/app/models/topic_models.py",
    "chars": 229,
    "preview": "from enum import Enum\nfrom typing import List, Dict\n\nfrom pydantic import BaseModel, constr\n\n\nclass CreateSessionDTO(Bas"
  },
  {
    "path": "talkieai-server/app/services/__init__.py",
    "chars": 411,
    "preview": "from app.services.sys_service import SysService\nfrom app.services.account_service import AccountService\nfrom app.service"
  },
  {
    "path": "talkieai-server/app/services/account_service.py",
    "chars": 12395,
    "preview": "import json\nimport os\nimport re\nimport datetime\n\nfrom sqlalchemy.orm import Session\n\nfrom app.config import Config\nfrom "
  },
  {
    "path": "talkieai-server/app/services/chat_service.py",
    "chars": 25548,
    "preview": "from sqlalchemy.orm import Session\n\nfrom app.core.utils import *\nfrom app.db.account_entities import *\nfrom app.db.chat_"
  },
  {
    "path": "talkieai-server/app/services/sys_service.py",
    "chars": 6003,
    "preview": "import json\nimport os\nfrom pydub import AudioSegment\nfrom sqlalchemy.orm import Session\nfrom app.core.utils import *\nfro"
  },
  {
    "path": "talkieai-server/app/services/topic_service.py",
    "chars": 18626,
    "preview": "import json\n\nfrom sqlalchemy.orm import Session\nfrom app.core.utils import *\nfrom app.models.topic_models import *\nfrom "
  },
  {
    "path": "talkieai-server/data/azure.json",
    "chars": 121623,
    "preview": "[{\n\t\"gender\": 1,\n\t\"locale\": \"af-ZA\",\n\t\"local_name\": \"Adri\",\n\t\"name\": \"Microsoft Server Speech Text to Speech Voice (af-Z"
  },
  {
    "path": "talkieai-server/data/azure_style_label.json",
    "chars": 1533,
    "preview": "[\n  { \"value\": \"chat\", \"label\": \"聊天\" },\n  { \"value\": \"customerservice\", \"label\": \"服务\" },\n  { \"value\": \"narration-profess"
  },
  {
    "path": "talkieai-server/data/default_topic_data.json",
    "chars": 3096,
    "preview": "{\n  \"groups\": [\n    {\n      \"id\": \"group_1\",\n      \"name\": \"自我介绍\",\n      \"type\": \"ROLE_PLAY\",\n      \"description\": \"做一个基"
  },
  {
    "path": "talkieai-server/data/language_demo_map.json",
    "chars": 3361,
    "preview": "{\n  \"en\": \"Hello, welcome to Talkie. We hope you have a good learning experience.\",\n  \"ga\": \"Dia duit, fáilte chuig talk"
  },
  {
    "path": "talkieai-server/data/sys_language.json",
    "chars": 788,
    "preview": "[\n  { \"label\": \"英语(美国)\", \"value\": \"en-US\", \"default_voice_role_name\":\"en-US-JennyNeural\" },\n  { \"label\": \"英语(英国)\", \"valu"
  },
  {
    "path": "talkieai-server/requirements.txt",
    "chars": 274,
    "preview": "uvicorn==0.20.0\nazure_storage==0.37.0\nfastapi==0.109.0\nopenai==1.9.0\npydantic==1.10.5\npydub==0.25.1\nPyJWT==2.8.0\npython-"
  },
  {
    "path": "talkieai-server/start.sh",
    "chars": 56,
    "preview": "nohup uvicorn app.main:app --host 0.0.0.0 --port 8097 &\n"
  },
  {
    "path": "talkieai-uniapp/index.html",
    "chars": 1338,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n\t<meta charset=\"utf-8\">\n\t<meta http-equiv=\"X-UA-Compatible\""
  },
  {
    "path": "talkieai-uniapp/package.json",
    "chars": 3215,
    "preview": "{\n  \"name\": \"uni-preset-vue\",\n  \"version\": \"0.0.0\",\n  \"scripts\": {\n    \"dev:app\": \"uni -p app\",\n    \"dev:app-android\": \""
  },
  {
    "path": "talkieai-uniapp/src/App.vue",
    "chars": 336,
    "preview": "<script setup lang=\"ts\">\nimport { ref } from \"vue\";\nimport { onLaunch, onShow, onHide } from \"@dcloudio/uni-app\";\nconst "
  },
  {
    "path": "talkieai-uniapp/src/api/account.ts",
    "chars": 1139,
    "preview": "import request from \"@/axios/api\";\nexport default {\n  visitorLogin: (data: any) => {\n    return request(\"/account/visito"
  },
  {
    "path": "talkieai-uniapp/src/api/chat.ts",
    "chars": 2650,
    "preview": "import request from \"@/axios/api\";\nexport default {\n  sessionCreate: (data: any) => {\n    return request(\"/sessions\", \"P"
  },
  {
    "path": "talkieai-uniapp/src/api/sys.ts",
    "chars": 568,
    "preview": "import request from \"@/axios/api\";\nexport default {\n  feedbackAdd: (data: any) => {\n    return request(\"/sys/feedback\", "
  },
  {
    "path": "talkieai-uniapp/src/api/topic.ts",
    "chars": 999,
    "preview": "import request from \"@/axios/api\";\nexport default {\n  getTopicData: (params: any) => {\n    return request(\"/topics\", \"GE"
  },
  {
    "path": "talkieai-uniapp/src/axios/api.ts",
    "chars": 1331,
    "preview": "import __config from \"@/config/env\";\n\nconst request = (\n  url: string,\n  method?:\n    | \"OPTIONS\"\n    | \"GET\"\n    | \"HEA"
  },
  {
    "path": "talkieai-uniapp/src/axios/axiosServer.ts",
    "chars": 310,
    "preview": "// import axios from \"./axiosService\";\n// import qs from \"query-string\";\n\n// export const PostJson = (url: string, param"
  },
  {
    "path": "talkieai-uniapp/src/axios/axiosService.ts",
    "chars": 1762,
    "preview": "// import axios from \"axios\";\n// import Cookies from \"js-cookie\";\n// axios.interceptors.request.use(\n//   function (conf"
  },
  {
    "path": "talkieai-uniapp/src/components/AudioPlayer.vue",
    "chars": 3342,
    "preview": "<template>\n  <view @tap=\"handleSpeech\" class=\"speech-container\" :title=\"speechLoading\">\n    <view class=\"playing-ico\">\n "
  },
  {
    "path": "talkieai-uniapp/src/components/Checkbox.vue",
    "chars": 1388,
    "preview": "<template>\n  <view class=\"checkbox-box\" @tap=\"checkIt\">\n    <image :class=\"isCheck ? 'checkbox-box-ico' : 'checkbox-box-"
  },
  {
    "path": "talkieai-uniapp/src/components/Collect.vue",
    "chars": 1948,
    "preview": "<template>\n    <view class=\"collect-icon-box\">\n        <LoadingRound v-show=\"collectLoading\"></LoadingRound>\n        <im"
  },
  {
    "path": "talkieai-uniapp/src/components/CommonHeader.vue",
    "chars": 2385,
    "preview": "<template>\n  <view class=\"common-header\"\n    :style=\"{ height: CustomBar + 'px', backgroundColor: (backgroundColor ? bac"
  },
  {
    "path": "talkieai-uniapp/src/components/FunctionalText.vue",
    "chars": 4759,
    "preview": "<template>\n  <view class=\"functional-text-container\" @tap=\"handleSpaceClick\">\n    <view class=\"text-box\">\n      <!-- 点击后"
  },
  {
    "path": "talkieai-uniapp/src/components/GithubLink.vue",
    "chars": 697,
    "preview": "<template>\n    <img class=\"github-link\" @tap=\"redirectToGithub\" target=\"_blank\" style=\"height: 32rpx; width: 32rpx;\"\n   "
  },
  {
    "path": "talkieai-uniapp/src/components/Loading.vue",
    "chars": 1065,
    "preview": "<template>\n  <view class=\"loading-box\">\n    <view class=\"loading-box-content\">\n      <view class=\"loading-box-item loadi"
  },
  {
    "path": "talkieai-uniapp/src/components/LoadingRound.vue",
    "chars": 897,
    "preview": "<template>\n  <view class=\"loading-round\" :style=\"containerStyle\">\n    <image class=\"loading-round-img\" src=\"/static/load"
  },
  {
    "path": "talkieai-uniapp/src/components/Rare2.vue",
    "chars": 8903,
    "preview": "<template>\n  <view class=\"progress_box\" :style=\"{ 'background-color': pageBg }\">\n    <!-- #ifdef MP-ALIPAY -->\n    <canv"
  },
  {
    "path": "talkieai-uniapp/src/components/Rate.vue",
    "chars": 769,
    "preview": "<template>\n  <view class=\"rate-container\">\n    <view v-if=\"rate && rate > 0\">\n      <SeekBar :width=\"140\" :processVal=\"r"
  },
  {
    "path": "talkieai-uniapp/src/components/Speech.vue",
    "chars": 7152,
    "preview": "<template>\n  <view class=\"speech-container\">\n    <!-- 未开始录音 -->\n    <view v-if=\"!recorder.start && !recorder.completed\" "
  },
  {
    "path": "talkieai-uniapp/src/components/WordAnalysisPopup.vue",
    "chars": 3379,
    "preview": "<template>\n    <uni-popup ref=\"wordAnalysisPopup\" type=\"bottom\" :background-color=\"popupBackgoundColor\">\n        <view c"
  },
  {
    "path": "talkieai-uniapp/src/components/audioPlayerExecuter.ts",
    "chars": 2007,
    "preview": "import __config from \"@/config/env\";\nimport { ref } from \"vue\";\n\ninterface Listener {\n  playing?: () => void;\n  success?"
  },
  {
    "path": "talkieai-uniapp/src/components/speechExecuter.ts",
    "chars": 7528,
    "preview": "import __config from \"@/config/env\";\n\n// #ifdef H5\nimport Recorder from \"recorder-core\";\nimport \"recorder-core/src/engin"
  },
  {
    "path": "talkieai-uniapp/src/config/env.ts",
    "chars": 164,
    "preview": "export default {\n  // basePath: \"http://192.168.0.102:8098/api/v1\"\n  basePath: \"http://localhost:8097/api/v1\"\n  // baseP"
  },
  {
    "path": "talkieai-uniapp/src/env.d.ts",
    "chars": 276,
    "preview": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n  import { DefineComponent } from 'vue'\n  // eslint-disa"
  },
  {
    "path": "talkieai-uniapp/src/global/globalCount.hooks.ts",
    "chars": 507,
    "preview": "import { ref, onMounted } from \"vue\";\nimport chatRequest from \"@/api/chat\";\n// 全局状态,创建在模块作用域下\nconst globalUserInfo = ref"
  },
  {
    "path": "talkieai-uniapp/src/less/global.less",
    "chars": 1329,
    "preview": "// 使用 * 的话 微信小程序会报错\n// [ WXSS 文件编译错误] ./pages/contact/index.wxss unexpected token *\n\nview {\n\tmargin: 0;\n\tpadding: 0;\n\t\n}"
  },
  {
    "path": "talkieai-uniapp/src/main.ts",
    "chars": 1132,
    "preview": "import { createSSRApp } from \"vue\";\nimport App from \"./App.vue\";\nimport EventBus from \"@/utils/bus\";\n\nconst getHeight = "
  },
  {
    "path": "talkieai-uniapp/src/manifest.json",
    "chars": 4862,
    "preview": "{\n    \"name\" : \"talkie\",\n    \"appid\" : \"__UNI__1EE8DB0\",\n    \"description\" : \"\",\n    \"versionName\" : \"1.0.0\",\n    \"versi"
  },
  {
    "path": "talkieai-uniapp/src/models/chat.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "talkieai-uniapp/src/models/models.ts",
    "chars": 1483,
    "preview": "export interface AccountInfo {\n  account_id: string;\n  today_chat_count: number;\n  total_chat_count: number;\n  target_la"
  },
  {
    "path": "talkieai-uniapp/src/models/sys.ts",
    "chars": 321,
    "preview": "export interface Language {\n    id?: string | null;\n    language?: string | null;\n    label: boolean;\n    description?: "
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/CommonAudioPlayer.vue",
    "chars": 1471,
    "preview": "<template></template>\n<script setup lang=\"ts\">\nimport { ref } from \"vue\";\n\nconst innerAudioContext = ref(null);\ninterfac"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/MessageContent.vue",
    "chars": 7195,
    "preview": "<template>\n  <view class=\"message-container\" :class=\"containerClass\">\n    <!-- loading -->\n    <view v-if=\"!message.cont"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/MessageGrammar.vue",
    "chars": 4755,
    "preview": "<template>\n  <!-- 语法评估 -->\n  <view class=\"grammar-box\">\n    <loading-round v-if=\"grammarAnalysisLoading\" />\n    <view v-"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/MessageGrammarPopup.vue",
    "chars": 5832,
    "preview": "<template>\n    <uni-popup @change=\"handlePopupChange\" ref=\"grammarPopup\" type=\"bottom\" :background-color=\"popupBackgound"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/MessagePronunciation.vue",
    "chars": 4206,
    "preview": "<template>\n  <!-- 发音评估 -->\n  <view>\n    <view class=\"pronunciation-content\">\n      <loading-round v-if=\"pronunciationLoa"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/MessageSpeech.vue",
    "chars": 720,
    "preview": "<template>\n  <Speech @success=\"handleSuccess\" ref=\"speechRef\">\n      <template v-slot:leftMenu>\n          <slot name=\"le"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/PhonemeBox.vue",
    "chars": 1026,
    "preview": "<template>\n<view class=\"phoneme-box\">\n        <view class=\"phoneme-text\" v-for=\"(phoneme, index) in word.phonemes\" :key="
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/Prompt.vue",
    "chars": 1716,
    "preview": "<template>\n    <view class=\"menu-container\">\n        <view @tap=\"handlePrompt\" class=\"menu-item\">\n            <image cla"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/PromptPopup.vue",
    "chars": 5406,
    "preview": "<template>\n  <uni-popup class=\"popup-container\" ref=\"promotPopup\" type=\"bottom\" :background-color=\"popupBackgoundColor\">"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/TextPronunciation.vue",
    "chars": 1048,
    "preview": "<template>\n    <view class=\"word-box\">\n        <view class=\"word-text\" v-for=\"word in pronunciation.words\" :key=\"word.wo"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/TranslationPopup.vue",
    "chars": 5836,
    "preview": "<template>\n  <uni-popup ref=\"translationPopup\" type=\"bottom\" :background-color=\"popupBackgoundColor\">\n    <view class=\"t"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/components/WordDetail.vue",
    "chars": 4801,
    "preview": "<template>\n    <view class=\"word-detail-container\">\n        <view>\n            <!-- 发单 -->\n            <LoadingRound v-i"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/index.vue",
    "chars": 11629,
    "preview": "<template>\n  <view class=\"chat-box\">\n    <CommonHeader background-color=\"#fff\" :leftIcon='true' :back-fn=\"handleBackPage"
  },
  {
    "path": "talkieai-uniapp/src/pages/chat/settings.vue",
    "chars": 7519,
    "preview": "<template>\n    <view>\n        <CommonHeader :leftIcon=\"true\" :back-fn=\"handleBackPage\" title=\"Talkie\">\n            <temp"
  },
  {
    "path": "talkieai-uniapp/src/pages/contact/index.vue",
    "chars": 764,
    "preview": "<template>\n\t<view>\n\t\t<CommonHeader :leftIcon='true' :back-fn=\"handleBackPage\" class=\"header\" title=\"Talkie\">\n\t\t\t<templat"
  },
  {
    "path": "talkieai-uniapp/src/pages/contact/less/index.less",
    "chars": 280,
    "preview": "@import url('../../../less/global.less');\n.contact{\n\tdisplay: flex;\n\tpadding-top: 100rpx;\n\tjustify-content: center;\n\tfle"
  },
  {
    "path": "talkieai-uniapp/src/pages/feedback/index.vue",
    "chars": 2080,
    "preview": "<template>\n\t<view>\n\t\t<CommonHeader :leftIcon=\"true\" :back-fn=\"handleBackPage\" class=\"header\" title=\"Talkie\">\n\t\t\t<templat"
  },
  {
    "path": "talkieai-uniapp/src/pages/feedback/less/index.less",
    "chars": 1604,
    "preview": "@import url(\"../../../less/global.less\");\n.feedback {\n  display: flex;\n  justify-content: center;\n  flex-direction: colu"
  },
  {
    "path": "talkieai-uniapp/src/pages/index/components/Topics.vue",
    "chars": 7781,
    "preview": "<template>\n    <view class=\"topic-container\">\n        <view class=\"home-data\">\n            <view class=\"tab-box\">\n      "
  },
  {
    "path": "talkieai-uniapp/src/pages/index/index.vue",
    "chars": 4339,
    "preview": "<template>\n  <view>\n    <CommonHeader backgroundColor=\"#fff\">\n      <template v-slot:content>\n        <text>Talkie</text"
  },
  {
    "path": "talkieai-uniapp/src/pages/index/switchRole.vue",
    "chars": 8573,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :left-icon=\"true\" style=\"background-color: none; color: #f"
  },
  {
    "path": "talkieai-uniapp/src/pages/login/index.vue",
    "chars": 2888,
    "preview": "<template>\n  <view class=\"container\">\n    <image class=\"logo\" src=\"/static/logo.png\"></image>\n    <text class=\"title\">\n "
  },
  {
    "path": "talkieai-uniapp/src/pages/login/service.ts",
    "chars": 244,
    "preview": "import request from \"@/axios/api\";\nexport default {\n  wechatLogin: (data: any) => {\n\t\treturn request('/wechat/code-login"
  },
  {
    "path": "talkieai-uniapp/src/pages/my/index.vue",
    "chars": 7982,
    "preview": "<template>\n\t<view class=\"my-container\">\n\t\t<CommonHeader class=\"header\" title=\"Talkie\">\n\t\t\t<template v-slot:content>\n\t\t\t\t"
  },
  {
    "path": "talkieai-uniapp/src/pages/my/learnLanguage.vue",
    "chars": 1977,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :leftIcon=\"redirectType !== 'init'\" background-color=\"#F5F"
  },
  {
    "path": "talkieai-uniapp/src/pages/my/less/index.less",
    "chars": 2058,
    "preview": "\n.my-container {\n\tbackground: linear-gradient(180deg, #F5F5FE 0%, #FFFFFF 100%);\n}\n.header {\n}\n.mine-content {\n\tmin-heig"
  },
  {
    "path": "talkieai-uniapp/src/pages/practice/components/Single.vue",
    "chars": 2613,
    "preview": "<template>\n  <view class=\"statement-container\">\n    <view class=\"chat-list-box\">\n      <view class=\"chat-list-left-box\">"
  },
  {
    "path": "talkieai-uniapp/src/pages/practice/components/Statement.vue",
    "chars": 2683,
    "preview": "<template>\n  <view class=\"statement-container\">\n    <view class=\"chat-list-box\">\n      <view class=\"chat-list-left-box\">"
  },
  {
    "path": "talkieai-uniapp/src/pages/practice/index.vue",
    "chars": 4954,
    "preview": "<template>\n  <view>\n    <CommonHeader title=\"Talkie\">\n      <template v-slot:content>\n        <text>练习</text>\n      </te"
  },
  {
    "path": "talkieai-uniapp/src/pages/topic/completion.vue",
    "chars": 7109,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :leftIcon=\"true\" :backFn=\"handleBackFn\" backgroundColor=\"#"
  },
  {
    "path": "talkieai-uniapp/src/pages/topic/history.vue",
    "chars": 6703,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :leftIcon=\"true\" :back-fn=\"handleBackPage\" backgroundColor"
  },
  {
    "path": "talkieai-uniapp/src/pages/topic/index.vue",
    "chars": 5689,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :leftIcon=\"true\" :back-fn=\"handleBackPage\" backgroundColor"
  },
  {
    "path": "talkieai-uniapp/src/pages/topic/phrase.vue",
    "chars": 3745,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :leftIcon=\"true\" :back-fn=\"handleBackPage\" backgroundColor"
  },
  {
    "path": "talkieai-uniapp/src/pages/topic/topicCreate.vue",
    "chars": 4975,
    "preview": "<template>\n    <view class=\"container\">\n        <CommonHeader :leftIcon=\"true\" backgroundColor=\"#F5F5FE\">\n            <t"
  },
  {
    "path": "talkieai-uniapp/src/pages.json",
    "chars": 2499,
    "preview": "{\n  \"pages\": [\n    {\n      \"path\": \"pages/login/index\",\n      \"style\": {\n        \"navigationStyle\": \"custom\"\n      }\n   "
  },
  {
    "path": "talkieai-uniapp/src/shime-uni.d.ts",
    "chars": 139,
    "preview": "export {}\n\ndeclare module \"vue\" {\n  type Hooks = App.AppInstance & Page.PageInstance;\n  interface ComponentCustomOptions"
  },
  {
    "path": "talkieai-uniapp/src/uni.scss",
    "chars": 1628,
    "preview": "/**\n * 这里是uni-app内置的常用样式变量\n *\n * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量\n * 如果你是插件开发者,建议你使用scss预"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-badge/changelog.md",
    "chars": 1282,
    "preview": "## 1.2.2(2023-01-28)\n- 修复 运行/打包 控制台警告问题\n## 1.2.1(2022-09-05)\n- 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css "
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-badge/components/uni-badge/uni-badge.vue",
    "chars": 5133,
    "preview": "<template>\n\t<view class=\"uni-badge--x\">\n\t\t<slot />\n\t\t<text v-if=\"text\" :class=\"classNames\" :style=\"[positionStyle, custo"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-badge/package.json",
    "chars": 1620,
    "preview": "{\n  \"id\": \"uni-badge\",\n  \"displayName\": \"uni-badge 数字角标\",\n  \"version\": \"1.2.2\",\n  \"description\": \"数字角标(徽章)组件,在元素周围展示消息提醒"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-badge/readme.md",
    "chars": 224,
    "preview": "## Badge 数字角标\n> **组件名:uni-badge**\n> 代码块: `uBadge`\n\n数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景,\n\n### [查看文档](https://unia"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/changelog.md",
    "chars": 716,
    "preview": "## 1.4.10(2023-04-10)\n- 修复 某些情况 monthSwitch 未触发的Bug\n## 1.4.9(2023-02-02)\n- 修复 某些情况切换月份错误的Bug\n## 1.4.8(2023-01-30)\n- 修复 某"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/calendar.js",
    "chars": 22987,
    "preview": "/**\n* @1900-2100区间内的公历、农历互转\n* @charset UTF-8\n* @github  https://github.com/jjonline/calendar.js\n* @Author  Jea杨(JJonline"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/en.json",
    "chars": 291,
    "preview": "{\n\t\"uni-calender.ok\": \"ok\",\n\t\"uni-calender.cancel\": \"cancel\",\n\t\"uni-calender.today\": \"today\",\n\t\"uni-calender.MON\": \"MON\""
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/index.js",
    "chars": 162,
    "preview": "import en from './en.json'\nimport zhHans from './zh-Hans.json'\nimport zhHant from './zh-Hant.json'\nexport default {\n\ten,"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hans.json",
    "chars": 270,
    "preview": "{\n\t\"uni-calender.ok\": \"确定\",\n\t\"uni-calender.cancel\": \"取消\",\n\t\"uni-calender.today\": \"今日\",\n\t\"uni-calender.SUN\": \"日\",\n\t\"uni-c"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/i18n/zh-Hant.json",
    "chars": 270,
    "preview": "{\n\t\"uni-calender.ok\": \"確定\",\n\t\"uni-calender.cancel\": \"取消\",\n\t\"uni-calender.today\": \"今日\",\n\t\"uni-calender.SUN\": \"日\",\n\t\"uni-c"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/uni-calendar-item.vue",
    "chars": 5441,
    "preview": "<template>\n\t<view class=\"uni-calendar-item__weeks-box\" :class=\"{\n\t\t'uni-calendar-item--disable':weeks.disable,\n\t\t'uni-ca"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/uni-calendar.vue",
    "chars": 12696,
    "preview": "<template>\n\t<view class=\"uni-calendar\">\n\t\t<view v-if=\"!insert&&show\" class=\"uni-calendar__mask\" :class=\"{'uni-calendar--"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/components/uni-calendar/util.js",
    "chars": 8285,
    "preview": "import CALENDAR from './calendar.js'\n\nclass Calendar {\n\tconstructor({\n\t\tdate,\n\t\tselected,\n\t\tstartDate,\n\t\tendDate,\n\t\trang"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/package.json",
    "chars": 1576,
    "preview": "{\n  \"id\": \"uni-calendar\",\n  \"displayName\": \"uni-calendar 日历\",\n  \"version\": \"1.4.10\",\n  \"description\": \"日历组件\",\n  \"keyword"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-calendar/readme.md",
    "chars": 2217,
    "preview": "\n\n## Calendar 日历\n> **组件名:uni-calendar**\n> 代码块: `uCalendar`\n\n\n日历组件\n\n> **注意事项**\n> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-card/changelog.md",
    "chars": 848,
    "preview": "## 1.3.1(2021-12-20)\n- 修复 在vue页面下略缩图显示不正常的bug\n## 1.3.0(2021-11-19)\n- 重构插槽的用法 ,header 替换为 title \n- 新增 actions 插槽\n- 新增 cov"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-card/components/uni-card/uni-card.vue",
    "chars": 6124,
    "preview": "<template>\n\t<view class=\"uni-card\" :class=\"{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':b"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-card/package.json",
    "chars": 1630,
    "preview": "{\n  \"id\": \"uni-card\",\n  \"displayName\": \"uni-card 卡片\",\n  \"version\": \"1.3.1\",\n  \"description\": \"Card 组件,提供常见的卡片样式。\",\n  \"ke"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-card/readme.md",
    "chars": 183,
    "preview": "\n\n## Card 卡片\n> **组件名:uni-card**\n> 代码块: `uCard`\n\n卡片视图组件。\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)\n#"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-collapse/changelog.md",
    "chars": 1121,
    "preview": "## 1.4.3(2022-01-25)\n- 修复 初始化的时候 ,open 属性失效的bug\n## 1.4.2(2022-01-21)\n- 修复 微信小程序resize后组件收起的bug\n## 1.4.1(2021-11-22)\n- 修复"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue",
    "chars": 3122,
    "preview": "<template>\n\t<view class=\"uni-collapse\">\n\t\t<slot />\n\t</view>\n</template>\n<script>\n\t/**\n\t * Collapse 折叠面板\n\t * @description"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-collapse/components/uni-collapse-item/uni-collapse-item.vue",
    "chars": 8644,
    "preview": "<template>\n\t<view class=\"uni-collapse-item\">\n\t\t<!-- onClick(!isOpen) -->\n\t\t<view @click=\"onClick(!isOpen)\" class=\"uni-co"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-collapse/package.json",
    "chars": 1644,
    "preview": "{\n  \"id\": \"uni-collapse\",\n  \"displayName\": \"uni-collapse 折叠面板\",\n  \"version\": \"1.4.3\",\n  \"description\": \"Collapse 组件,可以折叠"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-collapse/readme.md",
    "chars": 294,
    "preview": "\n\n## Collapse 折叠面板\n> **组件名:uni-collapse**\n> 代码块: `uCollapse`\n> 关联组件:`uni-collapse-item`、`uni-icons`。\n\n\n折叠面板用来折叠/显示过长的内容或"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-combox/changelog.md",
    "chars": 598,
    "preview": "## 1.0.1(2021-11-23)\n- 优化 label、label-width 属性\n## 1.0.0(2021-11-19)\n- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/compo"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-combox/components/uni-combox/uni-combox.vue",
    "chars": 5646,
    "preview": "<template>\n\t<view class=\"uni-combox\" :class=\"border ? '' : 'uni-combox__no-border'\">\n\t\t<view v-if=\"label\" class=\"uni-com"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-combox/package.json",
    "chars": 1639,
    "preview": "{\n  \"id\": \"uni-combox\",\n  \"displayName\": \"uni-combox 组合框\",\n  \"version\": \"1.0.1\",\n  \"description\": \"可以选择也可以输入的表单项 \",\n  \"k"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-combox/readme.md",
    "chars": 189,
    "preview": "\n\n## Combox 组合框\n> **组件名:uni-combox**\n> 代码块: `uCombox`\n\n\n组合框组件。\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/changelog.md",
    "chars": 779,
    "preview": "## 1.2.2(2022-01-19)\n- 修复 在微信小程序中样式不生效的bug\n## 1.2.1(2022-01-18)\n- 新增 update 方法 ,在动态更新时间后,刷新组件\n## 1.2.0(2021-11-19)\n- 优化 "
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/en.json",
    "chars": 107,
    "preview": "{\n\t\"uni-countdown.day\": \"day\",\n\t\"uni-countdown.h\": \"h\",\n\t\"uni-countdown.m\": \"m\",\n\t\"uni-countdown.s\": \"s\"\n}\n"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/index.js",
    "chars": 162,
    "preview": "import en from './en.json'\nimport zhHans from './zh-Hans.json'\nimport zhHant from './zh-Hant.json'\nexport default {\n\ten,"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hans.json",
    "chars": 105,
    "preview": "{\n\t\"uni-countdown.day\": \"天\",\n\t\"uni-countdown.h\": \"时\",\n\t\"uni-countdown.m\": \"分\",\n\t\"uni-countdown.s\": \"秒\"\n}\n"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/i18n/zh-Hant.json",
    "chars": 105,
    "preview": "{\n\t\"uni-countdown.day\": \"天\",\n\t\"uni-countdown.h\": \"時\",\n\t\"uni-countdown.m\": \"分\",\n\t\"uni-countdown.s\": \"秒\"\n}\n"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/components/uni-countdown/uni-countdown.vue",
    "chars": 6048,
    "preview": "<template>\n\t<view class=\"uni-countdown\">\n\t\t<text v-if=\"showDay\" :style=\"[timeStyle]\" class=\"uni-countdown__number\">{{ d "
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/package.json",
    "chars": 1612,
    "preview": "{\n  \"id\": \"uni-countdown\",\n  \"displayName\": \"uni-countdown 倒计时\",\n  \"version\": \"1.2.2\",\n  \"description\": \"CountDown 倒计时组件"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-countdown/readme.md",
    "chars": 200,
    "preview": "\n\n## CountDown 倒计时\n> **组件名:uni-countdown**\n> 代码块: `uCountDown`\n\n倒计时组件。\n\n### [查看文档](https://uniapp.dcloud.io/component/un"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-checkbox/changelog.md",
    "chars": 1324,
    "preview": "## 1.0.3(2022-09-16)\n- 可以使用 uni-scss 控制主题色\n## 1.0.2(2022-06-30)\n- 优化 在 uni-forms 中的依赖注入方式\n## 1.0.1(2022-02-07)\n- 修复 mult"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-checkbox/components/uni-data-checkbox/uni-data-checkbox.vue",
    "chars": 19600,
    "preview": "<template>\n\t<view class=\"uni-data-checklist\" :style=\"{'margin-top':isTop+'px'}\">\n\t\t<template v-if=\"!isLocal\">\n\t\t\t<view c"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-checkbox/package.json",
    "chars": 1626,
    "preview": "{\n  \"id\": \"uni-data-checkbox\",\n  \"displayName\": \"uni-data-checkbox 数据选择器\",\n  \"version\": \"1.0.3\",\n  \"description\": \"通过数据驱"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-checkbox/readme.md",
    "chars": 674,
    "preview": "\n\n## DataCheckbox 数据驱动的单选复选框\n> **组件名:uni-data-checkbox**\n> 代码块: `uDataCheckbox`\n\n\n本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/changelog.md",
    "chars": 2184,
    "preview": "## 1.1.2(2023-04-11)\n- 修复 更改 modelValue 报错的 bug\n- 修复 v-for 未使用 key 值控制台 warning\n## 1.1.1(2023-02-21)\n- 修复代码合并时引发 value 属"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-picker/keypress.js",
    "chars": 1088,
    "preview": "// #ifdef H5\nexport default {\n  name: 'Keypress',\n  props: {\n    disable: {\n      type: Boolean,\n      default: false\n  "
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue",
    "chars": 13379,
    "preview": "<template>\n  <view class=\"uni-data-tree\">\n    <view class=\"uni-data-tree-input\" @click=\"handleInput\">\n      <slot :optio"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js",
    "chars": 14120,
    "preview": "export default {\n  props: {\n    localdata: {\n      type: [Array, Object],\n      default () {\n        return []\n      }\n "
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue",
    "chars": 7469,
    "preview": "<template>\n  <view class=\"uni-data-pickerview\">\n    <scroll-view v-if=\"!isCloudDataList\" class=\"selected-area\" scroll-x="
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/package.json",
    "chars": 1703,
    "preview": "{\n  \"id\": \"uni-data-picker\",\n  \"displayName\": \"uni-data-picker 数据驱动的picker选择器\",\n  \"version\": \"1.1.2\",\n  \"description\": \""
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-picker/readme.md",
    "chars": 845,
    "preview": "## DataPicker 级联选择\n> **组件名:uni-data-picker**\n> 代码块: `uDataPicker`\n> 关联组件:`uni-data-pickerview`、`uni-load-more`。\n\n\n`<uni-"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-select/changelog.md",
    "chars": 787,
    "preview": "## 1.0.6(2023-04-12)\n- 修复 微信小程序点击时会改变背景颜色的 bug\n## 1.0.5(2023-02-03)\n- 修复 禁用时会显示清空按钮\n## 1.0.4(2023-02-02)\n- 优化 查询条件短期内多次变"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-select/components/uni-data-select/uni-data-select.vue",
    "chars": 11011,
    "preview": "<template>\n\t<view class=\"uni-stat__select\">\n\t\t<span v-if=\"label\" class=\"uni-label-text hide-on-phone\">{{label + ':'}}</s"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-select/package.json",
    "chars": 1639,
    "preview": "{\n  \"id\": \"uni-data-select\",\n  \"displayName\": \"uni-data-select 下拉框选择器\",\n  \"version\": \"1.0.6\",\n  \"description\": \"通过数据驱动的下"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-data-select/readme.md",
    "chars": 222,
    "preview": "## DataSelect 下拉框选择器\n> **组件名:uni-data-select**\n> 代码块: `uDataSelect`\n\n当选项过多时,使用下拉菜单展示并选择内容\n\n### [查看文档](https://uniapp.dcl"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-dateformat/changelog.md",
    "chars": 415,
    "preview": "## 1.0.0(2021-11-19)\n- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/c"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-dateformat/components/uni-dateformat/date-format.js",
    "chars": 3773,
    "preview": "// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型\nfunction pad(str, length = 2) {\n\tstr += ''\n\twhile (str.length < length) {\n\t\tstr = '0'"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-dateformat/components/uni-dateformat/uni-dateformat.vue",
    "chars": 1546,
    "preview": "<template>\n\t<text>{{dateShow}}</text>\n</template>\n\n<script>\n\timport {friendlyDate} from './date-format.js'\n\t/**\n\t * Date"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-dateformat/package.json",
    "chars": 1647,
    "preview": "{\n  \"id\": \"uni-dateformat\",\n  \"displayName\": \"uni-dateformat 日期格式化\",\n  \"version\": \"1.0.0\",\n  \"description\": \"日期格式化组件,可以将"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-dateformat/readme.md",
    "chars": 210,
    "preview": "\n\n### DateFormat 日期格式化\n> **组件名:uni-dateformat**\n> 代码块: `uDateformat`\n\n\n日期格式化组件。\n\n### [查看文档](https://uniapp.dcloud.io/com"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/changelog.md",
    "chars": 4330,
    "preview": "## 2.2.24(2023-06-02)\n- 修复 部分情况修改时间,开始、结束时间显示异常的Bug [详情](https://ask.dcloud.net.cn/question/171146)\n- 优化 当前月可以选择上月、下月的日期"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue",
    "chars": 4112,
    "preview": "<template>\n\t<view class=\"uni-calendar-item__weeks-box\" :class=\"{\n\t\t'uni-calendar-item--disable':weeks.disable,\n\t\t'uni-ca"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue",
    "chars": 21461,
    "preview": "<template>\n\t<view class=\"uni-calendar\" @mouseleave=\"leaveCale\">\n\n\t\t<view v-if=\"!insert && show\" class=\"uni-calendar__mas"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json",
    "chars": 763,
    "preview": "{\n\t\"uni-datetime-picker.selectDate\": \"select date\",\n\t\"uni-datetime-picker.selectTime\": \"select time\",\n\t\"uni-datetime-pic"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js",
    "chars": 162,
    "preview": "import en from './en.json'\nimport zhHans from './zh-Hans.json'\nimport zhHant from './zh-Hant.json'\nexport default {\n\ten,"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json",
    "chars": 689,
    "preview": "{\n\t\"uni-datetime-picker.selectDate\": \"选择日期\",\n\t\"uni-datetime-picker.selectTime\": \"选择时间\",\n\t\"uni-datetime-picker.selectDate"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json",
    "chars": 709,
    "preview": "{\n  \"uni-datetime-picker.selectDate\": \"選擇日期\",\n  \"uni-datetime-picker.selectTime\": \"選擇時間\",\n  \"uni-datetime-picker.selectD"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue",
    "chars": 21746,
    "preview": "<template>\n\t<view class=\"uni-datetime-picker\">\n\t\t<view @click=\"initTimePicker\">\n\t\t\t<slot>\n\t\t\t\t<view class=\"uni-datetime-"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue",
    "chars": 28701,
    "preview": "<template>\n\t<view class=\"uni-date\">\n\t\t<view class=\"uni-date-editor\" @click=\"show\">\n\t\t\t<slot>\n\t\t\t\t<view\n          class=\""
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js",
    "chars": 11711,
    "preview": "class Calendar {\n\tconstructor({\n\t\tselected,\n\t\tstartDate,\n\t\tendDate,\n\t\trange,\n\t} = {}) {\n\t\t// 当前日期\n\t\tthis.date = this.get"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/package.json",
    "chars": 1676,
    "preview": "{\n  \"id\": \"uni-datetime-picker\",\n  \"displayName\": \"uni-datetime-picker 日期选择器\",\n  \"version\": \"2.2.24\",\n  \"description\": \""
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-datetime-picker/readme.md",
    "chars": 631,
    "preview": "\n\n> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-seco"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-drawer/changelog.md",
    "chars": 534,
    "preview": "## 1.2.1(2021-11-22)\n- 修复 vue3中个别scss变量无法找到的问题\n## 1.2.0(2021-11-19)\n- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/compo"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-drawer/components/uni-drawer/keypress.js",
    "chars": 1097,
    "preview": "// #ifdef H5\nexport default {\n  name: 'Keypress',\n  props: {\n    disable: {\n      type: Boolean,\n      default: false\n  "
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-drawer/components/uni-drawer/uni-drawer.vue",
    "chars": 3726,
    "preview": "<template>\n\t<view v-if=\"visibleSync\" :class=\"{ 'uni-drawer--visible': showDrawer }\" class=\"uni-drawer\" @touchmove.stop.p"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-drawer/package.json",
    "chars": 1618,
    "preview": "{\n  \"id\": \"uni-drawer\",\n  \"displayName\": \"uni-drawer 抽屉\",\n  \"version\": \"1.2.1\",\n  \"description\": \"抽屉式导航,用于展示侧滑菜单,侧滑导航。\","
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-drawer/readme.md",
    "chars": 188,
    "preview": "\n\n## Drawer 抽屉\n> **组件名:uni-drawer**\n> 代码块: `uDrawer`\n\n抽屉侧滑菜单。\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-d"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-easyinput/changelog.md",
    "chars": 1721,
    "preview": "## 1.1.9(2023-04-11)\n- 修复 vue3 下 keyboardheightchange 事件报错的bug\n## 1.1.8(2023-03-29)\n- 优化 trim 属性默认值\n## 1.1.7(2023-03-29)"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-easyinput/components/uni-easyinput/common.js",
    "chars": 1142,
    "preview": "/**\n * @desc 函数防抖\n * @param func 目标函数\n * @param wait 延迟执行毫秒数\n * @param immediate true - 立即执行, false - 延迟执行\n */\nexport co"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue",
    "chars": 14173,
    "preview": "<template>\n\t<view class=\"uni-easyinput\" :class=\"{ 'uni-easyinput-error': msg }\" :style=\"boxStyle\">\n\t\t<view class=\"uni-ea"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-easyinput/package.json",
    "chars": 1645,
    "preview": "{\n  \"id\": \"uni-easyinput\",\n  \"displayName\": \"uni-easyinput 增强输入框\",\n  \"version\": \"1.1.9\",\n  \"description\": \"Easyinput 组件是"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-easyinput/readme.md",
    "chars": 329,
    "preview": "\n\n### Easyinput 增强输入框\n> **组件名:uni-easyinput**\n> 代码块: `uEasyinput`\n\n\neasyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](ht"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-fab/changelog.md",
    "chars": 964,
    "preview": "## 1.2.5(2023-03-29)\n- 新增 pattern.icon 属性,可自定义图标\n## 1.2.4(2022-09-07)\n小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-fab/components/uni-fab/uni-fab.vue",
    "chars": 10854,
    "preview": "<template>\n\t<view class=\"uni-cursor-point\">\n\t\t<view v-if=\"popMenu && (leftBottom||rightBottom||leftTop||rightTop) && con"
  },
  {
    "path": "talkieai-uniapp/src/uni_modules/uni-fab/package.json",
    "chars": 1610,
    "preview": "{\n  \"id\": \"uni-fab\",\n  \"displayName\": \"uni-fab 悬浮按钮\",\n  \"version\": \"1.2.5\",\n  \"description\": \"悬浮按钮 fab button ,点击可展开一个图标"
  }
]

// ... and 213 more files (download for full content)

About this extraction

This page contains the full source code of the maioria/chatgpt-talkieai GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 413 files (1.3 MB), approximately 465.9k tokens, and a symbol index with 561 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!