Showing preview only (2,354K chars total). Download the full file or copy to clipboard to get everything.
Repository: swuecho/chat
Branch: master
Commit: cf9af2ce0a19
Files: 547
Total size: 23.7 MB
Directory structure:
gitextract_n1v0oway/
├── .dockerignore
├── .github/
│ └── workflows/
│ ├── docker-image.yml
│ ├── fly.yml
│ ├── mobile-build.yml
│ └── publish.yml
├── .gitignore
├── AGENTS.md
├── CLAUDE.md
├── Dockerfile
├── README.md
├── api/
│ ├── .air.toml
│ ├── .github/
│ │ └── workflows/
│ │ └── go.yml
│ ├── .gitignore
│ ├── .vscode/
│ │ └── settings.json
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── admin_handler.go
│ ├── ai/
│ │ └── model.go
│ ├── artifact_instruction.txt
│ ├── auth/
│ │ ├── auth.go
│ │ ├── auth_test.go
│ │ ├── token.go
│ │ └── token_test.go
│ ├── bot_answer_history_handler.go
│ ├── bot_answer_history_service.go
│ ├── chat_artifact.go
│ ├── chat_auth_user_handler.go
│ ├── chat_auth_user_service.go
│ ├── chat_comment_handler.go
│ ├── chat_comment_service.go
│ ├── chat_main_handler.go
│ ├── chat_main_service.go
│ ├── chat_message_handler.go
│ ├── chat_message_service.go
│ ├── chat_message_service_test.go
│ ├── chat_model_handler.go
│ ├── chat_model_handler_test.go
│ ├── chat_model_privilege_handler.go
│ ├── chat_prompt_hander.go
│ ├── chat_prompt_service.go
│ ├── chat_prompt_service_test.go
│ ├── chat_session_handler.go
│ ├── chat_session_service.go
│ ├── chat_session_service_test.go
│ ├── chat_snapshot_handler.go
│ ├── chat_snapshot_handler_test.go
│ ├── chat_snapshot_service.go
│ ├── chat_user_active_chat_session_handler.go
│ ├── chat_user_active_chat_session_sevice.go
│ ├── chat_workspace_handler.go
│ ├── chat_workspace_service.go
│ ├── constants.go
│ ├── embed_debug_test.go
│ ├── errors.go
│ ├── file_upload_handler.go
│ ├── file_upload_service.go
│ ├── go.mod
│ ├── go.sum
│ ├── handle_tts.go
│ ├── jwt_secret_service.go
│ ├── llm/
│ │ ├── claude/
│ │ │ └── claude.go
│ │ ├── gemini/
│ │ │ ├── gemini.go
│ │ │ └── gemini_test.go
│ │ └── openai/
│ │ ├── chat.go
│ │ ├── client.go
│ │ ├── common.go
│ │ └── openai.go
│ ├── llm_openai.go
│ ├── llm_summary.go
│ ├── main.go
│ ├── main_test.go
│ ├── middleware_authenticate.go
│ ├── middleware_gzip.go
│ ├── middleware_lastRequestTime.go
│ ├── middleware_rateLimit.go
│ ├── middleware_validation.go
│ ├── model_claude3_service.go
│ ├── model_completion_service.go
│ ├── model_custom_service.go
│ ├── model_gemini_service.go
│ ├── model_ollama_service.go
│ ├── model_openai_service.go
│ ├── model_test_service.go
│ ├── models/
│ │ └── models.go
│ ├── models.go
│ ├── openai_test.go
│ ├── pre-commit.sh
│ ├── sqlc/
│ │ ├── README.txt
│ │ ├── queries/
│ │ │ ├── auth_user.sql
│ │ │ ├── auth_user_management.sql
│ │ │ ├── bot_answer_history.sql
│ │ │ ├── chat_comment.sql
│ │ │ ├── chat_file.sql
│ │ │ ├── chat_log.sql
│ │ │ ├── chat_message.sql
│ │ │ ├── chat_model.sql
│ │ │ ├── chat_prompt.sql
│ │ │ ├── chat_session.sql
│ │ │ ├── chat_snapshot.sql
│ │ │ ├── chat_workspace.sql
│ │ │ ├── jwt_secrets.sql
│ │ │ ├── user_active_chat_session.sql
│ │ │ └── user_chat_model_privilege.sql
│ │ └── schema.sql
│ ├── sqlc.yaml
│ ├── sqlc_queries/
│ │ ├── auth_user.sql.go
│ │ ├── auth_user_management.sql.go
│ │ ├── bot_answer_history.sql.go
│ │ ├── chat_comment.sql.go
│ │ ├── chat_file.sql.go
│ │ ├── chat_log.sql.go
│ │ ├── chat_message.sql.go
│ │ ├── chat_model.sql.go
│ │ ├── chat_prompt.sql.go
│ │ ├── chat_session.sql.go
│ │ ├── chat_snapshot.sql.go
│ │ ├── chat_workspace.sql.go
│ │ ├── db.go
│ │ ├── jwt_secrets.sql.go
│ │ ├── models.go
│ │ ├── user_active_chat_session.sql.go
│ │ ├── user_chat_model_privilege.sql.go
│ │ ├── zz_custom_method.go
│ │ └── zz_custom_query.go
│ ├── static/
│ │ ├── awesome-chatgpt-prompts-en.json
│ │ ├── awesome-chatgpt-prompts-zh.json
│ │ └── static.go
│ ├── streaming_helpers.go
│ ├── test_build
│ ├── text_buffer.go
│ ├── tools/
│ │ ├── apply_a_similar_change/
│ │ │ ├── README.md
│ │ │ ├── apply_diff.py
│ │ │ ├── apply_diff_uselib.py
│ │ │ ├── parse_diff.py
│ │ │ ├── parse_diff2.py
│ │ │ ├── parse_diff3.py
│ │ │ └── stream.diff
│ │ └── fix_eris.py
│ ├── util.go
│ ├── util_test.go
│ └── util_words_test.go
├── artifacts.md
├── chat.code-workspace
├── docker-compose.yaml
├── docs/
│ ├── add_model_en.md
│ ├── add_model_zh.md
│ ├── artifact_gallery_en.md
│ ├── artifact_gallery_zh.md
│ ├── code_runner_artifacts_tutorial.md
│ ├── code_runner_capabilities.md
│ ├── code_runner_csv_tutorial.md
│ ├── custom_model_api_en.md
│ ├── deployment_en.md
│ ├── deployment_zh.md
│ ├── dev/
│ │ ├── ERROR_HANDLING_STANDARDS.md
│ │ ├── INTEGRATION_GUIDE.md
│ │ ├── code_runner_manual.md
│ │ ├── conversation_patch_example.js
│ │ ├── conversation_vfs_integration.md
│ │ ├── python_async_execution.md
│ │ ├── sse_processing_logic.md
│ │ ├── vfs_integration_example.md
│ │ ├── virtual_file_system_plan.md
│ │ └── virtual_file_system_usage.md
│ ├── dev_locally_en.md
│ ├── dev_locally_zh.md
│ ├── ollama_en.md
│ ├── ollama_zh.md
│ ├── prompts.md
│ ├── snapshots_vs_chatbots_en.md
│ ├── snapshots_vs_chatbots_zh.md
│ ├── tool_use_code_runner.md
│ └── tool_use_showcase.md
├── e2e/
│ ├── .gitignore
│ ├── LICENSE
│ ├── Makefile
│ ├── lib/
│ │ ├── button-helpers.ts
│ │ ├── chat-test-setup.ts
│ │ ├── db/
│ │ │ ├── chat_message/
│ │ │ │ └── index.ts
│ │ │ ├── chat_model/
│ │ │ │ └── index.ts
│ │ │ ├── chat_prompt/
│ │ │ │ └── index.ts
│ │ │ ├── chat_session/
│ │ │ │ └── index.ts
│ │ │ ├── chat_workspace/
│ │ │ │ └── index.ts
│ │ │ ├── config.ts
│ │ │ └── user/
│ │ │ └── index.ts
│ │ ├── message-helpers.ts
│ │ └── sample.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── tests/
│ │ ├── 00_chat_gpt_web.spec.ts
│ │ ├── 01_register.spec.ts
│ │ ├── 02_simpe_prompt.spec.ts
│ │ ├── 03_chat_session.spec.ts
│ │ ├── 04_simpe_prompt_and_message.spec.ts
│ │ ├── 05_chat_session.spec.ts
│ │ ├── 06_clear_messages.spec.ts
│ │ ├── 07_set_session_max_len.spec.ts
│ │ ├── 08_session_config.spec.ts
│ │ ├── 09_session_answer.spec.ts
│ │ ├── 10_session_answer_regenerate.spec.ts
│ │ ├── 10_session_answer_regenerate_fixed.spec.ts
│ │ └── 11_workspace.spec.ts
│ └── tests-examples/
│ └── demo-todo-app.spec.ts
├── fly.toml
├── mobile/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android/
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ ├── debug/
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin/
│ │ │ │ │ └── com/
│ │ │ │ │ └── example/
│ │ │ │ │ └── chat_mobile/
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values/
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night/
│ │ │ │ └── styles.xml
│ │ │ └── profile/
│ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle.kts
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ └── settings.gradle.kts
│ ├── devtools_options.yaml
│ ├── ios/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── LaunchImage.imageset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── README.md
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ └── Runner-Bridging-Header.h
│ │ ├── Runner.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata/
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── RunnerTests/
│ │ └── RunnerTests.swift
│ ├── lib/
│ │ ├── api/
│ │ │ ├── api_config.dart
│ │ │ ├── api_exception.dart
│ │ │ └── chat_api.dart
│ │ ├── constants/
│ │ │ └── chat.dart
│ │ ├── main.dart
│ │ ├── models/
│ │ │ ├── auth_token_result.dart
│ │ │ ├── chat_message.dart
│ │ │ ├── chat_model.dart
│ │ │ ├── chat_session.dart
│ │ │ ├── chat_snapshot.dart
│ │ │ ├── suggestions_response.dart
│ │ │ └── workspace.dart
│ │ ├── screens/
│ │ │ ├── auth_gate.dart
│ │ │ ├── chat_screen.dart
│ │ │ ├── home_screen.dart
│ │ │ ├── login_screen.dart
│ │ │ ├── snapshot_list_screen.dart
│ │ │ └── snapshot_screen.dart
│ │ ├── state/
│ │ │ ├── auth_provider.dart
│ │ │ ├── message_provider.dart
│ │ │ ├── model_provider.dart
│ │ │ ├── session_provider.dart
│ │ │ └── workspace_provider.dart
│ │ ├── theme/
│ │ │ ├── app_theme.dart
│ │ │ └── color_utils.dart
│ │ ├── utils/
│ │ │ ├── api_error.dart
│ │ │ └── thinking_parser.dart
│ │ └── widgets/
│ │ ├── icon_map.dart
│ │ ├── message_bubble.dart
│ │ ├── message_composer.dart
│ │ ├── session_tile.dart
│ │ ├── suggested_questions.dart
│ │ ├── thinking_section.dart
│ │ └── workspace_selector.dart
│ ├── linux/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── flutter/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── generated_plugin_registrant.cc
│ │ │ ├── generated_plugin_registrant.h
│ │ │ └── generated_plugins.cmake
│ │ └── runner/
│ │ ├── CMakeLists.txt
│ │ ├── main.cc
│ │ ├── my_application.cc
│ │ └── my_application.h
│ ├── macos/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── Flutter-Debug.xcconfig
│ │ │ ├── Flutter-Release.xcconfig
│ │ │ └── GeneratedPluginRegistrant.swift
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ └── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── MainMenu.xib
│ │ │ ├── Configs/
│ │ │ │ ├── AppInfo.xcconfig
│ │ │ │ ├── Debug.xcconfig
│ │ │ │ ├── Release.xcconfig
│ │ │ │ └── Warnings.xcconfig
│ │ │ ├── DebugProfile.entitlements
│ │ │ ├── Info.plist
│ │ │ ├── MainFlutterWindow.swift
│ │ │ └── Release.entitlements
│ │ ├── Runner.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ └── xcshareddata/
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── RunnerTests/
│ │ └── RunnerTests.swift
│ ├── pubspec.yaml
│ ├── test/
│ │ └── widget_test.dart
│ ├── web/
│ │ ├── index.html
│ │ └── manifest.json
│ └── windows/
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── flutter/
│ │ ├── CMakeLists.txt
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ └── generated_plugins.cmake
│ └── runner/
│ ├── CMakeLists.txt
│ ├── Runner.rc
│ ├── flutter_window.cpp
│ ├── flutter_window.h
│ ├── main.cpp
│ ├── resource.h
│ ├── runner.exe.manifest
│ ├── utils.cpp
│ ├── utils.h
│ ├── win32_window.cpp
│ └── win32_window.h
├── scripts/
│ ├── branch_clean.py
│ ├── locale_missing_key.py
│ ├── merge_keys.py
│ └── remove_older_branch.py
└── web/
├── .commitlintrc.json
├── .editorconfig
├── .eslintrc.cjs
├── .gitattributes
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── docker-compose/
│ ├── docker-compose.yml
│ ├── nginx/
│ │ └── nginx.conf
│ └── readme.md
├── docs/
│ └── code_runner.md
├── index.html
├── license
├── package.json
├── postcss.config.js
├── public/
│ ├── awesome-chatgpt-prompts-en.json
│ └── awesome-chatgpt-prompts-zh.json
├── rsbuild.config.ts
├── src/
│ ├── App.vue
│ ├── api/
│ │ ├── admin.ts
│ │ ├── bot_answer_history.ts
│ │ ├── chat_active_user_session.ts
│ │ ├── chat_file.ts
│ │ ├── chat_instructions.ts
│ │ ├── chat_message.ts
│ │ ├── chat_model.ts
│ │ ├── chat_prompt.ts
│ │ ├── chat_session.ts
│ │ ├── chat_snapshot.ts
│ │ ├── chat_user_model_privilege.ts
│ │ ├── chat_workspace.ts
│ │ ├── comment.ts
│ │ ├── content.ts
│ │ ├── export.ts
│ │ ├── index.ts
│ │ ├── token.ts
│ │ ├── use_chat_session.ts
│ │ └── user.ts
│ ├── assets/
│ │ └── recommend.json
│ ├── components/
│ │ ├── admin/
│ │ │ ├── ModelCard.vue
│ │ │ ├── SessionSnapshotModal.vue
│ │ │ └── UserAnalysisModal.vue
│ │ ├── common/
│ │ │ ├── EnhancedNotification.vue
│ │ │ ├── HoverButton/
│ │ │ │ ├── Button.vue
│ │ │ │ └── index.vue
│ │ │ ├── NaiveProvider/
│ │ │ │ └── index.vue
│ │ │ ├── NotificationDemo.vue
│ │ │ ├── PromptStore/
│ │ │ │ └── index.vue
│ │ │ ├── Setting/
│ │ │ │ ├── Admin.vue
│ │ │ │ ├── General.vue
│ │ │ │ └── index.vue
│ │ │ ├── SvgIcon/
│ │ │ │ └── index.vue
│ │ │ ├── UserAvatar/
│ │ │ │ └── index.vue
│ │ │ └── index.ts
│ │ └── custom/
│ │ ├── GithubSite.vue
│ │ └── index.ts
│ ├── config/
│ │ └── api.ts
│ ├── constants/
│ │ ├── apiTypes.ts
│ │ └── chat.ts
│ ├── hooks/
│ │ ├── useBasicLayout.ts
│ │ ├── useChatModels.ts
│ │ ├── useCopyCode.ts
│ │ ├── useIconRender.ts
│ │ ├── useLanguage.ts
│ │ ├── useOnlineStatus.ts
│ │ ├── useTheme.ts
│ │ └── useWorkspaceRouting.ts
│ ├── icons/
│ │ ├── 403.vue
│ │ └── 500.vue
│ ├── locales/
│ │ ├── en-US-more.json
│ │ ├── en-US.json
│ │ ├── en.ts
│ │ ├── index.ts
│ │ ├── zh-CN.json
│ │ ├── zh-TW-more.json
│ │ └── zh-TW.json
│ ├── main.ts
│ ├── plugins/
│ │ ├── assets.ts
│ │ └── index.ts
│ ├── router/
│ │ ├── index.ts
│ │ └── permission.ts
│ ├── service/
│ │ └── snapshot.ts
│ ├── services/
│ │ └── codeTemplates.ts
│ ├── store/
│ │ ├── index.ts
│ │ └── modules/
│ │ ├── app/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ ├── auth/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── message/
│ │ │ └── index.ts
│ │ ├── prompt/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ ├── session/
│ │ │ └── index.ts
│ │ ├── user/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ └── workspace/
│ │ └── index.ts
│ ├── styles/
│ │ ├── global.less
│ │ └── lib/
│ │ ├── github-markdown.less
│ │ ├── highlight.less
│ │ └── tailwind.css
│ ├── types/
│ │ └── chat-models.ts
│ ├── typings/
│ │ ├── chat.d.ts
│ │ └── global.d.ts
│ ├── utils/
│ │ ├── __tests__/
│ │ │ └── date.test.ts
│ │ ├── artifacts.ts
│ │ ├── crypto/
│ │ │ └── index.ts
│ │ ├── date.ts
│ │ ├── download.ts
│ │ ├── errorHandler.ts
│ │ ├── format/
│ │ │ └── index.ts
│ │ ├── is/
│ │ │ └── index.ts
│ │ ├── jwt.ts
│ │ ├── logger.ts
│ │ ├── notificationManager.ts
│ │ ├── prompt.ts
│ │ ├── rand.ts
│ │ ├── request/
│ │ │ ├── axios.ts
│ │ │ └── index.ts
│ │ ├── sanitize.ts
│ │ ├── storage/
│ │ │ ├── index.ts
│ │ │ └── local.ts
│ │ ├── string.ts
│ │ ├── tooling.ts
│ │ └── workspaceUrls.ts
│ └── views/
│ ├── admin/
│ │ ├── index.vue
│ │ ├── model/
│ │ │ ├── AddModelForm.vue
│ │ │ └── index.vue
│ │ ├── modelRateLimit/
│ │ │ ├── addChatModelForm.vue
│ │ │ └── index.vue
│ │ └── user/
│ │ └── index.vue
│ ├── bot/
│ │ ├── all.vue
│ │ ├── components/
│ │ │ ├── AnswerHistory.vue
│ │ │ └── Message/
│ │ │ ├── index.vue
│ │ │ └── style.less
│ │ └── page.vue
│ ├── chat/
│ │ ├── components/
│ │ │ ├── ArtifactGallery.vue
│ │ │ ├── AudioPlayer/
│ │ │ │ └── index.vue
│ │ │ ├── Conversation.vue
│ │ │ ├── HeaderMobile/
│ │ │ │ └── index.vue
│ │ │ ├── JumpToBottom.vue
│ │ │ ├── Message/
│ │ │ │ ├── ArtifactContent.vue
│ │ │ │ ├── ArtifactEditor.vue
│ │ │ │ ├── ArtifactHeader.vue
│ │ │ │ ├── ArtifactViewer.vue
│ │ │ │ ├── ArtifactViewerBase.vue
│ │ │ │ ├── SuggestedQuestions.vue
│ │ │ │ └── index.vue
│ │ │ ├── MessageList.vue
│ │ │ ├── ModelSelector.vue
│ │ │ ├── PromptGallery/
│ │ │ │ ├── PromptCards.vue
│ │ │ │ └── index.vue
│ │ │ ├── RenderMessage.vue
│ │ │ ├── Session/
│ │ │ │ └── SessionConfig.vue
│ │ │ ├── UploadModal.vue
│ │ │ ├── Uploader.vue
│ │ │ ├── UploaderReadOnly.vue
│ │ │ ├── WorkspaceSelector/
│ │ │ │ ├── WorkspaceCard.vue
│ │ │ │ ├── WorkspaceManagementModal.vue
│ │ │ │ ├── WorkspaceModal.vue
│ │ │ │ └── index.vue
│ │ │ ├── __tests__/
│ │ │ │ └── modelSelectorUtils.test.ts
│ │ │ └── modelSelectorUtils.ts
│ │ ├── composables/
│ │ │ ├── README.md
│ │ │ ├── useChatActions.ts
│ │ │ ├── useConversationFlow.ts
│ │ │ ├── useErrorHandling.ts
│ │ │ ├── usePerformanceOptimizations.ts
│ │ │ ├── useRegenerate.ts
│ │ │ ├── useSearchAndPrompts.ts
│ │ │ ├── useStreamHandling.ts
│ │ │ └── useValidation.ts
│ │ ├── hooks/
│ │ │ ├── useChat.ts
│ │ │ ├── useCopyCode.ts
│ │ │ ├── useScroll.ts
│ │ │ ├── useSlashToFocus.ts
│ │ │ └── useUsingContext.ts
│ │ ├── index.vue
│ │ └── layout/
│ │ ├── Layout.vue
│ │ ├── index.ts
│ │ └── sider/
│ │ ├── Footer.vue
│ │ ├── List.vue
│ │ └── index.vue
│ ├── components/
│ │ ├── Avatar/
│ │ │ ├── MessageAvatar.vue
│ │ │ └── ModelAvatar.vue
│ │ ├── Message/
│ │ │ ├── AnswerContent.vue
│ │ │ ├── Text.vue
│ │ │ ├── ThinkingRenderer.vue
│ │ │ ├── Util.ts
│ │ │ ├── style.less
│ │ │ ├── thinkingParser.ts
│ │ │ ├── types/
│ │ │ │ └── thinking.ts
│ │ │ └── useThinkingContent.ts
│ │ └── Permission.vue
│ ├── exception/
│ │ ├── 404/
│ │ │ └── index.vue
│ │ └── 500/
│ │ └── index.vue
│ ├── prompt/
│ │ ├── components/
│ │ │ ├── Definitions.vue
│ │ │ ├── PromptCreator.vue
│ │ │ └── PromptProcess.vue
│ │ └── creator.vue
│ └── snapshot/
│ ├── all.vue
│ ├── components/
│ │ ├── Comment/
│ │ │ └── index.vue
│ │ ├── Header/
│ │ │ └── index.vue
│ │ ├── Message/
│ │ │ ├── index.vue
│ │ │ └── style.less
│ │ └── Search.vue
│ └── page.vue
├── tailwind.config.js
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
**/node_modules
**/dist
================================================
FILE: .github/workflows/docker-image.yml
================================================
name: e2e test
on:
push:
branches: "**"
pull_request:
branches: "**"
env:
PG_DB: postgres
PG_USER: postgres
PG_HOST: localhost
PG_PASS: thisisapassword
jobs:
build_chat:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: check locale
run: python scripts/locale_missing_key.py web/src/locales --base zh-CN
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: build web
run: |
npm install
npm run test
npm run build
working-directory: web
- name: copy to api/static
run: |
cp -R web/dist/* api/static/
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Build API
run: go build -v ./...
working-directory: api
- name: Test API
run: go test -v ./...
working-directory: api
- name: Build Chat image
run: |
docker build . --file Dockerfile -t ghcr.io/swuecho/chat:${GITHUB_SHA}
docker tag ghcr.io/swuecho/chat:${GITHUB_SHA} ghcr.io/swuecho/chat:latest
- name: docker compose
run: docker compose up -d
- name: show docker ps
run: docker compose ps
- name: show docker logs
run: docker compose logs
# Setup cache for node_modules
- name: Cache node modules
uses: actions/cache@v3
with:
path: e2e/node_modules
key: ${{ runner.os }}-node-${{ hashFiles('e2e/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install dependencies
run: npm ci
working-directory: e2e
- name: Install playwright browsers
run: npx playwright install --with-deps
working-directory: e2e
- run: npx playwright test
working-directory: e2e
================================================
FILE: .github/workflows/fly.yml
================================================
name: Fly Deploy
on:
push:
branches:
- master
jobs:
deploy:
name: Deploy app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
================================================
FILE: .github/workflows/mobile-build.yml
================================================
name: Build Mobile Package
on:
workflow_dispatch:
push:
paths:
- "mobile/**"
- ".github/workflows/mobile-build.yml"
pull_request:
paths:
- "mobile/**"
- ".github/workflows/mobile-build.yml"
jobs:
build-android:
runs-on: ubuntu-latest
defaults:
run:
working-directory: mobile
steps:
- uses: actions/checkout@v3
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.35.4"
- name: Install dependencies
run: flutter pub get
- name: Build Android APK
run: flutter build apk --release
- name: Build Android App Bundle
run: flutter build appbundle --release
- name: Upload Android artifacts
uses: actions/upload-artifact@v4
with:
name: chat-mobile-android
path: |
mobile/build/app/outputs/flutter-apk/app-release.apk
mobile/build/app/outputs/bundle/release/app-release.aab
build-ios:
runs-on: macos-latest
defaults:
run:
working-directory: mobile
steps:
- uses: actions/checkout@v3
- name: Set up Flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.35.4"
- name: Install dependencies
run: flutter pub get
- name: Build iOS app (no codesign)
run: flutter build ios --simulator
- name: Upload iOS artifact
uses: actions/upload-artifact@v4
with:
name: chat-mobile-ios
path: mobile/build/ios/iphonesimulator/Runner.app
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
push:
tags:
- "v*"
jobs:
build_api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: "18.x"
- name: build web
run: |
npm install
npm run test
npm run build
working-directory: web
- name: copy to api/static
run: |
cp -R web/dist/* api/static/
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.24
- name: Build Chat Binary
run: go build -v ./...
working-directory: api
- name: Test Chat
run: go test -v ./...
working-directory: api
# use root folder docker
- name: Build Chat image
run: |
docker build . --file Dockerfile -t ghcr.io/swuecho/chat:${GITHUB_REF#refs/tags/}
- name: Login to GitHub Container Registry
run: echo "${{ secrets.GHCR_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
- name: Push API image to GitHub Container Registry
run: |
docker push ghcr.io/swuecho/chat:${GITHUB_REF#refs/tags/}
docker tag ghcr.io/swuecho/chat:${GITHUB_REF#refs/tags/} ghcr.io/swuecho/chat:latest
docker push ghcr.io/swuecho/chat:latest
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: push to docker
run: |
docker tag ghcr.io/swuecho/chat:${GITHUB_REF#refs/tags/} echowuhao/chat:${GITHUB_REF#refs/tags/}
docker tag ghcr.io/swuecho/chat:${GITHUB_REF#refs/tags/} echowuhao/chat:latest
docker push echowuhao/chat:${GITHUB_REF#refs/tags/}
docker push echowuhao/chat:latest
================================================
FILE: .gitignore
================================================
.DS_Store
.env*
env.sh
env.ps
data
.python-version
.aider*
================================================
FILE: AGENTS.md
================================================
# Chat - Multi-LLM Chat Interface
A full-stack chat application that provides a unified interface for interacting with multiple large language models (LLMs) including OpenAI, Claude, Gemini, and Ollama.
## Project Overview
This project is a ChatGPT wrapper that extends beyond OpenAI to support multiple LLM providers. It features a Vue.js frontend with a Go backend, using PostgreSQL for data persistence.
### Key Features
- **Multi-LLM Support**: OpenAI, Claude, Gemini, and Ollama models
- **Chat Sessions**: Persistent conversation history and context management
- **Workspaces**: Organize chat sessions into customizable workspaces with colors and icons
- **User Management**: Authentication, rate limiting, and admin controls
- **File Uploads**: Support for text and multimedia files (model-dependent)
- **Snapshots**: Shareable conversation snapshots with full-text search
- **Prompt Management**: Built-in prompt templates with '/' shortcut
- **Internationalization**: Support for multiple languages
## Architecture
### Frontend (Vue.js)
- **Framework**: Vue 3 with Composition API
- **State Management**: Pinia
- **UI Library**: Naive UI
- **Routing**: Vue Router
- **Build Tool**: Rsbuild
- **Styling**: Tailwind CSS + Less
### Backend (Go)
- **Framework**: Standard Go HTTP with Gorilla Mux
- **Database**: PostgreSQL with SQLC for type-safe queries
- **Authentication**: JWT tokens
- **Rate Limiting**: Built-in rate limiting (100 calls/10min default)
- **File Upload**: Support for various file types
### Database (PostgreSQL)
- **Schema**: Located in `api/sqlc/schema.sql`
- **Queries**: Type-safe queries generated by SQLC
- **Tables**: Users, sessions, messages, models, prompts, snapshots, workspaces, etc.
## Project Structure
```
chat/
├── api/ # Go backend
│ ├── main.go # Application entry point
│ ├── llm/ # LLM provider integrations
│ │ ├── openai/ # OpenAI integration
│ │ ├── claude/ # Claude integration
│ │ └── gemini/ # Gemini integration
│ ├── sqlc/ # Database schema and queries
│ ├── sqlc_queries/ # Generated type-safe queries
│ └── static/ # Static assets
├── web/ # Vue.js frontend
│ ├── src/
│ │ ├── components/ # Reusable components
│ │ ├── views/ # Page components
│ │ ├── store/ # Pinia stores
│ │ ├── api/ # API client
│ │ └── utils/ # Utility functions
│ └── dist/ # Built frontend assets
├── docs/ # Documentation
├── e2e/ # End-to-end tests
└── data/ # Database dumps
```
## Development Setup
### Prerequisites
- Go 1.19+
- Node.js 18+
- PostgreSQL
- (Optional) Docker
### Backend Setup
```bash
cd api
go mod tidy
go run main.go
```
### Frontend Setup
```bash
cd web
npm install
npm run dev
```
### server reload
both frontend and backend will be auto-reload whe code change.
### Database Setup
1. Create a PostgreSQL database
2. Run the schema from `api/sqlc/schema.sql`
3. Configure database connection in environment variables
## Configuration
### Environment Variables
- `OPENAI_API_KEY`: OpenAI API key
- `CLAUDE_API_KEY`: Claude API key
- `GEMINI_API_KEY`: Gemini API key
- `DATABASE_URL`: PostgreSQL connection string
- `JWT_SECRET`: JWT signing secret
- `OPENAI_RATELIMIT`: Rate limit (default: 100)
## API Endpoints
### Authentication
- `POST /api/auth/login` - User login
- `POST /api/auth/register` - User registration
- `POST /api/auth/refresh` - Token refresh
### Chat
- `GET /api/chat/sessions` - Get user sessions
- `POST /api/chat/sessions` - Create new session
- `GET /api/chat/messages` - Get session messages
- `POST /api/chat/messages` - Send message
- `DELETE /api/chat/sessions/:id` - Delete session
### Workspaces
- `GET /api/workspaces` - Get user workspaces
- `POST /api/workspaces` - Create new workspace
- `GET /api/workspaces/{uuid}` - Get workspace by UUID
- `PUT /api/workspaces/{uuid}` - Update workspace
- `DELETE /api/workspaces/{uuid}` - Delete workspace
- `PUT /api/workspaces/{uuid}/reorder` - Update workspace order
- `PUT /api/workspaces/{uuid}/set-default` - Set default workspace
- `POST /api/workspaces/{uuid}/sessions` - Create session in workspace
- `GET /api/workspaces/{uuid}/sessions` - Get sessions in workspace
- `POST /api/workspaces/default` - Ensure default workspace exists
### Models
- `GET /api/models` - List available models
- `POST /api/models` - Add new model (admin)
- `PUT /api/models/:id` - Update model (admin)
### File Upload
- `POST /api/files` - Upload file
- `GET /api/files/:id` - Get file
## Key Components
### Frontend Components
- `chat/index.vue`: Main chat interface
- `chat/components/Message/`: Message rendering
- `chat/components/Session/`: Session management
- `admin/index.vue`: Admin dashboard
- `prompt/creator.vue`: Prompt management
### Backend Handlers
- `chat_main_handler.go`: Core chat functionality
- `chat_session_handler.go`: Session management
- `chat_message_handler.go`: Message handling
- `chat_workspace_handler.go`: Workspace management
- `auth_handler.go`: Authentication
- `admin_handler.go`: Admin operations
## Testing
### Backend Tests
```bash
cd api
go test ./...
```
### Frontend Tests
```bash
cd e2e
npm test
```
### E2E Tests
```bash
cd e2e
npm test
```
## Workspaces
The application supports organizing chat sessions into workspaces for better organization and context management.
### Workspace Features
- **Custom Organization**: Create themed workspaces for different projects or topics
- **Visual Customization**: Set custom colors and icons for easy identification
- **Session Management**: Sessions are automatically associated with workspaces
- **Default Workspace**: Each user has a default "General" workspace that's created automatically
- **Ordering**: Workspaces can be reordered for personal preference
- **Permission Control**: Users can only access their own workspaces
### Workspace Properties
- **Name**: Human-readable workspace name
- **Description**: Optional description for workspace purpose
- **Color**: Hex color code for visual theming (default: #6366f1)
- **Icon**: Icon identifier for visual representation (default: folder)
- **Default**: Boolean flag indicating if this is the user's default workspace
- **Order Position**: Integer for custom workspace ordering
### Database Schema
The `chat_workspace` table includes:
- `id`: Primary key
- `uuid`: Unique identifier for API operations
- `user_id`: Foreign key to owner user
- `name`: Workspace name (required)
- `description`: Optional description (default: empty string)
- `color`: Hex color code (default: #6366f1)
- `icon`: Icon identifier (default: folder)
- `is_default`: Boolean default flag
- `order_position`: Integer for ordering
- `created_at`/`updated_at`: Timestamps
Sessions are linked to workspaces via the `workspace_id` foreign key in the `chat_session` table.
## Model Support
- **OpenAI**: GPT-3.5, GPT-4 models
- **Claude**: Claude 3 Opus, Sonnet, Haiku, Claude 4
- **Gemini**: Gemini Pro
- **Ollama**: Local model hosting support
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Multi-LLM chat interface with Vue.js frontend, Go backend, and PostgreSQL database. Supports OpenAI, Claude, Gemini, and Ollama models with features like workspaces, snapshots, and file uploads.
## Development Commands
### Backend (Go)
```bash
cd api
# Install dependencies and hot-reload tool
make install
# Run server with hot reload (uses Air)
make serve
# Build
make build
# Format code
make fmt
# Run tests
go test ./...
# Regenerate SQLC code (after modifying queries or schema)
sqlc generate
```
**Important**: The backend uses Air for hot-reloading during development. Configuration is in `api/.air.toml`.
### Frontend (Vue.js)
```bash
cd web
# Install dependencies
npm install
# Development server with hot reload
npm run dev
# Build for production
npm run build
# Run linter
npm run lint
# Fix linting issues
npm run lint:fix
# Run tests
npm test
```
### E2E Tests (Playwright)
```bash
cd e2e
# Run all tests
npx playwright test
# Run with UI
npx playwright test --ui
```
## Architecture
### Request Flow
```
HTTP Request → Mux Router → Handler → Service → SQLC Queries → PostgreSQL
↓
LLM Provider (OpenAI/Claude/Gemini/Ollama)
```
### Backend Architecture (Go)
**Key Pattern**: The backend follows a handler → service → repository (SQLC) pattern:
1. **Handlers** (`*_handler.go`): HTTP request/response handling
- `chat_main_handler.go`: Core chat functionality
- `chat_session_handler.go`: Session CRUD operations
- `chat_message_handler.go`: Message operations
- `chat_workspace_handler.go`: Workspace management
- `chat_auth_user_handler.go`: Authentication
- `admin_handler.go`: Admin operations
2. **Services** (`*_service.go`): Business logic layer
- `chat_main_service.go`: Chat orchestration and LLM routing
3. **SQLC Generated Code** (`sqlc_queries/`): Type-safe database queries
- Schema: `api/sqlc/schema.sql`
- Queries: `api/sqlc/queries/*.sql`
- Generated Go: `api/sqlc_queries/*.go`
- Config: `api/sqlc.yaml`
4. **LLM Integrations** (`llm/`):
- `llm/openai/`: OpenAI API client
- `llm/claude/`: Claude API client
- `llm/gemini/`: Gemini API client
- Each provider has its own request/response formatting
**Router**: Uses Gorilla Mux for routing (configured in `main.go`)
### Frontend Architecture (Vue.js)
**Stack**: Vue 3 (Composition API) + Pinia + Naive UI + Rsbuild + Tailwind CSS
**Key Directories**:
- `web/src/views/`: Page components
- `web/src/components/`: Reusable components
- `web/src/store/modules/`: Pinia stores for state management
- `web/src/api/`: API client functions
- `web/src/views/chat/composables/`: Chat feature composables (refactored from monolithic component)
**Chat Composables Pattern**: The main chat interface uses a composable-based architecture for better separation of concerns:
- `useStreamHandling.ts`: Handles LLM streaming responses
- `useConversationFlow.ts`: Manages conversation lifecycle
- `useRegenerate.ts`: Message regeneration
- `useSearchAndPrompts.ts`: Search and prompt templates
- `useChatActions.ts`: Snapshot, bot creation, file uploads
- `useErrorHandling.ts`: Centralized error management
- `useValidation.ts`: Input validation rules
- `usePerformanceOptimizations.ts`: Debouncing, memoization
This pattern reduced the main component from 738 to 293 lines while adding better error handling and type safety.
### Database (PostgreSQL + SQLC)
**SQLC Workflow**:
1. Define schema in `api/sqlc/schema.sql`
2. Write SQL queries in `api/sqlc/queries/*.sql`
3. Run `sqlc generate` to create type-safe Go code
4. Use generated code in services
**Key Tables**:
- `auth_user`: User accounts (first registered user becomes admin)
- `chat_session`: Chat sessions
- `chat_message`: Messages within sessions
- `chat_workspace`: Workspace organization
- `chat_model`: Available LLM models
- `chat_prompt`: Prompt templates
- `chat_snapshot`: Shareable conversation snapshots
- `chat_file`: File uploads
**Default Context**: Latest 4 messages are included in context by default.
## Environment Variables
Required variables (set in shell or `.env`):
```bash
# Database (required)
DATABASE_URL=postgres://user:pass@host:port/dbname?sslmode=disable
# LLM API Keys (at least one required)
OPENAI_API_KEY=sk-...
CLAUDE_API_KEY=...
GEMINI_API_KEY=...
DEEPSEEK_API_KEY=...
# Optional
OPENAI_RATELIMIT=100 # Calls per 10 minutes (default: 100)
JWT_SECRET=... # For JWT token signing
```
**Note**: The "debug" model doesn't require API keys for testing.
## Key Features & Patterns
### Authentication & Authorization
- JWT-based authentication
- First registered user becomes administrator (`is_superuser=true`)
- Rate limiting per user (default: 100 calls/10min, configurable via `OPENAI_RATELIMIT`)
- Per-model rate limiting available for specific models (GPT-4, etc.)
### Workspaces
- Sessions are organized into workspaces
- Each user has a default "General" workspace
- Custom colors and icons for visual organization
- Workspace-specific session isolation
### Chat Flow
1. First message in a session is the system message (prompt)
2. User sends message → Handler validates → Service routes to appropriate LLM provider
3. LLM streams response → Server-Sent Events (SSE) → Frontend renders incrementally
4. Messages stored in PostgreSQL with full history
### File Uploads
- Text files supported for all models
- Multimedia files require model support (GPT-4 Vision, Claude 3+, Gemini)
- Files associated with messages via `chat_file` table
### Snapshots
- Create shareable static pages from conversations (like ShareGPT)
- Full-text search support (English) for organizing conversation history
- Can continue conversations from snapshots
### Prompt Management
- Built-in prompt templates stored in `chat_prompt` table
- Quick access via '/' shortcut in chat interface
## Testing
### Running Backend Tests
```bash
cd api
go test ./...
```
### Running Frontend Tests
```bash
cd web
npm test
```
### Running E2E Tests
```bash
cd e2e
export DATABASE_URL=postgres://...
npx playwright test
```
## Adding a New LLM Model
See documentation: `docs/add_model_en.md` and `docs/add_model_zh.md`
**Summary**:
1. Add model configuration to `chat_model` table (via admin UI or SQL)
2. Implement provider in `api/llm/<provider>/` if new provider type
3. Update routing logic in `chat_main_service.go` if needed
4. Set appropriate `api_type` field: `openai`, `claude`, `gemini`, `ollama`, or `custom`
## Common Gotchas
1. **SQLC Code Generation**: After modifying `schema.sql` or query files, always run `sqlc generate` from the `api/` directory
2. **Hot Reload**: Both frontend (Rsbuild) and backend (Air) auto-reload on code changes
3. **Database Migrations**: Schema changes are handled via `ALTER TABLE IF NOT EXISTS` statements in `schema.sql`
4. **Rate Limiting**: Applies globally (100/10min) unless per-model rate limiting is enabled
5. **Model API Types**: The `api_type` column determines which LLM provider client is used
6. **Session Context**: By default, only the latest 4 messages are sent to the LLM (plus system prompt)
7. **Title Generation**: Conversation titles are optionally generated by `gemini-2.0-flash`; if not configured, uses first 100 chars of prompt
## Documentation
- Local development: `docs/dev_locally_en.md`, `docs/dev_locally_zh.md`
- Deployment: `docs/deployment_en.md`, `docs/deployment_zh.md`
- Ollama integration: `docs/ollama_en.md`, `docs/ollama_zh.md`
- Snapshots vs ChatBots: `docs/snapshots_vs_chatbots_en.md`
- Adding models: `docs/add_model_en.md`
- Dev documentation: `docs/dev/` (VFS, error handling, integration guides)
## Technology Stack Summary
**Frontend**: Vue 3, Pinia, Naive UI, Rsbuild, Tailwind CSS, TypeScript
**Backend**: Go, Gorilla Mux, SQLC, PostgreSQL, Air (hot reload)
**Testing**: Playwright (E2E), Vitest (frontend unit tests)
**LLM SDKs**: Custom HTTP clients for each provider
================================================
FILE: Dockerfile
================================================
FROM node:16 as frontend_builder
# Set the working directory to /app
WORKDIR /app
# Copy the package.json and package-lock.json files to the container
COPY web/package*.json ./
# Install dependencies
RUN npm install
# Copy the remaining application files to the container
COPY web/ .
# Build the application
RUN npm run build
FROM golang:1.24-alpine3.20 AS builder
WORKDIR /app
COPY api/go.mod api/go.sum ./
RUN go mod download
COPY api/ .
# cp -rf /app/dist/* /app/static/
COPY --from=frontend_builder /app/dist/ ./static/
RUN CGO_ENABLED=0 GOARCH=amd64 GOOS=linux go build -a -installsuffix cgo -o /app/app
FROM alpine:3.20
WORKDIR /app
COPY --from=builder /app/app /app
# for go timezone work
COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip /app/zoneinfo.zip
ENV ZONEINFO=/app/zoneinfo.zip
EXPOSE 8080
ENTRYPOINT ["/app/app"]
================================================
FILE: README.md
================================================
## Demo
<img width="850" alt="image" src="https://github.com/user-attachments/assets/98940012-a1d9-41c0-b5c7-fc060e74546a" />
<img width="850" alt="image" src="https://github.com/user-attachments/assets/65b7286e-9df6-429c-98a4-64bd8ad1518b">
<img width="850" alt="thinking" src="https://github.com/user-attachments/assets/e5145dc9-ca4e-4fc3-a40c-ef28693d811a" />

<img width="850" alt="image" src="https://github.com/swuecho/chat/assets/666683/0c4f546a-e884-4dc1-91c0-d4b07e63a1a9.png">
<img width="850" alt="Screenshot 2025-09-11 at 8 05 03 PM" src="https://github.com/user-attachments/assets/d3ae5c15-7498-4352-95b4-bb96b7a4c2bb" />

<img width="850" alt="image" src="https://github.com/user-attachments/assets/13b0aff8-93c4-4406-acce-b48389ae0c88" />
<img width="850" alt="chat records" src="https://github.com/swuecho/chat/assets/666683/45dd865e-7f9f-4209-8587-4781e37dd928">
<img width="1601" alt="chat record comments" src="https://github.com/user-attachments/assets/9ce940b9-2023-47ba-bcbe-32f4846354b1" />
## 规则
- 第一个消息是系统消息(prompt)
- 上下文默认附带最新创建的4条消息
- 第一个注册的用户是管理员
- 默认限流 100 chatGPT call /10分钟 (OPENAI_RATELIMIT=100)
- 根据对话生成可以分享的静态页面(like ShareGPT), 也可以继续会话.
- 对话快照目录(对话集), 支持全文查找(English), 方便整理, 搜索会话记录.
- 支持OPEN AI, Claude 模型
- 支持Ollama host模型, 配置参考: https://github.com/swuecho/chat/discussions/396
- 支持上传文本文件
- 支持多媒体文件, 需要模型支持
- 提示词管理, 提示词快捷键 '/'
> (可选)对话标题用 `gemini-2.0-flash` 生成, 所以需要配置该模型, 不配置默认用提示词前100个字符
## 文档
- [添加新模型指南](https://github.com/swuecho/chat/blob/master/docs/add_model_zh.md)
- [快照 vs 聊天机器人](https://github.com/swuecho/chat/blob/master/docs/snapshots_vs_chatbots_zh.md)
- [使用本地Ollama](https://github.com/swuecho/chat/blob/master/docs/ollama_zh.md)
- [论坛](https://github.com/swuecho/chat/discussions)
## 开发指南
- [本地开发指南](https://github.com/swuecho/chat/blob/master/docs/dev_locally_zh.md)
## 部署指南
- [部署指南](https://github.com/swuecho/chat/blob/master/docs/deployment_zh.md)
## 致谢
- web: [ChatGPT-Web](https://github.com/Chanzhaoyu/chatgpt-web) 复制过来的 。
- api : 参考 [Kerwin1202](https://github.com/Kerwin1202)'s [Chanzhaoyu/chatgpt-web#589](https://github.com/Chanzhaoyu/chatgpt-web/pull/589) 的node版本在chatgpt帮助下写的
## LICENCE: MIT
## Rules
- The first message is a system message (prompt)
- By default, the latest 4 messages are included in context
- The first registered user becomes administrator
- Default rate limit: 100 ChatGPT calls per 10 minutes (OPENAI_RATELIMIT=100)
- Generate shareable static pages from conversations (like ShareGPT), or continue conversations
- Conversation snapshots directory supports full-text search (English), making it easy to organize and search conversation history
- Supports OpenAI and Claude models
- Supports Ollama host models, configuration reference: https://github.com/swuecho/chat/discussions/396
- Supports text file uploads
- Supports multimedia files (requires model support)
- Prompt management with '/' shortcut
> (Optional) Conversation titles are generated by `gemini-2.0-flash`, so this model needs to be configured. If not configured, the first 100 characters of the prompt will be used as the title.
## Documentation
- [Adding New Models Guide](https://github.com/swuecho/chat/blob/master/docs/add_model_en.md)
- [Snapshots vs ChatBots](https://github.com/swuecho/chat/blob/master/docs/snapshots_vs_chatbots_en.md)
- [Using Local Ollama](https://github.com/swuecho/chat/blob/master/docs/ollama_en.md)
- [Community Discussions](https://github.com/swuecho/chat/discussions)
## Development Guide
- [Local Development Guide](https://github.com/swuecho/chat/blob/master/docs/dev_locally_en.md)
## Deployment Guide
- [Deployment Guide](https://github.com/swuecho/chat/blob/master/docs/deployment_en.md)
## Acknowledgments
- web: copied from chatgpt-web <https://github.com/Chanzhaoyu/chatgpt-web>
- api: based on the node version of [Kerwin1202](https://github.com/Kerwin1202)'s [Chanzhaoyu/chatgpt-web#589](https://github.com/Chanzhaoyu/chatgpt-web/pull/589)
and written with the help of chatgpt.
================================================
FILE: api/.air.toml
================================================
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
#bin = "./tmp/main"
#cmd = "go build -o ./tmp/main ."
delay = 0
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true
================================================
FILE: api/.github/workflows/go.yml
================================================
# This workflow will build a golang project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
name: Go
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.24
- name: Build
run: go build -v ./...
- name: Test
run: go test -v ./...
# build docker image base on Dockerfile
================================================
FILE: api/.gitignore
================================================
tmp/
chat_backend
env.sh
static/static
================================================
FILE: api/.vscode/settings.json
================================================
{
"editor.fontFamily": "Go Mono",
"go.useLanguageServer": true,
"files.watcherExclude": {
"**/target": true
}
}
================================================
FILE: api/LICENSE
================================================
MIT License
Copyright (c) 2023-2024 Hao Wu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: api/Makefile
================================================
.DEFAULT_GOAL:=build
fmt:
go fmt ./...
.PHONY: fmt
lint: fmt
golint ./...
.PHONY: lint
vet: fmt
go vet ./...
.PHONY: vet
build: vet
go build
.PHONY: build
install:
go install github.com/air-verse/air@latest
go mod tidy
serve:
@echo "Starting server..."
echo 'sudo lsof -i -P -n | grep 8080'
echo $(OPENAI_API_KEY)
echo $(PG_HOST)
air
================================================
FILE: api/README.md
================================================
# architecture
request -> mux(router) -> sql generated code -> database -> sql
## library used
1. sqlc to connect go code to sql (sql is mostly generated by chatgpt)
2. mux as router
## ChatGPT version
When it comes to building web applications with Go, there are several choices for libraries and frameworks. One of the most popular options for creating a web server is mux, which provides a flexible and powerful routing system.
In addition to handling incoming requests and routing them to the appropriate handler, a web application will typically need to interact with a database. This is where sqlc comes in, a Go library that helps connect your Go code to your database using SQL.
Using these two tools together, you can quickly create a powerful and efficient web application that uses SQL for data storage and retrieval.
Here's how it all fits together. When a request comes into your application, it's handled by mux. Mux inspects the incoming request and routes it to the appropriate function or handler in your Go code.
Your Go code, in turn, uses sqlc generated go code (based on SQL) that interacts with your database. This generated go code is used to fetch data, store new data, and update existing data. The SQL code is compiled and executed by your database, and the result is returned to your Go code.
Overall, this architecture provides a clean and modular approach to building web applications with Go. By leveraging powerful libraries like mux and sqlc, you can focus on writing application logic rather than worrying about the low-level details of routing and database access.
In summary, if you're building a web application with Go, you should definitely consider using mux as your router and sqlc to connect your Go code to your database. The combination of these two libraries makes it easy to build scalable and reliable web applications that are both easy to maintain and performant.
================================================
FILE: api/admin_handler.go
================================================
package main
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type AdminHandler struct {
service *AuthUserService
}
func NewAdminHandler(service *AuthUserService) *AdminHandler {
return &AdminHandler{
service: service,
}
}
func (h *AdminHandler) RegisterRoutes(router *mux.Router) {
// admin routes (without /admin prefix since router already handles it)
router.HandleFunc("/users", h.CreateUser).Methods(http.MethodPost)
router.HandleFunc("/users", h.UpdateUser).Methods(http.MethodPut)
router.HandleFunc("/rate_limit", h.UpdateRateLimit).Methods(http.MethodPost)
router.HandleFunc("/user_stats", h.UserStatHandler).Methods(http.MethodPost)
router.HandleFunc("/user_analysis/{email}", h.UserAnalysisHandler).Methods(http.MethodGet)
router.HandleFunc("/user_session_history/{email}", h.UserSessionHistoryHandler).Methods(http.MethodGet)
router.HandleFunc("/session_messages/{sessionUuid}", h.SessionMessagesHandler).Methods(http.MethodGet)
}
func (h *AdminHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var userParams sqlc_queries.CreateAuthUserParams
err := json.NewDecoder(r.Body).Decode(&userParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
user, err := h.service.CreateAuthUser(r.Context(), userParams)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to create user"))
return
}
json.NewEncoder(w).Encode(user)
}
func (h *AdminHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
var userParams sqlc_queries.UpdateAuthUserByEmailParams
err := json.NewDecoder(r.Body).Decode(&userParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
user, err := h.service.q.UpdateAuthUserByEmail(r.Context(), userParams)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update user"))
return
}
json.NewEncoder(w).Encode(user)
}
func (h *AdminHandler) UserStatHandler(w http.ResponseWriter, r *http.Request) {
var pagination Pagination
err := json.NewDecoder(r.Body).Decode(&pagination)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
userStatsRows, total, err := h.service.GetUserStats(r.Context(), pagination, int32(appConfig.OPENAI.RATELIMIT))
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get user stats"))
return
}
// Create a new []interface{} slice with same length as userStatsRows
data := make([]interface{}, len(userStatsRows))
// Copy the contents of userStatsRows into data
for i, v := range userStatsRows {
divider := v.TotalChatMessages3Days
var avg int64
if divider > 0 {
avg = v.TotalTokenCount3Days / v.TotalChatMessages3Days
} else {
avg = 0
}
data[i] = UserStat{
Email: v.UserEmail,
FirstName: v.FirstName,
LastName: v.LastName,
TotalChatMessages: v.TotalChatMessages,
TotalChatMessages3Days: v.TotalChatMessages3Days,
RateLimit: v.RateLimit,
TotalChatMessagesTokenCount: v.TotalTokenCount,
TotalChatMessages3DaysTokenCount: v.TotalTokenCount3Days,
AvgChatMessages3DaysTokenCount: avg,
}
}
json.NewEncoder(w).Encode(Pagination{
Page: pagination.Page,
Size: pagination.Size,
Total: total,
Data: data,
})
}
func (h *AdminHandler) UpdateRateLimit(w http.ResponseWriter, r *http.Request) {
var rateLimitRequest RateLimitRequest
err := json.NewDecoder(r.Body).Decode(&rateLimitRequest)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
rate, err := h.service.q.UpdateAuthUserRateLimitByEmail(r.Context(),
sqlc_queries.UpdateAuthUserRateLimitByEmailParams{
Email: rateLimitRequest.Email,
RateLimit: rateLimitRequest.RateLimit,
})
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update rate limit"))
return
}
json.NewEncoder(w).Encode(
map[string]int32{
"rate": rate,
})
}
func (h *AdminHandler) UserAnalysisHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
email := vars["email"]
if email == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("Email parameter is required"))
return
}
analysisData, err := h.service.GetUserAnalysis(r.Context(), email, int32(appConfig.OPENAI.RATELIMIT))
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get user analysis"))
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(analysisData)
}
type SessionHistoryResponse struct {
Data []SessionHistoryInfo `json:"data"`
Total int64 `json:"total"`
Page int32 `json:"page"`
Size int32 `json:"size"`
}
func (h *AdminHandler) UserSessionHistoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
email := vars["email"]
if email == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("Email parameter is required"))
return
}
// Parse pagination parameters
pageStr := r.URL.Query().Get("page")
sizeStr := r.URL.Query().Get("size")
page := int32(1)
size := int32(10)
if pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
page = int32(p)
}
}
if sizeStr != "" {
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
size = int32(s)
}
}
sessionHistory, total, err := h.service.GetUserSessionHistory(r.Context(), email, page, size)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get user session history"))
return
}
response := SessionHistoryResponse{
Data: sessionHistory,
Total: total,
Page: page,
Size: size,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (h *AdminHandler) SessionMessagesHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionUuid := vars["sessionUuid"]
if sessionUuid == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("Session UUID parameter is required"))
return
}
messages, err := h.service.q.GetChatMessagesBySessionUUIDForAdmin(r.Context(), sessionUuid)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get session messages"))
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(messages)
}
================================================
FILE: api/ai/model.go
================================================
package ai
import (
"encoding/json"
"fmt"
)
type Role int
const (
System Role = iota
User
Assistant
)
func (r Role) String() string {
switch r {
case System:
return "system"
case User:
return "user"
case Assistant:
return "assistant"
default:
return ""
}
}
func StringToRole(s string) (Role, error) {
switch s {
case "system":
return System, nil
case "user":
return User, nil
case "assistant":
return Assistant, nil
default:
return 0, fmt.Errorf("invalid role string: %s", s)
}
}
func (r *Role) UnmarshalJSON(data []byte) error {
var roleStr string
err := json.Unmarshal(data, &roleStr)
if err != nil {
return err
}
switch roleStr {
case "system":
*r = System
case "user":
*r = User
case "assistant":
*r = Assistant
default:
return fmt.Errorf("invalid role string: %s", roleStr)
}
return nil
}
func (r Role) MarshalJSON() ([]byte, error) {
switch r {
case System:
return json.Marshal("system")
case User:
return json.Marshal("user")
case Assistant:
return json.Marshal("assistant")
default:
return nil, fmt.Errorf("invalid role value: %d", r)
}
}
================================================
FILE: api/artifact_instruction.txt
================================================
ARTIFACT CREATION GUIDELINES - MANDATORY COMPLIANCE REQUIRED:
⚠️ CRITICAL: These formatting rules are REQUIRED for artifact rendering. Deviation will cause display failures.
## MANDATORY ARTIFACT FORMATS (EXACT SYNTAX REQUIRED):
### HTML Applications:
```html <!-- artifact: Descriptive Title -->
[Complete HTML content with inline CSS and JavaScript(Preact)]
```
### SVG Graphics:
```svg <!-- artifact: Descriptive Title -->
[Complete SVG markup]
```
### Mermaid Diagrams:
```mermaid <!-- artifact: Descriptive Title -->
[Mermaid diagram syntax]
```
### JSON Data:
```json <!-- artifact: Descriptive Title -->
[Valid JSON data]
```
### Executable Code:
```javascript <!-- executable: Descriptive Title -->
[JavaScript/TypeScript code]
```
```python <!-- executable: Descriptive Title -->
[Python code]
```
## FORMATTING COMPLIANCE CHECKLIST:
✅ Comment MUST be on the SAME LINE as opening ```
✅ Use EXACT format: `<!-- artifact: Title -->` or `<!-- executable: Title -->`
✅ Include descriptive, specific title explaining functionality
✅ No extra spaces or characters in comment syntax
✅ Complete, self-contained code within blocks
❌ COMMON ERRORS TO AVOID:
- Comment on separate line from ```
- Missing or incorrect comment format
- Generic titles like "Code" or "Example"
- Incomplete or broken code
- External dependencies in HTML artifacts
## WHEN TO CREATE ARTIFACTS:
### ALWAYS create artifacts for:
- Interactive web applications, forms, games, tools
- Data visualizations, charts, graphs, dashboards
- Diagrams, flowcharts, visual representations
- Working code examples demonstrating functionality
- Calculators, converters, utility applications
- Rich data displays or formatted outputs
- Any content meant to be rendered/executed
### NEVER create artifacts for:
- Simple text responses
- Code snippets for reference only
- Incomplete or pseudo-code
- Content requiring external files
## HTML ARTIFACT STANDARDS:
### REQUIRED STRUCTURE:
```html <!-- artifact: [Specific App Name] -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[App Title]</title>
<style>
/* ALL CSS MUST BE INLINE */
</style>
</head>
<body>
<!-- COMPLETE APPLICATION CODE -->
<script type="module">
import { html, render, useState } from 'https://unpkg.com/htm/preact/standalone.module.js';
/* ALL JAVASCRIPT MUST BE INLINE, USE Preact INSTEAD OF PLAIN DOM OPERATION!!! */
</script>
</body>
</html>
```
### TECHNICAL REQUIREMENTS:
- Use Preact with HTM: `import { html, render } from 'https://unpkg.com/htm/preact/standalone.module.js'`
- Modern ES6+ syntax only
- Responsive design with proper viewport meta
- Semantic HTML5 elements
- Accessible UI with proper ARIA labels
- Error handling for user interactions
## EXECUTABLE CODE STANDARDS:
### JavaScript/TypeScript FEATURES:
- Output: console.log(), console.error(), console.warn()
- Graphics: createCanvas(width, height) for visualizations
- Libraries: `// @import libraryName` (lodash, d3, chart.js, moment, axios, rxjs, p5, three, fabric)
- Return values automatically displayed
- Built-in timeout and resource monitoring
### Python FEATURES:
- Output: print() for all results (auto-captured)
- Plotting: matplotlib plots auto-displayed as PNG
- Libraries: numpy, pandas, matplotlib, scipy, scikit-learn, requests, seaborn, plotly
- Memory and execution monitoring included
- No file/network access (sandboxed)
## QUALITY ASSURANCE:
### PRE-SUBMISSION CHECKLIST:
1. ✅ Verify exact comment syntax on same line as ```
2. ✅ Test all interactive functionality
3. ✅ Ensure complete self-contained code
4. ✅ Validate responsive design (HTML)
5. ✅ Confirm proper error handling
6. ✅ Check accessibility features
7. ✅ Verify cross-browser compatibility
### ARTIFACT TITLE GUIDELINES:
- Be specific and descriptive
- Include primary function/purpose
- Avoid generic terms
- Examples:
- ✅ "Interactive BMI Calculator with Health Recommendations"
- ✅ "Real-time Stock Price Chart with Technical Indicators"
- ❌ "Calculator"
- ❌ "Chart"
## RENDERER BEHAVIOR:
The artifact viewer uses specialized renderers:
- **HTML**: Full browser environment with Preact support
- **SVG**: Native SVG rendering with interactive capabilities
- **Mermaid**: Diagram engine with theme support
- **JSON**: Formatted tree view with syntax highlighting
- **JavaScript**: Node.js-like environment with canvas support
- **Python**: Scientific computing sandbox with plot display
⚠️ FINAL REMINDER: Artifacts that don't follow these exact formatting rules will fail to render. Always double-check syntax before submission.
================================================
FILE: api/auth/auth.go
================================================
package auth
import (
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"fmt"
"strings"
"github.com/rotisserie/eris"
"golang.org/x/crypto/pbkdf2"
)
const (
iterations = 260000
saltSize = 16
keySize = 32
)
func generateSalt() ([]byte, error) {
salt := make([]byte, saltSize)
_, err := rand.Read(salt)
return salt, err
}
func GeneratePasswordHash(password string) (string, error) {
salt, err := generateSalt()
if err != nil {
return "", eris.Wrap(err, "error generating salt: ")
}
hash := pbkdf2.Key([]byte(password), salt, iterations, keySize, sha256.New)
encodedHash := base64.StdEncoding.EncodeToString(hash)
encodedSalt := base64.StdEncoding.EncodeToString(salt)
passwordHash := fmt.Sprintf("pbkdf2_sha256$%d$%s$%s", iterations, encodedSalt, encodedHash)
return passwordHash, nil
}
func ValidatePassword(password, hash string) bool {
fields := strings.Split(hash, "$")
if len(fields) != 4 || fields[0] != "pbkdf2_sha256" || fields[1] != fmt.Sprintf("%d", iterations) {
return false
}
encodedSalt := fields[2]
decodedSalt, err := base64.StdEncoding.DecodeString(encodedSalt)
if err != nil {
return false
}
encodedHash := fields[3]
decodedHash, err := base64.StdEncoding.DecodeString(encodedHash)
if err != nil {
return false
}
computedHash := pbkdf2.Key([]byte(password), decodedSalt, iterations, keySize, sha256.New)
return subtle.ConstantTimeCompare(decodedHash, computedHash) == 1
}
func GenerateRandomPassword() (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
password := make([]byte, 12)
_, err := rand.Read(password)
if err != nil {
return "", eris.Wrap(err, "failed to generate random password")
}
for i := 0; i < len(password); i++ {
password[i] = letters[int(password[i])%len(letters)]
}
return string(password), nil
}
================================================
FILE: api/auth/auth_test.go
================================================
package auth
import (
"fmt"
"strings"
"testing"
)
func TestGeneratePasswordHash(t *testing.T) {
password := "mypassword"
hash, err := GeneratePasswordHash(password)
if err != nil {
t.Fatalf("error generating password hash: %v", err)
}
fmt.Println(hash)
// Check that the hash has the correct format
fields := strings.Split(hash, "$")
if len(fields) != 4 || fields[0] != "pbkdf2_sha256" || fields[1] != "260000" {
t.Errorf("unexpected hash format: %s", hash)
}
// Check that we can successfully validate the password using the hash
valid := ValidatePassword(password, hash)
if !valid {
t.Error("generated hash does not validate password")
}
}
func TestGeneratePasswordHash2(t *testing.T) {
password := "@WuHao5"
hash, err := GeneratePasswordHash(password)
if err != nil {
t.Fatalf("error generating password hash: %v", err)
}
fmt.Println(hash)
// Check that the hash has the correct format
fields := strings.Split(hash, "$")
if len(fields) != 4 || fields[0] != "pbkdf2_sha256" || fields[1] != "260000" {
t.Errorf("unexpected hash format: %s", hash)
}
// Check that we can successfully validate the password using the hash
valid := ValidatePassword(password, hash)
if !valid {
t.Error("generated hash does not validate password")
}
}
func TestPass(t *testing.T) {
hash := "pbkdf2_sha256$260000$TSefBGfPi5fY+4whotY5sQ==$/1CeWE2PG6aYdW2DSxYyVol+HEZBmAfDj7zMgEMlxgg="
password := "using555"
// Check that we can successfully validate the password using the hash
valid := ValidatePassword(password, hash)
if !valid {
t.Error("generated hash does not validate password")
}
}
================================================
FILE: api/auth/token.go
================================================
package auth
import (
"encoding/base64"
"errors"
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
jwt "github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
)
func NewUUID() string {
uuidv7, err := uuid.NewV7()
if err != nil {
return uuid.NewString()
}
return uuidv7.String()
}
var ErrInvalidToken = errors.New("invalid token")
const (
TokenTypeAccess = "access"
TokenTypeRefresh = "refresh"
)
func GenJwtSecretAndAudience() (string, string) {
// Generate a random byte string to use as the secret
secretBytes := make([]byte, 32)
rand.Read(secretBytes)
// Convert the byte string to a base64 encoded string
secret := base64.StdEncoding.EncodeToString(secretBytes)
// Generate a random string to use as the audience
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
audienceBytes := make([]byte, 32)
for i := range audienceBytes {
audienceBytes[i] = letters[rand.Intn(len(letters))]
}
audience := string(audienceBytes)
return secret, audience
}
func GenerateToken(userID int32, role string, secret, jwt_audience string, lifetime time.Duration, tokenType string) (string, error) {
if tokenType == "" {
tokenType = TokenTypeAccess
}
expires := time.Now().Add(lifetime).Unix()
notBefore := time.Now().Unix()
issuer := "https://www.bestqa.net"
claims := jwt.MapClaims{
"user_id": strconv.FormatInt(int64(userID), 10),
"exp": expires,
"role": role,
"jti": NewUUID(),
"iss": issuer,
"nbf": notBefore,
"aud": jwt_audience,
"token_type": tokenType,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token.Header["kid"] = "dfsafdsafdsafadsfdasdfs"
signedToken, err := token.SignedString([]byte(secret))
if err != nil {
return "", err
}
return signedToken, nil
}
func ValidateToken(tokenString string, secret string, expectedTokenType string) (int32, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Verify the signing algorithm and secret key used to sign the token
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token)
}
return []byte(secret), nil
})
if err != nil {
return 0, ErrInvalidToken
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
return 0, ErrInvalidToken
}
userIDStr, ok := claims["user_id"].(string)
if !ok {
return 0, ErrInvalidToken
}
tokenType, ok := claims["token_type"].(string)
if !ok {
// Support legacy tokens that were minted without token_type; treat them as access tokens
// so existing forever tokens continue to work.
if expectedTokenType == "" || expectedTokenType == TokenTypeAccess {
tokenType = TokenTypeAccess
} else {
return 0, ErrInvalidToken
}
}
if expectedTokenType != "" && tokenType != expectedTokenType {
return 0, ErrInvalidToken
}
i, err := strconv.Atoi(userIDStr)
if err != nil {
return -1, err
}
return int32(i), nil
}
func GetExpireSecureCookie(value string, isHttps bool) *http.Cookie {
utcOffset := time.Now().UTC().Add(-24 * time.Hour)
options := &http.Cookie{
Name: "jwt",
Value: value,
Path: "/",
HttpOnly: true,
Secure: isHttps,
SameSite: http.SameSiteStrictMode,
Expires: utcOffset,
}
return options
}
================================================
FILE: api/auth/token_test.go
================================================
package auth
import (
"fmt"
"testing"
"time"
)
func TestGenerateToken(t *testing.T) {
user_id := int32(0)
secret := "abedefg"
lifetime := 8 * time.Hour
token, err := GenerateToken(user_id, "user", secret, "aud", lifetime, TokenTypeAccess)
if err != nil {
t.Fatalf("error generating password hash: %v", err)
}
// Check that the hash has the correct format
// Check that we can successfully validate the password using the hash
fmt.Println(token)
user_id_after_valid, err := ValidateToken(token, secret, TokenTypeAccess)
if err != nil {
t.Error("generated token does not validate ")
}
if user_id != user_id_after_valid {
t.Error("generated token does not validate ")
}
}
================================================
FILE: api/bot_answer_history_handler.go
================================================
package main
import (
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type BotAnswerHistoryHandler struct {
service *BotAnswerHistoryService
}
func NewBotAnswerHistoryHandler(q *sqlc_queries.Queries) *BotAnswerHistoryHandler {
service := NewBotAnswerHistoryService(q)
return &BotAnswerHistoryHandler{service: service}
}
func (h *BotAnswerHistoryHandler) Register(router *mux.Router) {
router.HandleFunc("/bot_answer_history", h.CreateBotAnswerHistory).Methods(http.MethodPost)
router.HandleFunc("/bot_answer_history/{id}", h.GetBotAnswerHistoryByID).Methods(http.MethodGet)
router.HandleFunc("/bot_answer_history/bot/{bot_uuid}", h.GetBotAnswerHistoryByBotUUID).Methods(http.MethodGet)
router.HandleFunc("/bot_answer_history/user/{user_id}", h.GetBotAnswerHistoryByUserID).Methods(http.MethodGet)
router.HandleFunc("/bot_answer_history/{id}", h.UpdateBotAnswerHistory).Methods(http.MethodPut)
router.HandleFunc("/bot_answer_history/{id}", h.DeleteBotAnswerHistory).Methods(http.MethodDelete)
router.HandleFunc("/bot_answer_history/bot/{bot_uuid}/count", h.GetBotAnswerHistoryCountByBotUUID).Methods(http.MethodGet)
router.HandleFunc("/bot_answer_history/user/{user_id}/count", h.GetBotAnswerHistoryCountByUserID).Methods(http.MethodGet)
router.HandleFunc("/bot_answer_history/bot/{bot_uuid}/latest", h.GetLatestBotAnswerHistoryByBotUUID).Methods(http.MethodGet)
}
func (h *BotAnswerHistoryHandler) CreateBotAnswerHistory(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, err := getUserID(ctx)
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithDebugInfo(err.Error()))
return
}
var params sqlc_queries.CreateBotAnswerHistoryParams
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Invalid request body").WithDebugInfo(err.Error()))
return
}
// Set the user ID from context
params.UserID = userID
history, err := h.service.CreateBotAnswerHistory(ctx, params)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to create bot answer history"))
return
}
RespondWithJSON(w, http.StatusCreated, history)
}
func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryByID(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if id == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("ID is required"))
return
}
idInt, err := strconv.ParseInt(id, 10, 32)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Invalid ID format"))
return
}
history, err := h.service.GetBotAnswerHistoryByID(r.Context(), int32(idInt))
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get bot answer history"))
return
}
RespondWithJSON(w, http.StatusOK, history)
}
func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryByBotUUID(w http.ResponseWriter, r *http.Request) {
botUUID := mux.Vars(r)["bot_uuid"]
if botUUID == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("Bot UUID is required"))
return
}
limit, offset := getPaginationParams(r)
history, err := h.service.GetBotAnswerHistoryByBotUUID(r.Context(), botUUID, limit, offset)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get bot answer history"))
return
}
// Get total count for pagination
totalCount, err := h.service.GetBotAnswerHistoryCountByBotUUID(r.Context(), botUUID)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get bot answer history count"))
return
}
// Calculate total pages
totalPages := totalCount / int64(limit)
if totalCount%int64(limit) > 0 {
totalPages++
}
// Return paginated response
RespondWithJSON(w, http.StatusOK, map[string]interface{}{
"items": history,
"totalPages": totalPages,
"totalCount": totalCount,
})
}
func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryByUserID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, err := getUserID(ctx)
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithDebugInfo(err.Error()))
return
}
limit, offset := getPaginationParams(r)
history, err := h.service.GetBotAnswerHistoryByUserID(ctx, userID, limit, offset)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get bot answer history"))
return
}
RespondWithJSON(w, http.StatusOK, history)
}
func (h *BotAnswerHistoryHandler) UpdateBotAnswerHistory(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if id == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("ID is required"))
return
}
var params sqlc_queries.UpdateBotAnswerHistoryParams
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Invalid request body").WithDebugInfo(err.Error()))
return
}
history, err := h.service.UpdateBotAnswerHistory(r.Context(), params.ID, params.Answer, params.TokensUsed)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to update bot answer history"))
return
}
RespondWithJSON(w, http.StatusOK, history)
}
func (h *BotAnswerHistoryHandler) DeleteBotAnswerHistory(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if id == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("ID is required"))
return
}
idInt, err := strconv.ParseInt(id, 10, 32)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Invalid ID format"))
return
}
if err := h.service.DeleteBotAnswerHistory(r.Context(), int32(idInt)); err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to delete bot answer history"))
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryCountByBotUUID(w http.ResponseWriter, r *http.Request) {
botUUID := mux.Vars(r)["bot_uuid"]
if botUUID == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("Bot UUID is required"))
return
}
count, err := h.service.GetBotAnswerHistoryCountByBotUUID(r.Context(), botUUID)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get bot answer history count"))
return
}
RespondWithJSON(w, http.StatusOK, map[string]int64{"count": count})
}
func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryCountByUserID(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, err := getUserID(ctx)
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithDebugInfo(err.Error()))
return
}
count, err := h.service.GetBotAnswerHistoryCountByUserID(ctx, userID)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get bot answer history count"))
return
}
RespondWithJSON(w, http.StatusOK, map[string]int64{"count": count})
}
func (h *BotAnswerHistoryHandler) GetLatestBotAnswerHistoryByBotUUID(w http.ResponseWriter, r *http.Request) {
botUUID := mux.Vars(r)["bot_uuid"]
if botUUID == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("Bot UUID is required"))
return
}
limit := getLimitParam(r, 1)
history, err := h.service.GetLatestBotAnswerHistoryByBotUUID(r.Context(), botUUID, limit)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to get latest bot answer history"))
return
}
RespondWithJSON(w, http.StatusOK, history)
}
================================================
FILE: api/bot_answer_history_service.go
================================================
package main
import (
"context"
"github.com/rotisserie/eris"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type BotAnswerHistoryService struct {
q *sqlc_queries.Queries
}
// NewBotAnswerHistoryService creates a new BotAnswerHistoryService
func NewBotAnswerHistoryService(q *sqlc_queries.Queries) *BotAnswerHistoryService {
return &BotAnswerHistoryService{q: q}
}
// CreateBotAnswerHistory creates a new bot answer history entry
func (s *BotAnswerHistoryService) CreateBotAnswerHistory(ctx context.Context, params sqlc_queries.CreateBotAnswerHistoryParams) (sqlc_queries.BotAnswerHistory, error) {
history, err := s.q.CreateBotAnswerHistory(ctx, params)
if err != nil {
return sqlc_queries.BotAnswerHistory{}, eris.Wrap(err, "failed to create bot answer history")
}
return history, nil
}
// GetBotAnswerHistoryByID gets a bot answer history entry by ID
func (s *BotAnswerHistoryService) GetBotAnswerHistoryByID(ctx context.Context, id int32) (sqlc_queries.GetBotAnswerHistoryByIDRow, error) {
history, err := s.q.GetBotAnswerHistoryByID(ctx, id)
if err != nil {
return sqlc_queries.GetBotAnswerHistoryByIDRow{}, eris.Wrap(err, "failed to get bot answer history by ID")
}
return history, nil
}
// GetBotAnswerHistoryByBotUUID gets paginated bot answer history for a specific bot
func (s *BotAnswerHistoryService) GetBotAnswerHistoryByBotUUID(ctx context.Context, botUUID string, limit, offset int32) ([]sqlc_queries.GetBotAnswerHistoryByBotUUIDRow, error) {
params := sqlc_queries.GetBotAnswerHistoryByBotUUIDParams{
BotUuid: botUUID,
Limit: limit,
Offset: offset,
}
history, err := s.q.GetBotAnswerHistoryByBotUUID(ctx, params)
if err != nil {
return nil, eris.Wrap(err, "failed to get bot answer history by bot UUID")
}
return history, nil
}
// GetBotAnswerHistoryByUserID gets paginated bot answer history for a specific user
func (s *BotAnswerHistoryService) GetBotAnswerHistoryByUserID(ctx context.Context, userID, limit, offset int32) ([]sqlc_queries.GetBotAnswerHistoryByUserIDRow, error) {
params := sqlc_queries.GetBotAnswerHistoryByUserIDParams{
UserID: userID,
Limit: limit,
Offset: offset,
}
history, err := s.q.GetBotAnswerHistoryByUserID(ctx, params)
if err != nil {
return nil, eris.Wrap(err, "failed to get bot answer history by user ID")
}
return history, nil
}
// UpdateBotAnswerHistory updates an existing bot answer history entry
func (s *BotAnswerHistoryService) UpdateBotAnswerHistory(ctx context.Context, id int32, answer string, tokensUsed int32) (sqlc_queries.BotAnswerHistory, error) {
params := sqlc_queries.UpdateBotAnswerHistoryParams{
ID: id,
Answer: answer,
TokensUsed: tokensUsed,
}
history, err := s.q.UpdateBotAnswerHistory(ctx, params)
if err != nil {
return sqlc_queries.BotAnswerHistory{}, eris.Wrap(err, "failed to update bot answer history")
}
return history, nil
}
// DeleteBotAnswerHistory deletes a bot answer history entry by ID
func (s *BotAnswerHistoryService) DeleteBotAnswerHistory(ctx context.Context, id int32) error {
err := s.q.DeleteBotAnswerHistory(ctx, id)
if err != nil {
return eris.Wrap(err, "failed to delete bot answer history")
}
return nil
}
// GetBotAnswerHistoryCountByBotUUID gets the count of history entries for a bot
func (s *BotAnswerHistoryService) GetBotAnswerHistoryCountByBotUUID(ctx context.Context, botUUID string) (int64, error) {
count, err := s.q.GetBotAnswerHistoryCountByBotUUID(ctx, botUUID)
if err != nil {
return 0, eris.Wrap(err, "failed to get bot answer history count by bot UUID")
}
return count, nil
}
// GetBotAnswerHistoryCountByUserID gets the count of history entries for a user
func (s *BotAnswerHistoryService) GetBotAnswerHistoryCountByUserID(ctx context.Context, userID int32) (int64, error) {
count, err := s.q.GetBotAnswerHistoryCountByUserID(ctx, userID)
if err != nil {
return 0, eris.Wrap(err, "failed to get bot answer history count by user ID")
}
return count, nil
}
// GetLatestBotAnswerHistoryByBotUUID gets the latest history entries for a bot
func (s *BotAnswerHistoryService) GetLatestBotAnswerHistoryByBotUUID(ctx context.Context, botUUID string, limit int32) ([]sqlc_queries.GetLatestBotAnswerHistoryByBotUUIDRow, error) {
params := sqlc_queries.GetLatestBotAnswerHistoryByBotUUIDParams{
BotUuid: botUUID,
Limit: limit,
}
history, err := s.q.GetLatestBotAnswerHistoryByBotUUID(ctx, params)
if err != nil {
return nil, eris.Wrap(err, "failed to get latest bot answer history by bot UUID")
}
return history, nil
}
================================================
FILE: api/chat_artifact.go
================================================
package main
import (
"regexp"
"strings"
)
// extractArtifacts detects and extracts artifacts from message content
func extractArtifacts(content string) []Artifact {
var artifacts []Artifact
// Pattern for HTML artifacts (check specific types first)
// Example: ```html <!-- artifact: Interactive Demo -->
htmlArtifactRegex := regexp.MustCompile(`(?s)` + "```" + `html\s*<!--\s*artifact:\s*([^>]+?)\s*-->\s*\n(.*?)\n` + "```")
htmlMatches := htmlArtifactRegex.FindAllStringSubmatch(content, -1)
for _, match := range htmlMatches {
title := strings.TrimSpace(match[1])
artifactContent := strings.TrimSpace(match[2])
artifact := Artifact{
UUID: NewUUID(),
Type: "html",
Title: title,
Content: artifactContent,
Language: "html",
}
artifacts = append(artifacts, artifact)
}
// Pattern for SVG artifacts
// Example: ```svg <!-- artifact: Logo Design -->
svgArtifactRegex := regexp.MustCompile(`(?s)` + "```" + `svg\s*<!--\s*artifact:\s*([^>]+?)\s*-->\s*\n(.*?)\n` + "```")
svgMatches := svgArtifactRegex.FindAllStringSubmatch(content, -1)
for _, match := range svgMatches {
title := strings.TrimSpace(match[1])
artifactContent := strings.TrimSpace(match[2])
artifact := Artifact{
UUID: NewUUID(),
Type: "svg",
Title: title,
Content: artifactContent,
Language: "svg",
}
artifacts = append(artifacts, artifact)
}
// Pattern for Mermaid diagrams
// Example: ```mermaid <!-- artifact: Flow Chart -->
mermaidArtifactRegex := regexp.MustCompile(`(?s)` + "```" + `mermaid\s*<!--\s*artifact:\s*([^>]+?)\s*-->\s*\n(.*?)\n` + "```")
mermaidMatches := mermaidArtifactRegex.FindAllStringSubmatch(content, -1)
for _, match := range mermaidMatches {
title := strings.TrimSpace(match[1])
artifactContent := strings.TrimSpace(match[2])
artifact := Artifact{
UUID: NewUUID(),
Type: "mermaid",
Title: title,
Content: artifactContent,
Language: "mermaid",
}
artifacts = append(artifacts, artifact)
}
// Pattern for JSON artifacts
// Example: ```json <!-- artifact: API Response -->
jsonArtifactRegex := regexp.MustCompile(`(?s)` + "```" + `json\s*<!--\s*artifact:\s*([^>]+?)\s*-->\s*\n(.*?)\n` + "```")
jsonMatches := jsonArtifactRegex.FindAllStringSubmatch(content, -1)
for _, match := range jsonMatches {
title := strings.TrimSpace(match[1])
artifactContent := strings.TrimSpace(match[2])
artifact := Artifact{
UUID: NewUUID(),
Type: "json",
Title: title,
Content: artifactContent,
Language: "json",
}
artifacts = append(artifacts, artifact)
}
// Pattern for executable code artifacts
// Example: ```javascript <!-- executable: Calculator -->
executableArtifactRegex := regexp.MustCompile(`(?s)` + "```" + `(\w+)?\s*<!--\s*executable:\s*([^>]+?)\s*-->\s*\n(.*?)\n` + "```")
executableMatches := executableArtifactRegex.FindAllStringSubmatch(content, -1)
for _, match := range executableMatches {
language := match[1]
title := strings.TrimSpace(match[2])
artifactContent := strings.TrimSpace(match[3])
// Skip if already processed as HTML, SVG, Mermaid, or JSON
if language == "html" || language == "svg" || language == "mermaid" || language == "json" {
continue
}
if language == "" {
language = "javascript" // Default to JavaScript for executable code
}
// Only create executable artifacts for supported languages
if isExecutableLanguage(language) {
artifact := Artifact{
UUID: NewUUID(),
Type: "executable-code",
Title: title,
Content: artifactContent,
Language: language,
}
artifacts = append(artifacts, artifact)
}
}
// Pattern for general code artifacts (exclude html and svg which are handled above)
// Example: ```javascript <!-- artifact: React Component -->
codeArtifactRegex := regexp.MustCompile(`(?s)` + "```" + `(\w+)?\s*<!--\s*artifact:\s*([^>]+?)\s*-->\s*\n(.*?)\n` + "```")
matches := codeArtifactRegex.FindAllStringSubmatch(content, -1)
for _, match := range matches {
language := match[1]
title := strings.TrimSpace(match[2])
artifactContent := strings.TrimSpace(match[3])
// Skip if already processed as HTML, SVG, Mermaid, JSON, or executable
if language == "html" || language == "svg" || language == "mermaid" || language == "json" {
continue
}
if language == "" {
language = "text"
}
// Check if this should be an executable artifact for supported languages
artifactType := "code"
if isExecutableLanguage(language) {
// For supported languages, make them executable by default if they contain certain patterns
if containsExecutablePatterns(artifactContent) {
artifactType = "executable-code"
}
}
artifact := Artifact{
UUID: NewUUID(),
Type: artifactType,
Title: title,
Content: artifactContent,
Language: language,
}
artifacts = append(artifacts, artifact)
}
return artifacts
}
// isExecutableLanguage checks if a language is supported for code execution
func isExecutableLanguage(language string) bool {
executableLanguages := []string{
"javascript", "js", "typescript", "ts",
"python", "py",
}
language = strings.ToLower(strings.TrimSpace(language))
for _, execLang := range executableLanguages {
if language == execLang {
return true
}
}
return false
}
// containsExecutablePatterns checks if code contains patterns that suggest it should be executable
func containsExecutablePatterns(content string) bool {
// Patterns that suggest the code is meant to be executed
executablePatterns := []string{
// JavaScript patterns
"console.log",
"console.error",
"console.warn",
"function",
"const ",
"let ",
"var ",
"=>",
"if (",
"for (",
"while (",
"return ",
// Python patterns
"print(",
"import ",
"from ",
"def ",
"if __name__",
"class ",
"for ",
"while ",
}
contentLower := strings.ToLower(content)
for _, pattern := range executablePatterns {
if strings.Contains(contentLower, pattern) {
return true
}
}
return false
}
================================================
FILE: api/chat_auth_user_handler.go
================================================
package main
import (
"context"
"encoding/json"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/swuecho/chat_backend/auth"
"github.com/swuecho/chat_backend/sqlc_queries"
)
// Constants for token management
const (
AccessTokenLifetime = 30 * time.Minute
RefreshTokenLifetime = 7 * 24 * time.Hour // 7 days
RefreshTokenName = "refresh_token"
)
type AuthUserHandler struct {
service *AuthUserService
}
// isHTTPS checks if the request is using HTTPS or if we're in production
func isHTTPS(r *http.Request) bool {
// Check if request is HTTPS
if r.TLS != nil {
return true
}
// Check common proxy headers
if r.Header.Get("X-Forwarded-Proto") == "https" {
return true
}
if r.Header.Get("X-Forwarded-Ssl") == "on" {
return true
}
// Check if environment indicates production
env := os.Getenv("ENV")
if env == "" {
env = os.Getenv("ENVIRONMENT")
}
if env == "" {
env = os.Getenv("NODE_ENV")
}
return env == "production" || env == "prod"
}
// createSecureRefreshCookie creates a secure httpOnly cookie for refresh tokens
func createSecureRefreshCookie(name, value string, maxAge int, r *http.Request) *http.Cookie {
// Determine the appropriate SameSite setting based on environment
sameSite := http.SameSiteLaxMode // More permissive for development
if isHTTPS(r) {
sameSite = http.SameSiteStrictMode // Strict for HTTPS
}
// Determine domain based on environment
var domain string
host := r.Host
if host != "" && !strings.HasPrefix(host, "localhost") && !strings.HasPrefix(host, "127.0.0.1") {
// For production, set domain without port
if strings.Contains(host, ":") {
domain = strings.Split(host, ":")[0]
} else {
domain = host
}
}
cookie := &http.Cookie{
Name: name,
Value: value,
HttpOnly: true,
Secure: isHTTPS(r),
SameSite: sameSite,
Path: "/",
MaxAge: maxAge,
}
// Only set domain if it's not localhost
if domain != "" && domain != "localhost" && domain != "127.0.0.1" {
cookie.Domain = domain
}
return cookie
}
func NewAuthUserHandler(sqlc_q *sqlc_queries.Queries) *AuthUserHandler {
userService := NewAuthUserService(sqlc_q)
return &AuthUserHandler{service: userService}
}
func (h *AuthUserHandler) Register(router *mux.Router) {
// Authenticated user routes
router.HandleFunc("/users", h.GetUserByID).Methods(http.MethodGet)
router.HandleFunc("/users/{id}", h.UpdateSelf).Methods(http.MethodPut)
router.HandleFunc("/token_10years", h.ForeverToken).Methods(http.MethodGet)
}
func (h *AuthUserHandler) RegisterPublicRoutes(router *mux.Router) {
// Public routes (no authentication required)
router.HandleFunc("/signup", h.SignUp).Methods(http.MethodPost)
router.HandleFunc("/login", h.Login).Methods(http.MethodPost)
router.HandleFunc("/auth/refresh", h.RefreshToken).Methods(http.MethodPost)
router.HandleFunc("/logout", h.Logout).Methods(http.MethodPost)
}
func (h *AuthUserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var userParams sqlc_queries.CreateAuthUserParams
err := json.NewDecoder(r.Body).Decode(&userParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
user, err := h.service.CreateAuthUser(r.Context(), userParams)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to create user"))
return
}
json.NewEncoder(w).Encode(user)
}
func (h *AuthUserHandler) GetUserByID(w http.ResponseWriter, r *http.Request) {
userID, err := getUserID(r.Context())
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithDebugInfo(err.Error()))
return
}
user, err := h.service.GetAuthUserByID(r.Context(), userID)
if err != nil {
RespondWithAPIError(w, ErrResourceNotFound("user"))
return
}
json.NewEncoder(w).Encode(user)
}
func (h *AuthUserHandler) UpdateSelf(w http.ResponseWriter, r *http.Request) {
userID, err := getUserID(r.Context())
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithDebugInfo(err.Error()))
return
}
var userParams sqlc_queries.UpdateAuthUserParams
err = json.NewDecoder(r.Body).Decode(&userParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
userParams.ID = userID
user, err := h.service.q.UpdateAuthUser(r.Context(), userParams)
if err != nil {
log.WithError(err).Error("Failed to update user")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to update user").WithDebugInfo(err.Error()))
return
}
json.NewEncoder(w).Encode(user)
}
func (h *AuthUserHandler) UpdateUser(w http.ResponseWriter, r *http.Request) {
// get user id from var
// to int32
var userParams sqlc_queries.UpdateAuthUserByEmailParams
err := json.NewDecoder(r.Body).Decode(&userParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
user, err := h.service.q.UpdateAuthUserByEmail(r.Context(), userParams)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update user"))
return
}
json.NewEncoder(w).Encode(user)
}
type LoginParams struct {
Email string `json:"email"`
Password string `json:"password"`
}
func (h *AuthUserHandler) SignUp(w http.ResponseWriter, r *http.Request) {
var params LoginParams
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
log.WithFields(log.Fields{
"error": err.Error(),
"ip": r.RemoteAddr,
"action": "signup_decode_error",
}).Warn("Failed to decode signup request")
RespondWithAPIError(w, ErrValidationInvalidInput("Invalid request: unable to decode JSON body").WithDebugInfo(err.Error()))
return
}
log.WithFields(log.Fields{
"email": params.Email,
"ip": r.RemoteAddr,
"action": "signup_attempt",
}).Info("User signup attempt")
hash, err := auth.GeneratePasswordHash(params.Password)
if err != nil {
log.WithFields(log.Fields{
"email": params.Email,
"error": err.Error(),
}).Error("Failed to generate password hash")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate password hash").WithDebugInfo(err.Error()))
return
}
userParams := sqlc_queries.CreateAuthUserParams{
Password: hash,
Email: params.Email,
Username: params.Email,
}
user, err := h.service.CreateAuthUser(r.Context(), userParams)
if err != nil {
log.WithFields(log.Fields{
"email": params.Email,
"error": err.Error(),
}).Error("Failed to create user")
RespondWithAPIError(w, WrapError(err, "Failed to create user"))
return
}
// Generate access token using constant
tokenString, err := auth.GenerateToken(user.ID, user.Role(), jwtSecretAndAud.Secret, jwtSecretAndAud.Audience, AccessTokenLifetime, auth.TokenTypeAccess)
if err != nil {
log.WithFields(log.Fields{
"user_id": user.ID,
"error": err.Error(),
}).Error("Failed to generate access token")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate token").WithDebugInfo(err.Error()))
return
}
// Generate refresh token using constant
refreshToken, err := auth.GenerateToken(user.ID, user.Role(), jwtSecretAndAud.Secret, jwtSecretAndAud.Audience, RefreshTokenLifetime, auth.TokenTypeRefresh)
if err != nil {
log.WithFields(log.Fields{
"user_id": user.ID,
"error": err.Error(),
}).Error("Failed to generate refresh token")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate refresh token").WithDebugInfo(err.Error()))
return
}
// Use helper function to create refresh token cookie
refreshCookie := createSecureRefreshCookie(RefreshTokenName, refreshToken, int(RefreshTokenLifetime.Seconds()), r)
http.SetCookie(w, refreshCookie)
log.WithFields(log.Fields{
"user_id": user.ID,
"email": user.Email,
"action": "signup_success",
}).Info("User signup successful")
w.Header().Set("Content-Type", "application/json")
expiresIn := time.Now().Add(AccessTokenLifetime).Unix()
json.NewEncoder(w).Encode(TokenResult{AccessToken: tokenString, ExpiresIn: int(expiresIn)})
w.WriteHeader(http.StatusOK)
}
func (h *AuthUserHandler) Login(w http.ResponseWriter, r *http.Request) {
var loginParams LoginParams
err := json.NewDecoder(r.Body).Decode(&loginParams)
if err != nil {
log.WithFields(log.Fields{
"error": err.Error(),
"ip": r.RemoteAddr,
"action": "login_decode_error",
}).Warn("Failed to decode login request")
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
log.WithFields(log.Fields{
"email": loginParams.Email,
"ip": r.RemoteAddr,
"action": "login_attempt",
}).Info("User login attempt")
user, err := h.service.Authenticate(r.Context(), loginParams.Email, loginParams.Password)
if err != nil {
log.WithFields(log.Fields{
"email": loginParams.Email,
"ip": r.RemoteAddr,
"error": err.Error(),
"action": "login_failed",
}).Warn("User login failed")
RespondWithAPIError(w, ErrAuthInvalidEmailOrPassword.WithDebugInfo(err.Error()))
return
}
// Generate access token using constant
accessToken, err := auth.GenerateToken(user.ID, user.Role(), jwtSecretAndAud.Secret, jwtSecretAndAud.Audience, AccessTokenLifetime, auth.TokenTypeAccess)
if err != nil {
log.WithFields(log.Fields{
"user_id": user.ID,
"error": err.Error(),
}).Error("Failed to generate access token")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate access token").WithDebugInfo(err.Error()))
return
}
// Generate refresh token using constant
refreshToken, err := auth.GenerateToken(user.ID, user.Role(), jwtSecretAndAud.Secret, jwtSecretAndAud.Audience, RefreshTokenLifetime, auth.TokenTypeRefresh)
if err != nil {
log.WithFields(log.Fields{
"user_id": user.ID,
"error": err.Error(),
}).Error("Failed to generate refresh token")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate refresh token").WithDebugInfo(err.Error()))
return
}
// Use helper function to create refresh token cookie
refreshCookie := createSecureRefreshCookie(RefreshTokenName, refreshToken, int(RefreshTokenLifetime.Seconds()), r)
http.SetCookie(w, refreshCookie)
// Debug: Log cookie details
log.WithFields(log.Fields{
"user_id": user.ID,
"name": refreshCookie.Name,
"domain": refreshCookie.Domain,
"path": refreshCookie.Path,
"secure": refreshCookie.Secure,
"sameSite": refreshCookie.SameSite,
"action": "login_cookie_set",
}).Info("Refresh token cookie set")
log.WithFields(log.Fields{
"user_id": user.ID,
"email": user.Email,
"action": "login_success",
}).Info("User login successful")
w.Header().Set("Content-Type", "application/json")
expiresIn := time.Now().Add(AccessTokenLifetime).Unix()
json.NewEncoder(w).Encode(TokenResult{AccessToken: accessToken, ExpiresIn: int(expiresIn)})
w.WriteHeader(http.StatusOK)
}
func (h *AuthUserHandler) ForeverToken(w http.ResponseWriter, r *http.Request) {
lifetime := time.Duration(10*365*24) * time.Hour
userId, _ := getUserID(r.Context())
userRole := r.Context().Value(userContextKey).(string)
token, err := auth.GenerateToken(userId, userRole, jwtSecretAndAud.Secret, jwtSecretAndAud.Audience, lifetime, auth.TokenTypeAccess)
if err != nil {
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate token").WithDebugInfo(err.Error()))
return
}
w.Header().Set("Content-Type", "application/json")
expiresIn := time.Now().Add(lifetime).Unix()
json.NewEncoder(w).Encode(TokenResult{AccessToken: token, ExpiresIn: int(expiresIn)})
w.WriteHeader(http.StatusOK)
}
func (h *AuthUserHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"action": "refresh_attempt",
}).Info("Token refresh attempt")
// Debug: Log all cookies to help diagnose the issue
allCookies := r.Cookies()
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"cookies": len(allCookies),
"action": "refresh_debug_cookies",
}).Info("All cookies received")
for _, cookie := range allCookies {
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"name": cookie.Name,
"domain": cookie.Domain,
"path": cookie.Path,
"action": "refresh_debug_cookie",
}).Info("Cookie details")
}
// Get refresh token from httpOnly cookie
refreshCookie, err := r.Cookie(RefreshTokenName)
if err != nil {
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"error": err.Error(),
"action": "refresh_missing_cookie",
}).Warn("Missing refresh token cookie")
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithMessage("Missing refresh token"))
return
}
// Validate refresh token
result := parseAndValidateJWT(refreshCookie.Value, auth.TokenTypeRefresh)
if result.Error != nil {
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"error": result.Error.Detail,
"action": "refresh_invalid_token",
}).Warn("Invalid refresh token")
RespondWithAPIError(w, *result.Error)
return
}
// Convert UserID string back to int32
userIDInt, err := strconv.ParseInt(result.UserID, 10, 32)
if err != nil {
log.WithFields(log.Fields{
"user_id": result.UserID,
"error": err.Error(),
"action": "refresh_invalid_user_id",
}).Error("Invalid user ID in refresh token")
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithMessage("Invalid user ID in token"))
return
}
// Generate new access token using constant
accessToken, err := auth.GenerateToken(int32(userIDInt), result.Role, jwtSecretAndAud.Secret, jwtSecretAndAud.Audience, AccessTokenLifetime, auth.TokenTypeAccess)
if err != nil {
log.WithFields(log.Fields{
"user_id": userIDInt,
"error": err.Error(),
}).Error("Failed to generate access token during refresh")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate access token").WithDebugInfo(err.Error()))
return
}
log.WithFields(log.Fields{
"user_id": userIDInt,
"action": "refresh_success",
}).Info("Token refresh successful")
w.Header().Set("Content-Type", "application/json")
expiresIn := time.Now().Add(AccessTokenLifetime).Unix()
json.NewEncoder(w).Encode(TokenResult{AccessToken: accessToken, ExpiresIn: int(expiresIn)})
}
func (h *AuthUserHandler) Logout(w http.ResponseWriter, r *http.Request) {
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"action": "logout_attempt",
}).Info("User logout attempt")
// Clear refresh token cookie using the same domain logic as creation
refreshCookie := createSecureRefreshCookie(RefreshTokenName, "", -1, r)
http.SetCookie(w, refreshCookie)
log.WithFields(log.Fields{
"ip": r.RemoteAddr,
"action": "logout_success",
}).Info("User logout successful")
w.WriteHeader(http.StatusOK)
}
type TokenRequest struct {
Token string `json:"token"`
}
type ResetPasswordRequest struct {
Email string `json:"email"`
}
func (h *AuthUserHandler) ResetPasswordHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
var req ResetPasswordRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
// Retrieve user account from the database by email address
user, err := h.service.q.GetUserByEmail(context.Background(), req.Email)
if err != nil {
RespondWithAPIError(w, ErrResourceNotFound("user"))
return
}
// Generate temporary password
tempPassword, err := auth.GenerateRandomPassword()
if err != nil {
log.WithError(err).Error("Failed to generate temporary password")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to generate temporary password").WithDebugInfo(err.Error()))
return
}
// Hash temporary password
hashedPassword, err := auth.GeneratePasswordHash(tempPassword)
if err != nil {
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to hash password").WithDebugInfo(err.Error()))
return
}
// Update user account with new hashed password
err = h.service.q.UpdateUserPassword(
context.Background(),
sqlc_queries.UpdateUserPasswordParams{
Email: req.Email,
Password: hashedPassword,
})
if err != nil {
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to update password").WithDebugInfo(err.Error()))
return
}
// Send email to the user with temporary password and instructions
err = SendPasswordResetEmail(user.Email, tempPassword)
if err != nil {
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to send password reset email").WithDebugInfo(err.Error()))
return
}
w.WriteHeader(http.StatusOK)
}
func SendPasswordResetEmail(email, tempPassword string) error {
return nil
}
type ChangePasswordRequest struct {
Email string `json:"email"`
NewPassword string `json:"new_password"`
}
func (h *AuthUserHandler) ChangePasswordHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
var req ChangePasswordRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
// Hash new password
hashedPassword, err := auth.GeneratePasswordHash(req.NewPassword)
if err != nil {
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("Failed to hash password").WithDebugInfo(err.Error()))
return
}
// Update password in the database
err = h.service.q.UpdateUserPassword(context.Background(), sqlc_queries.UpdateUserPasswordParams{
Email: req.Email,
Password: string(hashedPassword),
})
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update password"))
return
}
w.WriteHeader(http.StatusOK)
}
type UserStat struct {
Email string `json:"email"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
TotalChatMessages int64 `json:"totalChatMessages"`
TotalChatMessagesTokenCount int64 `json:"totalChatMessagesTokenCount"`
TotalChatMessages3Days int64 `json:"totalChatMessages3Days"`
TotalChatMessages3DaysTokenCount int64 `json:"totalChatMessages3DaysTokenCount"`
AvgChatMessages3DaysTokenCount int64 `json:"avgChatMessages3DaysTokenCount"`
RateLimit int32 `json:"rateLimit"`
}
func (h *AuthUserHandler) UserStatHandler(w http.ResponseWriter, r *http.Request) {
var pagination Pagination
err := json.NewDecoder(r.Body).Decode(&pagination)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
userStatsRows, total, err := h.service.GetUserStats(r.Context(), pagination, int32(appConfig.OPENAI.RATELIMIT))
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get user stats"))
return
}
// Create a new []interface{} slice with same length as userStatsRows
data := make([]interface{}, len(userStatsRows))
// Copy the contents of userStatsRows into data
for i, v := range userStatsRows {
divider := v.TotalChatMessages3Days
var avg int64
if divider > 0 {
avg = v.TotalTokenCount3Days / v.TotalChatMessages3Days
} else {
avg = 0
}
data[i] = UserStat{
Email: v.UserEmail,
FirstName: v.FirstName,
LastName: v.LastName,
TotalChatMessages: v.TotalChatMessages,
TotalChatMessages3Days: v.TotalChatMessages3Days,
RateLimit: v.RateLimit,
TotalChatMessagesTokenCount: v.TotalTokenCount,
TotalChatMessages3DaysTokenCount: v.TotalTokenCount3Days,
AvgChatMessages3DaysTokenCount: avg,
}
}
json.NewEncoder(w).Encode(Pagination{
Page: pagination.Page,
Size: pagination.Size,
Total: total,
Data: data,
})
}
type RateLimitRequest struct {
Email string `json:"email"`
RateLimit int32 `json:"rateLimit"`
}
func (h *AuthUserHandler) UpdateRateLimit(w http.ResponseWriter, r *http.Request) {
var rateLimitRequest RateLimitRequest
err := json.NewDecoder(r.Body).Decode(&rateLimitRequest)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
rate, err := h.service.q.UpdateAuthUserRateLimitByEmail(r.Context(),
sqlc_queries.UpdateAuthUserRateLimitByEmailParams{
Email: rateLimitRequest.Email,
RateLimit: rateLimitRequest.RateLimit,
})
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update rate limit"))
return
}
json.NewEncoder(w).Encode(
map[string]int32{
"rate": rate,
})
}
func (h *AuthUserHandler) GetRateLimit(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
userID, err := getUserID(ctx)
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithDebugInfo(err.Error()))
return
}
rate, err := h.service.q.GetRateLimit(ctx, userID)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get rate limit"))
return
}
json.NewEncoder(w).Encode(map[string]int32{
"rate": rate,
})
}
================================================
FILE: api/chat_auth_user_service.go
================================================
package main
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/rotisserie/eris"
"github.com/swuecho/chat_backend/auth"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type AuthUserService struct {
q *sqlc_queries.Queries
}
// NewAuthUserService creates a new AuthUserService.
func NewAuthUserService(q *sqlc_queries.Queries) *AuthUserService {
return &AuthUserService{q: q}
}
// CreateAuthUser creates a new authentication user record.
func (s *AuthUserService) CreateAuthUser(ctx context.Context, auth_user_params sqlc_queries.CreateAuthUserParams) (sqlc_queries.AuthUser, error) {
totalUserCount, err := s.q.GetTotalActiveUserCount(ctx)
if err != nil {
return sqlc_queries.AuthUser{}, errors.New("failed to retrieve total user count")
}
if totalUserCount == 0 {
auth_user_params.IsSuperuser = true
fmt.Println("First user is superuser.")
}
auth_user, err := s.q.CreateAuthUser(ctx, auth_user_params)
if err != nil {
return sqlc_queries.AuthUser{}, err
}
return auth_user, nil
}
// GetAuthUserByID returns an authentication user record by ID.
func (s *AuthUserService) GetAuthUserByID(ctx context.Context, id int32) (sqlc_queries.AuthUser, error) {
auth_user, err := s.q.GetAuthUserByID(ctx, id)
if err != nil {
return sqlc_queries.AuthUser{}, errors.New("failed to retrieve authentication user")
}
return auth_user, nil
}
// GetAllAuthUsers returns all authentication user records.
func (s *AuthUserService) GetAllAuthUsers(ctx context.Context) ([]sqlc_queries.AuthUser, error) {
auth_users, err := s.q.GetAllAuthUsers(ctx)
if err != nil {
return nil, errors.New("failed to retrieve authentication users")
}
return auth_users, nil
}
func (s *AuthUserService) Authenticate(ctx context.Context, email, password string) (sqlc_queries.AuthUser, error) {
user, err := s.q.GetUserByEmail(ctx, email)
if err != nil {
return sqlc_queries.AuthUser{}, err
}
if !auth.ValidatePassword(password, user.Password) {
return sqlc_queries.AuthUser{}, ErrAuthInvalidCredentials
}
return user, nil
}
func (s *AuthUserService) Logout(tokenString string) (*http.Cookie, error) {
userID, err := auth.ValidateToken(tokenString, jwtSecretAndAud.Secret, auth.TokenTypeAccess)
if err != nil {
return nil, err
}
// Implement a mechanism to track invalidated tokens for the given user ID
// auth.AddInvalidToken(userID, "insert-invalidated-token-here")
cookie := auth.GetExpireSecureCookie(strconv.Itoa(int(userID)), false)
return cookie, nil
}
// backend api
// GetUserStat(page, page_size) -> {data: [{user_email, total_sessions, total_messages, total_sessions_3_days, total_messages_3_days, rate_limit}], total: 100}
// GetTotalUserCount
// GetUserStat(page, page_size) ->[{user_email, total_sessions, total_messages, total_sessions_3_days, total_messages_3_days, rate_limit}]
func (s *AuthUserService) GetUserStats(ctx context.Context, p Pagination, defaultRateLimit int32) ([]sqlc_queries.GetUserStatsRow, int64, error) {
auth_users_stat, err := s.q.GetUserStats(ctx,
sqlc_queries.GetUserStatsParams{
Offset: p.Offset(),
Limit: p.Size,
DefaultRateLimit: defaultRateLimit,
})
if err != nil {
return nil, 0, eris.Wrap(err, "failed to retrieve user stats ")
}
total, err := s.q.GetTotalActiveUserCount(ctx)
if err != nil {
return nil, 0, errors.New("failed to retrieve total active user count")
}
return auth_users_stat, total, nil
}
// UpdateRateLimit(user_email, rate_limit) -> { rate_limit: 100 }
func (s *AuthUserService) UpdateRateLimit(ctx context.Context, user_email string, rate_limit int32) (int32, error) {
auth_user_params := sqlc_queries.UpdateAuthUserRateLimitByEmailParams{
Email: user_email,
RateLimit: rate_limit,
}
rate, err := s.q.UpdateAuthUserRateLimitByEmail(ctx, auth_user_params)
if err != nil {
return -1, errors.New("failed to update authentication user")
}
return rate, nil
}
// get ratelimit for user_id
func (s *AuthUserService) GetRateLimit(ctx context.Context, user_id int32) (int32, error) {
rate, err := s.q.GetRateLimit(ctx, user_id)
if err != nil {
return -1, errors.New("failed to get rate limit")
}
return rate, nil
}
// UserAnalysisData represents the complete user analysis response
type UserAnalysisData struct {
UserInfo UserAnalysisInfo `json:"userInfo"`
ModelUsage []ModelUsageInfo `json:"modelUsage"`
RecentActivity []ActivityInfo `json:"recentActivity"`
}
type UserAnalysisInfo struct {
Email string `json:"email"`
TotalMessages int64 `json:"totalMessages"`
TotalTokens int64 `json:"totalTokens"`
TotalSessions int64 `json:"totalSessions"`
Messages3Days int64 `json:"messages3Days"`
Tokens3Days int64 `json:"tokens3Days"`
RateLimit int32 `json:"rateLimit"`
}
type ModelUsageInfo struct {
Model string `json:"model"`
MessageCount int64 `json:"messageCount"`
TokenCount int64 `json:"tokenCount"`
Percentage float64 `json:"percentage"`
LastUsed time.Time `json:"lastUsed"`
}
type ActivityInfo struct {
Date time.Time `json:"date"`
Messages int64 `json:"messages"`
Tokens int64 `json:"tokens"`
Sessions int64 `json:"sessions"`
}
type SessionHistoryInfo struct {
SessionID string `json:"sessionId"`
Model string `json:"model"`
MessageCount int64 `json:"messageCount"`
TokenCount int64 `json:"tokenCount"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// GetUserAnalysis retrieves comprehensive user analysis data
func (s *AuthUserService) GetUserAnalysis(ctx context.Context, email string, defaultRateLimit int32) (*UserAnalysisData, error) {
// Get basic user info
userInfo, err := s.q.GetUserAnalysisByEmail(ctx, sqlc_queries.GetUserAnalysisByEmailParams{
Email: email,
DefaultRateLimit: defaultRateLimit,
})
if err != nil {
return nil, eris.Wrap(err, "failed to get user analysis")
}
// Get model usage
modelUsageRows, err := s.q.GetUserModelUsageByEmail(ctx, email)
if err != nil {
return nil, eris.Wrap(err, "failed to get user model usage")
}
// Calculate total tokens for percentage calculation
var totalTokens int64
for _, row := range modelUsageRows {
if row.TokenCount != nil {
if tc, ok := row.TokenCount.(int64); ok {
totalTokens += tc
}
}
}
modelUsage := make([]ModelUsageInfo, len(modelUsageRows))
for i, row := range modelUsageRows {
// Convert interface{} to int64 safely
tokenCount := int64(0)
if row.TokenCount != nil {
if tc, ok := row.TokenCount.(int64); ok {
tokenCount = tc
}
}
percentage := float64(0)
if totalTokens > 0 {
percentage = float64(tokenCount) / float64(totalTokens) * 100
}
modelUsage[i] = ModelUsageInfo{
Model: row.Model,
MessageCount: row.MessageCount,
TokenCount: tokenCount,
Percentage: percentage,
LastUsed: row.LastUsed,
}
}
// Get recent activity
activityRows, err := s.q.GetUserRecentActivityByEmail(ctx, email)
if err != nil {
return nil, eris.Wrap(err, "failed to get user recent activity")
}
recentActivity := make([]ActivityInfo, len(activityRows))
for i, row := range activityRows {
// Convert interface{} to int64 safely
tokens := int64(0)
if row.Tokens != nil {
if t, ok := row.Tokens.(int64); ok {
tokens = t
}
}
recentActivity[i] = ActivityInfo{
Date: row.ActivityDate,
Messages: row.Messages,
Tokens: tokens,
Sessions: row.Sessions,
}
}
analysisData := &UserAnalysisData{
UserInfo: UserAnalysisInfo{
Email: userInfo.UserEmail,
TotalMessages: userInfo.TotalMessages,
TotalTokens: userInfo.TotalTokens,
TotalSessions: userInfo.TotalSessions,
Messages3Days: userInfo.Messages3Days,
Tokens3Days: userInfo.Tokens3Days,
RateLimit: userInfo.RateLimit,
},
ModelUsage: modelUsage,
RecentActivity: recentActivity,
}
return analysisData, nil
}
// GetUserSessionHistory retrieves paginated session history for a user
func (s *AuthUserService) GetUserSessionHistory(ctx context.Context, email string, page, pageSize int32) ([]SessionHistoryInfo, int64, error) {
offset := (page - 1) * pageSize
// Get session history with pagination
sessionRows, err := s.q.GetUserSessionHistoryByEmail(ctx, sqlc_queries.GetUserSessionHistoryByEmailParams{
Email: email,
Limit: pageSize,
Offset: offset,
})
if err != nil {
return nil, 0, eris.Wrap(err, "failed to get user session history")
}
// Get total count
totalCount, err := s.q.GetUserSessionHistoryCountByEmail(ctx, email)
if err != nil {
return nil, 0, eris.Wrap(err, "failed to get user session history count")
}
sessionHistory := make([]SessionHistoryInfo, len(sessionRows))
for i, row := range sessionRows {
// Convert interface{} to int64 safely
messageCount := int64(0)
if row.MessageCount != nil {
if mc, ok := row.MessageCount.(int64); ok {
messageCount = mc
}
}
tokenCount := int64(0)
if row.TokenCount != nil {
if tc, ok := row.TokenCount.(int64); ok {
tokenCount = tc
}
}
sessionHistory[i] = SessionHistoryInfo{
SessionID: row.SessionID,
Model: row.Model,
MessageCount: messageCount,
TokenCount: tokenCount,
CreatedAt: row.CreatedAt,
UpdatedAt: row.UpdatedAt,
}
}
return sessionHistory, totalCount, nil
}
================================================
FILE: api/chat_comment_handler.go
================================================
package main
import (
"encoding/json"
"net/http"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatCommentHandler struct {
service *ChatCommentService
}
func NewChatCommentHandler(sqlc_q *sqlc_queries.Queries) *ChatCommentHandler {
chatCommentService := NewChatCommentService(sqlc_q)
return &ChatCommentHandler{
service: chatCommentService,
}
}
func (h *ChatCommentHandler) Register(router *mux.Router) {
router.HandleFunc("/uuid/chat_sessions/{sessionUUID}/chat_messages/{messageUUID}/comments", h.CreateChatComment).Methods(http.MethodPost)
router.HandleFunc("/uuid/chat_sessions/{sessionUUID}/comments", h.GetCommentsBySessionUUID).Methods(http.MethodGet)
router.HandleFunc("/uuid/chat_messages/{messageUUID}/comments", h.GetCommentsByMessageUUID).Methods(http.MethodGet)
}
func (h *ChatCommentHandler) CreateChatComment(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
sessionUUID := vars["sessionUUID"]
messageUUID := vars["messageUUID"]
var req struct {
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
userID, err := getUserID(r.Context())
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithMessage("unauthorized").WithDebugInfo(err.Error()))
return
}
comment, err := h.service.CreateChatComment(r.Context(), sqlc_queries.CreateChatCommentParams{
Uuid: uuid.New().String(),
ChatSessionUuid: sessionUUID,
ChatMessageUuid: messageUUID,
Content: req.Content,
CreatedBy: userID,
})
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to create chat comment"))
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(comment)
}
func (h *ChatCommentHandler) GetCommentsBySessionUUID(w http.ResponseWriter, r *http.Request) {
sessionUUID := mux.Vars(r)["sessionUUID"]
comments, err := h.service.GetCommentsBySessionUUID(r.Context(), sessionUUID)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get comments by session"))
return
}
json.NewEncoder(w).Encode(comments)
}
func (h *ChatCommentHandler) GetCommentsByMessageUUID(w http.ResponseWriter, r *http.Request) {
messageUUID := mux.Vars(r)["messageUUID"]
comments, err := h.service.GetCommentsByMessageUUID(r.Context(), messageUUID)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get comments by message"))
return
}
json.NewEncoder(w).Encode(comments)
}
================================================
FILE: api/chat_comment_service.go
================================================
package main
import (
"context"
"time"
"github.com/rotisserie/eris"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatCommentService struct {
q *sqlc_queries.Queries
}
func NewChatCommentService(q *sqlc_queries.Queries) *ChatCommentService {
return &ChatCommentService{q: q}
}
// CreateChatComment creates a new chat comment
func (s *ChatCommentService) CreateChatComment(ctx context.Context, params sqlc_queries.CreateChatCommentParams) (sqlc_queries.ChatComment, error) {
comment, err := s.q.CreateChatComment(ctx, params)
if err != nil {
return sqlc_queries.ChatComment{}, eris.Wrap(err, "failed to create comment")
}
return comment, nil
}
// GetCommentsBySessionUUID returns comments for a session with author info
func (s *ChatCommentService) GetCommentsBySessionUUID(ctx context.Context, sessionUUID string) ([]sqlc_queries.GetCommentsBySessionUUIDRow, error) {
comments, err := s.q.GetCommentsBySessionUUID(ctx, sessionUUID)
if err != nil {
return nil, eris.Wrap(err, "failed to get comments by session UUID")
}
return comments, nil
}
// GetCommentsByMessageUUID returns comments for a message with author info
func (s *ChatCommentService) GetCommentsByMessageUUID(ctx context.Context, messageUUID string) ([]sqlc_queries.GetCommentsByMessageUUIDRow, error) {
comments, err := s.q.GetCommentsByMessageUUID(ctx, messageUUID)
if err != nil {
return nil, eris.Wrap(err, "failed to get comments by message UUID")
}
return comments, nil
}
// CommentWithAuthor represents a comment with author information
type CommentWithAuthor struct {
UUID string `json:"uuid"`
Content string `json:"content"`
CreatedAt time.Time `json:"createdAt"`
AuthorUsername string `json:"authorUsername"`
AuthorEmail string `json:"authorEmail"`
}
// GetCommentsBySession returns comments for a session with author info
func (s *ChatCommentService) GetCommentsBySession(ctx context.Context, sessionUUID string) ([]CommentWithAuthor, error) {
comments, err := s.q.GetCommentsBySessionUUID(ctx, sessionUUID)
if err != nil {
return nil, eris.Wrap(err, "failed to get comments by session")
}
result := make([]CommentWithAuthor, len(comments))
for i, c := range comments {
result[i] = CommentWithAuthor{
UUID: c.Uuid,
Content: c.Content,
CreatedAt: c.CreatedAt,
AuthorUsername: c.AuthorUsername,
AuthorEmail: c.AuthorEmail,
}
}
return result, nil
}
// GetCommentsByMessage returns comments for a message with author info
func (s *ChatCommentService) GetCommentsByMessage(ctx context.Context, messageUUID string) ([]CommentWithAuthor, error) {
comments, err := s.q.GetCommentsByMessageUUID(ctx, messageUUID)
if err != nil {
return nil, eris.Wrap(err, "failed to get comments by message")
}
result := make([]CommentWithAuthor, len(comments))
for i, c := range comments {
result[i] = CommentWithAuthor{
UUID: c.Uuid,
Content: c.Content,
CreatedAt: c.CreatedAt,
AuthorUsername: c.AuthorUsername,
AuthorEmail: c.AuthorEmail,
}
}
return result, nil
}
================================================
FILE: api/chat_main_handler.go
================================================
package main
import (
"context"
"database/sql"
"encoding/json"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"net/http"
"strings"
"time"
mapset "github.com/deckarep/golang-set/v2"
openai "github.com/sashabaranov/go-openai"
"github.com/gorilla/mux"
"github.com/swuecho/chat_backend/models"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatHandler struct {
service *ChatService
chatfileService *ChatFileService
requestCtx context.Context // Store the request context for streaming
}
const sessionTitleGenerationTimeout = 30 * time.Second
func NewChatHandler(sqlc_q *sqlc_queries.Queries) *ChatHandler {
// create a new ChatService instance
chatService := NewChatService(sqlc_q)
ChatFileService := NewChatFileService(sqlc_q)
return &ChatHandler{
service: chatService,
chatfileService: ChatFileService,
requestCtx: context.Background(),
}
}
func (h *ChatHandler) Register(router *mux.Router) {
router.HandleFunc("/chat_stream", h.ChatCompletionHandler).Methods(http.MethodPost)
// for bot
// given a chat_uuid, a user message, return the answer
//
router.HandleFunc("/chatbot", h.ChatBotCompletionHandler).Methods(http.MethodPost)
router.HandleFunc("/chat_instructions", h.GetChatInstructions).Methods(http.MethodGet)
}
type ChatRequest struct {
Prompt string
SessionUuid string
ChatUuid string
Regenerate bool
Stream bool `json:"stream,omitempty"`
}
type ChatCompletionResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int `json:"created"`
Model string `json:"model"`
Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
} `json:"usage"`
Choices []Choice `json:"choices"`
}
type Choice struct {
Message openai.ChatCompletionMessage `json:"message"`
FinishReason any `json:"finish_reason"`
Index int `json:"index"`
}
type OpenaiChatRequest struct {
Model string `json:"model"`
Messages []openai.ChatCompletionMessage `json:"messages"`
}
type BotRequest struct {
Message string `json:"message"`
SnapshotUuid string `json:"snapshot_uuid"`
Stream bool `json:"stream"`
}
type ChatInstructionResponse struct {
ArtifactInstruction string `json:"artifactInstruction"`
}
func (h *ChatHandler) GetChatInstructions(w http.ResponseWriter, r *http.Request) {
artifactInstruction, err := loadArtifactInstruction()
if err != nil {
log.Printf("Warning: Failed to load artifact instruction: %v", err)
artifactInstruction = ""
}
json.NewEncoder(w).Encode(ChatInstructionResponse{
ArtifactInstruction: artifactInstruction,
})
}
// ChatCompletionHandler is an HTTP handler that sends the stream to the client as Server-Sent Events (SSE)
func (h *ChatHandler) ChatBotCompletionHandler(w http.ResponseWriter, r *http.Request) {
var req BotRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
snapshotUuid := req.SnapshotUuid
newQuestion := req.Message
log.Printf("snapshotUuid: %s", snapshotUuid)
log.Printf("newQuestion: %s", newQuestion)
ctx := r.Context()
userID, err := getUserID(ctx)
if err != nil {
log.Printf("Error getting user ID: %v", err)
apiErr := ErrAuthInvalidCredentials
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
fmt.Printf("userID: %d", userID)
chatSnapshot, err := h.service.q.ChatSnapshotByUserIdAndUuid(ctx, sqlc_queries.ChatSnapshotByUserIdAndUuidParams{
UserID: userID,
Uuid: snapshotUuid,
})
if err != nil {
apiErr := ErrResourceNotFound("Chat snapshot")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
fmt.Printf("chatSnapshot: %+v", chatSnapshot)
var session sqlc_queries.ChatSession
err = json.Unmarshal(chatSnapshot.Session, &session)
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to deserialize chat session"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
var simpleChatMessages []SimpleChatMessage
err = json.Unmarshal(chatSnapshot.Conversation, &simpleChatMessages)
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to deserialize conversation"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
genBotAnswer(h, w, session, simpleChatMessages, snapshotUuid, newQuestion, userID, req.Stream)
}
// ChatCompletionHandler is an HTTP handler that sends the stream to the client as Server-Sent Events (SSE)
func (h *ChatHandler) ChatCompletionHandler(w http.ResponseWriter, r *http.Request) {
var req ChatRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
log.Printf("Error decoding request: %v", err)
apiErr := ErrValidationInvalidInput("Invalid request format")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
chatSessionUuid := req.SessionUuid
chatUuid := req.ChatUuid
newQuestion := req.Prompt
log.Printf("chatSessionUuid: %s", chatSessionUuid)
log.Printf("chatUuid: %s", chatUuid)
log.Printf("newQuestion: %s", newQuestion)
ctx := r.Context()
userID, err := getUserID(ctx)
if err != nil {
log.Printf("Error getting user ID: %v", err)
apiErr := ErrAuthInvalidCredentials
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
if req.Regenerate {
regenerateAnswer(h, w, ctx, chatSessionUuid, chatUuid, req.Stream)
} else {
genAnswer(h, w, ctx, chatSessionUuid, chatUuid, newQuestion, userID, req.Stream)
}
}
// validateChatSession validates the chat session and returns the session and model info.
// It performs comprehensive validation including:
// - Session existence check
// - Model availability verification
// - Base URL extraction
// - UUID validation
// Returns: session, model, baseURL, success
func (h *ChatHandler) validateChatSession(ctx context.Context, w http.ResponseWriter, chatSessionUuid string) (*sqlc_queries.ChatSession, *sqlc_queries.ChatModel, string, bool) {
chatSession, err := h.service.q.GetChatSessionByUUID(ctx, chatSessionUuid)
if err != nil {
log.Printf("Invalid session UUID: %s, error: %v", chatSessionUuid, err)
RespondWithAPIError(w, ErrResourceNotFound("chat session").WithMessage(chatSessionUuid))
return nil, nil, "", false
}
chatModel, err := h.service.q.ChatModelByName(ctx, chatSession.Model)
if err != nil {
RespondWithAPIError(w, ErrResourceNotFound("chat model: "+chatSession.Model))
return nil, nil, "", false
}
baseURL, _ := getModelBaseUrl(chatModel.Url)
if chatSession.Uuid == "" {
log.Printf("Empty session UUID for chat: %s", chatSessionUuid)
RespondWithAPIError(w, ErrValidationInvalidInput("Invalid session UUID"))
return nil, nil, "", false
}
return &chatSession, &chatModel, baseURL, true
}
// handlePromptCreation handles creating new prompt or adding user message to existing conversation.
// This function manages the logic for:
// - Detecting existing prompts in the session
// - Creating new prompts for fresh conversations
// - Adding user messages to ongoing conversations
// - Handling empty questions for regeneration scenarios
func (h *ChatHandler) handlePromptCreation(ctx context.Context, w http.ResponseWriter, chatSession *sqlc_queries.ChatSession, chatUuid, newQuestion string, userID int32, baseURL string) bool {
existingPrompt := true
prompt, err := h.service.q.GetOneChatPromptBySessionUUID(ctx, chatSession.Uuid)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
log.Printf("No existing prompt found for session: %s", chatSession.Uuid)
existingPrompt = false
} else {
log.Printf("Error checking prompt for session %s: %v", chatSession.Uuid, err)
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to get prompt", err.Error()))
return false
}
} else {
log.Printf("Found existing prompt ID %d for session %s", prompt.ID, chatSession.Uuid)
}
if existingPrompt {
if newQuestion != "" {
_, err := h.service.CreateChatMessageSimple(ctx, chatSession.Uuid, chatUuid, "user", newQuestion, "", chatSession.Model, userID, baseURL, chatSession.SummarizeMode)
if err != nil {
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to create message", err.Error()))
return false
}
} else {
log.Println("no new question, regenerate answer")
}
} else {
chatPrompt, err := h.service.CreateChatPromptSimple(ctx, chatSession.Uuid, DefaultSystemPromptText, userID)
if err != nil {
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to create prompt", err.Error()))
return false
}
log.Printf("%+v\n", chatPrompt)
if newQuestion != "" {
_, err := h.service.CreateChatMessageSimple(ctx, chatSession.Uuid, chatUuid, "user", newQuestion, "", chatSession.Model, userID, baseURL, chatSession.SummarizeMode)
if err != nil {
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to create message", err.Error()))
return false
}
}
// Update session title with first 10 words of the first user message.
if newQuestion != "" {
sessionTitle := firstNWords(newQuestion, 10)
if sessionTitle != "" {
updateParams := sqlc_queries.UpdateChatSessionTopicByUUIDParams{
Uuid: chatSession.Uuid,
UserID: userID,
Topic: sessionTitle,
}
_, err := h.service.q.UpdateChatSessionTopicByUUID(ctx, updateParams)
if err != nil {
log.Printf("Warning: Failed to update session title for session %s: %v", chatSession.Uuid, err)
} else {
log.Printf("Updated session %s title to: %s", chatSession.Uuid, sessionTitle)
}
}
}
}
return true
}
// generateAndSaveAnswer generates the LLM response and saves it to the database
func (h *ChatHandler) generateAndSaveAnswer(ctx context.Context, w http.ResponseWriter, chatSession *sqlc_queries.ChatSession, chatUuid string, userID int32, baseURL string, streamOutput bool) bool {
msgs, err := h.service.getAskMessages(*chatSession, chatUuid, false)
if err != nil {
log.Printf("Error collecting messages for session %s: %v", chatSession.Uuid, err)
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to collect messages", err.Error()))
return false
}
log.Printf("Collected messages for processing - SessionUUID: %s, MessageCount: %d, Model: %s", chatSession.Uuid, len(msgs), chatSession.Model)
// Store the request context so models can access it
h.requestCtx = ctx
model := h.chooseChatModel(*chatSession, msgs)
LLMAnswer, err := model.Stream(w, *chatSession, msgs, chatUuid, false, streamOutput)
if err != nil {
log.Printf("Error generating answer: %v", err)
RespondWithAPIError(w, WrapError(err, "Failed to generate answer"))
return false
}
if LLMAnswer == nil {
log.Printf("Error generating answer: LLMAnswer is nil")
RespondWithAPIError(w, ErrInternalUnexpected.WithMessage("LLMAnswer is nil"))
return false
}
if !isTest(msgs) {
log.Printf("LLMAnswer: %+v", LLMAnswer)
h.service.logChat(*chatSession, msgs, LLMAnswer.ReasoningContent+LLMAnswer.Answer)
}
chatMessage, err := h.service.CreateChatMessageWithSuggestedQuestions(ctx, chatSession.Uuid, LLMAnswer.AnswerId, "assistant", LLMAnswer.Answer, LLMAnswer.ReasoningContent, chatSession.Model, userID, baseURL, chatSession.SummarizeMode, chatSession.ExploreMode, msgs)
if err != nil {
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to create message", err.Error()))
return false
}
// Send suggested questions as a separate streaming event if streaming is enabled and exploreMode is on
if streamOutput && chatSession.ExploreMode && chatMessage.SuggestedQuestions != nil {
h.sendSuggestedQuestionsStream(w, LLMAnswer.AnswerId, chatMessage.SuggestedQuestions)
}
// Generate a better title using LLM for the first exchange (async, non-blocking).
// Detach from the request context so the follow-up DB/model call can finish
// after the streaming response closes.
go h.generateSessionTitle(chatSession, userID)
return true
}
// generateSessionTitle regenerates the session title from the latest conversation state.
func (h *ChatHandler) generateSessionTitle(chatSession *sqlc_queries.ChatSession, userID int32) {
ctx, cancel := context.WithTimeout(context.Background(), sessionTitleGenerationTimeout)
defer cancel()
// Regenerate from the latest conversation after each assistant response.
messages, err := h.service.q.GetChatMessagesBySessionUUID(ctx, sqlc_queries.GetChatMessagesBySessionUUIDParams{
Uuid: chatSession.Uuid,
Offset: 0,
Limit: 100,
})
if err != nil {
log.Printf("Warning: Failed to get messages for title generation: %v", err)
return
}
var chatText string
for _, msg := range messages {
if msg.Role == "user" {
chatText += "user: " + msg.Content + "\n"
} else if msg.Role == "assistant" {
chatText += "assistant: " + msg.Content + "\n"
}
}
if strings.TrimSpace(chatText) == "" {
return
}
// Use the same approach as chat_snapshot_service.go - check if gemini-2.0-flash is available
model := "gemini-2.0-flash"
_, err = h.service.q.ChatModelByName(ctx, model)
if err != nil {
// Model not available, skip title generation
return
}
// Generate title using Gemini
genTitle, err := GenerateChatTitle(ctx, model, chatText)
if err != nil {
log.Printf("Warning: Failed to generate session title: %v", err)
return
}
if genTitle == "" {
return
}
// Update the session title
updateParams := sqlc_queries.UpdateChatSessionTopicByUUIDParams{
Uuid: chatSession.Uuid,
UserID: userID,
Topic: genTitle,
}
_, err = h.service.q.UpdateChatSessionTopicByUUID(ctx, updateParams)
if err != nil {
log.Printf("Warning: Failed to update session title: %v", err)
return
}
log.Printf("Generated LLM title for session %s: %s", chatSession.Uuid, genTitle)
}
// sendSuggestedQuestionsStream sends suggested questions as a separate streaming event
func (h *ChatHandler) sendSuggestedQuestionsStream(w http.ResponseWriter, answerID string, suggestedQuestionsJSON json.RawMessage) {
// Parse the suggested questions JSON
var suggestedQuestions []string
if err := json.Unmarshal(suggestedQuestionsJSON, &suggestedQuestions); err != nil {
log.Printf("Warning: Failed to parse suggested questions for streaming: %v", err)
return
}
// Only send if we have questions
if len(suggestedQuestions) == 0 {
return
}
// Get the flusher for streaming
flusher, ok := w.(http.Flusher)
if !ok {
log.Printf("Warning: Response writer does not support flushing, cannot send suggested questions stream")
return
}
// Create a special response with suggested questions
suggestedQuestionsResponse := map[string]interface{}{
"id": answerID,
"object": "chat.completion.chunk",
"choices": []map[string]interface{}{
{
"index": 0,
"delta": map[string]interface{}{
"content": "", // Empty content
"suggestedQuestions": suggestedQuestions,
},
"finish_reason": nil,
},
},
}
data, err := json.Marshal(suggestedQuestionsResponse)
if err != nil {
log.Printf("Warning: Failed to marshal suggested questions response: %v", err)
return
}
// Send the streaming event
fmt.Fprintf(w, "data: %v\n\n", string(data))
flusher.Flush()
log.Printf("Sent suggested questions stream for answer ID: %s, questions: %v", answerID, suggestedQuestions)
}
// genAnswer is an HTTP handler that sends the stream to the client as Server-Sent Events (SSE)
// if there is no prompt yet, it will create a new prompt and use it as request
// otherwise, it will create a message, use prompt + get latest N message + newQuestion as request
func genAnswer(h *ChatHandler, w http.ResponseWriter, ctx context.Context, chatSessionUuid string, chatUuid string, newQuestion string, userID int32, streamOutput bool) {
// Validate chat session and get model info
chatSession, _, baseURL, ok := h.validateChatSession(ctx, w, chatSessionUuid)
if !ok {
return
}
log.Printf("Processing chat session - SessionUUID: %s, UserID: %d, Model: %s", chatSession.Uuid, userID, chatSession.Model)
// Handle prompt creation or user message addition
if !h.handlePromptCreation(ctx, w, chatSession, chatUuid, newQuestion, userID, baseURL) {
return
}
// Generate and save the answer
h.generateAndSaveAnswer(ctx, w, chatSession, chatUuid, userID, baseURL, streamOutput)
}
func genBotAnswer(h *ChatHandler, w http.ResponseWriter, session sqlc_queries.ChatSession, simpleChatMessages []SimpleChatMessage, snapshotUuid, newQuestion string, userID int32, streamOutput bool) {
_, err := h.service.q.ChatModelByName(context.Background(), session.Model)
if err != nil {
apiErr := ErrResourceNotFound("Chat model: " + session.Model)
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
messages := simpleChatMessagesToMessages(simpleChatMessages)
messages = append(messages, models.Message{
Role: "user",
Content: newQuestion,
})
model := h.chooseChatModel(session, messages)
LLMAnswer, err := model.Stream(w, session, messages, "", false, streamOutput)
if err != nil {
RespondWithAPIError(w, WrapError(err, "Failed to generate answer"))
return
}
ctx := context.Background()
// Save to bot answer history
historyParams := sqlc_queries.CreateBotAnswerHistoryParams{
BotUuid: snapshotUuid,
UserID: userID,
Prompt: newQuestion,
Answer: LLMAnswer.Answer,
Model: session.Model,
TokensUsed: int32(len(LLMAnswer.Answer)) / 4, // Approximate token count
}
if _, err := h.service.q.CreateBotAnswerHistory(ctx, historyParams); err != nil {
log.Printf("Failed to save bot answer history: %v", err)
// Don't fail the request, just log the error
}
if !isTest(messages) {
h.service.logChat(session, messages, LLMAnswer.Answer)
}
}
// Helper function to convert SimpleChatMessage to Message
func simpleChatMessagesToMessages(simpleChatMessages []SimpleChatMessage) []models.Message {
messages := make([]models.Message, len(simpleChatMessages))
for i, scm := range simpleChatMessages {
role := "user"
if scm.Inversion {
role = "assistant"
}
if i == 0 {
role = "system"
}
messages[i] = models.Message{
Role: role,
Content: scm.Text,
}
}
return messages
}
func regenerateAnswer(h *ChatHandler, w http.ResponseWriter, ctx context.Context, chatSessionUuid string, chatUuid string, stream bool) {
// Validate chat session
chatSession, _, _, ok := h.validateChatSession(ctx, w, chatSessionUuid)
if !ok {
return
}
msgs, err := h.service.getAskMessages(*chatSession, chatUuid, true)
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to get chat messages"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
// Store the request context so models can access it
h.requestCtx = ctx
model := h.chooseChatModel(*chatSession, msgs)
LLMAnswer, err := model.Stream(w, *chatSession, msgs, chatUuid, true, stream)
if err != nil {
log.Printf("Error regenerating answer: %v", err)
return
}
h.service.logChat(*chatSession, msgs, LLMAnswer.Answer)
if err := h.service.UpdateChatMessageContent(ctx, chatUuid, LLMAnswer.Answer); err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to update message"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
// Generate suggested questions if explore mode is enabled
if chatSession.ExploreMode {
suggestedQuestions := h.service.generateSuggestedQuestions(LLMAnswer.Answer, msgs)
if len(suggestedQuestions) > 0 {
// Update the message with suggested questions in database
questionsJSON, err := json.Marshal(suggestedQuestions)
if err == nil {
h.service.UpdateChatMessageSuggestions(ctx, chatUuid, questionsJSON)
// Stream suggested questions to frontend
if stream {
h.sendSuggestedQuestionsStream(w, LLMAnswer.AnswerId, questionsJSON)
}
}
}
}
}
// GetRequestContext returns the current request context for streaming operations
func (h *ChatHandler) GetRequestContext() context.Context {
return h.requestCtx
}
func (h *ChatHandler) chooseChatModel(chat_session sqlc_queries.ChatSession, msgs []models.Message) ChatModel {
model := chat_session.Model
isTestChat := isTest(msgs)
// If this is a test chat, return the test model immediately
if isTestChat {
return &TestChatModel{h: h}
}
// Get the chat model from database to access api_type field
chatModel, err := GetChatModel(h.service.q, model)
if err != nil {
// Fallback to OpenAI if model not found in database
return &OpenAIChatModel{h: h}
}
// Use api_type field from database instead of string prefix matching
apiType := chatModel.ApiType
completionModel := mapset.NewSet[string]()
// completionModel.Add(openai.GPT3TextDavinci002)
isCompletion := completionModel.Contains(model)
var chatModelImpl ChatModel
switch apiType {
case "claude":
chatModelImpl = &Claude3ChatModel{h: h}
case "ollama":
chatModelImpl = &OllamaChatModel{h: h}
case "gemini":
chatModelImpl = NewGeminiChatModel(h)
case "custom":
chatModelImpl = &CustomChatModel{h: h}
case "openai":
if isCompletion {
chatModelImpl = &CompletionChatModel{h: h}
} else {
chatModelImpl = &OpenAIChatModel{h: h}
}
default:
// Default to OpenAI for unknown api types
chatModelImpl = &OpenAIChatModel{h: h}
}
return chatModelImpl
}
// isTest determines if the chat messages indicate this is a test scenario
func isTest(msgs []models.Message) bool {
if len(msgs) == 0 {
return false
}
lastMsgs := msgs[len(msgs)-1]
promptMsg := msgs[0]
// Check if either first or last message contains test demo marker
return (len(promptMsg.Content) >= TestPrefixLength && promptMsg.Content[:TestPrefixLength] == TestDemoPrefix) ||
(len(lastMsgs.Content) >= TestPrefixLength && lastMsgs.Content[:TestPrefixLength] == TestDemoPrefix)
}
func (h *ChatHandler) CheckModelAccess(w http.ResponseWriter, chatSessionUuid string, model string, userID int32) bool {
chatModel, err := h.service.q.ChatModelByName(context.Background(), model)
if err != nil {
log.WithError(err).WithField("model", model).Error("Chat model not found")
RespondWithAPIError(w, ErrResourceNotFound("chat model: "+model))
return true
}
log.Printf("%+v", chatModel)
if !chatModel.EnablePerModeRatelimit {
return false
}
ctx := context.Background()
rate, err := h.service.q.RateLimiteByUserAndSessionUUID(ctx,
sqlc_queries.RateLimiteByUserAndSessionUUIDParams{
Uuid: chatSessionUuid,
UserID: userID,
})
log.Printf("%+v", rate)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
// If no rate limit is found, use a default value instead of returning an error
log.Printf("No rate limit found for user %d and session %s, using default", userID, chatSessionUuid)
return false
}
apiErr := WrapError(MapDatabaseError(err), "Failed to get rate limit")
RespondWithAPIError(w, apiErr)
return true
}
// get last model usage in 10min
usage10Min, err := h.service.q.GetChatMessagesCountByUserAndModel(ctx,
sqlc_queries.GetChatMessagesCountByUserAndModelParams{
UserID: userID,
Model: rate.ChatModelName,
})
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to get usage data"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return true
}
log.Printf("%+v", usage10Min)
if int32(usage10Min) > rate.RateLimit {
apiErr := ErrTooManyRequests
apiErr.Message = fmt.Sprintf("Rate limit exceeded for %s", rate.ChatModelName)
apiErr.Detail = fmt.Sprintf("Usage: %d, Limit: %d", usage10Min, rate.RateLimit)
RespondWithAPIError(w, apiErr)
return true
}
return false
}
================================================
FILE: api/chat_main_service.go
================================================
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
"time"
_ "embed"
"github.com/rotisserie/eris"
"github.com/samber/lo"
openai "github.com/sashabaranov/go-openai"
"github.com/swuecho/chat_backend/llm/gemini"
models "github.com/swuecho/chat_backend/models"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatService struct {
q *sqlc_queries.Queries
}
//go:embed artifact_instruction.txt
var artifactInstructionText string
// NewChatService creates a new ChatService with database queries.
func NewChatService(q *sqlc_queries.Queries) *ChatService {
return &ChatService{q: q}
}
// loadArtifactInstruction loads the artifact instruction from file.
// Returns the instruction content or an error if the file cannot be read.
func loadArtifactInstruction() (string, error) {
if artifactInstructionText == "" {
return "", eris.New("artifact instruction text is empty")
}
return artifactInstructionText, nil
}
func appendInstructionToSystemMessage(msgs []models.Message, instruction string) {
if instruction == "" || len(msgs) == 0 {
return
}
systemMsgFound := false
for i, msg := range msgs {
if msg.Role == "system" {
msgs[i].Content = msg.Content + "\n" + instruction
msgs[i].SetTokenCount(int32(len(msgs[i].Content) / TokenEstimateRatio))
systemMsgFound = true
break
}
}
if !systemMsgFound {
msgs[0].Content = msgs[0].Content + "\n" + instruction
msgs[0].SetTokenCount(int32(len(msgs[0].Content) / TokenEstimateRatio))
}
}
// getAskMessages retrieves and processes chat messages for LLM requests.
// It combines prompts and messages, applies length limits, and adds artifact instructions (unless explore mode is enabled).
// Parameters:
// - chatSession: The chat session containing configuration
// - chatUuid: UUID for message identification (used in regenerate mode)
// - regenerate: If true, excludes the target message from history
//
// Returns combined message array or error.
func (s *ChatService) getAskMessages(chatSession sqlc_queries.ChatSession, chatUuid string, regenerate bool) ([]models.Message, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*RequestTimeoutSeconds)
defer cancel()
chatSessionUuid := chatSession.Uuid
lastN := chatSession.MaxLength
if chatSession.MaxLength == 0 {
lastN = DefaultMaxLength
}
chat_prompts, err := s.q.GetChatPromptsBySessionUUID(ctx, chatSessionUuid)
if err != nil {
return nil, eris.Wrap(err, "fail to get prompt: ")
}
var chatMessages []sqlc_queries.ChatMessage
if regenerate {
chatMessages, err = s.q.GetLastNChatMessages(ctx,
sqlc_queries.GetLastNChatMessagesParams{
ChatSessionUuid: chatSessionUuid,
Uuid: chatUuid,
Limit: lastN,
})
} else {
chatMessages, err = s.q.GetLatestMessagesBySessionUUID(ctx,
sqlc_queries.GetLatestMessagesBySessionUUIDParams{ChatSessionUuid: chatSession.Uuid, Limit: lastN})
}
if err != nil {
return nil, eris.Wrap(err, "fail to get messages: ")
}
chatPromptMsgs := lo.Map(chat_prompts, func(m sqlc_queries.ChatPrompt, _ int) models.Message {
msg := models.Message{Role: m.Role, Content: m.Content}
msg.SetTokenCount(int32(m.TokenCount))
return msg
})
chatMessageMsgs := lo.Map(chatMessages, func(m sqlc_queries.ChatMessage, _ int) models.Message {
msg := models.Message{Role: m.Role, Content: m.Content}
msg.SetTokenCount(int32(m.TokenCount))
return msg
})
msgs := append(chatPromptMsgs, chatMessageMsgs...)
// Add artifact instruction to system messages only if artifact mode is enabled
if chatSession.ArtifactEnabled {
artifactInstruction, err := loadArtifactInstruction()
if err != nil {
log.Printf("Warning: Failed to load artifact instruction: %v", err)
artifactInstruction = "" // Use empty string if file can't be loaded
}
appendInstructionToSystemMessage(msgs, artifactInstruction)
}
return msgs, nil
}
// CreateChatPromptSimple creates a new chat prompt for a session.
// This is typically used to start a new conversation with a system message.
func (s *ChatService) CreateChatPromptSimple(ctx context.Context, chatSessionUuid string, newQuestion string, userID int32) (sqlc_queries.ChatPrompt, error) {
tokenCount, err := getTokenCount(newQuestion)
if err != nil {
log.Printf("Warning: Failed to get token count for prompt: %v", err)
tokenCount = len(newQuestion) / TokenEstimateRatio // Fallback estimate
}
chatPrompt, err := s.q.CreateChatPrompt(ctx,
sqlc_queries.CreateChatPromptParams{
Uuid: NewUUID(),
ChatSessionUuid: chatSessionUuid,
Role: "system",
Content: newQuestion,
UserID: userID,
CreatedBy: userID,
UpdatedBy: userID,
TokenCount: int32(tokenCount),
})
return chatPrompt, err
}
// CreateChatMessageSimple creates a new chat message with optional summarization and artifact extraction.
// Handles token counting, content summarization for long messages, and artifact parsing.
// Parameters:
// - ctx: Request context for cancellation
// - sessionUuid, uuid: Message and session identifiers
// - role: Message role (user/assistant/system)
// - content, reasoningContent: Message content and reasoning (if any)
// - model: LLM model name
// - userId: User ID for ownership
// - baseURL: API base URL for summarization
// - is_summarize_mode: Whether to enable automatic summarization
//
// Returns created message or error.
func (s *ChatService) CreateChatMessageSimple(ctx context.Context, sessionUuid, uuid, role, content, reasoningContent, model string, userId int32, baseURL string, is_summarize_mode bool) (sqlc_queries.ChatMessage, error) {
numTokens, err := getTokenCount(content)
if err != nil {
log.Printf("Warning: Failed to get token count: %v", err)
numTokens = len(content) / TokenEstimateRatio // Fallback estimate
}
summary := ""
if is_summarize_mode && numTokens > SummarizeThreshold {
log.Println("summarizing")
summary = llm_summarize_with_timeout(baseURL, content)
log.Println("summarizing: " + summary)
}
// Extract artifacts from content
artifacts := extractArtifacts(content)
artifactsJSON, err := json.Marshal(artifacts)
if err != nil {
log.Printf("Warning: Failed to marshal artifacts: %v", err)
artifactsJSON = json.RawMessage([]byte("[]"))
}
chatMessage := sqlc_queries.CreateChatMessageParams{
ChatSessionUuid: sessionUuid,
Uuid: uuid,
Role: role,
Content: content,
ReasoningContent: reasoningContent,
Model: model,
UserID: userId,
CreatedBy: userId,
UpdatedBy: userId,
LlmSummary: summary,
TokenCount: int32(numTokens),
Raw: json.RawMessage([]byte("{}")),
Artifacts: artifactsJSON,
SuggestedQuestions: json.RawMessage([]byte("[]")),
}
message, err := s.q.CreateChatMessage(ctx, chatMessage)
if err != nil {
return sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to create message ")
}
return message, nil
}
// CreateChatMessageWithSuggestedQuestions creates a chat message with optional suggested questions for explore mode
func (s *ChatService) CreateChatMessageWithSuggestedQuestions(ctx context.Context, sessionUuid, uuid, role, content, reasoningContent, model string, userId int32, baseURL string, is_summarize_mode, exploreMode bool, messages []models.Message) (sqlc_queries.ChatMessage, error) {
numTokens, err := getTokenCount(content)
if err != nil {
log.Printf("Warning: Failed to get token count: %v", err)
numTokens = len(content) / TokenEstimateRatio // Fallback estimate
}
summary := ""
if is_summarize_mode && numTokens > SummarizeThreshold {
log.Println("summarizing")
summary = llm_summarize_with_timeout(baseURL, content)
log.Println("summarizing: " + summary)
}
// Extract artifacts from content
artifacts := extractArtifacts(content)
artifactsJSON, err := json.Marshal(artifacts)
if err != nil {
log.Printf("Warning: Failed to marshal artifacts: %v", err)
artifactsJSON = json.RawMessage([]byte("[]"))
}
// Generate suggested questions if explore mode is enabled and role is assistant
suggestedQuestions := json.RawMessage([]byte("[]"))
if exploreMode && role == "assistant" && messages != nil {
questions := s.generateSuggestedQuestions(content, messages)
if questionsJSON, err := json.Marshal(questions); err == nil {
suggestedQuestions = questionsJSON
} else {
log.Printf("Warning: Failed to marshal suggested questions: %v", err)
}
}
chatMessage := sqlc_queries.CreateChatMessageParams{
ChatSessionUuid: sessionUuid,
Uuid: uuid,
Role: role,
Content: content,
ReasoningContent: reasoningContent,
Model: model,
UserID: userId,
CreatedBy: userId,
UpdatedBy: userId,
LlmSummary: summary,
TokenCount: int32(numTokens),
Raw: json.RawMessage([]byte("{}")),
Artifacts: artifactsJSON,
SuggestedQuestions: suggestedQuestions,
}
message, err := s.q.CreateChatMessage(ctx, chatMessage)
if err != nil {
return sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to create message ")
}
return message, nil
}
// generateSuggestedQuestions generates follow-up questions based on the conversation context
func (s *ChatService) generateSuggestedQuestions(content string, messages []models.Message) []string {
// Create a simplified prompt to generate follow-up questions
prompt := `Based on the following conversation, generate 3 thoughtful follow-up questions that would help explore the topic further. Return only the questions, one per line, without numbering or bullet points.
Conversation context:
`
// Add the last few messages for context (limit to avoid token overflow)
contextMessages := messages
if len(messages) > 6 {
contextMessages = messages[len(messages)-6:]
}
for _, msg := range contextMessages {
prompt += fmt.Sprintf("%s: %s\n", msg.Role, msg.Content)
}
prompt += fmt.Sprintf("assistant: %s\n\nGenerate 3 follow-up questions:", content)
// Use the preferred models (deepseek-chat or gemini-2.0-flash) to generate suggestions
questions := s.callLLMForSuggestions(prompt)
// Parse the response into individual questions
lines := strings.Split(strings.TrimSpace(questions), "\n")
var result []string
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" && len(result) < 3 {
// Clean up any numbering or bullet points that might remain
line = strings.TrimPrefix(line, "1. ")
line = strings.TrimPrefix(line, "2. ")
line = strings.TrimPrefix(line, "3. ")
line = strings.TrimPrefix(line, "- ")
line = strings.TrimPrefix(line, "• ")
result = append(result, line)
}
}
return result
}
// callLLMForSuggestions makes a simple API call to generate suggested questions
func (s *ChatService) callLLMForSuggestions(prompt string) string {
ctx := context.Background()
// Get all models and find preferred models for suggestions
allModels, err := s.q.ListChatModels(ctx)
if err != nil {
log.Printf("Warning: Failed to list models for suggestions: %v", err)
return ""
}
// Filter for enabled models and prioritize deepseek-chat or gemini-2.0-flash
var selectedModel sqlc_queries.ChatModel
var foundPreferred bool
// First pass: look for preferred models
for _, model := range allModels {
if !model.IsEnable {
continue
}
modelNameLower := strings.ToLower(model.Name)
if strings.Contains(modelNameLower, "deepseek-chat") || strings.Contains(modelNameLower, "gemini-2.0-flash") {
selectedModel = model
foundPreferred = true
break
}
}
// Second pass: fallback to any gemini or openai model if preferred not found
if !foundPreferred {
for _, model := range allModels {
if !model.IsEnable {
continue
}
apiType := strings.ToLower(model.ApiType)
modelName := strings.ToLower(model.Name)
// Prefer gemini models, then openai
if apiType == "gemini" || (apiType == "openai" && strings.Contains(modelName, "gpt")) {
selectedModel = model
break
}
}
}
if selectedModel.ID == 0 {
log.Printf("Warning: No suitable models available for suggestions")
return ""
}
// Use different API calls based on model type
apiType := strings.ToLower(selectedModel.ApiType)
modelName := strings.ToLower(selectedModel.Name)
if apiType == "gemini" || strings.Contains(modelName, "gemini") {
return s.callGeminiForSuggestions(ctx, selectedModel, prompt)
} else if strings.Contains(modelName, "deepseek") || apiType == "openai" {
return s.callOpenAICompatibleForSuggestions(ctx, selectedModel, prompt)
}
log.Printf("Warning: Unsupported model type for suggestions: %s", selectedModel.ApiType)
return ""
}
// callGeminiForSuggestions makes a Gemini API call for suggestions
func (s *ChatService) callGeminiForSuggestions(ctx context.Context, model sqlc_queries.ChatModel, prompt string) string {
// Validate API key
apiKey := os.Getenv("GEMINI_API_KEY")
if apiKey == "" {
log.Printf("Warning: GEMINI_API_KEY environment variable not set")
return ""
}
// Create messages for Gemini
messages := []models.Message{
{
Role: "user",
Content: prompt,
},
}
// Generate Gemini payload
payloadBytes, err := gemini.GenGemminPayload(messages, nil)
if err != nil {
log.Printf("Warning: Failed to generate Gemini payload for suggestions: %v", err)
return ""
}
// Build URL
url := gemini.BuildAPIURL(model.Name, false)
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payloadBytes))
if err != nil {
log.Printf("Warning: Failed to create Gemini request for suggestions: %v", err)
return ""
}
req.Header.Set("Content-Type", "application/json")
// Make the API call with timeout
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
answer, err := gemini.HandleRegularResponse(http.Client{Timeout: 30 * time.Second}, req)
if err != nil {
log.Printf("Warning: Failed to get Gemini response for suggestions: %v", err)
return ""
}
if answer == nil || answer.Answer == "" {
log.Printf("Warning: Empty response from Gemini for suggestions")
return ""
}
return answer.Answer
}
// callOpenAICompatibleForSuggestions makes an OpenAI-compatible API call for suggestions (including deepseek)
func (s *ChatService) callOpenAICompatibleForSuggestions(ctx context.Context, model sqlc_queries.ChatModel, prompt string) string {
// Generate OpenAI client configuration
config, err := genOpenAIConfig(model)
if err != nil {
log.Printf("Warning: Failed to generate OpenAI configuration for suggestions: %v", err)
return ""
}
client := openai.NewClientWithConfig(config)
// Create a simple chat completion request for generating suggestions
req := openai.ChatCompletionRequest{
Model: model.Name,
Temperature: DefaultTemperature,
Messages: []openai.ChatCompletionMessage{
{
Role: "user",
Content: prompt,
},
},
MaxTokens: 200, // Keep suggestions concise
}
// Make the API call with timeout
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
resp, err := client.CreateChatCompletion(ctx, req)
if err != nil {
log.Printf("Warning: Failed to generate suggested questions with %s: %v", model.Name, err)
return ""
}
if len(resp.Choices) == 0 {
log.Printf("Warning: No response choices returned for suggested questions from %s", model.Name)
return ""
}
return resp.Choices[0].Message.Content
}
// UpdateChatMessageContent updates the content of an existing chat message.
// Recalculates token count for the updated content.
func (s *ChatService) UpdateChatMessageContent(ctx context.Context, uuid, content string) error {
// encode
// num_tokens
num_tokens, err := getTokenCount(content)
if err != nil {
log.Printf("Warning: Failed to get token count for update: %v", err)
num_tokens = len(content) / TokenEstimateRatio // Fallback estimate
}
err = s.q.UpdateChatMessageContent(ctx, sqlc_queries.UpdateChatMessageContentParams{
Uuid: uuid,
Content: content,
TokenCount: int32(num_tokens),
})
return err
}
// UpdateChatMessageSuggestions updates the suggested questions for a chat message
func (s *ChatService) UpdateChatMessageSuggestions(ctx context.Context, uuid string, suggestedQuestions json.RawMessage) error {
_, err := s.q.UpdateChatMessageSuggestions(ctx, sqlc_queries.UpdateChatMessageSuggestionsParams{
Uuid: uuid,
SuggestedQuestions: suggestedQuestions,
})
return err
}
// logChat creates a chat log entry for analytics and debugging.
// Logs the session, messages, and LLM response for audit purposes.
func (s *ChatService) logChat(chatSession sqlc_queries.ChatSession, msgs []models.Message, answerText string) {
// log chat
sessionRaw := chatSession.ToRawMessage()
if sessionRaw == nil {
log.Println("failed to marshal chat session")
return
}
question, err := json.Marshal(msgs)
if err != nil {
log.Printf("Warning: Failed to marshal chat messages: %v", err)
return // Skip logging if marshalling fails
}
answerRaw, err := json.Marshal(answerText)
if err != nil {
log.Printf("Warning: Failed to marshal answer: %v", err)
return // Skip logging if marshalling fails
}
s.q.CreateChatLog(context.Background(), sqlc_queries.CreateChatLogParams{
Session: *sessionRaw,
Question: question,
Answer: answerRaw,
})
}
================================================
FILE: api/chat_message_handler.go
================================================
package main
import (
"database/sql"
"encoding/json"
"errors"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/samber/lo"
"github.com/swuecho/chat_backend/models"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatMessageHandler struct {
service *ChatMessageService
}
func NewChatMessageHandler(sqlc_q *sqlc_queries.Queries) *ChatMessageHandler {
chatMessageService := NewChatMessageService(sqlc_q)
return &ChatMessageHandler{
service: chatMessageService,
}
}
func (h *ChatMessageHandler) Register(router *mux.Router) {
router.HandleFunc("/chat_messages", h.CreateChatMessage).Methods(http.MethodPost)
router.HandleFunc("/chat_messages/{id}", h.GetChatMessageByID).Methods(http.MethodGet)
router.HandleFunc("/chat_messages/{id}", h.UpdateChatMessage).Methods(http.MethodPut)
router.HandleFunc("/chat_messages/{id}", h.DeleteChatMessage).Methods(http.MethodDelete)
router.HandleFunc("/chat_messages", h.GetAllChatMessages).Methods(http.MethodGet)
router.HandleFunc("/uuid/chat_messages/{uuid}", h.GetChatMessageByUUID).Methods(http.MethodGet)
router.HandleFunc("/uuid/chat_messages/{uuid}", h.UpdateChatMessageByUUID).Methods(http.MethodPut)
router.HandleFunc("/uuid/chat_messages/{uuid}", h.DeleteChatMessageByUUID).Methods(http.MethodDelete)
router.HandleFunc("/uuid/chat_messages/{uuid}/generate-suggestions", h.GenerateMoreSuggestions).Methods(http.MethodPost)
router.HandleFunc("/uuid/chat_messages/chat_sessions/{uuid}", h.GetChatHistoryBySessionUUID).Methods(http.MethodGet)
router.HandleFunc("/uuid/chat_messages/chat_sessions/{uuid}", h.DeleteChatMessagesBySesionUUID).Methods(http.MethodDelete)
}
//type userIdContextKey string
//const userIDKey = userIdContextKey("userID")
func (h *ChatMessageHandler) CreateChatMessage(w http.ResponseWriter, r *http.Request) {
var messageParams sqlc_queries.CreateChatMessageParams
err := json.NewDecoder(r.Body).Decode(&messageParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
message, err := h.service.CreateChatMessage(r.Context(), messageParams)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to create chat message"))
return
}
json.NewEncoder(w).Encode(message)
}
func (h *ChatMessageHandler) GetChatMessageByID(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := strconv.Atoi(idStr)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("invalid chat message ID"))
return
}
message, err := h.service.GetChatMessageByID(r.Context(), int32(id))
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get chat message"))
return
}
json.NewEncoder(w).Encode(message)
}
func (h *ChatMessageHandler) UpdateChatMessage(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := strconv.Atoi(idStr)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("invalid chat message ID"))
return
}
var messageParams sqlc_queries.UpdateChatMessageParams
err = json.NewDecoder(r.Body).Decode(&messageParams)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
messageParams.ID = int32(id)
message, err := h.service.UpdateChatMessage(r.Context(), messageParams)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update chat message"))
return
}
json.NewEncoder(w).Encode(message)
}
func (h *ChatMessageHandler) DeleteChatMessage(w http.ResponseWriter, r *http.Request) {
idStr := mux.Vars(r)["id"]
id, err := strconv.Atoi(idStr)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("invalid chat message ID"))
return
}
err = h.service.DeleteChatMessage(r.Context(), int32(id))
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to delete chat message"))
return
}
w.WriteHeader(http.StatusOK)
}
func (h *ChatMessageHandler) GetAllChatMessages(w http.ResponseWriter, r *http.Request) {
messages, err := h.service.GetAllChatMessages(r.Context())
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get chat messages"))
return
}
json.NewEncoder(w).Encode(messages)
}
// GetChatMessageByUUID get chat message by uuid
func (h *ChatMessageHandler) GetChatMessageByUUID(w http.ResponseWriter, r *http.Request) {
uuidStr := mux.Vars(r)["uuid"]
message, err := h.service.GetChatMessageByUUID(r.Context(), uuidStr)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get chat message"))
return
}
json.NewEncoder(w).Encode(message)
}
// UpdateChatMessageByUUID update chat message by uuid
func (h *ChatMessageHandler) UpdateChatMessageByUUID(w http.ResponseWriter, r *http.Request) {
var simple_msg SimpleChatMessage
err := json.NewDecoder(r.Body).Decode(&simple_msg)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("Failed to decode request body").WithDebugInfo(err.Error()))
return
}
var messageParams sqlc_queries.UpdateChatMessageByUUIDParams
messageParams.Uuid = simple_msg.Uuid
messageParams.Content = simple_msg.Text
tokenCount, _ := getTokenCount(simple_msg.Text)
messageParams.TokenCount = int32(tokenCount)
messageParams.IsPin = simple_msg.IsPin
message, err := h.service.UpdateChatMessageByUUID(r.Context(), messageParams)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update chat message"))
return
}
json.NewEncoder(w).Encode(message)
}
// DeleteChatMessageByUUID delete chat message by uuid
func (h *ChatMessageHandler) DeleteChatMessageByUUID(w http.ResponseWriter, r *http.Request) {
uuidStr := mux.Vars(r)["uuid"]
err := h.service.DeleteChatMessageByUUID(r.Context(), uuidStr)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to delete chat message"))
return
}
w.WriteHeader(http.StatusOK)
}
// GetChatMessagesBySessionUUID get chat messages by session uuid
func (h *ChatMessageHandler) GetChatMessagesBySessionUUID(w http.ResponseWriter, r *http.Request) {
uuidStr := mux.Vars(r)["uuid"]
pageNum, err := strconv.Atoi(r.URL.Query().Get("page"))
if err != nil {
pageNum = 1
}
pageSize, err := strconv.Atoi(r.URL.Query().Get("page_size"))
if err != nil {
pageSize = 200
}
messages, err := h.service.GetChatMessagesBySessionUUID(r.Context(), uuidStr, int32(pageNum), int32(pageSize))
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get chat messages"))
return
}
simple_msgs := lo.Map(messages, func(message sqlc_queries.ChatMessage, _ int) SimpleChatMessage {
// Extract artifacts from database
var artifacts []Artifact
if message.Artifacts != nil {
err := json.Unmarshal(message.Artifacts, &artifacts)
if err != nil {
// Log error but don't fail the request
artifacts = []Artifact{}
}
}
return SimpleChatMessage{
DateTime: message.UpdatedAt.Format(time.RFC3339),
Text: message.Content,
Inversion: message.Role != "user",
Error: false,
Loading: false,
Artifacts: artifacts,
}
})
json.NewEncoder(w).Encode(simple_msgs)
}
// GetChatMessagesBySessionUUID get chat messages by session uuid
func (h *ChatMessageHandler) GetChatHistoryBySessionUUID(w http.ResponseWriter, r *http.Request) {
uuidStr := mux.Vars(r)["uuid"]
pageNum, err := strconv.Atoi(r.URL.Query().Get("page"))
if err != nil {
pageNum = 1
}
pageSize, err := strconv.Atoi(r.URL.Query().Get("page_size"))
if err != nil {
pageSize = 200
}
simple_msgs, err := h.service.q.GetChatHistoryBySessionUUID(r.Context(), uuidStr, int32(pageNum), int32(pageSize))
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get chat history"))
return
}
json.NewEncoder(w).Encode(simple_msgs)
}
// DeleteChatMessagesBySesionUUID delete chat messages by session uuid
func (h *ChatMessageHandler) DeleteChatMessagesBySesionUUID(w http.ResponseWriter, r *http.Request) {
uuidStr := mux.Vars(r)["uuid"]
err := h.service.DeleteChatMessagesBySesionUUID(r.Context(), uuidStr)
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to delete chat messages"))
return
}
w.WriteHeader(http.StatusOK)
}
// GenerateMoreSuggestions generates additional suggested questions for a message
func (h *ChatMessageHandler) GenerateMoreSuggestions(w http.ResponseWriter, r *http.Request) {
messageUUID := mux.Vars(r)["uuid"]
// Get the existing message
message, err := h.service.q.GetChatMessageByUUID(r.Context(), messageUUID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrChatMessageNotFound.WithMessage("Message not found").WithDebugInfo(err.Error()))
} else {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get message"))
}
return
}
// Only allow suggestions for assistant messages
if message.Role != "assistant" {
RespondWithAPIError(w, ErrValidationInvalidInput("Suggestions can only be generated for assistant messages"))
return
}
// Get the session to check if explore mode is enabled
session, err := h.service.q.GetChatSessionByUUID(r.Context(), message.ChatSessionUuid)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrChatSessionNotFound.WithMessage("Session not found").WithDebugInfo(err.Error()))
} else {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get session"))
}
return
}
// Check if explore mode is enabled
if !session.ExploreMode {
RespondWithAPIError(w, ErrValidationInvalidInput("Suggestions are only available in explore mode"))
return
}
// Get conversation context - last 6 messages
contextMessages, err := h.service.q.GetLatestMessagesBySessionUUID(r.Context(),
sqlc_queries.GetLatestMessagesBySessionUUIDParams{
ChatSessionUuid: session.Uuid,
Limit: 6,
})
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to get conversation context"))
return
}
// Convert to models.Message format for suggestion generation
var msgs []models.Message
for _, msg := range contextMessages {
msgs = append(msgs, models.Message{
Role: msg.Role,
Content: msg.Content,
})
}
// Create a new ChatService to access suggestion generation methods
chatService := NewChatService(h.service.q)
// Generate new suggested questions
newSuggestions := chatService.generateSuggestedQuestions(message.Content, msgs)
if len(newSuggestions) == 0 {
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to generate suggestions", "no suggestions returned"))
return
}
// Parse existing suggestions
var existingSuggestions []string
if len(message.SuggestedQuestions) > 0 {
if err := json.Unmarshal(message.SuggestedQuestions, &existingSuggestions); err != nil {
// If unmarshal fails, treat as empty array
existingSuggestions = []string{}
}
}
// Combine existing and new suggestions (avoiding duplicates)
allSuggestions := append(existingSuggestions, newSuggestions...)
// Remove duplicates
seenSuggestions := make(map[string]bool)
var uniqueSuggestions []string
for _, suggestion := range allSuggestions {
if !seenSuggestions[suggestion] {
seenSuggestions[suggestion] = true
uniqueSuggestions = append(uniqueSuggestions, suggestion)
}
}
// Update the message with new suggestions
suggestionsJSON, err := json.Marshal(uniqueSuggestions)
if err != nil {
RespondWithAPIError(w, createAPIError(ErrInternalUnexpected, "Failed to serialize suggestions", err.Error()))
return
}
_, err = h.service.q.UpdateChatMessageSuggestions(r.Context(),
sqlc_queries.UpdateChatMessageSuggestionsParams{
Uuid: messageUUID,
SuggestedQuestions: suggestionsJSON,
})
if err != nil {
RespondWithAPIError(w, WrapError(MapDatabaseError(err), "Failed to update message with suggestions"))
return
}
// Return the new suggestions to the client
response := map[string]interface{}{
"newSuggestions": newSuggestions,
"allSuggestions": uniqueSuggestions,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
================================================
FILE: api/chat_message_service.go
================================================
package main
import (
"context"
"encoding/json"
"errors"
"github.com/rotisserie/eris"
"github.com/swuecho/chat_backend/ai"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatMessageService struct {
q *sqlc_queries.Queries
}
// NewChatMessageService creates a new ChatMessageService.
func NewChatMessageService(q *sqlc_queries.Queries) *ChatMessageService {
return &ChatMessageService{q: q}
}
// CreateChatMessage creates a new chat message.
func (s *ChatMessageService) CreateChatMessage(ctx context.Context, message_params sqlc_queries.CreateChatMessageParams) (sqlc_queries.ChatMessage, error) {
message, err := s.q.CreateChatMessage(ctx, message_params)
if err != nil {
return sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to create message ")
}
return message, nil
}
// GetChatMessageByID returns a chat message by ID.
func (s *ChatMessageService) GetChatMessageByID(ctx context.Context, id int32) (sqlc_queries.ChatMessage, error) {
message, err := s.q.GetChatMessageByID(ctx, id)
if err != nil {
return sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to create message ")
}
return message, nil
}
// UpdateChatMessage updates an existing chat message.
func (s *ChatMessageService) UpdateChatMessage(ctx context.Context, message_params sqlc_queries.UpdateChatMessageParams) (sqlc_queries.ChatMessage, error) {
message_u, err := s.q.UpdateChatMessage(ctx, message_params)
if err != nil {
return sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to update message ")
}
return message_u, nil
}
// DeleteChatMessage deletes a chat message by ID.
func (s *ChatMessageService) DeleteChatMessage(ctx context.Context, id int32) error {
err := s.q.DeleteChatMessage(ctx, id)
if err != nil {
return eris.Wrap(err, "failed to delete message ")
}
return nil
}
// DeleteChatMessageByUUID deletes a chat message by uuid
func (s *ChatMessageService) DeleteChatMessageByUUID(ctx context.Context, uuid string) error {
err := s.q.DeleteChatMessageByUUID(ctx, uuid)
if err != nil {
return eris.Wrap(err, "failed to delete message ")
}
return nil
}
// GetAllChatMessages returns all chat messages.
func (s *ChatMessageService) GetAllChatMessages(ctx context.Context) ([]sqlc_queries.ChatMessage, error) {
messages, err := s.q.GetAllChatMessages(ctx)
if err != nil {
return nil, eris.Wrap(err, "failed to retrieve messages ")
}
return messages, nil
}
func (s *ChatMessageService) GetLatestMessagesBySessionID(ctx context.Context, chatSessionUuid string, limit int32) ([]sqlc_queries.ChatMessage, error) {
params := sqlc_queries.GetLatestMessagesBySessionUUIDParams{ChatSessionUuid: chatSessionUuid, Limit: limit}
msgs, err := s.q.GetLatestMessagesBySessionUUID(ctx, params)
if err != nil {
return []sqlc_queries.ChatMessage{}, err
}
return msgs, nil
}
func (s *ChatMessageService) GetFirstMessageBySessionUUID(ctx context.Context, chatSessionUuid string) (sqlc_queries.ChatMessage, error) {
msg, err := s.q.GetFirstMessageBySessionUUID(ctx, chatSessionUuid)
if err != nil {
return sqlc_queries.ChatMessage{}, err
}
return msg, nil
}
func (s *ChatMessageService) AddMessage(ctx context.Context, chatSessionUuid string, uuid string, role ai.Role, content string, raw []byte) (sqlc_queries.ChatMessage, error) {
params := sqlc_queries.CreateChatMessageParams{
ChatSessionUuid: chatSessionUuid,
Uuid: uuid,
Role: role.String(),
Content: content,
Raw: json.RawMessage(raw),
}
msg, err := s.q.CreateChatMessage(ctx, params)
if err != nil {
return sqlc_queries.ChatMessage{}, err
}
return msg, nil
}
// GetChatMessageByUUID returns a chat message by ID.
func (s *ChatMessageService) GetChatMessageByUUID(ctx context.Context, uuid string) (sqlc_queries.ChatMessage, error) {
message, err := s.q.GetChatMessageByUUID(ctx, uuid)
if err != nil {
return sqlc_queries.ChatMessage{}, errors.New("failed to retrieve message")
}
return message, nil
}
// UpdateChatMessageByUUID updates an existing chat message.
func (s *ChatMessageService) UpdateChatMessageByUUID(ctx context.Context, message_params sqlc_queries.UpdateChatMessageByUUIDParams) (sqlc_queries.ChatMessage, error) {
message_u, err := s.q.UpdateChatMessageByUUID(ctx, message_params)
if err != nil {
return sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to update message ")
}
return message_u, nil
}
// GetChatMessagesBySessionUUID returns a chat message by session uuid.
func (s *ChatMessageService) GetChatMessagesBySessionUUID(ctx context.Context, uuid string, pageNum, pageSize int32) ([]sqlc_queries.ChatMessage, error) {
param := sqlc_queries.GetChatMessagesBySessionUUIDParams{
Uuid: uuid,
Offset: pageNum - 1,
Limit: pageSize,
}
message, err := s.q.GetChatMessagesBySessionUUID(ctx, param)
if err != nil {
return []sqlc_queries.ChatMessage{}, eris.Wrap(err, "failed to retrieve message ")
}
return message, nil
}
// DeleteChatMessagesBySesionUUID deletes chat messages by session uuid.
func (s *ChatMessageService) DeleteChatMessagesBySesionUUID(ctx context.Context, uuid string) error {
err := s.q.DeleteChatMessagesBySesionUUID(ctx, uuid)
return err
}
func (s *ChatMessageService) GetChatMessagesCount(ctx context.Context, userID int32) (int32, error) {
count, err := s.q.GetChatMessagesCount(ctx, userID)
if err != nil {
return 0, err
}
return int32(count), nil
}
================================================
FILE: api/chat_message_service_test.go
================================================
package main
import (
"context"
"database/sql"
"encoding/json"
"errors"
"testing"
"github.com/swuecho/chat_backend/sqlc_queries"
)
func TestChatMessageService(t *testing.T) {
// Create a new ChatMessageService with the test database connection
q := sqlc_queries.New(db)
service := NewChatMessageService(q)
// Insert a new chat message into the database
msg_params := sqlc_queries.CreateChatMessageParams{
ChatSessionUuid: "1",
Uuid: "test-uuid-1",
Role: "Test Role",
Content: "Test Message",
ReasoningContent: "",
Model: "test-model",
TokenCount: 100,
Score: 0.5,
UserID: 1,
CreatedBy: 1,
UpdatedBy: 1,
LlmSummary: "",
Raw: json.RawMessage([]byte("{}")),
Artifacts: json.RawMessage([]byte("[]")),
SuggestedQuestions: json.RawMessage([]byte("[]")),
}
msg, err := service.CreateChatMessage(context.Background(), msg_params)
if err != nil {
t.Fatalf("failed to create chat message: %v", err)
}
// Retrieve the inserted chat message from the database and check that it matches the expected values
retrieved_msg, err := service.GetChatMessageByID(context.Background(), msg.ID)
if err != nil {
t.Fatalf("failed to retrieve chat message: %v", err)
}
if retrieved_msg.ID != msg.ID || retrieved_msg.ChatSessionUuid != msg.ChatSessionUuid ||
retrieved_msg.Role != msg.Role || retrieved_msg.Content != msg.Content || retrieved_msg.Score != msg.Score ||
retrieved_msg.UserID != msg.UserID || !retrieved_msg.CreatedAt.Equal(msg.CreatedAt) || !retrieved_msg.UpdatedAt.Equal(msg.UpdatedAt) ||
retrieved_msg.CreatedBy != msg.CreatedBy || retrieved_msg.UpdatedBy != msg.UpdatedBy {
t.Error("retrieved chat message does not match expected values")
}
// Delete the chat prompt and check that it was deleted from the database
if err := service.DeleteChatMessage(context.Background(), msg.ID); err != nil {
t.Fatalf("failed to delete chat prompt: %v", err)
}
_, err = service.GetChatMessageByID(context.Background(), msg.ID)
if err == nil || !errors.Is(err, sql.ErrNoRows) {
t.Error("expected error due to missing chat prompt, but got no error or different error")
}
}
func TestGetChatMessagesBySessionID(t *testing.T) {
// Create a new ChatMessageService with the test database connection
q := sqlc_queries.New(db)
service := NewChatMessageService(q)
// Insert two chat messages into the database with different chat session IDs
msg1_params := sqlc_queries.CreateChatMessageParams{
ChatSessionUuid: "1",
Uuid: "test-uuid-1",
Role: "Test Role 1",
Content: "Test Message 1",
ReasoningContent: "",
Model: "test-model",
TokenCount: 100,
Score: 0.5,
UserID: 1,
CreatedBy: 1,
UpdatedBy: 1,
LlmSummary: "",
Raw: json.RawMessage([]byte("{}")),
Artifacts: json.RawMessage([]byte("[]")),
SuggestedQuestions: json.RawMessage([]byte("[]")),
}
msg1, err := service.CreateChatMessage(context.Background(), msg1_params)
if err != nil {
t.Fatalf("failed to create chat message: %v", err)
}
msg2_params := sqlc_queries.CreateChatMessageParams{
ChatSessionUuid: "2",
Uuid: "test-uuid-2",
Role: "Test Role 2",
Content: "Test Message 2",
ReasoningContent: "",
Model: "test-model",
TokenCount: 100,
Score: 0.75,
UserID: 2,
CreatedBy: 2,
UpdatedBy: 2,
LlmSummary: "",
Raw: json.RawMessage([]byte("{}")),
Artifacts: json.RawMessage([]byte("[]")),
SuggestedQuestions: json.RawMessage([]byte("[]")),
}
msg2, err := service.CreateChatMessage(context.Background(), msg2_params)
if err != nil {
t.Fatalf("failed to create chat message: %v", err)
}
// Retrieve chat messages by chat session ID and check that they match the expected values
// skip because of there is no chatSession with uuid "1" avaialble
// chatSessionID := "1"
// msgs, err := service.GetChatMessagesBySessionUUID(context.Background(), chatSessionID, 1, 10)
// if err != nil {
// t.Fatalf("failed to retrieve chat messages: %v", err)
// }
// if len(msgs) != 1 {
// t.Errorf("expected 1 chat message, but got %d", len(msgs))
// }
// if msgs[0].ChatSessionUuid != msg1.ChatSessionUuid || msgs[0].Role != msg1.Role || msgs[0].Content != msg1.Content ||
// msgs[0].Score != msg1.Score || msgs[0].UserID != msg1.UserID {
// t.Error("retrieved chat messages do not match expected values")
// }
// Delete the chat prompt and check that it was deleted from the database
if err := service.DeleteChatMessage(context.Background(), msg1.ID); err != nil {
t.Fatalf("failed to delete chat prompt: %v", err)
}
// Delete the chat prompt and check that it was deleted from the database
if err := service.DeleteChatMessage(context.Background(), msg2.ID); err != nil {
t.Fatalf("failed to delete chat prompt: %v", err)
}
_, err = service.GetChatMessageByID(context.Background(), msg1.ID)
if err == nil || !errors.Is(err, sql.ErrNoRows) {
t.Error("expected error due to missing chat prompt, but got no error or different error")
}
_, err = service.GetChatMessageByID(context.Background(), msg2.ID)
if err == nil || !errors.Is(err, sql.ErrNoRows) {
t.Error("expected error due to missing chat prompt, but got no error or different error")
}
}
================================================
FILE: api/chat_model_handler.go
================================================
package main
import (
"encoding/json"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/samber/lo"
"github.com/swuecho/chat_backend/sqlc_queries"
)
type ChatModelHandler struct {
db *sqlc_queries.Queries
}
func NewChatModelHandler(db *sqlc_queries.Queries) *ChatModelHandler {
return &ChatModelHandler{
db: db,
}
}
func (h *ChatModelHandler) Register(r *mux.Router) {
// Assuming db is an instance of the SQLC generated DB struct
//handler := NewChatModelHandler(db)
// r := mux.NewRouter()
// TODO: user can read, remove user_id field from the response
r.HandleFunc("/chat_model", h.ListSystemChatModels).Methods("GET")
r.HandleFunc("/chat_model/default", h.GetDefaultChatModel).Methods("GET")
r.HandleFunc("/chat_model/{id}", h.ChatModelByID).Methods("GET")
// create delete update self's chat model
r.HandleFunc("/chat_model", h.CreateChatModel).Methods("POST")
r.HandleFunc("/chat_model/{id}", h.UpdateChatModel).Methods("PUT")
r.HandleFunc("/chat_model/{id}", h.DeleteChatModel).Methods("DELETE")
}
func (h *ChatModelHandler) ListSystemChatModels(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ChatModels, err := h.db.ListSystemChatModels(ctx)
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to list chat models"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
latestUsageTimeOfModels, err := h.db.GetLatestUsageTimeOfModel(ctx, "30 days")
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to get model usage data"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
// create a map of model id to usage time
usageTimeMap := make(map[string]sqlc_queries.GetLatestUsageTimeOfModelRow)
for _, usageTime := range latestUsageTimeOfModels {
usageTimeMap[usageTime.Model] = usageTime
}
// create a ChatModelWithUsage struct
type ChatModelWithUsage struct {
sqlc_queries.ChatModel
LastUsageTime time.Time `json:"lastUsageTime,omitempty"`
MessageCount int64 `json:"messageCount"`
}
// merge ChatModels and usageTimeMap with pre-allocated slice
chatModelsWithUsage := lo.Map(ChatModels, func(model sqlc_queries.ChatModel, _ int) ChatModelWithUsage {
usage := usageTimeMap[model.Name]
return ChatModelWithUsage{
ChatModel: model,
LastUsageTime: usage.LatestMessageTime,
MessageCount: usage.MessageCount,
}
})
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(chatModelsWithUsage)
}
func (h *ChatModelHandler) ChatModelByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
ctx := r.Context()
id, err := strconv.Atoi(vars["id"])
if err != nil {
apiErr := ErrValidationInvalidInput("Invalid chat model ID")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
ChatModel, err := h.db.ChatModelByID(ctx, int32(id))
if err != nil {
apiErr := ErrResourceNotFound("Chat model")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(ChatModel)
}
func (h *ChatModelHandler) CreateChatModel(w http.ResponseWriter, r *http.Request) {
userID, err := getUserID(r.Context())
if err != nil {
apiErr := ErrAuthInvalidCredentials
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
var input struct {
Name string `json:"name"`
Label string `json:"label"`
IsDefault bool `json:"isDefault"`
URL string `json:"url"`
ApiAuthHeader string `json:"apiAuthHeader"`
ApiAuthKey string `json:"apiAuthKey"`
EnablePerModeRatelimit bool `json:"enablePerModeRatelimit"`
ApiType string `json:"apiType"`
}
err = json.NewDecoder(r.Body).Decode(&input)
if err != nil {
apiErr := ErrValidationInvalidInput("Failed to parse request body")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
// Set default api_type if not provided
apiType := input.ApiType
if apiType == "" {
apiType = "openai" // default api type
}
// Validate api_type
validApiTypes := map[string]bool{
"openai": true,
"claude": true,
"gemini": true,
"ollama": true,
"custom": true,
}
if !validApiTypes[apiType] {
apiErr := ErrValidationInvalidInput("Invalid API type. Valid types are: openai, claude, gemini, ollama, custom")
RespondWithAPIError(w, apiErr)
return
}
ChatModel, err := h.db.CreateChatModel(r.Context(), sqlc_queries.CreateChatModelParams{
Name: input.Name,
Label: input.Label,
IsDefault: input.IsDefault,
Url: input.URL,
ApiAuthHeader: input.ApiAuthHeader,
ApiAuthKey: input.ApiAuthKey,
UserID: userID,
EnablePerModeRatelimit: input.EnablePerModeRatelimit,
MaxToken: 4096, // default max token
DefaultToken: 2048, // default token
OrderNumber: 0, // default order
HttpTimeOut: 120, // default timeout
ApiType: apiType,
})
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to create chat model"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(ChatModel)
}
func (h *ChatModelHandler) UpdateChatModel(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
apiErr := ErrValidationInvalidInput("Invalid chat model ID")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
userID, err := getUserID(r.Context())
if err != nil {
apiErr := ErrAuthInvalidCredentials
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
var input struct {
Name string `json:"name"`
Label string `json:"label"`
IsDefault bool `json:"isDefault"`
URL string `json:"url"`
ApiAuthHeader string `json:"apiAuthHeader"`
ApiAuthKey string `json:"apiAuthKey"`
EnablePerModeRatelimit bool `json:"enablePerModeRatelimit"`
OrderNumber int32 `json:"orderNumber"`
DefaultToken int32 `json:"defaultToken"`
MaxToken int32 `json:"maxToken"`
HttpTimeOut int32 `json:"httpTimeOut"`
IsEnable bool `json:"isEnable"`
ApiType string `json:"apiType"`
}
err = json.NewDecoder(r.Body).Decode(&input)
if err != nil {
apiErr := ErrValidationInvalidInput("Failed to parse request body")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
// Set default api_type if not provided
apiType := input.ApiType
if apiType == "" {
apiType = "openai" // default api type
}
// Validate api_type
validApiTypes := map[string]bool{
"openai": true,
"claude": true,
"gemini": true,
"ollama": true,
"custom": true,
}
if !validApiTypes[apiType] {
apiErr := ErrValidationInvalidInput("Invalid API type. Valid types are: openai, claude, gemini, ollama, custom")
RespondWithAPIError(w, apiErr)
return
}
ChatModel, err := h.db.UpdateChatModel(r.Context(), sqlc_queries.UpdateChatModelParams{
ID: int32(id),
Name: input.Name,
Label: input.Label,
IsDefault: input.IsDefault,
Url: input.URL,
ApiAuthHeader: input.ApiAuthHeader,
ApiAuthKey: input.ApiAuthKey,
UserID: userID,
EnablePerModeRatelimit: input.EnablePerModeRatelimit,
OrderNumber: input.OrderNumber,
DefaultToken: input.DefaultToken,
MaxToken: input.MaxToken,
HttpTimeOut: input.HttpTimeOut,
IsEnable: input.IsEnable,
ApiType: apiType,
})
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to update chat model"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(ChatModel)
}
func (h *ChatModelHandler) DeleteChatModel(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
apiErr := ErrValidationInvalidInput("Invalid chat model ID")
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
userID, err := getUserID(r.Context())
if err != nil {
apiErr := ErrAuthInvalidCredentials
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
err = h.db.DeleteChatModel(r.Context(),
sqlc_queries.DeleteChatModelParams{
ID: int32(id),
UserID: userID,
})
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to delete chat model"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
w.WriteHeader(http.StatusOK)
}
func (h *ChatModelHandler) GetDefaultChatModel(w http.ResponseWriter, r *http.Request) {
ChatModel, err := h.db.GetDefaultChatModel(r.Context())
if err != nil {
apiErr := ErrInternalUnexpected
apiErr.Detail = "Failed to retrieve default chat model"
apiErr.DebugInfo = err.Error()
RespondWithAPIError(w, apiErr)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(ChatModel)
}
================================================
FILE: api/chat_model_handler_test.go
================================================
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/gorilla/mux"
"github.com/samber/lo"
"github.com/swuecho/chat_backend/sqlc_queries"
"gotest.tools/v3/assert"
)
func createTwoChatModel(q *sqlc_queries.Queries) (sqlc_queries.AuthUser, []sqlc_queries.ChatModel) {
// add a system user
admin, err := q.CreateAuthUser(context.Background(), sqlc_queries.CreateAuthUserParams{
Email: "admin@a.com",
Username: "test",
Password: "test",
IsSuperuser: true,
})
if err != nil {
fmt.Printf("Error creating test data: %s", err.Error())
}
expectedResults := []sqlc_queries.ChatModel{
{
Name: "Test API 1",
Label: "Test Label 1",
IsDefault: false,
Url: "http://test.url.com",
ApiAuthHeader: "Authorization",
ApiAuthKey: "TestKey1",
UserID: admin.ID,
},
{
Name: "Test API 2",
Label: "Test Label 2",
IsDefault: false,
Url: "http://test.url2.com",
ApiAuthHeader: "Authorization",
ApiAuthKey: "TestKey2",
UserID: admin.ID,
},
}
for _, api := range expectedResults {
_, err := q.CreateChatModel(context.Background(), sqlc_queries.CreateChatModelParams{
Name: api.Name,
Label: api.Label,
IsDefault: api.IsDefault,
Url: api.Url,
ApiAuthHeader: api.ApiAuthHeader,
ApiAuthKey: api.ApiAuthKey,
UserID: api.UserID,
})
if err != nil {
fmt.Printf("Error creating test data: %s", err.Error())
}
}
return admin, expectedResults
}
func clearChatModelsIfExists(q *sqlc_queries.Queries) {
defaultApis, _ := q.ListChatModels(context.Background())
for _, api := range defaultApis {
q.DeleteChatModel(context.Background(),
sqlc_queries.DeleteChatModelParams{
ID: api.ID,
UserID: api.UserID,
})
}
}
func unmarshalResponseToChatModel(t *testing.T, rr *httptest.ResponseRecorder) []sqlc_queries.ChatModel {
// read the response body
// unmarshal the response body into a list of ChatModel
var results []sqlc_queries.ChatModel
err := json.NewDecoder(rr.Body).Decode(&results)
assert.NilError(t, err)
return results
}
// the code below do db update directly in instead of using handler, please change to use handler
func TestChatModelTest(t *testing.T) {
q := sqlc_queries.New(db)
h := NewChatModelHandler(q) // create a new ChatModelHandler instance for testing
router := mux.NewRouter()
h.Register(router)
// delete all existing chat APIs
clearChatModelsIfExists(q)
// Now let's create our expected results. Create two results and insert them into the database using the queries.
admin, expectedResults := createTwoChatModel(q)
// ensure that we get an array of two chat APIs in the response body
// ensure the returned values are what we expect them to be
results := checkGetModels(t, router, expectedResults)
// Now lets update the the first element of our expected results array and call PUT on the endpoint
// Create an HTTP request so we can simulate a PUT with the payload
// ensure the new values are returned and were also updated in the database
firstRecordID := results[0].ID
updateFirstRecord(t, router, firstRecordID, admin, expectedResults[0])
// delete first model
deleteReq, _ := http.NewRequest("DELETE", fmt.Sprintf("/chat_model/%d", firstRecordID), nil)
deleteReq = deleteReq.WithContext(getContextWithUser(int(admin.ID)))
deleteRR := httptest.NewRecorder()
router.ServeHTTP(deleteRR, deleteReq)
assert.Equal(t, deleteRR.Code, http.StatusOK)
// check only one model left
req, _ := http.NewRequest("GET", "/chat_model", nil)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
// ensure that we get an array of one chat API in the response body
results = unmarshalResponseToChatModel(t, rr)
assert.Equal(t, len(results), 1)
assert.Equal(t, results[0].Name, "Test API 1")
// delete the last model
deleteRequest, _ := http.NewRequest("DELETE", fmt.Sprintf("/chat_model/%d", results[0].ID), nil)
contextWithUser := getContextWithUser(int(admin.ID))
deleteRequest = deleteRequest.WithContext(contextWithUser)
deleteResponseRecorder := httptest.NewRecorder()
router.ServeHTTP(deleteResponseRecorder, deleteRequest)
assert.Equal(t, deleteResponseRecorder.Code, http.StatusOK)
// check no models left
getRequest, _ := http.NewRequest("GET", "/chat_model", nil)
// Create a ResponseRecorder to record the response
getResponseRecorder := httptest.NewRecorder()
router.ServeHTTP(getResponseRecorder, getRequest)
results = unmarshalResponseToChatModel(t, getResponseRecorder)
assert.Equal(t, len(results), 0)
}
func checkGetModels(t *testing.T, router *mux.Router, expectedResults []sqlc_queries.ChatModel) []sqlc_queries.ChatModel {
req, _ := http.NewRequest("GET", "/chat_model", nil)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
assert.Equal(t, rr.Code, http.StatusOK)
var results []sqlc_queries.ChatModel
err := json.NewDecoder(rr.Body).Decode(&results)
if err != nil {
t.Errorf("error parsing response body: %s", err.Error())
}
assert.Equal(t, len(results), 2)
assert.DeepEqual(t, lo.Reverse(expectedResults), results, cmpopts.IgnoreFields(sqlc_queries.ChatModel{}, "ID", "IsEnable"))
return results
}
func updateFirstRecord(t *testing.T, router *mux.Router, chatModelID int32, admin sqlc_queries.AuthUser, rec sqlc_queries.ChatModel) {
rec.Name = "Test API 1 Updated"
rec.Label = "Test Label 1 Updated"
updateBytes, err := json.Marshal(rec)
if err != nil {
t.Errorf("Error marshaling update payload: %s", err.Error())
}
updateReq, _ := http.NewRequest("PUT", fmt.Sprintf("/chat_model/%d", chatModelID), bytes.NewBuffer(updateBytes))
updateReq = updateReq.WithContext(getContextWithUser(int(admin.ID)))
updateRR := httptest.NewRecorder()
router.ServeHTTP(updateRR, updateReq)
assert.Equal(t, updateRR.Code, http.StatusOK)
var updatedResult sqlc_queries.ChatModel
err = json.Unmarshal(updateRR.Body.Bytes(), &updatedResult)
if err != nil {
t.Errorf("Error parsing response body: %s", err.Error())
}
assert.Equal(t, rec.Name, updatedResult.Name)
assert.Equal(t, rec.Label, updatedResult.Label)
}
================================================
FILE: api/chat_model_privilege_handler.go
================================================
package main
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/samber/lo"
"github.com/swuecho/chat_backend/sqlc_queries"
)
// UserChatModelPrivilegeHandler handles requests related to user chat model privileges
type UserChatModelPrivilegeHandler struct {
db *sqlc_queries.Queries
}
// NewUserChatModelPrivilegeHandler creates a new handler instance
func NewUserChatModelPrivilegeHandler(db *sqlc_queries.Queries) *UserChatModelPrivilegeHandler {
return &UserChatModelPrivilegeHandler{
db: db,
}
}
// Register sets up the handler routes
func (h *UserChatModelPrivilegeHandler) Register(r *mux.Router) {
r.HandleFunc("/admin/user_chat_model_privilege", h.ListUserChatModelPrivileges).Methods(http.MethodGet)
r.HandleFunc("/admin/user_chat_model_privilege", h.CreateUserChatModelPrivilege).Methods(http.MethodPost)
r.HandleFunc("/admin/user_chat_model_privilege/{id}", h.DeleteUserChatModelPrivilege).Methods(http.MethodDelete)
r.HandleFunc("/admin/user_chat_model_privilege/{id}", h.UpdateUserChatModelPrivilege).Methods(http.MethodPut)
}
type ChatModelPrivilege struct {
ID int32 `json:"id"`
FullName string `json:"fullName"`
UserEmail string `json:"userEmail"`
ChatModelName string `json:"chatModelName"`
RateLimit int32 `json:"rateLimit"`
}
// ListUserChatModelPrivileges handles GET requests to list all user chat model privileges
func (h *UserChatModelPrivilegeHandler) ListUserChatModelPrivileges(w http.ResponseWriter, r *http.Request) {
// TODO: check user is super_user
userChatModelRows, err := h.db.ListUserChatModelPrivilegesRateLimit(r.Context())
if err != nil {
RespondWithAPIError(w, WrapError(err, "failed to list user chat model privileges"))
return
}
log.Printf("Listing user chat model privileges")
output := lo.Map(userChatModelRows, func(r sqlc_queries.ListUserChatModelPrivilegesRateLimitRow, idx int) ChatModelPrivilege {
return ChatModelPrivilege{
ID: r.ID,
FullName: r.FullName,
UserEmail: r.UserEmail,
ChatModelName: r.ChatModelName,
RateLimit: r.RateLimit,
}
})
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(output)
}
func (h *UserChatModelPrivilegeHandler) UserChatModelPrivilegeByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("invalid user chat model privilege ID"))
return
}
userChatModelPrivilege, err := h.db.UserChatModelPrivilegeByID(r.Context(), int32(id))
if err != nil {
RespondWithAPIError(w, WrapError(err, "failed to get user chat model privilege"))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(userChatModelPrivilege)
}
// CreateUserChatModelPrivilege handles POST requests to create a new user chat model privilege
func (h *UserChatModelPrivilegeHandler) CreateUserChatModelPrivilege(w http.ResponseWriter, r *http.Request) {
var input ChatModelPrivilege
err := json.NewDecoder(r.Body).Decode(&input)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("failed to parse request body"))
return
}
// Validate input
if input.UserEmail == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("user email is required"))
return
}
if input.ChatModelName == "" {
RespondWithAPIError(w, ErrValidationInvalidInput("chat model name is required"))
return
}
if input.RateLimit <= 0 {
RespondWithAPIError(w, ErrValidationInvalidInput("rate limit must be positive").WithMessage(
fmt.Sprintf("invalid rate limit: %d", input.RateLimit)))
return
}
log.Printf("Creating chat model privilege for user %s with model %s",
input.UserEmail, input.ChatModelName)
user, err := h.db.GetAuthUserByEmail(r.Context(), input.UserEmail)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrResourceNotFound("user").WithMessage(
fmt.Sprintf("user with email %s not found", input.UserEmail)))
} else {
RespondWithAPIError(w, WrapError(err, "failed to get user by email"))
}
return
}
chatModel, err := h.db.ChatModelByName(r.Context(), input.ChatModelName)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrChatModelNotFound.WithMessage(fmt.Sprintf("chat model %s not found", input.ChatModelName)))
} else {
RespondWithAPIError(w, WrapError(err, "failed to get chat model"))
}
return
}
userChatModelPrivilege, err := h.db.CreateUserChatModelPrivilege(r.Context(), sqlc_queries.CreateUserChatModelPrivilegeParams{
UserID: user.ID,
ChatModelID: chatModel.ID,
RateLimit: input.RateLimit,
CreatedBy: user.ID,
UpdatedBy: user.ID,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrResourceNotFound("chat model privilege"))
} else {
RespondWithAPIError(w, WrapError(err, "failed to create user chat model privilege"))
}
return
}
output := ChatModelPrivilege{
ID: userChatModelPrivilege.ID,
UserEmail: user.Email,
ChatModelName: chatModel.Name,
RateLimit: userChatModelPrivilege.RateLimit,
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(output)
}
// UpdateUserChatModelPrivilege handles PUT requests to update a user chat model privilege
func (h *UserChatModelPrivilegeHandler) UpdateUserChatModelPrivilege(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("invalid user chat model privilege ID"))
return
}
userID, err := getUserID(r.Context())
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithMessage("missing or invalid user ID"))
return
}
var input ChatModelPrivilege
err = json.NewDecoder(r.Body).Decode(&input)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("failed to parse request body"))
return
}
// Validate input
if input.RateLimit <= 0 {
RespondWithAPIError(w, ErrValidationInvalidInput("rate limit must be positive"))
return
}
log.Printf("Updating chat model privilege %d for user %d", id, userID)
userChatModelPrivilege, err := h.db.UpdateUserChatModelPrivilege(r.Context(), sqlc_queries.UpdateUserChatModelPrivilegeParams{
ID: int32(id),
RateLimit: input.RateLimit,
UpdatedBy: userID,
})
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrResourceNotFound("chat model privilege"))
} else {
RespondWithAPIError(w, WrapError(err, "failed to update user chat model privilege"))
}
return
}
output := ChatModelPrivilege{
ID: userChatModelPrivilege.ID,
UserEmail: input.UserEmail,
ChatModelName: input.ChatModelName,
RateLimit: userChatModelPrivilege.RateLimit,
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(output)
}
func (h *UserChatModelPrivilegeHandler) DeleteUserChatModelPrivilege(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("invalid user chat model privilege ID"))
return
}
err = h.db.DeleteUserChatModelPrivilege(r.Context(), int32(id))
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
RespondWithAPIError(w, ErrResourceNotFound("chat model privilege"))
} else {
RespondWithAPIError(w, WrapError(err, "failed to delete user chat model privilege"))
}
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h *UserChatModelPrivilegeHandler) UserChatModelPrivilegeByUserAndModelID(w http.ResponseWriter, r *http.Request) {
_, err := getUserID(r.Context())
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithMessage("missing or invalid user ID"))
return
}
var input struct {
UserID int32
ChatModelID int32
}
err = json.NewDecoder(r.Body).Decode(&input)
if err != nil {
RespondWithAPIError(w, ErrValidationInvalidInput("failed to parse request body"))
return
}
userChatModelPrivilege, err := h.db.UserChatModelPrivilegeByUserAndModelID(r.Context(),
sqlc_queries.UserChatModelPrivilegeByUserAndModelIDParams{
UserID: input.UserID,
ChatModelID: input.ChatModelID,
})
if err != nil {
RespondWithAPIError(w, WrapError(err, "failed to get user chat model privilege"))
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(userChatModelPrivilege)
}
func (h *UserChatModelPrivilegeHandler) ListUserChatModelPrivilegesByUserID(w http.ResponseWriter, r *http.Request) {
userID, err := getUserID(r.Context())
if err != nil {
RespondWithAPIError(w, ErrAuthInvalidCredentials.WithMessage("missing or invalid user ID"
gitextract_n1v0oway/
├── .dockerignore
├── .github/
│ └── workflows/
│ ├── docker-image.yml
│ ├── fly.yml
│ ├── mobile-build.yml
│ └── publish.yml
├── .gitignore
├── AGENTS.md
├── CLAUDE.md
├── Dockerfile
├── README.md
├── api/
│ ├── .air.toml
│ ├── .github/
│ │ └── workflows/
│ │ └── go.yml
│ ├── .gitignore
│ ├── .vscode/
│ │ └── settings.json
│ ├── LICENSE
│ ├── Makefile
│ ├── README.md
│ ├── admin_handler.go
│ ├── ai/
│ │ └── model.go
│ ├── artifact_instruction.txt
│ ├── auth/
│ │ ├── auth.go
│ │ ├── auth_test.go
│ │ ├── token.go
│ │ └── token_test.go
│ ├── bot_answer_history_handler.go
│ ├── bot_answer_history_service.go
│ ├── chat_artifact.go
│ ├── chat_auth_user_handler.go
│ ├── chat_auth_user_service.go
│ ├── chat_comment_handler.go
│ ├── chat_comment_service.go
│ ├── chat_main_handler.go
│ ├── chat_main_service.go
│ ├── chat_message_handler.go
│ ├── chat_message_service.go
│ ├── chat_message_service_test.go
│ ├── chat_model_handler.go
│ ├── chat_model_handler_test.go
│ ├── chat_model_privilege_handler.go
│ ├── chat_prompt_hander.go
│ ├── chat_prompt_service.go
│ ├── chat_prompt_service_test.go
│ ├── chat_session_handler.go
│ ├── chat_session_service.go
│ ├── chat_session_service_test.go
│ ├── chat_snapshot_handler.go
│ ├── chat_snapshot_handler_test.go
│ ├── chat_snapshot_service.go
│ ├── chat_user_active_chat_session_handler.go
│ ├── chat_user_active_chat_session_sevice.go
│ ├── chat_workspace_handler.go
│ ├── chat_workspace_service.go
│ ├── constants.go
│ ├── embed_debug_test.go
│ ├── errors.go
│ ├── file_upload_handler.go
│ ├── file_upload_service.go
│ ├── go.mod
│ ├── go.sum
│ ├── handle_tts.go
│ ├── jwt_secret_service.go
│ ├── llm/
│ │ ├── claude/
│ │ │ └── claude.go
│ │ ├── gemini/
│ │ │ ├── gemini.go
│ │ │ └── gemini_test.go
│ │ └── openai/
│ │ ├── chat.go
│ │ ├── client.go
│ │ ├── common.go
│ │ └── openai.go
│ ├── llm_openai.go
│ ├── llm_summary.go
│ ├── main.go
│ ├── main_test.go
│ ├── middleware_authenticate.go
│ ├── middleware_gzip.go
│ ├── middleware_lastRequestTime.go
│ ├── middleware_rateLimit.go
│ ├── middleware_validation.go
│ ├── model_claude3_service.go
│ ├── model_completion_service.go
│ ├── model_custom_service.go
│ ├── model_gemini_service.go
│ ├── model_ollama_service.go
│ ├── model_openai_service.go
│ ├── model_test_service.go
│ ├── models/
│ │ └── models.go
│ ├── models.go
│ ├── openai_test.go
│ ├── pre-commit.sh
│ ├── sqlc/
│ │ ├── README.txt
│ │ ├── queries/
│ │ │ ├── auth_user.sql
│ │ │ ├── auth_user_management.sql
│ │ │ ├── bot_answer_history.sql
│ │ │ ├── chat_comment.sql
│ │ │ ├── chat_file.sql
│ │ │ ├── chat_log.sql
│ │ │ ├── chat_message.sql
│ │ │ ├── chat_model.sql
│ │ │ ├── chat_prompt.sql
│ │ │ ├── chat_session.sql
│ │ │ ├── chat_snapshot.sql
│ │ │ ├── chat_workspace.sql
│ │ │ ├── jwt_secrets.sql
│ │ │ ├── user_active_chat_session.sql
│ │ │ └── user_chat_model_privilege.sql
│ │ └── schema.sql
│ ├── sqlc.yaml
│ ├── sqlc_queries/
│ │ ├── auth_user.sql.go
│ │ ├── auth_user_management.sql.go
│ │ ├── bot_answer_history.sql.go
│ │ ├── chat_comment.sql.go
│ │ ├── chat_file.sql.go
│ │ ├── chat_log.sql.go
│ │ ├── chat_message.sql.go
│ │ ├── chat_model.sql.go
│ │ ├── chat_prompt.sql.go
│ │ ├── chat_session.sql.go
│ │ ├── chat_snapshot.sql.go
│ │ ├── chat_workspace.sql.go
│ │ ├── db.go
│ │ ├── jwt_secrets.sql.go
│ │ ├── models.go
│ │ ├── user_active_chat_session.sql.go
│ │ ├── user_chat_model_privilege.sql.go
│ │ ├── zz_custom_method.go
│ │ └── zz_custom_query.go
│ ├── static/
│ │ ├── awesome-chatgpt-prompts-en.json
│ │ ├── awesome-chatgpt-prompts-zh.json
│ │ └── static.go
│ ├── streaming_helpers.go
│ ├── test_build
│ ├── text_buffer.go
│ ├── tools/
│ │ ├── apply_a_similar_change/
│ │ │ ├── README.md
│ │ │ ├── apply_diff.py
│ │ │ ├── apply_diff_uselib.py
│ │ │ ├── parse_diff.py
│ │ │ ├── parse_diff2.py
│ │ │ ├── parse_diff3.py
│ │ │ └── stream.diff
│ │ └── fix_eris.py
│ ├── util.go
│ ├── util_test.go
│ └── util_words_test.go
├── artifacts.md
├── chat.code-workspace
├── docker-compose.yaml
├── docs/
│ ├── add_model_en.md
│ ├── add_model_zh.md
│ ├── artifact_gallery_en.md
│ ├── artifact_gallery_zh.md
│ ├── code_runner_artifacts_tutorial.md
│ ├── code_runner_capabilities.md
│ ├── code_runner_csv_tutorial.md
│ ├── custom_model_api_en.md
│ ├── deployment_en.md
│ ├── deployment_zh.md
│ ├── dev/
│ │ ├── ERROR_HANDLING_STANDARDS.md
│ │ ├── INTEGRATION_GUIDE.md
│ │ ├── code_runner_manual.md
│ │ ├── conversation_patch_example.js
│ │ ├── conversation_vfs_integration.md
│ │ ├── python_async_execution.md
│ │ ├── sse_processing_logic.md
│ │ ├── vfs_integration_example.md
│ │ ├── virtual_file_system_plan.md
│ │ └── virtual_file_system_usage.md
│ ├── dev_locally_en.md
│ ├── dev_locally_zh.md
│ ├── ollama_en.md
│ ├── ollama_zh.md
│ ├── prompts.md
│ ├── snapshots_vs_chatbots_en.md
│ ├── snapshots_vs_chatbots_zh.md
│ ├── tool_use_code_runner.md
│ └── tool_use_showcase.md
├── e2e/
│ ├── .gitignore
│ ├── LICENSE
│ ├── Makefile
│ ├── lib/
│ │ ├── button-helpers.ts
│ │ ├── chat-test-setup.ts
│ │ ├── db/
│ │ │ ├── chat_message/
│ │ │ │ └── index.ts
│ │ │ ├── chat_model/
│ │ │ │ └── index.ts
│ │ │ ├── chat_prompt/
│ │ │ │ └── index.ts
│ │ │ ├── chat_session/
│ │ │ │ └── index.ts
│ │ │ ├── chat_workspace/
│ │ │ │ └── index.ts
│ │ │ ├── config.ts
│ │ │ └── user/
│ │ │ └── index.ts
│ │ ├── message-helpers.ts
│ │ └── sample.ts
│ ├── package.json
│ ├── playwright.config.ts
│ ├── tests/
│ │ ├── 00_chat_gpt_web.spec.ts
│ │ ├── 01_register.spec.ts
│ │ ├── 02_simpe_prompt.spec.ts
│ │ ├── 03_chat_session.spec.ts
│ │ ├── 04_simpe_prompt_and_message.spec.ts
│ │ ├── 05_chat_session.spec.ts
│ │ ├── 06_clear_messages.spec.ts
│ │ ├── 07_set_session_max_len.spec.ts
│ │ ├── 08_session_config.spec.ts
│ │ ├── 09_session_answer.spec.ts
│ │ ├── 10_session_answer_regenerate.spec.ts
│ │ ├── 10_session_answer_regenerate_fixed.spec.ts
│ │ └── 11_workspace.spec.ts
│ └── tests-examples/
│ └── demo-todo-app.spec.ts
├── fly.toml
├── mobile/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── android/
│ │ ├── .gitignore
│ │ ├── app/
│ │ │ ├── build.gradle.kts
│ │ │ └── src/
│ │ │ ├── debug/
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin/
│ │ │ │ │ └── com/
│ │ │ │ │ └── example/
│ │ │ │ │ └── chat_mobile/
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── drawable-v21/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ ├── values/
│ │ │ │ │ └── styles.xml
│ │ │ │ └── values-night/
│ │ │ │ └── styles.xml
│ │ │ └── profile/
│ │ │ └── AndroidManifest.xml
│ │ ├── build.gradle.kts
│ │ ├── gradle/
│ │ │ └── wrapper/
│ │ │ └── gradle-wrapper.properties
│ │ ├── gradle.properties
│ │ └── settings.gradle.kts
│ ├── devtools_options.yaml
│ ├── ios/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── AppFrameworkInfo.plist
│ │ │ ├── Debug.xcconfig
│ │ │ └── Release.xcconfig
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ └── LaunchImage.imageset/
│ │ │ │ ├── Contents.json
│ │ │ │ └── README.md
│ │ │ ├── Base.lproj/
│ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ └── Main.storyboard
│ │ │ ├── Info.plist
│ │ │ └── Runner-Bridging-Header.h
│ │ ├── Runner.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ └── xcshareddata/
│ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ └── RunnerTests/
│ │ └── RunnerTests.swift
│ ├── lib/
│ │ ├── api/
│ │ │ ├── api_config.dart
│ │ │ ├── api_exception.dart
│ │ │ └── chat_api.dart
│ │ ├── constants/
│ │ │ └── chat.dart
│ │ ├── main.dart
│ │ ├── models/
│ │ │ ├── auth_token_result.dart
│ │ │ ├── chat_message.dart
│ │ │ ├── chat_model.dart
│ │ │ ├── chat_session.dart
│ │ │ ├── chat_snapshot.dart
│ │ │ ├── suggestions_response.dart
│ │ │ └── workspace.dart
│ │ ├── screens/
│ │ │ ├── auth_gate.dart
│ │ │ ├── chat_screen.dart
│ │ │ ├── home_screen.dart
│ │ │ ├── login_screen.dart
│ │ │ ├── snapshot_list_screen.dart
│ │ │ └── snapshot_screen.dart
│ │ ├── state/
│ │ │ ├── auth_provider.dart
│ │ │ ├── message_provider.dart
│ │ │ ├── model_provider.dart
│ │ │ ├── session_provider.dart
│ │ │ └── workspace_provider.dart
│ │ ├── theme/
│ │ │ ├── app_theme.dart
│ │ │ └── color_utils.dart
│ │ ├── utils/
│ │ │ ├── api_error.dart
│ │ │ └── thinking_parser.dart
│ │ └── widgets/
│ │ ├── icon_map.dart
│ │ ├── message_bubble.dart
│ │ ├── message_composer.dart
│ │ ├── session_tile.dart
│ │ ├── suggested_questions.dart
│ │ ├── thinking_section.dart
│ │ └── workspace_selector.dart
│ ├── linux/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── flutter/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── generated_plugin_registrant.cc
│ │ │ ├── generated_plugin_registrant.h
│ │ │ └── generated_plugins.cmake
│ │ └── runner/
│ │ ├── CMakeLists.txt
│ │ ├── main.cc
│ │ ├── my_application.cc
│ │ └── my_application.h
│ ├── macos/
│ │ ├── .gitignore
│ │ ├── Flutter/
│ │ │ ├── Flutter-Debug.xcconfig
│ │ │ ├── Flutter-Release.xcconfig
│ │ │ └── GeneratedPluginRegistrant.swift
│ │ ├── Podfile
│ │ ├── Runner/
│ │ │ ├── AppDelegate.swift
│ │ │ ├── Assets.xcassets/
│ │ │ │ └── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ ├── Base.lproj/
│ │ │ │ └── MainMenu.xib
│ │ │ ├── Configs/
│ │ │ │ ├── AppInfo.xcconfig
│ │ │ │ ├── Debug.xcconfig
│ │ │ │ ├── Release.xcconfig
│ │ │ │ └── Warnings.xcconfig
│ │ │ ├── DebugProfile.entitlements
│ │ │ ├── Info.plist
│ │ │ ├── MainFlutterWindow.swift
│ │ │ └── Release.entitlements
│ │ ├── Runner.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ ├── project.xcworkspace/
│ │ │ │ └── xcshareddata/
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ └── xcshareddata/
│ │ │ └── xcschemes/
│ │ │ └── Runner.xcscheme
│ │ ├── Runner.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── RunnerTests/
│ │ └── RunnerTests.swift
│ ├── pubspec.yaml
│ ├── test/
│ │ └── widget_test.dart
│ ├── web/
│ │ ├── index.html
│ │ └── manifest.json
│ └── windows/
│ ├── .gitignore
│ ├── CMakeLists.txt
│ ├── flutter/
│ │ ├── CMakeLists.txt
│ │ ├── generated_plugin_registrant.cc
│ │ ├── generated_plugin_registrant.h
│ │ └── generated_plugins.cmake
│ └── runner/
│ ├── CMakeLists.txt
│ ├── Runner.rc
│ ├── flutter_window.cpp
│ ├── flutter_window.h
│ ├── main.cpp
│ ├── resource.h
│ ├── runner.exe.manifest
│ ├── utils.cpp
│ ├── utils.h
│ ├── win32_window.cpp
│ └── win32_window.h
├── scripts/
│ ├── branch_clean.py
│ ├── locale_missing_key.py
│ ├── merge_keys.py
│ └── remove_older_branch.py
└── web/
├── .commitlintrc.json
├── .editorconfig
├── .eslintrc.cjs
├── .gitattributes
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .vscode/
│ ├── extensions.json
│ └── settings.json
├── docker-compose/
│ ├── docker-compose.yml
│ ├── nginx/
│ │ └── nginx.conf
│ └── readme.md
├── docs/
│ └── code_runner.md
├── index.html
├── license
├── package.json
├── postcss.config.js
├── public/
│ ├── awesome-chatgpt-prompts-en.json
│ └── awesome-chatgpt-prompts-zh.json
├── rsbuild.config.ts
├── src/
│ ├── App.vue
│ ├── api/
│ │ ├── admin.ts
│ │ ├── bot_answer_history.ts
│ │ ├── chat_active_user_session.ts
│ │ ├── chat_file.ts
│ │ ├── chat_instructions.ts
│ │ ├── chat_message.ts
│ │ ├── chat_model.ts
│ │ ├── chat_prompt.ts
│ │ ├── chat_session.ts
│ │ ├── chat_snapshot.ts
│ │ ├── chat_user_model_privilege.ts
│ │ ├── chat_workspace.ts
│ │ ├── comment.ts
│ │ ├── content.ts
│ │ ├── export.ts
│ │ ├── index.ts
│ │ ├── token.ts
│ │ ├── use_chat_session.ts
│ │ └── user.ts
│ ├── assets/
│ │ └── recommend.json
│ ├── components/
│ │ ├── admin/
│ │ │ ├── ModelCard.vue
│ │ │ ├── SessionSnapshotModal.vue
│ │ │ └── UserAnalysisModal.vue
│ │ ├── common/
│ │ │ ├── EnhancedNotification.vue
│ │ │ ├── HoverButton/
│ │ │ │ ├── Button.vue
│ │ │ │ └── index.vue
│ │ │ ├── NaiveProvider/
│ │ │ │ └── index.vue
│ │ │ ├── NotificationDemo.vue
│ │ │ ├── PromptStore/
│ │ │ │ └── index.vue
│ │ │ ├── Setting/
│ │ │ │ ├── Admin.vue
│ │ │ │ ├── General.vue
│ │ │ │ └── index.vue
│ │ │ ├── SvgIcon/
│ │ │ │ └── index.vue
│ │ │ ├── UserAvatar/
│ │ │ │ └── index.vue
│ │ │ └── index.ts
│ │ └── custom/
│ │ ├── GithubSite.vue
│ │ └── index.ts
│ ├── config/
│ │ └── api.ts
│ ├── constants/
│ │ ├── apiTypes.ts
│ │ └── chat.ts
│ ├── hooks/
│ │ ├── useBasicLayout.ts
│ │ ├── useChatModels.ts
│ │ ├── useCopyCode.ts
│ │ ├── useIconRender.ts
│ │ ├── useLanguage.ts
│ │ ├── useOnlineStatus.ts
│ │ ├── useTheme.ts
│ │ └── useWorkspaceRouting.ts
│ ├── icons/
│ │ ├── 403.vue
│ │ └── 500.vue
│ ├── locales/
│ │ ├── en-US-more.json
│ │ ├── en-US.json
│ │ ├── en.ts
│ │ ├── index.ts
│ │ ├── zh-CN.json
│ │ ├── zh-TW-more.json
│ │ └── zh-TW.json
│ ├── main.ts
│ ├── plugins/
│ │ ├── assets.ts
│ │ └── index.ts
│ ├── router/
│ │ ├── index.ts
│ │ └── permission.ts
│ ├── service/
│ │ └── snapshot.ts
│ ├── services/
│ │ └── codeTemplates.ts
│ ├── store/
│ │ ├── index.ts
│ │ └── modules/
│ │ ├── app/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ ├── auth/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── message/
│ │ │ └── index.ts
│ │ ├── prompt/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ ├── session/
│ │ │ └── index.ts
│ │ ├── user/
│ │ │ ├── helper.ts
│ │ │ └── index.ts
│ │ └── workspace/
│ │ └── index.ts
│ ├── styles/
│ │ ├── global.less
│ │ └── lib/
│ │ ├── github-markdown.less
│ │ ├── highlight.less
│ │ └── tailwind.css
│ ├── types/
│ │ └── chat-models.ts
│ ├── typings/
│ │ ├── chat.d.ts
│ │ └── global.d.ts
│ ├── utils/
│ │ ├── __tests__/
│ │ │ └── date.test.ts
│ │ ├── artifacts.ts
│ │ ├── crypto/
│ │ │ └── index.ts
│ │ ├── date.ts
│ │ ├── download.ts
│ │ ├── errorHandler.ts
│ │ ├── format/
│ │ │ └── index.ts
│ │ ├── is/
│ │ │ └── index.ts
│ │ ├── jwt.ts
│ │ ├── logger.ts
│ │ ├── notificationManager.ts
│ │ ├── prompt.ts
│ │ ├── rand.ts
│ │ ├── request/
│ │ │ ├── axios.ts
│ │ │ └── index.ts
│ │ ├── sanitize.ts
│ │ ├── storage/
│ │ │ ├── index.ts
│ │ │ └── local.ts
│ │ ├── string.ts
│ │ ├── tooling.ts
│ │ └── workspaceUrls.ts
│ └── views/
│ ├── admin/
│ │ ├── index.vue
│ │ ├── model/
│ │ │ ├── AddModelForm.vue
│ │ │ └── index.vue
│ │ ├── modelRateLimit/
│ │ │ ├── addChatModelForm.vue
│ │ │ └── index.vue
│ │ └── user/
│ │ └── index.vue
│ ├── bot/
│ │ ├── all.vue
│ │ ├── components/
│ │ │ ├── AnswerHistory.vue
│ │ │ └── Message/
│ │ │ ├── index.vue
│ │ │ └── style.less
│ │ └── page.vue
│ ├── chat/
│ │ ├── components/
│ │ │ ├── ArtifactGallery.vue
│ │ │ ├── AudioPlayer/
│ │ │ │ └── index.vue
│ │ │ ├── Conversation.vue
│ │ │ ├── HeaderMobile/
│ │ │ │ └── index.vue
│ │ │ ├── JumpToBottom.vue
│ │ │ ├── Message/
│ │ │ │ ├── ArtifactContent.vue
│ │ │ │ ├── ArtifactEditor.vue
│ │ │ │ ├── ArtifactHeader.vue
│ │ │ │ ├── ArtifactViewer.vue
│ │ │ │ ├── ArtifactViewerBase.vue
│ │ │ │ ├── SuggestedQuestions.vue
│ │ │ │ └── index.vue
│ │ │ ├── MessageList.vue
│ │ │ ├── ModelSelector.vue
│ │ │ ├── PromptGallery/
│ │ │ │ ├── PromptCards.vue
│ │ │ │ └── index.vue
│ │ │ ├── RenderMessage.vue
│ │ │ ├── Session/
│ │ │ │ └── SessionConfig.vue
│ │ │ ├── UploadModal.vue
│ │ │ ├── Uploader.vue
│ │ │ ├── UploaderReadOnly.vue
│ │ │ ├── WorkspaceSelector/
│ │ │ │ ├── WorkspaceCard.vue
│ │ │ │ ├── WorkspaceManagementModal.vue
│ │ │ │ ├── WorkspaceModal.vue
│ │ │ │ └── index.vue
│ │ │ ├── __tests__/
│ │ │ │ └── modelSelectorUtils.test.ts
│ │ │ └── modelSelectorUtils.ts
│ │ ├── composables/
│ │ │ ├── README.md
│ │ │ ├── useChatActions.ts
│ │ │ ├── useConversationFlow.ts
│ │ │ ├── useErrorHandling.ts
│ │ │ ├── usePerformanceOptimizations.ts
│ │ │ ├── useRegenerate.ts
│ │ │ ├── useSearchAndPrompts.ts
│ │ │ ├── useStreamHandling.ts
│ │ │ └── useValidation.ts
│ │ ├── hooks/
│ │ │ ├── useChat.ts
│ │ │ ├── useCopyCode.ts
│ │ │ ├── useScroll.ts
│ │ │ ├── useSlashToFocus.ts
│ │ │ └── useUsingContext.ts
│ │ ├── index.vue
│ │ └── layout/
│ │ ├── Layout.vue
│ │ ├── index.ts
│ │ └── sider/
│ │ ├── Footer.vue
│ │ ├── List.vue
│ │ └── index.vue
│ ├── components/
│ │ ├── Avatar/
│ │ │ ├── MessageAvatar.vue
│ │ │ └── ModelAvatar.vue
│ │ ├── Message/
│ │ │ ├── AnswerContent.vue
│ │ │ ├── Text.vue
│ │ │ ├── ThinkingRenderer.vue
│ │ │ ├── Util.ts
│ │ │ ├── style.less
│ │ │ ├── thinkingParser.ts
│ │ │ ├── types/
│ │ │ │ └── thinking.ts
│ │ │ └── useThinkingContent.ts
│ │ └── Permission.vue
│ ├── exception/
│ │ ├── 404/
│ │ │ └── index.vue
│ │ └── 500/
│ │ └── index.vue
│ ├── prompt/
│ │ ├── components/
│ │ │ ├── Definitions.vue
│ │ │ ├── PromptCreator.vue
│ │ │ └── PromptProcess.vue
│ │ └── creator.vue
│ └── snapshot/
│ ├── all.vue
│ ├── components/
│ │ ├── Comment/
│ │ │ └── index.vue
│ │ ├── Header/
│ │ │ └── index.vue
│ │ ├── Message/
│ │ │ ├── index.vue
│ │ │ └── style.less
│ │ └── Search.vue
│ └── page.vue
├── tailwind.config.js
└── tsconfig.json
SYMBOL INDEX (1780 symbols across 229 files)
FILE: api/admin_handler.go
type AdminHandler (line 12) | type AdminHandler struct
method RegisterRoutes (line 22) | func (h *AdminHandler) RegisterRoutes(router *mux.Router) {
method CreateUser (line 33) | func (h *AdminHandler) CreateUser(w http.ResponseWriter, r *http.Reque...
method UpdateUser (line 48) | func (h *AdminHandler) UpdateUser(w http.ResponseWriter, r *http.Reque...
method UserStatHandler (line 63) | func (h *AdminHandler) UserStatHandler(w http.ResponseWriter, r *http....
method UpdateRateLimit (line 110) | func (h *AdminHandler) UpdateRateLimit(w http.ResponseWriter, r *http....
method UserAnalysisHandler (line 133) | func (h *AdminHandler) UserAnalysisHandler(w http.ResponseWriter, r *h...
method UserSessionHistoryHandler (line 159) | func (h *AdminHandler) UserSessionHistoryHandler(w http.ResponseWriter...
method SessionMessagesHandler (line 204) | func (h *AdminHandler) SessionMessagesHandler(w http.ResponseWriter, r...
function NewAdminHandler (line 16) | func NewAdminHandler(service *AuthUserService) *AdminHandler {
type SessionHistoryResponse (line 152) | type SessionHistoryResponse struct
FILE: api/ai/model.go
type Role (line 8) | type Role
method String (line 16) | func (r Role) String() string {
method UnmarshalJSON (line 42) | func (r *Role) UnmarshalJSON(data []byte) error {
method MarshalJSON (line 61) | func (r Role) MarshalJSON() ([]byte, error) {
constant System (line 11) | System Role = iota
constant User (line 12) | User
constant Assistant (line 13) | Assistant
function StringToRole (line 29) | func StringToRole(s string) (Role, error) {
FILE: api/auth/auth.go
constant iterations (line 16) | iterations = 260000
constant saltSize (line 17) | saltSize = 16
constant keySize (line 18) | keySize = 32
function generateSalt (line 21) | func generateSalt() ([]byte, error) {
function GeneratePasswordHash (line 27) | func GeneratePasswordHash(password string) (string, error) {
function ValidatePassword (line 42) | func ValidatePassword(password, hash string) bool {
function GenerateRandomPassword (line 62) | func GenerateRandomPassword() (string, error) {
FILE: api/auth/auth_test.go
function TestGeneratePasswordHash (line 9) | func TestGeneratePasswordHash(t *testing.T) {
function TestGeneratePasswordHash2 (line 30) | func TestGeneratePasswordHash2(t *testing.T) {
function TestPass (line 51) | func TestPass(t *testing.T) {
FILE: api/auth/token.go
function NewUUID (line 16) | func NewUUID() string {
constant TokenTypeAccess (line 27) | TokenTypeAccess = "access"
constant TokenTypeRefresh (line 28) | TokenTypeRefresh = "refresh"
function GenJwtSecretAndAudience (line 31) | func GenJwtSecretAndAudience() (string, string) {
function GenerateToken (line 49) | func GenerateToken(userID int32, role string, secret, jwt_audience strin...
function ValidateToken (line 79) | func ValidateToken(tokenString string, secret string, expectedTokenType ...
function GetExpireSecureCookie (line 125) | func GetExpireSecureCookie(value string, isHttps bool) *http.Cookie {
FILE: api/auth/token_test.go
function TestGenerateToken (line 9) | func TestGenerateToken(t *testing.T) {
FILE: api/bot_answer_history_handler.go
type BotAnswerHistoryHandler (line 12) | type BotAnswerHistoryHandler struct
method Register (line 21) | func (h *BotAnswerHistoryHandler) Register(router *mux.Router) {
method CreateBotAnswerHistory (line 33) | func (h *BotAnswerHistoryHandler) CreateBotAnswerHistory(w http.Respon...
method GetBotAnswerHistoryByID (line 59) | func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryByID(w http.Respo...
method GetBotAnswerHistoryByBotUUID (line 81) | func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryByBotUUID(w http....
method GetBotAnswerHistoryByUserID (line 116) | func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryByUserID(w http.R...
method UpdateBotAnswerHistory (line 134) | func (h *BotAnswerHistoryHandler) UpdateBotAnswerHistory(w http.Respon...
method DeleteBotAnswerHistory (line 156) | func (h *BotAnswerHistoryHandler) DeleteBotAnswerHistory(w http.Respon...
method GetBotAnswerHistoryCountByBotUUID (line 177) | func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryCountByBotUUID(w ...
method GetBotAnswerHistoryCountByUserID (line 193) | func (h *BotAnswerHistoryHandler) GetBotAnswerHistoryCountByUserID(w h...
method GetLatestBotAnswerHistoryByBotUUID (line 210) | func (h *BotAnswerHistoryHandler) GetLatestBotAnswerHistoryByBotUUID(w...
function NewBotAnswerHistoryHandler (line 16) | func NewBotAnswerHistoryHandler(q *sqlc_queries.Queries) *BotAnswerHisto...
FILE: api/bot_answer_history_service.go
type BotAnswerHistoryService (line 10) | type BotAnswerHistoryService struct
method CreateBotAnswerHistory (line 20) | func (s *BotAnswerHistoryService) CreateBotAnswerHistory(ctx context.C...
method GetBotAnswerHistoryByID (line 29) | func (s *BotAnswerHistoryService) GetBotAnswerHistoryByID(ctx context....
method GetBotAnswerHistoryByBotUUID (line 38) | func (s *BotAnswerHistoryService) GetBotAnswerHistoryByBotUUID(ctx con...
method GetBotAnswerHistoryByUserID (line 52) | func (s *BotAnswerHistoryService) GetBotAnswerHistoryByUserID(ctx cont...
method UpdateBotAnswerHistory (line 66) | func (s *BotAnswerHistoryService) UpdateBotAnswerHistory(ctx context.C...
method DeleteBotAnswerHistory (line 80) | func (s *BotAnswerHistoryService) DeleteBotAnswerHistory(ctx context.C...
method GetBotAnswerHistoryCountByBotUUID (line 89) | func (s *BotAnswerHistoryService) GetBotAnswerHistoryCountByBotUUID(ct...
method GetBotAnswerHistoryCountByUserID (line 98) | func (s *BotAnswerHistoryService) GetBotAnswerHistoryCountByUserID(ctx...
method GetLatestBotAnswerHistoryByBotUUID (line 107) | func (s *BotAnswerHistoryService) GetLatestBotAnswerHistoryByBotUUID(c...
function NewBotAnswerHistoryService (line 15) | func NewBotAnswerHistoryService(q *sqlc_queries.Queries) *BotAnswerHisto...
FILE: api/chat_artifact.go
function extractArtifacts (line 9) | func extractArtifacts(content string) []Artifact {
function isExecutableLanguage (line 161) | func isExecutableLanguage(language string) bool {
function containsExecutablePatterns (line 177) | func containsExecutablePatterns(content string) bool {
FILE: api/chat_auth_user_handler.go
constant AccessTokenLifetime (line 20) | AccessTokenLifetime = 30 * time.Minute
constant RefreshTokenLifetime (line 21) | RefreshTokenLifetime = 7 * 24 * time.Hour
constant RefreshTokenName (line 22) | RefreshTokenName = "refresh_token"
type AuthUserHandler (line 25) | type AuthUserHandler struct
method Register (line 100) | func (h *AuthUserHandler) Register(router *mux.Router) {
method RegisterPublicRoutes (line 107) | func (h *AuthUserHandler) RegisterPublicRoutes(router *mux.Router) {
method CreateUser (line 115) | func (h *AuthUserHandler) CreateUser(w http.ResponseWriter, r *http.Re...
method GetUserByID (line 130) | func (h *AuthUserHandler) GetUserByID(w http.ResponseWriter, r *http.R...
method UpdateSelf (line 144) | func (h *AuthUserHandler) UpdateSelf(w http.ResponseWriter, r *http.Re...
method UpdateUser (line 167) | func (h *AuthUserHandler) UpdateUser(w http.ResponseWriter, r *http.Re...
method SignUp (line 189) | func (h *AuthUserHandler) SignUp(w http.ResponseWriter, r *http.Reques...
method Login (line 271) | func (h *AuthUserHandler) Login(w http.ResponseWriter, r *http.Request) {
method ForeverToken (line 351) | func (h *AuthUserHandler) ForeverToken(w http.ResponseWriter, r *http....
method RefreshToken (line 369) | func (h *AuthUserHandler) RefreshToken(w http.ResponseWriter, r *http....
method Logout (line 450) | func (h *AuthUserHandler) Logout(w http.ResponseWriter, r *http.Reques...
method ResetPasswordHandler (line 476) | func (h *AuthUserHandler) ResetPasswordHandler(w http.ResponseWriter, ...
method ChangePasswordHandler (line 542) | func (h *AuthUserHandler) ChangePasswordHandler(w http.ResponseWriter,...
method UserStatHandler (line 587) | func (h *AuthUserHandler) UserStatHandler(w http.ResponseWriter, r *ht...
method UpdateRateLimit (line 639) | func (h *AuthUserHandler) UpdateRateLimit(w http.ResponseWriter, r *ht...
method GetRateLimit (line 661) | func (h *AuthUserHandler) GetRateLimit(w http.ResponseWriter, r *http....
function isHTTPS (line 30) | func isHTTPS(r *http.Request) bool {
function createSecureRefreshCookie (line 58) | func createSecureRefreshCookie(name, value string, maxAge int, r *http.R...
function NewAuthUserHandler (line 95) | func NewAuthUserHandler(sqlc_q *sqlc_queries.Queries) *AuthUserHandler {
type LoginParams (line 184) | type LoginParams struct
type TokenRequest (line 468) | type TokenRequest struct
type ResetPasswordRequest (line 472) | type ResetPasswordRequest struct
function SendPasswordResetEmail (line 533) | func SendPasswordResetEmail(email, tempPassword string) error {
type ChangePasswordRequest (line 537) | type ChangePasswordRequest struct
type UserStat (line 575) | type UserStat struct
type RateLimitRequest (line 634) | type RateLimitRequest struct
FILE: api/chat_auth_user_service.go
type AuthUserService (line 16) | type AuthUserService struct
method CreateAuthUser (line 26) | func (s *AuthUserService) CreateAuthUser(ctx context.Context, auth_use...
method GetAuthUserByID (line 43) | func (s *AuthUserService) GetAuthUserByID(ctx context.Context, id int3...
method GetAllAuthUsers (line 52) | func (s *AuthUserService) GetAllAuthUsers(ctx context.Context) ([]sqlc...
method Authenticate (line 60) | func (s *AuthUserService) Authenticate(ctx context.Context, email, pas...
method Logout (line 71) | func (s *AuthUserService) Logout(tokenString string) (*http.Cookie, er...
method GetUserStats (line 86) | func (s *AuthUserService) GetUserStats(ctx context.Context, p Paginati...
method UpdateRateLimit (line 104) | func (s *AuthUserService) UpdateRateLimit(ctx context.Context, user_em...
method GetRateLimit (line 117) | func (s *AuthUserService) GetRateLimit(ctx context.Context, user_id in...
method GetUserAnalysis (line 168) | func (s *AuthUserService) GetUserAnalysis(ctx context.Context, email s...
method GetUserSessionHistory (line 259) | func (s *AuthUserService) GetUserSessionHistory(ctx context.Context, e...
function NewAuthUserService (line 21) | func NewAuthUserService(q *sqlc_queries.Queries) *AuthUserService {
type UserAnalysisData (line 127) | type UserAnalysisData struct
type UserAnalysisInfo (line 133) | type UserAnalysisInfo struct
type ModelUsageInfo (line 143) | type ModelUsageInfo struct
type ActivityInfo (line 151) | type ActivityInfo struct
type SessionHistoryInfo (line 158) | type SessionHistoryInfo struct
FILE: api/chat_comment_handler.go
type ChatCommentHandler (line 12) | type ChatCommentHandler struct
method Register (line 23) | func (h *ChatCommentHandler) Register(router *mux.Router) {
method CreateChatComment (line 29) | func (h *ChatCommentHandler) CreateChatComment(w http.ResponseWriter, ...
method GetCommentsBySessionUUID (line 64) | func (h *ChatCommentHandler) GetCommentsBySessionUUID(w http.ResponseW...
method GetCommentsByMessageUUID (line 76) | func (h *ChatCommentHandler) GetCommentsByMessageUUID(w http.ResponseW...
function NewChatCommentHandler (line 16) | func NewChatCommentHandler(sqlc_q *sqlc_queries.Queries) *ChatCommentHan...
FILE: api/chat_comment_service.go
type ChatCommentService (line 11) | type ChatCommentService struct
method CreateChatComment (line 20) | func (s *ChatCommentService) CreateChatComment(ctx context.Context, pa...
method GetCommentsBySessionUUID (line 29) | func (s *ChatCommentService) GetCommentsBySessionUUID(ctx context.Cont...
method GetCommentsByMessageUUID (line 38) | func (s *ChatCommentService) GetCommentsByMessageUUID(ctx context.Cont...
method GetCommentsBySession (line 56) | func (s *ChatCommentService) GetCommentsBySession(ctx context.Context,...
method GetCommentsByMessage (line 76) | func (s *ChatCommentService) GetCommentsByMessage(ctx context.Context,...
function NewChatCommentService (line 15) | func NewChatCommentService(q *sqlc_queries.Queries) *ChatCommentService {
type CommentWithAuthor (line 47) | type CommentWithAuthor struct
FILE: api/chat_main_handler.go
type ChatHandler (line 23) | type ChatHandler struct
method Register (line 42) | func (h *ChatHandler) Register(router *mux.Router) {
method GetChatInstructions (line 93) | func (h *ChatHandler) GetChatInstructions(w http.ResponseWriter, r *ht...
method ChatBotCompletionHandler (line 106) | func (h *ChatHandler) ChatBotCompletionHandler(w http.ResponseWriter, ...
method ChatCompletionHandler (line 169) | func (h *ChatHandler) ChatCompletionHandler(w http.ResponseWriter, r *...
method validateChatSession (line 213) | func (h *ChatHandler) validateChatSession(ctx context.Context, w http....
method handlePromptCreation (line 244) | func (h *ChatHandler) handlePromptCreation(ctx context.Context, w http...
method generateAndSaveAnswer (line 308) | func (h *ChatHandler) generateAndSaveAnswer(ctx context.Context, w htt...
method generateSessionTitle (line 357) | func (h *ChatHandler) generateSessionTitle(chatSession *sqlc_queries.C...
method sendSuggestedQuestionsStream (line 420) | func (h *ChatHandler) sendSuggestedQuestionsStream(w http.ResponseWrit...
method GetRequestContext (line 607) | func (h *ChatHandler) GetRequestContext() context.Context {
method chooseChatModel (line 611) | func (h *ChatHandler) chooseChatModel(chat_session sqlc_queries.ChatSe...
method CheckModelAccess (line 671) | func (h *ChatHandler) CheckModelAccess(w http.ResponseWriter, chatSess...
constant sessionTitleGenerationTimeout (line 29) | sessionTitleGenerationTimeout = 30 * time.Second
function NewChatHandler (line 31) | func NewChatHandler(sqlc_q *sqlc_queries.Queries) *ChatHandler {
type ChatRequest (line 51) | type ChatRequest struct
type ChatCompletionResponse (line 59) | type ChatCompletionResponse struct
type Choice (line 72) | type Choice struct
type OpenaiChatRequest (line 78) | type OpenaiChatRequest struct
type BotRequest (line 83) | type BotRequest struct
type ChatInstructionResponse (line 89) | type ChatInstructionResponse struct
function genAnswer (line 472) | func genAnswer(h *ChatHandler, w http.ResponseWriter, ctx context.Contex...
function genBotAnswer (line 490) | func genBotAnswer(h *ChatHandler, w http.ResponseWriter, session sqlc_qu...
function simpleChatMessagesToMessages (line 534) | func simpleChatMessagesToMessages(simpleChatMessages []SimpleChatMessage...
function regenerateAnswer (line 552) | func regenerateAnswer(h *ChatHandler, w http.ResponseWriter, ctx context...
function isTest (line 658) | func isTest(msgs []models.Message) bool {
FILE: api/chat_main_service.go
type ChatService (line 23) | type ChatService struct
method getAskMessages (line 73) | func (s *ChatService) getAskMessages(chatSession sqlc_queries.ChatSess...
method CreateChatPromptSimple (line 135) | func (s *ChatService) CreateChatPromptSimple(ctx context.Context, chat...
method CreateChatMessageSimple (line 168) | func (s *ChatService) CreateChatMessageSimple(ctx context.Context, ses...
method CreateChatMessageWithSuggestedQuestions (line 215) | func (s *ChatService) CreateChatMessageWithSuggestedQuestions(ctx cont...
method generateSuggestedQuestions (line 272) | func (s *ChatService) generateSuggestedQuestions(content string, messa...
method callLLMForSuggestions (line 314) | func (s *ChatService) callLLMForSuggestions(prompt string) string {
method callGeminiForSuggestions (line 378) | func (s *ChatService) callGeminiForSuggestions(ctx context.Context, mo...
method callOpenAICompatibleForSuggestions (line 429) | func (s *ChatService) callOpenAICompatibleForSuggestions(ctx context.C...
method UpdateChatMessageContent (line 472) | func (s *ChatService) UpdateChatMessageContent(ctx context.Context, uu...
method UpdateChatMessageSuggestions (line 490) | func (s *ChatService) UpdateChatMessageSuggestions(ctx context.Context...
method logChat (line 500) | func (s *ChatService) logChat(chatSession sqlc_queries.ChatSession, ms...
function NewChatService (line 31) | func NewChatService(q *sqlc_queries.Queries) *ChatService {
function loadArtifactInstruction (line 37) | func loadArtifactInstruction() (string, error) {
function appendInstructionToSystemMessage (line 44) | func appendInstructionToSystemMessage(msgs []models.Message, instruction...
FILE: api/chat_message_handler.go
type ChatMessageHandler (line 17) | type ChatMessageHandler struct
method Register (line 28) | func (h *ChatMessageHandler) Register(router *mux.Router) {
method CreateChatMessage (line 47) | func (h *ChatMessageHandler) CreateChatMessage(w http.ResponseWriter, ...
method GetChatMessageByID (line 62) | func (h *ChatMessageHandler) GetChatMessageByID(w http.ResponseWriter,...
method UpdateChatMessage (line 77) | func (h *ChatMessageHandler) UpdateChatMessage(w http.ResponseWriter, ...
method DeleteChatMessage (line 99) | func (h *ChatMessageHandler) DeleteChatMessage(w http.ResponseWriter, ...
method GetAllChatMessages (line 114) | func (h *ChatMessageHandler) GetAllChatMessages(w http.ResponseWriter,...
method GetChatMessageByUUID (line 124) | func (h *ChatMessageHandler) GetChatMessageByUUID(w http.ResponseWrite...
method UpdateChatMessageByUUID (line 136) | func (h *ChatMessageHandler) UpdateChatMessageByUUID(w http.ResponseWr...
method DeleteChatMessageByUUID (line 158) | func (h *ChatMessageHandler) DeleteChatMessageByUUID(w http.ResponseWr...
method GetChatMessagesBySessionUUID (line 169) | func (h *ChatMessageHandler) GetChatMessagesBySessionUUID(w http.Respo...
method GetChatHistoryBySessionUUID (line 210) | func (h *ChatMessageHandler) GetChatHistoryBySessionUUID(w http.Respon...
method DeleteChatMessagesBySesionUUID (line 229) | func (h *ChatMessageHandler) DeleteChatMessagesBySesionUUID(w http.Res...
method GenerateMoreSuggestions (line 240) | func (h *ChatMessageHandler) GenerateMoreSuggestions(w http.ResponseWr...
function NewChatMessageHandler (line 21) | func NewChatMessageHandler(sqlc_q *sqlc_queries.Queries) *ChatMessageHan...
FILE: api/chat_message_service.go
type ChatMessageService (line 13) | type ChatMessageService struct
method CreateChatMessage (line 23) | func (s *ChatMessageService) CreateChatMessage(ctx context.Context, me...
method GetChatMessageByID (line 32) | func (s *ChatMessageService) GetChatMessageByID(ctx context.Context, i...
method UpdateChatMessage (line 41) | func (s *ChatMessageService) UpdateChatMessage(ctx context.Context, me...
method DeleteChatMessage (line 50) | func (s *ChatMessageService) DeleteChatMessage(ctx context.Context, id...
method DeleteChatMessageByUUID (line 59) | func (s *ChatMessageService) DeleteChatMessageByUUID(ctx context.Conte...
method GetAllChatMessages (line 68) | func (s *ChatMessageService) GetAllChatMessages(ctx context.Context) (...
method GetLatestMessagesBySessionID (line 76) | func (s *ChatMessageService) GetLatestMessagesBySessionID(ctx context....
method GetFirstMessageBySessionUUID (line 85) | func (s *ChatMessageService) GetFirstMessageBySessionUUID(ctx context....
method AddMessage (line 93) | func (s *ChatMessageService) AddMessage(ctx context.Context, chatSessi...
method GetChatMessageByUUID (line 109) | func (s *ChatMessageService) GetChatMessageByUUID(ctx context.Context,...
method UpdateChatMessageByUUID (line 118) | func (s *ChatMessageService) UpdateChatMessageByUUID(ctx context.Conte...
method GetChatMessagesBySessionUUID (line 127) | func (s *ChatMessageService) GetChatMessagesBySessionUUID(ctx context....
method DeleteChatMessagesBySesionUUID (line 141) | func (s *ChatMessageService) DeleteChatMessagesBySesionUUID(ctx contex...
method GetChatMessagesCount (line 146) | func (s *ChatMessageService) GetChatMessagesCount(ctx context.Context,...
function NewChatMessageService (line 18) | func NewChatMessageService(q *sqlc_queries.Queries) *ChatMessageService {
FILE: api/chat_message_service_test.go
function TestChatMessageService (line 13) | func TestChatMessageService(t *testing.T) {
function TestGetChatMessagesBySessionID (line 63) | func TestGetChatMessagesBySessionID(t *testing.T) {
FILE: api/chat_model_handler.go
type ChatModelHandler (line 14) | type ChatModelHandler struct
method Register (line 24) | func (h *ChatModelHandler) Register(r *mux.Router) {
method ListSystemChatModels (line 40) | func (h *ChatModelHandler) ListSystemChatModels(w http.ResponseWriter,...
method ChatModelByID (line 86) | func (h *ChatModelHandler) ChatModelByID(w http.ResponseWriter, r *htt...
method CreateChatModel (line 109) | func (h *ChatModelHandler) CreateChatModel(w http.ResponseWriter, r *h...
method UpdateChatModel (line 186) | func (h *ChatModelHandler) UpdateChatModel(w http.ResponseWriter, r *h...
method DeleteChatModel (line 278) | func (h *ChatModelHandler) DeleteChatModel(w http.ResponseWriter, r *h...
method GetDefaultChatModel (line 312) | func (h *ChatModelHandler) GetDefaultChatModel(w http.ResponseWriter, ...
function NewChatModelHandler (line 18) | func NewChatModelHandler(db *sqlc_queries.Queries) *ChatModelHandler {
FILE: api/chat_model_handler_test.go
function createTwoChatModel (line 19) | func createTwoChatModel(q *sqlc_queries.Queries) (sqlc_queries.AuthUser,...
function clearChatModelsIfExists (line 68) | func clearChatModelsIfExists(q *sqlc_queries.Queries) {
function unmarshalResponseToChatModel (line 80) | func unmarshalResponseToChatModel(t *testing.T, rr *httptest.ResponseRec...
function TestChatModelTest (line 91) | func TestChatModelTest(t *testing.T) {
function checkGetModels (line 146) | func checkGetModels(t *testing.T, router *mux.Router, expectedResults []...
function updateFirstRecord (line 162) | func updateFirstRecord(t *testing.T, router *mux.Router, chatModelID int...
FILE: api/chat_model_privilege_handler.go
type UserChatModelPrivilegeHandler (line 18) | type UserChatModelPrivilegeHandler struct
method Register (line 30) | func (h *UserChatModelPrivilegeHandler) Register(r *mux.Router) {
method ListUserChatModelPrivileges (line 46) | func (h *UserChatModelPrivilegeHandler) ListUserChatModelPrivileges(w ...
method UserChatModelPrivilegeByID (line 69) | func (h *UserChatModelPrivilegeHandler) UserChatModelPrivilegeByID(w h...
method CreateUserChatModelPrivilege (line 87) | func (h *UserChatModelPrivilegeHandler) CreateUserChatModelPrivilege(w...
method UpdateUserChatModelPrivilege (line 162) | func (h *UserChatModelPrivilegeHandler) UpdateUserChatModelPrivilege(w...
method DeleteUserChatModelPrivilege (line 215) | func (h *UserChatModelPrivilegeHandler) DeleteUserChatModelPrivilege(w...
method UserChatModelPrivilegeByUserAndModelID (line 236) | func (h *UserChatModelPrivilegeHandler) UserChatModelPrivilegeByUserAn...
method ListUserChatModelPrivilegesByUserID (line 268) | func (h *UserChatModelPrivilegeHandler) ListUserChatModelPrivilegesByU...
function NewUserChatModelPrivilegeHandler (line 23) | func NewUserChatModelPrivilegeHandler(db *sqlc_queries.Queries) *UserCha...
type ChatModelPrivilege (line 37) | type ChatModelPrivilege struct
FILE: api/chat_prompt_hander.go
type ChatPromptHandler (line 15) | type ChatPromptHandler struct
method Register (line 26) | func (h *ChatPromptHandler) Register(router *mux.Router) {
method CreateChatPrompt (line 37) | func (h *ChatPromptHandler) CreateChatPrompt(w http.ResponseWriter, r ...
method GetChatPromptByID (line 91) | func (h *ChatPromptHandler) GetChatPromptByID(w http.ResponseWriter, r...
method UpdateChatPrompt (line 106) | func (h *ChatPromptHandler) UpdateChatPrompt(w http.ResponseWriter, r ...
method DeleteChatPrompt (line 128) | func (h *ChatPromptHandler) DeleteChatPrompt(w http.ResponseWriter, r ...
method GetAllChatPrompts (line 143) | func (h *ChatPromptHandler) GetAllChatPrompts(w http.ResponseWriter, r...
method GetChatPromptsByUserID (line 152) | func (h *ChatPromptHandler) GetChatPromptsByUserID(w http.ResponseWrit...
method DeleteChatPromptByUUID (line 167) | func (h *ChatPromptHandler) DeleteChatPromptByUUID(w http.ResponseWrit...
method UpdateChatPromptByUUID (line 177) | func (h *ChatPromptHandler) UpdateChatPromptByUUID(w http.ResponseWrit...
function NewChatPromptHandler (line 19) | func NewChatPromptHandler(sqlc_q *sqlc_queries.Queries) *ChatPromptHandl...
FILE: api/chat_prompt_service.go
type ChatPromptService (line 11) | type ChatPromptService struct
method CreateChatPrompt (line 21) | func (s *ChatPromptService) CreateChatPrompt(ctx context.Context, prom...
method CreateChatPromptWithUUID (line 29) | func (s *ChatPromptService) CreateChatPromptWithUUID(ctx context.Conte...
method GetChatPromptByID (line 40) | func (s *ChatPromptService) GetChatPromptByID(ctx context.Context, id ...
method UpdateChatPrompt (line 49) | func (s *ChatPromptService) UpdateChatPrompt(ctx context.Context, prom...
method DeleteChatPrompt (line 58) | func (s *ChatPromptService) DeleteChatPrompt(ctx context.Context, id i...
method GetAllChatPrompts (line 67) | func (s *ChatPromptService) GetAllChatPrompts(ctx context.Context) ([]...
method GetChatPromptsByUserID (line 75) | func (s *ChatPromptService) GetChatPromptsByUserID(ctx context.Context...
method GetChatPromptsBySessionUUID (line 83) | func (s *ChatPromptService) GetChatPromptsBySessionUUID(ctx context.Co...
method DeleteChatPromptByUUID (line 92) | func (s *ChatPromptService) DeleteChatPromptByUUID(ctx context.Context...
method UpdateChatPromptByUUID (line 101) | func (s *ChatPromptService) UpdateChatPromptByUUID(ctx context.Context...
function NewChatPromptService (line 16) | func NewChatPromptService(q *sqlc_queries.Queries) *ChatPromptService {
FILE: api/chat_prompt_service_test.go
function TestChatPromptService (line 14) | func TestChatPromptService(t *testing.T) {
function TestGetAllChatPrompts (line 67) | func TestGetAllChatPrompts(t *testing.T) {
function TestGetChatPromptsByTopic (line 113) | func TestGetChatPromptsByTopic(t *testing.T) {
function TestGetChatPromptsByUserID (line 161) | func TestGetChatPromptsByUserID(t *testing.T) {
FILE: api/chat_session_handler.go
type ChatSessionHandler (line 15) | type ChatSessionHandler struct
method Register (line 27) | func (h *ChatSessionHandler) Register(router *mux.Router) {
method getChatSessionByUUID (line 40) | func (h *ChatSessionHandler) getChatSessionByUUID(w http.ResponseWrite...
method createChatSessionByUUID (line 68) | func (h *ChatSessionHandler) createChatSessionByUUID(w http.ResponseWr...
method createOrUpdateChatSessionByUUID (line 164) | func (h *ChatSessionHandler) createOrUpdateChatSessionByUUID(w http.Re...
method deleteChatSessionByUUID (line 234) | func (h *ChatSessionHandler) deleteChatSessionByUUID(w http.ResponseWr...
method getSimpleChatSessionsByUserID (line 248) | func (h *ChatSessionHandler) getSimpleChatSessionsByUserID(w http.Resp...
method updateChatSessionTopicByUUID (line 270) | func (h *ChatSessionHandler) updateChatSessionTopicByUUID(w http.Respo...
method updateSessionMaxLength (line 305) | func (h *ChatSessionHandler) updateSessionMaxLength(w http.ResponseWri...
method createChatSessionFromSnapshot (line 334) | func (h *ChatSessionHandler) createChatSessionFromSnapshot(w http.Resp...
function NewChatSessionHandler (line 19) | func NewChatSessionHandler(sqlc_q *sqlc_queries.Queries) *ChatSessionHan...
type UpdateChatSessionRequest (line 147) | type UpdateChatSessionRequest struct
FILE: api/chat_session_service.go
type ChatSessionService (line 15) | type ChatSessionService struct
method CreateChatSession (line 25) | func (s *ChatSessionService) CreateChatSession(ctx context.Context, se...
method GetChatSessionByID (line 34) | func (s *ChatSessionService) GetChatSessionByID(ctx context.Context, i...
method UpdateChatSession (line 43) | func (s *ChatSessionService) UpdateChatSession(ctx context.Context, se...
method DeleteChatSession (line 52) | func (s *ChatSessionService) DeleteChatSession(ctx context.Context, id...
method GetAllChatSessions (line 61) | func (s *ChatSessionService) GetAllChatSessions(ctx context.Context) (...
method GetChatSessionsByUserID (line 69) | func (s *ChatSessionService) GetChatSessionsByUserID(ctx context.Conte...
method GetSimpleChatSessionsByUserID (line 77) | func (s *ChatSessionService) GetSimpleChatSessionsByUserID(ctx context...
method GetChatSessionByUUID (line 109) | func (s *ChatSessionService) GetChatSessionByUUID(ctx context.Context,...
method UpdateChatSessionByUUID (line 118) | func (s *ChatSessionService) UpdateChatSessionByUUID(ctx context.Conte...
method UpdateChatSessionTopicByUUID (line 127) | func (s *ChatSessionService) UpdateChatSessionTopicByUUID(ctx context....
method CreateOrUpdateChatSessionByUUID (line 136) | func (s *ChatSessionService) CreateOrUpdateChatSessionByUUID(ctx conte...
method DeleteChatSessionByUUID (line 145) | func (s *ChatSessionService) DeleteChatSessionByUUID(ctx context.Conte...
method UpdateSessionMaxLength (line 155) | func (s *ChatSessionService) UpdateSessionMaxLength(ctx context.Contex...
method EnsureDefaultSystemPrompt (line 165) | func (s *ChatSessionService) EnsureDefaultSystemPrompt(ctx context.Con...
function NewChatSessionService (line 20) | func NewChatSessionService(q *sqlc_queries.Queries) *ChatSessionService {
FILE: api/chat_session_service_test.go
function TestChatSessionService (line 14) | func TestChatSessionService(t *testing.T) {
function TestGetChatSessionsByUserID (line 67) | func TestGetChatSessionsByUserID(t *testing.T) {
function TestGetAllChatSessions (line 103) | func TestGetAllChatSessions(t *testing.T) {
FILE: api/chat_snapshot_handler.go
type ChatSnapshotHandler (line 13) | type ChatSnapshotHandler struct
method Register (line 24) | func (h *ChatSnapshotHandler) Register(router *mux.Router) {
method CreateChatSnapshot (line 34) | func (h *ChatSnapshotHandler) CreateChatSnapshot(w http.ResponseWriter...
method CreateChatBot (line 56) | func (h *ChatSnapshotHandler) CreateChatBot(w http.ResponseWriter, r *...
method GetChatSnapshot (line 80) | func (h *ChatSnapshotHandler) GetChatSnapshot(w http.ResponseWriter, r...
method ChatSnapshotMetaByUserID (line 92) | func (h *ChatSnapshotHandler) ChatSnapshotMetaByUserID(w http.Response...
method UpdateChatSnapshotMetaByUUID (line 162) | func (h *ChatSnapshotHandler) UpdateChatSnapshotMetaByUUID(w http.Resp...
method DeleteChatSnapshot (line 208) | func (h *ChatSnapshotHandler) DeleteChatSnapshot(w http.ResponseWriter...
method ChatSnapshotSearch (line 233) | func (h *ChatSnapshotHandler) ChatSnapshotSearch(w http.ResponseWriter...
function NewChatSnapshotHandler (line 18) | func NewChatSnapshotHandler(sqlc_q *sqlc_queries.Queries) *ChatSnapshotH...
FILE: api/chat_snapshot_handler_test.go
function TestChatSnapshot (line 19) | func TestChatSnapshot(t *testing.T) {
FILE: api/chat_snapshot_service.go
type ChatSnapshotService (line 14) | type ChatSnapshotService struct
method CreateChatSnapshot (line 23) | func (s *ChatSnapshotService) CreateChatSnapshot(ctx context.Context, ...
method CreateChatBot (line 83) | func (s *ChatSnapshotService) CreateChatBot(ctx context.Context, chatS...
function NewChatSnapshotService (line 19) | func NewChatSnapshotService(q *sqlc_queries.Queries) *ChatSnapshotService {
function GenTitle (line 66) | func GenTitle(q *sqlc_queries.Queries, ctx context.Context, chatSession ...
FILE: api/chat_user_active_chat_session_handler.go
type UserActiveChatSessionHandler (line 17) | type UserActiveChatSessionHandler struct
method Register (line 30) | func (h *UserActiveChatSessionHandler) Register(router *mux.Router) {
method GetUserActiveChatSessionHandler (line 42) | func (h *UserActiveChatSessionHandler) GetUserActiveChatSessionHandler...
method CreateOrUpdateUserActiveChatSessionHandler (line 75) | func (h *UserActiveChatSessionHandler) CreateOrUpdateUserActiveChatSes...
method GetWorkspaceActiveSessionHandler (line 119) | func (h *UserActiveChatSessionHandler) GetWorkspaceActiveSessionHandle...
method SetWorkspaceActiveSessionHandler (line 165) | func (h *UserActiveChatSessionHandler) SetWorkspaceActiveSessionHandle...
method GetAllWorkspaceActiveSessionsHandler (line 237) | func (h *UserActiveChatSessionHandler) GetAllWorkspaceActiveSessionsHa...
function NewUserActiveChatSessionHandler (line 22) | func NewUserActiveChatSessionHandler(sqlc_q *sqlc.Queries) *UserActiveCh...
FILE: api/chat_user_active_chat_session_sevice.go
type UserActiveChatSessionService (line 10) | type UserActiveChatSessionService struct
method UpsertActiveSession (line 21) | func (s *UserActiveChatSessionService) UpsertActiveSession(ctx context...
method GetActiveSession (line 39) | func (s *UserActiveChatSessionService) GetActiveSession(ctx context.Co...
method GetAllActiveSessions (line 56) | func (s *UserActiveChatSessionService) GetAllActiveSessions(ctx contex...
method DeleteActiveSession (line 65) | func (s *UserActiveChatSessionService) DeleteActiveSession(ctx context...
function NewUserActiveChatSessionService (line 14) | func NewUserActiveChatSessionService(q *sqlc.Queries) *UserActiveChatSes...
FILE: api/chat_workspace_handler.go
type ChatWorkspaceHandler (line 16) | type ChatWorkspaceHandler struct
method Register (line 27) | func (h *ChatWorkspaceHandler) Register(router *mux.Router) {
method createWorkspace (line 74) | func (h *ChatWorkspaceHandler) createWorkspace(w http.ResponseWriter, ...
method getWorkspaceByUUID (line 154) | func (h *ChatWorkspaceHandler) getWorkspaceByUUID(w http.ResponseWrite...
method getWorkspacesByUserID (line 212) | func (h *ChatWorkspaceHandler) getWorkspacesByUserID(w http.ResponseWr...
method updateWorkspace (line 250) | func (h *ChatWorkspaceHandler) updateWorkspace(w http.ResponseWriter, ...
method updateWorkspaceOrder (line 316) | func (h *ChatWorkspaceHandler) updateWorkspaceOrder(w http.ResponseWri...
method deleteWorkspace (line 379) | func (h *ChatWorkspaceHandler) deleteWorkspace(w http.ResponseWriter, ...
method setDefaultWorkspace (line 432) | func (h *ChatWorkspaceHandler) setDefaultWorkspace(w http.ResponseWrit...
method setWorkspaceAsDefaultForUser (line 480) | func (h *ChatWorkspaceHandler) setWorkspaceAsDefaultForUser(ctx contex...
method ensureDefaultWorkspace (line 505) | func (h *ChatWorkspaceHandler) ensureDefaultWorkspace(w http.ResponseW...
method createSessionInWorkspace (line 544) | func (h *ChatWorkspaceHandler) createSessionInWorkspace(w http.Respons...
method getSessionsByWorkspace (line 636) | func (h *ChatWorkspaceHandler) getSessionsByWorkspace(w http.ResponseW...
method autoMigrateLegacySessions (line 706) | func (h *ChatWorkspaceHandler) autoMigrateLegacySessions(w http.Respon...
function NewChatWorkspaceHandler (line 20) | func NewChatWorkspaceHandler(sqlc_q *sqlc_queries.Queries) *ChatWorkspac...
type CreateWorkspaceRequest (line 41) | type CreateWorkspaceRequest struct
type UpdateWorkspaceRequest (line 49) | type UpdateWorkspaceRequest struct
type UpdateWorkspaceOrderRequest (line 56) | type UpdateWorkspaceOrderRequest struct
type WorkspaceResponse (line 60) | type WorkspaceResponse struct
type CreateSessionInWorkspaceRequest (line 537) | type CreateSessionInWorkspaceRequest struct
FILE: api/chat_workspace_service.go
type ChatWorkspaceService (line 15) | type ChatWorkspaceService struct
method CreateWorkspace (line 25) | func (s *ChatWorkspaceService) CreateWorkspace(ctx context.Context, pa...
method GetWorkspaceByUUID (line 34) | func (s *ChatWorkspaceService) GetWorkspaceByUUID(ctx context.Context,...
method GetWorkspacesByUserID (line 43) | func (s *ChatWorkspaceService) GetWorkspacesByUserID(ctx context.Conte...
method GetWorkspaceWithSessionCount (line 52) | func (s *ChatWorkspaceService) GetWorkspaceWithSessionCount(ctx contex...
method UpdateWorkspace (line 61) | func (s *ChatWorkspaceService) UpdateWorkspace(ctx context.Context, pa...
method UpdateWorkspaceOrder (line 70) | func (s *ChatWorkspaceService) UpdateWorkspaceOrder(ctx context.Contex...
method DeleteWorkspace (line 79) | func (s *ChatWorkspaceService) DeleteWorkspace(ctx context.Context, wo...
method GetDefaultWorkspaceByUserID (line 88) | func (s *ChatWorkspaceService) GetDefaultWorkspaceByUserID(ctx context...
method SetDefaultWorkspace (line 97) | func (s *ChatWorkspaceService) SetDefaultWorkspace(ctx context.Context...
method CreateDefaultWorkspace (line 106) | func (s *ChatWorkspaceService) CreateDefaultWorkspace(ctx context.Cont...
method EnsureDefaultWorkspaceExists (line 120) | func (s *ChatWorkspaceService) EnsureDefaultWorkspaceExists(ctx contex...
method HasWorkspacePermission (line 134) | func (s *ChatWorkspaceService) HasWorkspacePermission(ctx context.Cont...
method MigrateSessionsToDefaultWorkspace (line 151) | func (s *ChatWorkspaceService) MigrateSessionsToDefaultWorkspace(ctx c...
function NewChatWorkspaceService (line 20) | func NewChatWorkspaceService(q *sqlc_queries.Queries) *ChatWorkspaceServ...
FILE: api/constants.go
constant DefaultRequestTimeout (line 11) | DefaultRequestTimeout = 5 * time.Minute
constant MaxStreamingLoopIterations (line 14) | MaxStreamingLoopIterations = 10000
constant SmallAnswerThreshold (line 17) | SmallAnswerThreshold = 200
constant FlushCharacterThreshold (line 18) | FlushCharacterThreshold = 500
constant TestPrefixLength (line 19) | TestPrefixLength = 16
constant DefaultPageSize (line 22) | DefaultPageSize = 200
constant MaxHistoryItems (line 23) | MaxHistoryItems = 10000
constant DefaultPageLimit (line 26) | DefaultPageLimit = 30
constant TestDemoPrefix (line 29) | TestDemoPrefix = "test_demo_bestqa"
constant DefaultMaxLength (line 32) | DefaultMaxLength = 10
constant DefaultTemperature (line 33) | DefaultTemperature = 0.7
constant DefaultMaxTokens (line 34) | DefaultMaxTokens = 4096
constant DefaultTopP (line 35) | DefaultTopP = 1.0
constant DefaultN (line 36) | DefaultN = 1
constant RequestTimeoutSeconds (line 37) | RequestTimeoutSeconds = 10
constant TokenEstimateRatio (line 38) | TokenEstimateRatio = 4
constant SummarizeThreshold (line 39) | SummarizeThreshold = 300
constant DefaultSystemPromptText (line 40) | DefaultSystemPromptText = "You are a helpful, concise assistant. Ask cla...
constant ErrorStreamUnsupported (line 45) | ErrorStreamUnsupported = "Streaming unsupported by client"
constant ErrorNoContent (line 46) | ErrorNoContent = "no content in answer"
constant ErrorEndOfStream (line 47) | ErrorEndOfStream = "End of stream reached"
constant ErrorDoneBreak (line 48) | ErrorDoneBreak = "DONE break"
constant ContentTypeJSON (line 53) | ContentTypeJSON = "application/json"
constant AcceptEventStream (line 54) | AcceptEventStream = "text/event-stream"
constant CacheControlNoCache (line 55) | CacheControlNoCache = "no-cache"
constant ConnectionKeepAlive (line 56) | ConnectionKeepAlive = "keep-alive"
FILE: api/embed_debug_test.go
function TestEmbedInstructions (line 5) | func TestEmbedInstructions(t *testing.T) {
FILE: api/errors.go
type APIError (line 19) | type APIError struct
method WithMessage (line 37) | func (e APIError) WithMessage(message string) APIError {
method WithDetail (line 43) | func (e APIError) WithDetail(detail string) APIError {
method WithDebugInfo (line 49) | func (e APIError) WithDebugInfo(debugInfo string) APIError {
method Error (line 54) | func (e APIError) Error() string {
function NewAPIError (line 28) | func NewAPIError(httpCode int, code, message string) APIError {
constant ErrAuth (line 60) | ErrAuth = "AUTH"
constant ErrValidation (line 61) | ErrValidation = "VALD"
constant ErrResource (line 62) | ErrResource = "RES"
constant ErrDatabase (line 63) | ErrDatabase = "DB"
constant ErrExternal (line 64) | ErrExternal = "EXT"
constant ErrInternal (line 65) | ErrInternal = "INTN"
constant ErrModel (line 66) | ErrModel = "MODEL"
function ErrResourceNotFound (line 265) | func ErrResourceNotFound(resource string) APIError {
function ErrResourceAlreadyExists (line 271) | func ErrResourceAlreadyExists(resource string) APIError {
function ErrValidationInvalidInput (line 277) | func ErrValidationInvalidInput(detail string) APIError {
function RespondWithAPIError (line 288) | func RespondWithAPIError(w http.ResponseWriter, err APIError) {
function MapDatabaseError (line 314) | func MapDatabaseError(err error) error {
function WrapError (line 428) | func WrapError(err error, detail string) APIError {
function IsErrorCode (line 475) | func IsErrorCode(err error, code string) bool {
function ErrorCatalogHandler (line 483) | func ErrorCatalogHandler(w http.ResponseWriter, r *http.Request) {
function createAPIError (line 510) | func createAPIError(baseErr APIError, detail string, debugInfo string) A...
FILE: api/file_upload_handler.go
type ChatFileHandler (line 19) | type ChatFileHandler struct
method Register (line 30) | func (h *ChatFileHandler) Register(router *mux.Router) {
method ReceiveFile (line 61) | func (h *ChatFileHandler) ReceiveFile(w http.ResponseWriter, r *http.R...
method DownloadFile (line 153) | func (h *ChatFileHandler) DownloadFile(w http.ResponseWriter, r *http....
method DeleteFile (line 180) | func (h *ChatFileHandler) DeleteFile(w http.ResponseWriter, r *http.Re...
method ChatFilesBySessionUUID (line 191) | func (h *ChatFileHandler) ChatFilesBySessionUUID(w http.ResponseWriter...
function NewChatFileHandler (line 23) | func NewChatFileHandler(sqlc_q *sqlc_queries.Queries) *ChatFileHandler {
constant maxUploadSize (line 38) | maxUploadSize = 32 << 20
function isValidFileType (line 50) | func isValidFileType(mimeType, fileName string) bool {
FILE: api/file_upload_service.go
type ChatFileService (line 11) | type ChatFileService struct
method CreateChatUpload (line 21) | func (s *ChatFileService) CreateChatUpload(ctx context.Context, params...
method GetChatFile (line 49) | func (s *ChatFileService) GetChatFile(ctx context.Context, id int32) (...
method DeleteChatFile (line 65) | func (s *ChatFileService) DeleteChatFile(ctx context.Context, id int32...
method ListChatFilesBySession (line 81) | func (s *ChatFileService) ListChatFilesBySession(ctx context.Context, ...
function NewChatFileService (line 16) | func NewChatFileService(q *sqlc_queries.Queries) *ChatFileService {
FILE: api/handle_tts.go
function handleTTSRequest (line 10) | func handleTTSRequest(w http.ResponseWriter, r *http.Request) {
FILE: api/jwt_secret_service.go
type JWTSecretService (line 16) | type JWTSecretService struct
method GetJwtSecret (line 26) | func (s *JWTSecretService) GetJwtSecret(ctx context.Context, name stri...
method GetOrCreateJwtSecret (line 36) | func (s *JWTSecretService) GetOrCreateJwtSecret(ctx context.Context, n...
function NewJWTSecretService (line 21) | func NewJWTSecretService(q *sqlc_queries.Queries) *JWTSecretService {
FILE: api/llm/claude/claude.go
type Delta (line 11) | type Delta struct
type ContentBlockDelta (line 16) | type ContentBlockDelta struct
type ContentBlock (line 22) | type ContentBlock struct
type StartBlock (line 27) | type StartBlock struct
function AnswerFromBlockDelta (line 33) | func AnswerFromBlockDelta(line []byte) string {
function AnswerFromBlockStart (line 39) | func AnswerFromBlockStart(line []byte) string {
function FormatClaudePrompt (line 45) | func FormatClaudePrompt(chat_compeletion_messages []models.Message) stri...
type Response (line 63) | type Response struct
type Content (line 74) | type Content struct
type Usage (line 79) | type Usage struct
FILE: api/llm/gemini/gemini.go
type Part (line 19) | type Part interface
type PartString (line 23) | type PartString struct
method toPart (line 33) | func (p *PartString) toPart() string {
function TextData (line 27) | func TextData(text string) PartString {
type PartBlob (line 37) | type PartBlob struct
method toPart (line 41) | func (p PartBlob) toPart() string {
type Blob (line 50) | type Blob struct
function ImageData (line 64) | func ImageData(mimeType string, data []byte) Blob {
type GeminiMessage (line 71) | type GeminiMessage struct
type GeminPayload (line 76) | type GeminPayload struct
type Content (line 80) | type Content struct
type SafetyRating (line 88) | type SafetyRating struct
type Candidate (line 93) | type Candidate struct
type PromptFeedback (line 100) | type PromptFeedback struct
type ResponseBody (line 104) | type ResponseBody struct
function ParseRespLine (line 109) | func ParseRespLine(line []byte, answer string) string {
function ParseRespLineDelta (line 132) | func ParseRespLineDelta(line []byte) string {
function SupportedMimeTypes (line 155) | func SupportedMimeTypes() mapset.Set[string] {
function GenGemminPayload (line 180) | func GenGemminPayload(chat_compeletion_messages []models.Message, chatFi...
type ErrorResponse (line 222) | type ErrorResponse struct
function HandleRegularResponse (line 230) | func HandleRegularResponse(client http.Client, req *http.Request) (*mode...
function BuildAPIURL (line 288) | func BuildAPIURL(model string, stream bool) string {
type GoogleApiError (line 298) | type GoogleApiError struct
method String (line 307) | func (gae *GoogleApiError) String() string {
FILE: api/llm/gemini/gemini_test.go
function TestBuildAPIURL (line 8) | func TestBuildAPIURL(t *testing.T) {
FILE: api/llm/openai/chat.go
constant ChatMessageRoleSystem (line 10) | ChatMessageRoleSystem = "system"
constant ChatMessageRoleUser (line 11) | ChatMessageRoleUser = "user"
constant ChatMessageRoleAssistant (line 12) | ChatMessageRoleAssistant = "assistant"
constant ChatMessageRoleFunction (line 13) | ChatMessageRoleFunction = "function"
constant ChatMessageRoleTool (line 14) | ChatMessageRoleTool = "tool"
constant chatCompletionsSuffix (line 17) | chatCompletionsSuffix = "/chat/completions"
type Hate (line 25) | type Hate struct
type SelfHarm (line 29) | type SelfHarm struct
type Sexual (line 33) | type Sexual struct
type Violence (line 37) | type Violence struct
type JailBreak (line 42) | type JailBreak struct
type Profanity (line 47) | type Profanity struct
type ContentFilterResults (line 52) | type ContentFilterResults struct
type PromptAnnotation (line 61) | type PromptAnnotation struct
type ImageURLDetail (line 66) | type ImageURLDetail
constant ImageURLDetailHigh (line 69) | ImageURLDetailHigh ImageURLDetail = "high"
constant ImageURLDetailLow (line 70) | ImageURLDetailLow ImageURLDetail = "low"
constant ImageURLDetailAuto (line 71) | ImageURLDetailAuto ImageURLDetail = "auto"
type ChatMessageImageURL (line 74) | type ChatMessageImageURL struct
type ChatMessagePartType (line 79) | type ChatMessagePartType
constant ChatMessagePartTypeText (line 82) | ChatMessagePartTypeText ChatMessagePartType = "text"
constant ChatMessagePartTypeImageURL (line 83) | ChatMessagePartTypeImageURL ChatMessagePartType = "image_url"
type ChatMessagePart (line 86) | type ChatMessagePart struct
type ChatCompletionMessage (line 92) | type ChatCompletionMessage struct
method MarshalJSON (line 113) | func (m ChatCompletionMessage) MarshalJSON() ([]byte, error) {
method UnmarshalJSON (line 144) | func (m *ChatCompletionMessage) UnmarshalJSON(bs []byte) error {
type ToolCall (line 177) | type ToolCall struct
type FunctionCall (line 185) | type FunctionCall struct
type ChatCompletionResponseFormatType (line 191) | type ChatCompletionResponseFormatType
constant ChatCompletionResponseFormatTypeJSONObject (line 194) | ChatCompletionResponseFormatTypeJSONObject ChatCompletionResponseFormatT...
constant ChatCompletionResponseFormatTypeJSONSchema (line 195) | ChatCompletionResponseFormatTypeJSONSchema ChatCompletionResponseFormatT...
constant ChatCompletionResponseFormatTypeText (line 196) | ChatCompletionResponseFormatTypeText ChatCompletionResponseFormatT...
type ChatCompletionResponseFormat (line 199) | type ChatCompletionResponseFormat struct
type ChatCompletionResponseFormatJSONSchema (line 204) | type ChatCompletionResponseFormatJSONSchema struct
type ChatCompletionRequest (line 212) | type ChatCompletionRequest struct
type StreamOptions (line 263) | type StreamOptions struct
type ToolType (line 271) | type ToolType
constant ToolTypeFunction (line 274) | ToolTypeFunction ToolType = "function"
type Tool (line 277) | type Tool struct
type ToolChoice (line 282) | type ToolChoice struct
type ToolFunction (line 287) | type ToolFunction struct
type FunctionDefinition (line 291) | type FunctionDefinition struct
type TopLogProbs (line 306) | type TopLogProbs struct
type LogProb (line 313) | type LogProb struct
type LogProbs (line 323) | type LogProbs struct
type FinishReason (line 328) | type FinishReason
method MarshalJSON (line 339) | func (r FinishReason) MarshalJSON() ([]byte, error) {
constant FinishReasonStop (line 331) | FinishReasonStop FinishReason = "stop"
constant FinishReasonLength (line 332) | FinishReasonLength FinishReason = "length"
constant FinishReasonFunctionCall (line 333) | FinishReasonFunctionCall FinishReason = "function_call"
constant FinishReasonToolCalls (line 334) | FinishReasonToolCalls FinishReason = "tool_calls"
constant FinishReasonContentFilter (line 335) | FinishReasonContentFilter FinishReason = "content_filter"
constant FinishReasonNull (line 336) | FinishReasonNull FinishReason = "null"
type ChatCompletionChoice (line 346) | type ChatCompletionChoice struct
type ChatCompletionResponse (line 362) | type ChatCompletionResponse struct
FILE: api/llm/openai/client.go
type httpHeader (line 5) | type httpHeader
method SetHeader (line 7) | func (h *httpHeader) SetHeader(header http.Header) {
method Header (line 11) | func (h *httpHeader) Header() http.Header {
FILE: api/llm/openai/common.go
type Usage (line 6) | type Usage struct
type CompletionTokensDetails (line 15) | type CompletionTokensDetails struct
type PromptTokensDetails (line 21) | type PromptTokensDetails struct
FILE: api/llm/openai/openai.go
type ChatCompletionStreamChoiceDelta (line 4) | type ChatCompletionStreamChoiceDelta struct
type ChatCompletionStreamChoiceLogprobs (line 13) | type ChatCompletionStreamChoiceLogprobs struct
type ChatCompletionTokenLogprob (line 18) | type ChatCompletionTokenLogprob struct
type ChatCompletionTokenLogprobTopLogprob (line 25) | type ChatCompletionTokenLogprobTopLogprob struct
type ChatCompletionStreamChoice (line 31) | type ChatCompletionStreamChoice struct
type PromptFilterResult (line 39) | type PromptFilterResult struct
type ChatCompletionStreamResponse (line 44) | type ChatCompletionStreamResponse struct
FILE: api/llm_openai.go
function SupportedMimeTypes (line 20) | func SupportedMimeTypes() mapset.Set[string] {
function messagesToOpenAIMesages (line 45) | func messagesToOpenAIMesages(messages []models.Message, chatFiles []sqlc...
function byteToImageURL (line 84) | func byteToImageURL(mimeType string, data []byte) string {
function getModelBaseUrl (line 90) | func getModelBaseUrl(apiUrl string) (string, error) {
function configOpenAIProxy (line 117) | func configOpenAIProxy(config *openai.ClientConfig) {
function genOpenAIConfig (line 134) | func genOpenAIConfig(chatModel sqlc_queries.ChatModel) (openai.ClientCon...
FILE: api/llm_summary.go
function llm_summarize_with_timeout (line 15) | func llm_summarize_with_timeout(baseURL, content string) string {
function llm_summarize (line 26) | func llm_summarize(ctx context.Context, baseURL string, doc string) stri...
FILE: api/main.go
type AppConfig (line 27) | type AppConfig struct
function getFlattenKeys (line 48) | func getFlattenKeys(prefix string, v reflect.Value) (keys []string) {
function bindEnvironmentVariables (line 62) | func bindEnvironmentVariables() {
function main (line 80) | func main() {
FILE: api/main_test.go
function TestMain (line 19) | func TestMain(m *testing.M) {
FILE: api/middleware_authenticate.go
function CheckPermission (line 13) | func CheckPermission(userID int, ctx context.Context) bool {
type AuthTokenResult (line 33) | type AuthTokenResult struct
function extractBearerToken (line 43) | func extractBearerToken(r *http.Request) string {
function createUserContext (line 53) | func createUserContext(r *http.Request, userID, role string) *http.Reque...
function parseAndValidateJWT (line 59) | func parseAndValidateJWT(bearerToken string, expectedTokenType string) *...
type contextKey (line 145) | type contextKey
constant roleContextKey (line 148) | roleContextKey contextKey = "role"
constant userContextKey (line 149) | userContextKey contextKey = "user"
constant guidContextKey (line 150) | guidContextKey contextKey = "guid"
constant snapshotPrefix (line 152) | snapshotPrefix = "/api/uuid/chat_snapshot/"
function IsChatSnapshotUUID (line 154) | func IsChatSnapshotUUID(r *http.Request) bool {
function AdminOnlyHandler (line 166) | func AdminOnlyHandler(h http.Handler) http.Handler {
function AdminOnlyHandlerFunc (line 186) | func AdminOnlyHandlerFunc(handlerFunc http.HandlerFunc) http.HandlerFunc {
function AdminRouteMiddleware (line 207) | func AdminRouteMiddleware(next http.Handler) http.Handler {
function AdminAuthMiddleware (line 228) | func AdminAuthMiddleware(handler http.Handler) http.Handler {
function UserAuthMiddleware (line 252) | func UserAuthMiddleware(handler http.Handler) http.Handler {
function IsAuthorizedMiddleware (line 267) | func IsAuthorizedMiddleware(handler http.Handler) http.Handler {
FILE: api/middleware_gzip.go
type gzipResponseWriter (line 10) | type gzipResponseWriter struct
method Write (line 17) | func (w gzipResponseWriter) Write(b []byte) (int, error) {
function makeGzipHandler (line 21) | func makeGzipHandler(fn http.HandlerFunc) http.HandlerFunc {
FILE: api/middleware_lastRequestTime.go
function UpdateLastRequestTime (line 9) | func UpdateLastRequestTime(next http.Handler) http.Handler {
FILE: api/middleware_rateLimit.go
function RateLimitByUserID (line 12) | func RateLimitByUserID(q *sqlc_queries.Queries) func(http.Handler) http....
FILE: api/middleware_validation.go
type ValidationConfig (line 24) | type ValidationConfig struct
type FieldValidator (line 33) | type FieldValidator
function ValidateEmail (line 36) | func ValidateEmail(value interface{}) error {
function ValidateUUID (line 50) | func ValidateUUID(value interface{}) error {
function ValidateStringLength (line 61) | func ValidateStringLength(min, max int) FieldValidator {
function ValidateNonEmpty (line 80) | func ValidateNonEmpty(value interface{}) error {
function ValidationMiddleware (line 92) | func ValidationMiddleware(config ValidationConfig) func(http.Handler) ht...
FILE: api/model_claude3_service.go
type ClaudeResponse (line 23) | type ClaudeResponse struct
type Claude3ChatModel (line 34) | type Claude3ChatModel struct
method Stream (line 38) | func (m *Claude3ChatModel) Stream(w http.ResponseWriter, chatSession s...
function doGenerateClaude3 (line 150) | func doGenerateClaude3(ctx context.Context, client http.Client, req *htt...
method chatStreamClaude3 (line 173) | func (h *ChatHandler) chatStreamClaude3(ctx context.Context, w http.Resp...
FILE: api/model_completion_service.go
type CompletionChatModel (line 20) | type CompletionChatModel struct
method Stream (line 25) | func (m *CompletionChatModel) Stream(w http.ResponseWriter, chatSessio...
method completionStream (line 32) | func (m *CompletionChatModel) completionStream(ctx context.Context, w ...
FILE: api/model_custom_service.go
type CustomModelResponse (line 22) | type CustomModelResponse struct
type CustomChatModel (line 33) | type CustomChatModel struct
method Stream (line 38) | func (m *CustomChatModel) Stream(w http.ResponseWriter, chatSession sq...
method customChatStream (line 45) | func (m *CustomChatModel) customChatStream(ctx context.Context, w http...
FILE: api/model_gemini_service.go
type GeminiClient (line 33) | type GeminiClient struct
function NewGeminiClient (line 38) | func NewGeminiClient() *GeminiClient {
type GeminiChatModel (line 45) | type GeminiChatModel struct
method Stream (line 57) | func (m *GeminiChatModel) Stream(w http.ResponseWriter, chatSession sq...
method handleStreamResponse (line 169) | func (m *GeminiChatModel) handleStreamResponse(ctx context.Context, w ...
function NewGeminiChatModel (line 50) | func NewGeminiChatModel(h *ChatHandler) *GeminiChatModel {
function GenerateChatTitle (line 99) | func GenerateChatTitle(ctx context.Context, model, chatText string) (str...
FILE: api/model_ollama_service.go
type OllamaResponse (line 22) | type OllamaResponse struct
type OllamaChatModel (line 36) | type OllamaChatModel struct
method Stream (line 40) | func (m *OllamaChatModel) Stream(w http.ResponseWriter, chatSession sq...
method chatOllamStream (line 46) | func (h *ChatHandler) chatOllamStream(ctx context.Context, w http.Respon...
FILE: api/model_openai_service.go
type OpenAIChatModel (line 22) | type OpenAIChatModel struct
method Stream (line 26) | func (m *OpenAIChatModel) Stream(w http.ResponseWriter, chatSession sq...
function handleRegularResponse (line 66) | func handleRegularResponse(w http.ResponseWriter, client *openai.Client,...
function doChatStream (line 84) | func doChatStream(w http.ResponseWriter, client *openai.Client, req open...
function processDelta (line 206) | func processDelta(delta llm_openai.ChatCompletionStreamChoiceDelta, reas...
function NewUserMessage (line 233) | func NewUserMessage(content string) openai.ChatCompletionMessage {
function NewChatCompletionRequest (line 238) | func NewChatCompletionRequest(chatSession sqlc_queries.ChatSession, chat...
FILE: api/model_test_service.go
type TestChatModel (line 13) | type TestChatModel struct
method Stream (line 18) | func (m *TestChatModel) Stream(w http.ResponseWriter, chatSession sqlc...
method chatStreamTest (line 23) | func (m *TestChatModel) chatStreamTest(w http.ResponseWriter, chatSess...
FILE: api/models.go
type TokenResult (line 11) | type TokenResult struct
type ConversationRequest (line 16) | type ConversationRequest struct
type RequestOption (line 22) | type RequestOption struct
type Artifact (line 27) | type Artifact struct
type SimpleChatMessage (line 35) | type SimpleChatMessage struct
method GetRole (line 47) | func (msg SimpleChatMessage) GetRole() string {
type SimpleChatSession (line 58) | type SimpleChatSession struct
type ChatMessageResponse (line 74) | type ChatMessageResponse struct
type ChatSessionResponse (line 88) | type ChatSessionResponse struct
type Pagination (line 97) | type Pagination struct
method Offset (line 104) | func (p *Pagination) Offset() int32 {
type ChatModel (line 109) | type ChatModel interface
FILE: api/models/models.go
function getTokenCount (line 9) | func getTokenCount(content string) (int, error) {
type Message (line 20) | type Message struct
method TokenCount (line 26) | func (m Message) TokenCount() int32 {
method SetTokenCount (line 38) | func (m *Message) SetTokenCount(tokenCount int32) *Message {
type LLMAnswer (line 43) | type LLMAnswer struct
FILE: api/openai_test.go
function Test_getModelBaseUrl (line 5) | func Test_getModelBaseUrl(t *testing.T) {
FILE: api/sqlc/schema.sql
type jwt_secrets (line 1) | CREATE TABLE IF NOT EXISTS jwt_secrets (
type chat_model (line 13) | CREATE TABLE IF NOT EXISTS chat_model (
type jwt_secrets_name_idx (line 71) | CREATE INDEX IF NOT EXISTS jwt_secrets_name_idx ON jwt_secrets (name)
type auth_user (line 74) | CREATE TABLE IF NOT EXISTS auth_user (
type auth_user_email_idx (line 89) | CREATE INDEX IF NOT EXISTS auth_user_email_idx ON auth_user (email)
type auth_user_management (line 91) | CREATE TABLE IF NOT EXISTS auth_user_management (
type auth_user_management_user_id_idx (line 100) | CREATE INDEX IF NOT EXISTS auth_user_management_user_id_idx ON auth_user...
type user_chat_model_privilege (line 107) | CREATE TABLE IF NOT EXISTS user_chat_model_privilege(
type chat_workspace (line 119) | CREATE TABLE IF NOT EXISTS chat_workspace (
type chat_workspace_user_id_idx (line 134) | CREATE INDEX IF NOT EXISTS chat_workspace_user_id_idx ON chat_workspace ...
type chat_workspace_uuid_idx (line 137) | CREATE INDEX IF NOT EXISTS chat_workspace_uuid_idx ON chat_workspace usi...
type chat_workspace_single_default_per_user_idx (line 153) | CREATE UNIQUE INDEX IF NOT EXISTS chat_workspace_single_default_per_user...
type chat_session (line 157) | CREATE TABLE IF NOT EXISTS chat_session (
type chat_session_uuid_idx (line 193) | CREATE INDEX IF NOT EXISTS chat_session_uuid_idx ON chat_session using h...
type chat_session_user_id_idx (line 196) | CREATE INDEX IF NOT EXISTS chat_session_user_id_idx ON chat_session (use...
type chat_session_workspace_id_idx (line 199) | CREATE INDEX IF NOT EXISTS chat_session_workspace_id_idx ON chat_session...
type chat_message (line 201) | CREATE TABLE IF NOT EXISTS chat_message (
type chat_message_uuid_idx (line 234) | CREATE INDEX IF NOT EXISTS chat_message_uuid_idx ON chat_message using h...
type chat_message_chat_session_uuid_idx (line 237) | CREATE INDEX IF NOT EXISTS chat_message_chat_session_uuid_idx ON chat_me...
type chat_message_user_id_idx (line 240) | CREATE INDEX IF NOT EXISTS chat_message_user_id_idx ON chat_message (use...
type chat_message_created_at_idx (line 243) | CREATE INDEX IF NOT EXISTS chat_message_created_at_idx ON chat_message u...
type chat_prompt (line 245) | CREATE TABLE IF NOT EXISTS chat_prompt (
type chat_prompt_uuid_idx (line 266) | CREATE INDEX IF NOT EXISTS chat_prompt_uuid_idx ON chat_prompt using has...
type chat_prompt_chat_session_uuid_idx (line 269) | CREATE INDEX IF NOT EXISTS chat_prompt_chat_session_uuid_idx ON chat_pro...
type chat_prompt_user_id_idx (line 272) | CREATE INDEX IF NOT EXISTS chat_prompt_user_id_idx ON chat_prompt (user_id)
type chat_prompt_unique_active_system_per_session_idx (line 289) | CREATE UNIQUE INDEX IF NOT EXISTS chat_prompt_unique_active_system_per_s...
type chat_logs (line 293) | CREATE TABLE IF NOT EXISTS chat_logs (
type chat_logs_created_at_idx (line 302) | CREATE INDEX IF NOT EXISTS chat_logs_created_at_idx ON chat_logs using b...
type user_active_chat_session (line 307) | CREATE TABLE IF NOT EXISTS user_active_chat_session (
type user_active_chat_session_user_id_idx (line 316) | CREATE INDEX IF NOT EXISTS user_active_chat_session_user_id_idx ON user_...
type unique_user_workspace_active_session_coalesce_idx (line 340) | CREATE UNIQUE INDEX unique_user_workspace_active_session_coalesce_idx
type user_active_chat_session_workspace_id_idx (line 344) | CREATE INDEX IF NOT EXISTS user_active_chat_session_workspace_id_idx ON ...
type chat_snapshot (line 348) | CREATE TABLE IF NOT EXISTS chat_snapshot (
type search_vector_gin_idx (line 372) | CREATE INDEX IF NOT EXISTS search_vector_gin_idx on chat_snapshot using ...
type chat_snapshot_user_id_idx (line 375) | CREATE INDEX IF NOT EXISTS chat_snapshot_user_id_idx ON chat_snapshot (u...
type chat_snapshot_created_at_idx (line 378) | CREATE INDEX IF NOT EXISTS chat_snapshot_created_at_idx ON chat_snapshot...
type chat_file (line 383) | CREATE TABLE IF NOT EXISTS chat_file (
type bot_answer_history (line 394) | CREATE TABLE IF NOT EXISTS bot_answer_history (
type bot_answer_history_bot_uuid_idx (line 407) | CREATE INDEX IF NOT EXISTS bot_answer_history_bot_uuid_idx ON bot_answer...
type bot_answer_history_user_id_idx (line 408) | CREATE INDEX IF NOT EXISTS bot_answer_history_user_id_idx ON bot_answer_...
type bot_answer_history_created_at_idx (line 409) | CREATE INDEX IF NOT EXISTS bot_answer_history_created_at_idx ON bot_answ...
type chat_comment (line 411) | CREATE TABLE IF NOT EXISTS chat_comment (
type chat_comment_chat_session_uuid_idx (line 424) | CREATE INDEX IF NOT EXISTS chat_comment_chat_session_uuid_idx ON chat_co...
type chat_comment_created_by_idx (line 425) | CREATE INDEX IF NOT EXISTS chat_comment_created_by_idx ON chat_comment (...
FILE: api/sqlc_queries/auth_user.sql.go
constant createAuthUser (line 13) | createAuthUser = `-- name: CreateAuthUser :one
type CreateAuthUserParams (line 19) | type CreateAuthUserParams struct
method CreateAuthUser (line 29) | func (q *Queries) CreateAuthUser(ctx context.Context, arg CreateAuthUser...
constant deleteAuthUser (line 56) | deleteAuthUser = `-- name: DeleteAuthUser :exec
method DeleteAuthUser (line 60) | func (q *Queries) DeleteAuthUser(ctx context.Context, email string) error {
constant getAllAuthUsers (line 65) | getAllAuthUsers = `-- name: GetAllAuthUsers :many
method GetAllAuthUsers (line 69) | func (q *Queries) GetAllAuthUsers(ctx context.Context) ([]AuthUser, erro...
constant getAuthUserByEmail (line 104) | getAuthUserByEmail = `-- name: GetAuthUserByEmail :one
method GetAuthUserByEmail (line 108) | func (q *Queries) GetAuthUserByEmail(ctx context.Context, email string) ...
constant getAuthUserByID (line 127) | getAuthUserByID = `-- name: GetAuthUserByID :one
method GetAuthUserByID (line 131) | func (q *Queries) GetAuthUserByID(ctx context.Context, id int32) (AuthUs...
constant getTotalActiveUserCount (line 150) | getTotalActiveUserCount = `-- name: GetTotalActiveUserCount :one
method GetTotalActiveUserCount (line 154) | func (q *Queries) GetTotalActiveUserCount(ctx context.Context) (int64, e...
constant getUserAnalysisByEmail (line 161) | getUserAnalysisByEmail = `-- name: GetUserAnalysisByEmail :one
type GetUserAnalysisByEmailParams (line 193) | type GetUserAnalysisByEmailParams struct
type GetUserAnalysisByEmailRow (line 198) | type GetUserAnalysisByEmailRow struct
method GetUserAnalysisByEmail (line 210) | func (q *Queries) GetUserAnalysisByEmail(ctx context.Context, arg GetUse...
constant getUserByEmail (line 227) | getUserByEmail = `-- name: GetUserByEmail :one
method GetUserByEmail (line 231) | func (q *Queries) GetUserByEmail(ctx context.Context, email string) (Aut...
constant getUserModelUsageByEmail (line 250) | getUserModelUsageByEmail = `-- name: GetUserModelUsageByEmail :many
type GetUserModelUsageByEmailRow (line 267) | type GetUserModelUsageByEmailRow struct
method GetUserModelUsageByEmail (line 274) | func (q *Queries) GetUserModelUsageByEmail(ctx context.Context, email st...
constant getUserRecentActivityByEmail (line 302) | getUserRecentActivityByEmail = `-- name: GetUserRecentActivityByEmail :many
type GetUserRecentActivityByEmailRow (line 318) | type GetUserRecentActivityByEmailRow struct
method GetUserRecentActivityByEmail (line 325) | func (q *Queries) GetUserRecentActivityByEmail(ctx context.Context, emai...
constant getUserSessionHistoryByEmail (line 353) | getUserSessionHistoryByEmail = `-- name: GetUserSessionHistoryByEmail :many
type GetUserSessionHistoryByEmailParams (line 370) | type GetUserSessionHistoryByEmailParams struct
type GetUserSessionHistoryByEmailRow (line 376) | type GetUserSessionHistoryByEmailRow struct
method GetUserSessionHistoryByEmail (line 385) | func (q *Queries) GetUserSessionHistoryByEmail(ctx context.Context, arg ...
constant getUserSessionHistoryCountByEmail (line 415) | getUserSessionHistoryCountByEmail = `-- name: GetUserSessionHistoryCount...
method GetUserSessionHistoryCountByEmail (line 422) | func (q *Queries) GetUserSessionHistoryCountByEmail(ctx context.Context,...
constant getUserStats (line 429) | getUserStats = `-- name: GetUserStats :many
type GetUserStatsParams (line 459) | type GetUserStatsParams struct
type GetUserStatsRow (line 465) | type GetUserStatsRow struct
method GetUserStats (line 476) | func (q *Queries) GetUserStats(ctx context.Context, arg GetUserStatsPara...
constant listAuthUsers (line 508) | listAuthUsers = `-- name: ListAuthUsers :many
type ListAuthUsersParams (line 512) | type ListAuthUsersParams struct
method ListAuthUsers (line 517) | func (q *Queries) ListAuthUsers(ctx context.Context, arg ListAuthUsersPa...
constant updateAuthUser (line 552) | updateAuthUser = `-- name: UpdateAuthUser :one
type UpdateAuthUserParams (line 558) | type UpdateAuthUserParams struct
type UpdateAuthUserRow (line 564) | type UpdateAuthUserRow struct
method UpdateAuthUser (line 570) | func (q *Queries) UpdateAuthUser(ctx context.Context, arg UpdateAuthUser...
constant updateAuthUserByEmail (line 577) | updateAuthUserByEmail = `-- name: UpdateAuthUserByEmail :one
type UpdateAuthUserByEmailParams (line 583) | type UpdateAuthUserByEmailParams struct
type UpdateAuthUserByEmailRow (line 589) | type UpdateAuthUserByEmailRow struct
method UpdateAuthUserByEmail (line 595) | func (q *Queries) UpdateAuthUserByEmail(ctx context.Context, arg UpdateA...
constant updateAuthUserRateLimitByEmail (line 602) | updateAuthUserRateLimitByEmail = `-- name: UpdateAuthUserRateLimitByEmai...
type UpdateAuthUserRateLimitByEmailParams (line 609) | type UpdateAuthUserRateLimitByEmailParams struct
method UpdateAuthUserRateLimitByEmail (line 614) | func (q *Queries) UpdateAuthUserRateLimitByEmail(ctx context.Context, ar...
constant updateUserPassword (line 621) | updateUserPassword = `-- name: UpdateUserPassword :exec
type UpdateUserPasswordParams (line 625) | type UpdateUserPasswordParams struct
method UpdateUserPassword (line 630) | func (q *Queries) UpdateUserPassword(ctx context.Context, arg UpdateUser...
FILE: api/sqlc_queries/auth_user_management.sql.go
constant getRateLimit (line 12) | getRateLimit = `-- name: GetRateLimit :one
method GetRateLimit (line 20) | func (q *Queries) GetRateLimit(ctx context.Context, userID int32) (int32...
FILE: api/sqlc_queries/bot_answer_history.sql.go
constant createBotAnswerHistory (line 13) | createBotAnswerHistory = `-- name: CreateBotAnswerHistory :one
type CreateBotAnswerHistoryParams (line 27) | type CreateBotAnswerHistoryParams struct
method CreateBotAnswerHistory (line 37) | func (q *Queries) CreateBotAnswerHistory(ctx context.Context, arg Create...
constant deleteBotAnswerHistory (line 61) | deleteBotAnswerHistory = `-- name: DeleteBotAnswerHistory :exec
method DeleteBotAnswerHistory (line 65) | func (q *Queries) DeleteBotAnswerHistory(ctx context.Context, id int32) ...
constant getBotAnswerHistoryByBotUUID (line 70) | getBotAnswerHistoryByBotUUID = `-- name: GetBotAnswerHistoryByBotUUID :many
type GetBotAnswerHistoryByBotUUIDParams (line 90) | type GetBotAnswerHistoryByBotUUIDParams struct
type GetBotAnswerHistoryByBotUUIDRow (line 96) | type GetBotAnswerHistoryByBotUUIDRow struct
method GetBotAnswerHistoryByBotUUID (line 110) | func (q *Queries) GetBotAnswerHistoryByBotUUID(ctx context.Context, arg ...
constant getBotAnswerHistoryByID (line 145) | getBotAnswerHistoryByID = `-- name: GetBotAnswerHistoryByID :one
type GetBotAnswerHistoryByIDRow (line 163) | type GetBotAnswerHistoryByIDRow struct
method GetBotAnswerHistoryByID (line 177) | func (q *Queries) GetBotAnswerHistoryByID(ctx context.Context, id int32)...
constant getBotAnswerHistoryByUserID (line 196) | getBotAnswerHistoryByUserID = `-- name: GetBotAnswerHistoryByUserID :many
type GetBotAnswerHistoryByUserIDParams (line 216) | type GetBotAnswerHistoryByUserIDParams struct
type GetBotAnswerHistoryByUserIDRow (line 222) | type GetBotAnswerHistoryByUserIDRow struct
method GetBotAnswerHistoryByUserID (line 236) | func (q *Queries) GetBotAnswerHistoryByUserID(ctx context.Context, arg G...
constant getBotAnswerHistoryCountByBotUUID (line 271) | getBotAnswerHistoryCountByBotUUID = `-- name: GetBotAnswerHistoryCountBy...
method GetBotAnswerHistoryCountByBotUUID (line 275) | func (q *Queries) GetBotAnswerHistoryCountByBotUUID(ctx context.Context,...
constant getBotAnswerHistoryCountByUserID (line 282) | getBotAnswerHistoryCountByUserID = `-- name: GetBotAnswerHistoryCountByU...
method GetBotAnswerHistoryCountByUserID (line 286) | func (q *Queries) GetBotAnswerHistoryCountByUserID(ctx context.Context, ...
constant getLatestBotAnswerHistoryByBotUUID (line 293) | getLatestBotAnswerHistoryByBotUUID = `-- name: GetLatestBotAnswerHistory...
type GetLatestBotAnswerHistoryByBotUUIDParams (line 313) | type GetLatestBotAnswerHistoryByBotUUIDParams struct
type GetLatestBotAnswerHistoryByBotUUIDRow (line 318) | type GetLatestBotAnswerHistoryByBotUUIDRow struct
method GetLatestBotAnswerHistoryByBotUUID (line 332) | func (q *Queries) GetLatestBotAnswerHistoryByBotUUID(ctx context.Context...
constant updateBotAnswerHistory (line 367) | updateBotAnswerHistory = `-- name: UpdateBotAnswerHistory :one
type UpdateBotAnswerHistoryParams (line 377) | type UpdateBotAnswerHistoryParams struct
method UpdateBotAnswerHistory (line 383) | func (q *Queries) UpdateBotAnswerHistory(ctx context.Context, arg Update...
FILE: api/sqlc_queries/chat_comment.sql.go
constant createChatComment (line 13) | createChatComment = `-- name: CreateChatComment :one
type CreateChatCommentParams (line 26) | type CreateChatCommentParams struct
method CreateChatComment (line 34) | func (q *Queries) CreateChatComment(ctx context.Context, arg CreateChatC...
constant getCommentsByMessageUUID (line 57) | getCommentsByMessageUUID = `-- name: GetCommentsByMessageUUID :many
type GetCommentsByMessageUUIDRow (line 70) | type GetCommentsByMessageUUIDRow struct
method GetCommentsByMessageUUID (line 78) | func (q *Queries) GetCommentsByMessageUUID(ctx context.Context, chatMess...
constant getCommentsBySessionUUID (line 107) | getCommentsBySessionUUID = `-- name: GetCommentsBySessionUUID :many
type GetCommentsBySessionUUIDRow (line 121) | type GetCommentsBySessionUUIDRow struct
method GetCommentsBySessionUUID (line 130) | func (q *Queries) GetCommentsBySessionUUID(ctx context.Context, chatSess...
FILE: api/sqlc_queries/chat_file.sql.go
constant createChatFile (line 13) | createChatFile = `-- name: CreateChatFile :one
type CreateChatFileParams (line 19) | type CreateChatFileParams struct
method CreateChatFile (line 27) | func (q *Queries) CreateChatFile(ctx context.Context, arg CreateChatFile...
constant deleteChatFile (line 48) | deleteChatFile = `-- name: DeleteChatFile :one
method DeleteChatFile (line 54) | func (q *Queries) DeleteChatFile(ctx context.Context, id int32) (ChatFil...
constant getChatFileByID (line 69) | getChatFileByID = `-- name: GetChatFileByID :one
type GetChatFileByIDRow (line 75) | type GetChatFileByIDRow struct
method GetChatFileByID (line 84) | func (q *Queries) GetChatFileByID(ctx context.Context, id int32) (GetCha...
constant listChatFilesBySessionUUID (line 98) | listChatFilesBySessionUUID = `-- name: ListChatFilesBySessionUUID :many
type ListChatFilesBySessionUUIDParams (line 105) | type ListChatFilesBySessionUUIDParams struct
type ListChatFilesBySessionUUIDRow (line 110) | type ListChatFilesBySessionUUIDRow struct
method ListChatFilesBySessionUUID (line 115) | func (q *Queries) ListChatFilesBySessionUUID(ctx context.Context, arg Li...
constant listChatFilesWithContentBySessionUUID (line 138) | listChatFilesWithContentBySessionUUID = `-- name: ListChatFilesWithConte...
method ListChatFilesWithContentBySessionUUID (line 145) | func (q *Queries) ListChatFilesWithContentBySessionUUID(ctx context.Cont...
FILE: api/sqlc_queries/chat_log.sql.go
constant chatLogByID (line 13) | chatLogByID = `-- name: ChatLogByID :one
method ChatLogByID (line 17) | func (q *Queries) ChatLogByID(ctx context.Context, id int32) (ChatLog, e...
constant createChatLog (line 30) | createChatLog = `-- name: CreateChatLog :one
type CreateChatLogParams (line 36) | type CreateChatLogParams struct
method CreateChatLog (line 42) | func (q *Queries) CreateChatLog(ctx context.Context, arg CreateChatLogPa...
constant deleteChatLog (line 55) | deleteChatLog = `-- name: DeleteChatLog :exec
method DeleteChatLog (line 59) | func (q *Queries) DeleteChatLog(ctx context.Context, id int32) error {
constant listChatLogs (line 64) | listChatLogs = `-- name: ListChatLogs :many
method ListChatLogs (line 68) | func (q *Queries) ListChatLogs(ctx context.Context) ([]ChatLog, error) {
constant updateChatLog (line 97) | updateChatLog = `-- name: UpdateChatLog :one
type UpdateChatLogParams (line 103) | type UpdateChatLogParams struct
method UpdateChatLog (line 110) | func (q *Queries) UpdateChatLog(ctx context.Context, arg UpdateChatLogPa...
FILE: api/sqlc_queries/chat_message.sql.go
constant createChatMessage (line 14) | createChatMessage = `-- name: CreateChatMessage :one
type CreateChatMessageParams (line 20) | type CreateChatMessageParams struct
method CreateChatMessage (line 38) | func (q *Queries) CreateChatMessage(ctx context.Context, arg CreateChatM...
constant deleteChatMessage (line 82) | deleteChatMessage = `-- name: DeleteChatMessage :exec
method DeleteChatMessage (line 87) | func (q *Queries) DeleteChatMessage(ctx context.Context, id int32) error {
constant deleteChatMessageByUUID (line 92) | deleteChatMessageByUUID = `-- name: DeleteChatMessageByUUID :exec
method DeleteChatMessageByUUID (line 97) | func (q *Queries) DeleteChatMessageByUUID(ctx context.Context, uuid stri...
constant deleteChatMessagesBySesionUUID (line 102) | deleteChatMessagesBySesionUUID = `-- name: DeleteChatMessagesBySesionUUI...
method DeleteChatMessagesBySesionUUID (line 108) | func (q *Queries) DeleteChatMessagesBySesionUUID(ctx context.Context, ch...
constant getAllChatMessages (line 113) | getAllChatMessages = `-- name: GetAllChatMessages :many
method GetAllChatMessages (line 119) | func (q *Queries) GetAllChatMessages(ctx context.Context) ([]ChatMessage...
constant getChatMessageByID (line 163) | getChatMessageByID = `-- name: GetChatMessageByID :one
method GetChatMessageByID (line 168) | func (q *Queries) GetChatMessageByID(ctx context.Context, id int32) (Cha...
constant getChatMessageBySessionUUID (line 196) | getChatMessageBySessionUUID = `-- name: GetChatMessageBySessionUUID :one
type GetChatMessageBySessionUUIDParams (line 206) | type GetChatMessageBySessionUUIDParams struct
method GetChatMessageBySessionUUID (line 211) | func (q *Queries) GetChatMessageBySessionUUID(ctx context.Context, arg G...
constant getChatMessageByUUID (line 239) | getChatMessageByUUID = `-- name: GetChatMessageByUUID :one
method GetChatMessageByUUID (line 246) | func (q *Queries) GetChatMessageByUUID(ctx context.Context, uuid string)...
constant getChatMessagesBySessionUUID (line 274) | getChatMessagesBySessionUUID = `-- name: GetChatMessagesBySessionUUID :many
type GetChatMessagesBySessionUUIDParams (line 284) | type GetChatMessagesBySessionUUIDParams struct
method GetChatMessagesBySessionUUID (line 290) | func (q *Queries) GetChatMessagesBySessionUUID(ctx context.Context, arg ...
constant getChatMessagesBySessionUUIDForAdmin (line 334) | getChatMessagesBySessionUUIDForAdmin = `-- name: GetChatMessagesBySessio...
type GetChatMessagesBySessionUUIDForAdminRow (line 386) | type GetChatMessagesBySessionUUIDForAdminRow struct
method GetChatMessagesBySessionUUIDForAdmin (line 399) | func (q *Queries) GetChatMessagesBySessionUUIDForAdmin(ctx context.Conte...
constant getChatMessagesCount (line 433) | getChatMessagesCount = `-- name: GetChatMessagesCount :one
method GetChatMessagesCount (line 441) | func (q *Queries) GetChatMessagesCount(ctx context.Context, userID int32...
constant getChatMessagesCountByUserAndModel (line 448) | getChatMessagesCountByUserAndModel = `-- name: GetChatMessagesCountByUse...
type GetChatMessagesCountByUserAndModelParams (line 457) | type GetChatMessagesCountByUserAndModelParams struct
method GetChatMessagesCountByUserAndModel (line 463) | func (q *Queries) GetChatMessagesCountByUserAndModel(ctx context.Context...
constant getFirstMessageBySessionUUID (line 470) | getFirstMessageBySessionUUID = `-- name: GetFirstMessageBySessionUUID :one
method GetFirstMessageBySessionUUID (line 478) | func (q *Queries) GetFirstMessageBySessionUUID(ctx context.Context, chat...
constant getLastNChatMessages (line 506) | getLastNChatMessages = `-- name: GetLastNChatMessages :many
type GetLastNChatMessagesParams (line 527) | type GetLastNChatMessagesParams struct
method GetLastNChatMessages (line 533) | func (q *Queries) GetLastNChatMessages(ctx context.Context, arg GetLastN...
constant getLatestMessagesBySessionUUID (line 577) | getLatestMessagesBySessionUUID = `-- name: GetLatestMessagesBySessionUUI...
type GetLatestMessagesBySessionUUIDParams (line 597) | type GetLatestMessagesBySessionUUIDParams struct
method GetLatestMessagesBySessionUUID (line 602) | func (q *Queries) GetLatestMessagesBySessionUUID(ctx context.Context, ar...
constant getLatestUsageTimeOfModel (line 646) | getLatestUsageTimeOfModel = `-- name: GetLatestUsageTimeOfModel :many
type GetLatestUsageTimeOfModelRow (line 661) | type GetLatestUsageTimeOfModelRow struct
method GetLatestUsageTimeOfModel (line 667) | func (q *Queries) GetLatestUsageTimeOfModel(ctx context.Context, timeInt...
constant hasChatMessagePermission (line 690) | hasChatMessagePermission = `-- name: HasChatMessagePermission :one
type HasChatMessagePermissionParams (line 698) | type HasChatMessagePermissionParams struct
method HasChatMessagePermission (line 703) | func (q *Queries) HasChatMessagePermission(ctx context.Context, arg HasC...
constant updateChatMessage (line 710) | updateChatMessage = `-- name: UpdateChatMessage :one
type UpdateChatMessageParams (line 716) | type UpdateChatMessageParams struct
method UpdateChatMessage (line 727) | func (q *Queries) UpdateChatMessage(ctx context.Context, arg UpdateChatM...
constant updateChatMessageByUUID (line 764) | updateChatMessageByUUID = `-- name: UpdateChatMessageByUUID :one
type UpdateChatMessageByUUIDParams (line 770) | type UpdateChatMessageByUUIDParams struct
method UpdateChatMessageByUUID (line 779) | func (q *Queries) UpdateChatMessageByUUID(ctx context.Context, arg Updat...
constant updateChatMessageContent (line 814) | updateChatMessageContent = `-- name: UpdateChatMessageContent :exec
type UpdateChatMessageContentParams (line 820) | type UpdateChatMessageContentParams struct
method UpdateChatMessageContent (line 826) | func (q *Queries) UpdateChatMessageContent(ctx context.Context, arg Upda...
constant updateChatMessageSuggestions (line 831) | updateChatMessageSuggestions = `-- name: UpdateChatMessageSuggestions :one
type UpdateChatMessageSuggestionsParams (line 838) | type UpdateChatMessageSuggestionsParams struct
method UpdateChatMessageSuggestions (line 843) | func (q *Queries) UpdateChatMessageSuggestions(ctx context.Context, arg ...
FILE: api/sqlc_queries/chat_model.sql.go
constant chatModelByID (line 12) | chatModelByID = `-- name: ChatModelByID :one
method ChatModelByID (line 16) | func (q *Queries) ChatModelByID(ctx context.Context, id int32) (ChatMode...
constant chatModelByName (line 39) | chatModelByName = `-- name: ChatModelByName :one
method ChatModelByName (line 43) | func (q *Queries) ChatModelByName(ctx context.Context, name string) (Cha...
constant createChatModel (line 66) | createChatModel = `-- name: CreateChatModel :one
type CreateChatModelParams (line 72) | type CreateChatModelParams struct
method CreateChatModel (line 88) | func (q *Queries) CreateChatModel(ctx context.Context, arg CreateChatMod...
constant deleteChatModel (line 125) | deleteChatModel = `-- name: DeleteChatModel :exec
type DeleteChatModelParams (line 129) | type DeleteChatModelParams struct
method DeleteChatModel (line 134) | func (q *Queries) DeleteChatModel(ctx context.Context, arg DeleteChatMod...
constant getDefaultChatModel (line 139) | getDefaultChatModel = `-- name: GetDefaultChatModel :one
method GetDefaultChatModel (line 146) | func (q *Queries) GetDefaultChatModel(ctx context.Context) (ChatModel, e...
constant listChatModels (line 169) | listChatModels = `-- name: ListChatModels :many
method ListChatModels (line 173) | func (q *Queries) ListChatModels(ctx context.Context) ([]ChatModel, erro...
constant listSystemChatModels (line 212) | listSystemChatModels = `-- name: ListSystemChatModels :many
method ListSystemChatModels (line 218) | func (q *Queries) ListSystemChatModels(ctx context.Context) ([]ChatModel...
constant updateChatModel (line 257) | updateChatModel = `-- name: UpdateChatModel :one
type UpdateChatModelParams (line 264) | type UpdateChatModelParams struct
method UpdateChatModel (line 282) | func (q *Queries) UpdateChatModel(ctx context.Context, arg UpdateChatMod...
constant updateChatModelKey (line 321) | updateChatModelKey = `-- name: UpdateChatModelKey :one
type UpdateChatModelKeyParams (line 327) | type UpdateChatModelKeyParams struct
method UpdateChatModelKey (line 332) | func (q *Queries) UpdateChatModelKey(ctx context.Context, arg UpdateChat...
FILE: api/sqlc_queries/chat_prompt.sql.go
constant createChatPrompt (line 12) | createChatPrompt = `-- name: CreateChatPrompt :one
type CreateChatPromptParams (line 18) | type CreateChatPromptParams struct
method CreateChatPrompt (line 29) | func (q *Queries) CreateChatPrompt(ctx context.Context, arg CreateChatPr...
constant deleteChatPrompt (line 59) | deleteChatPrompt = `-- name: DeleteChatPrompt :exec
method DeleteChatPrompt (line 65) | func (q *Queries) DeleteChatPrompt(ctx context.Context, id int32) error {
constant deleteChatPromptByUUID (line 70) | deleteChatPromptByUUID = `-- name: DeleteChatPromptByUUID :exec
method DeleteChatPromptByUUID (line 76) | func (q *Queries) DeleteChatPromptByUUID(ctx context.Context, uuid strin...
constant getAllChatPrompts (line 81) | getAllChatPrompts = `-- name: GetAllChatPrompts :many
method GetAllChatPrompts (line 87) | func (q *Queries) GetAllChatPrompts(ctx context.Context) ([]ChatPrompt, ...
constant getChatPromptByID (line 124) | getChatPromptByID = `-- name: GetChatPromptByID :one
method GetChatPromptByID (line 129) | func (q *Queries) GetChatPromptByID(ctx context.Context, id int32) (Chat...
constant getChatPromptByUUID (line 150) | getChatPromptByUUID = `-- name: GetChatPromptByUUID :one
method GetChatPromptByUUID (line 155) | func (q *Queries) GetChatPromptByUUID(ctx context.Context, uuid string) ...
constant getChatPromptsBySessionUUID (line 176) | getChatPromptsBySessionUUID = `-- name: GetChatPromptsBySessionUUID :many
method GetChatPromptsBySessionUUID (line 183) | func (q *Queries) GetChatPromptsBySessionUUID(ctx context.Context, chatS...
constant getChatPromptsByUserID (line 220) | getChatPromptsByUserID = `-- name: GetChatPromptsByUserID :many
method GetChatPromptsByUserID (line 227) | func (q *Queries) GetChatPromptsByUserID(ctx context.Context, userID int...
constant getChatPromptsBysession_uuid (line 264) | getChatPromptsBysession_uuid = `-- name: GetChatPromptsBysession_uuid :many
method GetChatPromptsBysession_uuid (line 271) | func (q *Queries) GetChatPromptsBysession_uuid(ctx context.Context, chat...
constant getOneChatPromptBySessionUUID (line 308) | getOneChatPromptBySessionUUID = `-- name: GetOneChatPromptBySessionUUID ...
method GetOneChatPromptBySessionUUID (line 316) | func (q *Queries) GetOneChatPromptBySessionUUID(ctx context.Context, cha...
constant hasChatPromptPermission (line 337) | hasChatPromptPermission = `-- name: HasChatPromptPermission :one
type HasChatPromptPermissionParams (line 344) | type HasChatPromptPermissionParams struct
method HasChatPromptPermission (line 349) | func (q *Queries) HasChatPromptPermission(ctx context.Context, arg HasCh...
constant updateChatPrompt (line 356) | updateChatPrompt = `-- name: UpdateChatPrompt :one
type UpdateChatPromptParams (line 362) | type UpdateChatPromptParams struct
method UpdateChatPrompt (line 372) | func (q *Queries) UpdateChatPrompt(ctx context.Context, arg UpdateChatPr...
constant updateChatPromptByUUID (line 401) | updateChatPromptByUUID = `-- name: UpdateChatPromptByUUID :one
type UpdateChatPromptByUUIDParams (line 407) | type UpdateChatPromptByUUIDParams struct
method UpdateChatPromptByUUID (line 413) | func (q *Queries) UpdateChatPromptByUUID(ctx context.Context, arg Update...
FILE: api/sqlc_queries/chat_session.sql.go
constant createChatSession (line 14) | createChatSession = `-- name: CreateChatSession :one
type CreateChatSessionParams (line 20) | type CreateChatSessionParams struct
method CreateChatSession (line 28) | func (q *Queries) CreateChatSession(ctx context.Context, arg CreateChatS...
constant createChatSessionByUUID (line 60) | createChatSessionByUUID = `-- name: CreateChatSessionByUUID :one
type CreateChatSessionByUUIDParams (line 66) | type CreateChatSessionByUUIDParams struct
method CreateChatSessionByUUID (line 76) | func (q *Queries) CreateChatSessionByUUID(ctx context.Context, arg Creat...
constant createChatSessionInWorkspace (line 110) | createChatSessionInWorkspace = `-- name: CreateChatSessionInWorkspace :one
type CreateChatSessionInWorkspaceParams (line 116) | type CreateChatSessionInWorkspaceParams struct
method CreateChatSessionInWorkspace (line 127) | func (q *Queries) CreateChatSessionInWorkspace(ctx context.Context, arg ...
constant createOrUpdateChatSessionByUUID (line 162) | createOrUpdateChatSessionByUUID = `-- name: CreateOrUpdateChatSessionByU...
type CreateOrUpdateChatSessionByUUIDParams (line 183) | type CreateOrUpdateChatSessionByUUIDParams struct
method CreateOrUpdateChatSessionByUUID (line 200) | func (q *Queries) CreateOrUpdateChatSessionByUUID(ctx context.Context, a...
constant deleteChatSession (line 241) | deleteChatSession = `-- name: DeleteChatSession :exec
method DeleteChatSession (line 246) | func (q *Queries) DeleteChatSession(ctx context.Context, id int32) error {
constant deleteChatSessionByUUID (line 251) | deleteChatSessionByUUID = `-- name: DeleteChatSessionByUUID :exec
method DeleteChatSessionByUUID (line 257) | func (q *Queries) DeleteChatSessionByUUID(ctx context.Context, uuid stri...
constant getAllChatSessions (line 262) | getAllChatSessions = `-- name: GetAllChatSessions :many
method GetAllChatSessions (line 268) | func (q *Queries) GetAllChatSessions(ctx context.Context) ([]ChatSession...
constant getChatSessionByID (line 310) | getChatSessionByID = `-- name: GetChatSessionByID :one
method GetChatSessionByID (line 314) | func (q *Queries) GetChatSessionByID(ctx context.Context, id int32) (Cha...
constant getChatSessionByUUID (line 340) | getChatSessionByUUID = `-- name: GetChatSessionByUUID :one
method GetChatSessionByUUID (line 346) | func (q *Queries) GetChatSessionByUUID(ctx context.Context, uuid string)...
constant getChatSessionByUUIDWithInActive (line 372) | getChatSessionByUUIDWithInActive = `-- name: GetChatSessionByUUIDWithInA...
method GetChatSessionByUUIDWithInActive (line 378) | func (q *Queries) GetChatSessionByUUIDWithInActive(ctx context.Context, ...
constant getChatSessionsByUserID (line 404) | getChatSessionsByUserID = `-- name: GetChatSessionsByUserID :many
method GetChatSessionsByUserID (line 418) | func (q *Queries) GetChatSessionsByUserID(ctx context.Context, userID in...
constant getSessionsByWorkspaceID (line 460) | getSessionsByWorkspaceID = `-- name: GetSessionsByWorkspaceID :many
method GetSessionsByWorkspaceID (line 474) | func (q *Queries) GetSessionsByWorkspaceID(ctx context.Context, workspac...
constant getSessionsGroupedByWorkspace (line 516) | getSessionsGroupedByWorkspace = `-- name: GetSessionsGroupedByWorkspace ...
type GetSessionsGroupedByWorkspaceRow (line 537) | type GetSessionsGroupedByWorkspaceRow struct
method GetSessionsGroupedByWorkspace (line 562) | func (q *Queries) GetSessionsGroupedByWorkspace(ctx context.Context, use...
constant getSessionsWithoutWorkspace (line 608) | getSessionsWithoutWorkspace = `-- name: GetSessionsWithoutWorkspace :many
method GetSessionsWithoutWorkspace (line 613) | func (q *Queries) GetSessionsWithoutWorkspace(ctx context.Context, userI...
constant hasChatSessionPermission (line 655) | hasChatSessionPermission = `-- name: HasChatSessionPermission :one
type HasChatSessionPermissionParams (line 663) | type HasChatSessionPermissionParams struct
method HasChatSessionPermission (line 672) | func (q *Queries) HasChatSessionPermission(ctx context.Context, arg HasC...
constant migrateSessionsToDefaultWorkspace (line 679) | migrateSessionsToDefaultWorkspace = `-- name: MigrateSessionsToDefaultWo...
type MigrateSessionsToDefaultWorkspaceParams (line 685) | type MigrateSessionsToDefaultWorkspaceParams struct
method MigrateSessionsToDefaultWorkspace (line 690) | func (q *Queries) MigrateSessionsToDefaultWorkspace(ctx context.Context,...
constant updateChatSession (line 695) | updateChatSession = `-- name: UpdateChatSession :one
type UpdateChatSessionParams (line 701) | type UpdateChatSessionParams struct
method UpdateChatSession (line 708) | func (q *Queries) UpdateChatSession(ctx context.Context, arg UpdateChatS...
constant updateChatSessionByUUID (line 739) | updateChatSessionByUUID = `-- name: UpdateChatSessionByUUID :one
type UpdateChatSessionByUUIDParams (line 745) | type UpdateChatSessionByUUIDParams struct
method UpdateChatSessionByUUID (line 751) | func (q *Queries) UpdateChatSessionByUUID(ctx context.Context, arg Updat...
constant updateChatSessionTopicByUUID (line 777) | updateChatSessionTopicByUUID = `-- name: UpdateChatSessionTopicByUUID :one
type UpdateChatSessionTopicByUUIDParams (line 787) | type UpdateChatSessionTopicByUUIDParams struct
method UpdateChatSessionTopicByUUID (line 793) | func (q *Queries) UpdateChatSessionTopicByUUID(ctx context.Context, arg ...
constant updateSessionMaxLength (line 819) | updateSessionMaxLength = `-- name: UpdateSessionMaxLength :one
type UpdateSessionMaxLengthParams (line 827) | type UpdateSessionMaxLengthParams struct
method UpdateSessionMaxLength (line 832) | func (q *Queries) UpdateSessionMaxLength(ctx context.Context, arg Update...
constant updateSessionWorkspace (line 858) | updateSessionWorkspace = `-- name: UpdateSessionWorkspace :one
type UpdateSessionWorkspaceParams (line 865) | type UpdateSessionWorkspaceParams struct
method UpdateSessionWorkspace (line 870) | func (q *Queries) UpdateSessionWorkspace(ctx context.Context, arg Update...
FILE: api/sqlc_queries/chat_snapshot.sql.go
constant chatSnapshotByID (line 14) | chatSnapshotByID = `-- name: ChatSnapshotByID :one
method ChatSnapshotByID (line 18) | func (q *Queries) ChatSnapshotByID(ctx context.Context, id int32) (ChatS...
constant chatSnapshotByUUID (line 39) | chatSnapshotByUUID = `-- name: ChatSnapshotByUUID :one
method ChatSnapshotByUUID (line 43) | func (q *Queries) ChatSnapshotByUUID(ctx context.Context, uuid string) (...
constant chatSnapshotByUserIdAndUuid (line 64) | chatSnapshotByUserIdAndUuid = `-- name: ChatSnapshotByUserIdAndUuid :one
type ChatSnapshotByUserIdAndUuidParams (line 68) | type ChatSnapshotByUserIdAndUuidParams struct
method ChatSnapshotByUserIdAndUuid (line 73) | func (q *Queries) ChatSnapshotByUserIdAndUuid(ctx context.Context, arg C...
constant chatSnapshotCountByUserIDAndType (line 94) | chatSnapshotCountByUserIDAndType = `-- name: ChatSnapshotCountByUserIDAn...
type ChatSnapshotCountByUserIDAndTypeParams (line 100) | type ChatSnapshotCountByUserIDAndTypeParams struct
method ChatSnapshotCountByUserIDAndType (line 105) | func (q *Queries) ChatSnapshotCountByUserIDAndType(ctx context.Context, ...
constant chatSnapshotMetaByUserID (line 112) | chatSnapshotMetaByUserID = `-- name: ChatSnapshotMetaByUserID :many
type ChatSnapshotMetaByUserIDParams (line 119) | type ChatSnapshotMetaByUserIDParams struct
type ChatSnapshotMetaByUserIDRow (line 126) | type ChatSnapshotMetaByUserIDRow struct
method ChatSnapshotMetaByUserID (line 135) | func (q *Queries) ChatSnapshotMetaByUserID(ctx context.Context, arg Chat...
constant chatSnapshotSearch (line 170) | chatSnapshotSearch = `-- name: ChatSnapshotSearch :many
type ChatSnapshotSearchParams (line 178) | type ChatSnapshotSearchParams struct
type ChatSnapshotSearchRow (line 183) | type ChatSnapshotSearchRow struct
method ChatSnapshotSearch (line 189) | func (q *Queries) ChatSnapshotSearch(ctx context.Context, arg ChatSnapsh...
constant createChatBot (line 212) | createChatBot = `-- name: CreateChatBot :one
type CreateChatBotParams (line 218) | type CreateChatBotParams struct
method CreateChatBot (line 231) | func (q *Queries) CreateChatBot(ctx context.Context, arg CreateChatBotPa...
constant createChatSnapshot (line 263) | createChatSnapshot = `-- name: CreateChatSnapshot :one
type CreateChatSnapshotParams (line 269) | type CreateChatSnapshotParams struct
method CreateChatSnapshot (line 281) | func (q *Queries) CreateChatSnapshot(ctx context.Context, arg CreateChat...
constant deleteChatSnapshot (line 312) | deleteChatSnapshot = `-- name: DeleteChatSnapshot :one
type DeleteChatSnapshotParams (line 318) | type DeleteChatSnapshotParams struct
method DeleteChatSnapshot (line 323) | func (q *Queries) DeleteChatSnapshot(ctx context.Context, arg DeleteChat...
constant listChatSnapshots (line 344) | listChatSnapshots = `-- name: ListChatSnapshots :many
method ListChatSnapshots (line 348) | func (q *Queries) ListChatSnapshots(ctx context.Context) ([]ChatSnapshot...
constant updateChatSnapshot (line 385) | updateChatSnapshot = `-- name: UpdateChatSnapshot :one
type UpdateChatSnapshotParams (line 392) | type UpdateChatSnapshotParams struct
method UpdateChatSnapshot (line 403) | func (q *Queries) UpdateChatSnapshot(ctx context.Context, arg UpdateChat...
constant updateChatSnapshotMetaByUUID (line 433) | updateChatSnapshotMetaByUUID = `-- name: UpdateChatSnapshotMetaByUUID :exec
type UpdateChatSnapshotMetaByUUIDParams (line 439) | type UpdateChatSnapshotMetaByUUIDParams struct
method UpdateChatSnapshotMetaByUUID (line 446) | func (q *Queries) UpdateChatSnapshotMetaByUUID(ctx context.Context, arg ...
FILE: api/sqlc_queries/chat_workspace.sql.go
constant createDefaultWorkspace (line 13) | createDefaultWorkspace = `-- name: CreateDefaultWorkspace :one
type CreateDefaultWorkspaceParams (line 19) | type CreateDefaultWorkspaceParams struct
method CreateDefaultWorkspace (line 24) | func (q *Queries) CreateDefaultWorkspace(ctx context.Context, arg Create...
constant createWorkspace (line 43) | createWorkspace = `-- name: CreateWorkspace :one
type CreateWorkspaceParams (line 49) | type CreateWorkspaceParams struct
method CreateWorkspace (line 60) | func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspa...
constant deleteWorkspace (line 88) | deleteWorkspace = `-- name: DeleteWorkspace :exec
method DeleteWorkspace (line 93) | func (q *Queries) DeleteWorkspace(ctx context.Context, uuid string) error {
constant getDefaultWorkspaceByUserID (line 98) | getDefaultWorkspaceByUserID = `-- name: GetDefaultWorkspaceByUserID :one
method GetDefaultWorkspaceByUserID (line 104) | func (q *Queries) GetDefaultWorkspaceByUserID(ctx context.Context, userI...
constant getWorkspaceByUUID (line 123) | getWorkspaceByUUID = `-- name: GetWorkspaceByUUID :one
method GetWorkspaceByUUID (line 128) | func (q *Queries) GetWorkspaceByUUID(ctx context.Context, uuid string) (...
constant getWorkspaceWithSessionCount (line 147) | getWorkspaceWithSessionCount = `-- name: GetWorkspaceWithSessionCount :many
type GetWorkspaceWithSessionCountRow (line 158) | type GetWorkspaceWithSessionCountRow struct
method GetWorkspaceWithSessionCount (line 173) | func (q *Queries) GetWorkspaceWithSessionCount(ctx context.Context, user...
constant getWorkspacesByUserID (line 209) | getWorkspacesByUserID = `-- name: GetWorkspacesByUserID :many
method GetWorkspacesByUserID (line 215) | func (q *Queries) GetWorkspacesByUserID(ctx context.Context, userID int3...
constant hasWorkspacePermission (line 250) | hasWorkspacePermission = `-- name: HasWorkspacePermission :one
type HasWorkspacePermissionParams (line 264) | type HasWorkspacePermissionParams struct
method HasWorkspacePermission (line 269) | func (q *Queries) HasWorkspacePermission(ctx context.Context, arg HasWor...
constant setDefaultWorkspace (line 276) | setDefaultWorkspace = `-- name: SetDefaultWorkspace :one
type SetDefaultWorkspaceParams (line 283) | type SetDefaultWorkspaceParams struct
method SetDefaultWorkspace (line 288) | func (q *Queries) SetDefaultWorkspace(ctx context.Context, arg SetDefaul...
constant updateWorkspace (line 307) | updateWorkspace = `-- name: UpdateWorkspace :one
type UpdateWorkspaceParams (line 314) | type UpdateWorkspaceParams struct
method UpdateWorkspace (line 322) | func (q *Queries) UpdateWorkspace(ctx context.Context, arg UpdateWorkspa...
constant updateWorkspaceOrder (line 347) | updateWorkspaceOrder = `-- name: UpdateWorkspaceOrder :one
type UpdateWorkspaceOrderParams (line 354) | type UpdateWorkspaceOrderParams struct
method UpdateWorkspaceOrder (line 359) | func (q *Queries) UpdateWorkspaceOrder(ctx context.Context, arg UpdateWo...
FILE: api/sqlc_queries/db.go
type DBTX (line 12) | type DBTX interface
function New (line 19) | func New(db DBTX) *Queries {
type Queries (line 23) | type Queries struct
method WithTx (line 27) | func (q *Queries) WithTx(tx *sql.Tx) *Queries {
FILE: api/sqlc_queries/jwt_secrets.sql.go
constant createJwtSecret (line 12) | createJwtSecret = `-- name: CreateJwtSecret :one
type CreateJwtSecretParams (line 17) | type CreateJwtSecretParams struct
method CreateJwtSecret (line 23) | func (q *Queries) CreateJwtSecret(ctx context.Context, arg CreateJwtSecr...
constant deleteAllJwtSecrets (line 36) | deleteAllJwtSecrets = `-- name: DeleteAllJwtSecrets :execrows
method DeleteAllJwtSecrets (line 40) | func (q *Queries) DeleteAllJwtSecrets(ctx context.Context) (int64, error) {
constant getJwtSecret (line 48) | getJwtSecret = `-- name: GetJwtSecret :one
method GetJwtSecret (line 52) | func (q *Queries) GetJwtSecret(ctx context.Context, name string) (JwtSec...
FILE: api/sqlc_queries/models.go
type AuthUser (line 13) | type AuthUser struct
type AuthUserManagement (line 27) | type AuthUserManagement struct
type BotAnswerHistory (line 35) | type BotAnswerHistory struct
type ChatComment (line 47) | type ChatComment struct
type ChatFile (line 59) | type ChatFile struct
type ChatLog (line 69) | type ChatLog struct
type ChatMessage (line 77) | type ChatMessage struct
type ChatModel (line 100) | type ChatModel struct
type ChatPrompt (line 118) | type ChatPrompt struct
type ChatSession (line 134) | type ChatSession struct
type ChatSnapshot (line 155) | type ChatSnapshot struct
type ChatWorkspace (line 171) | type ChatWorkspace struct
type JwtSecret (line 185) | type JwtSecret struct
type UserActiveChatSession (line 193) | type UserActiveChatSession struct
type UserChatModelPrivilege (line 202) | type UserChatModelPrivilege struct
FILE: api/sqlc_queries/user_active_chat_session.sql.go
constant deleteUserActiveSession (line 13) | deleteUserActiveSession = `-- name: DeleteUserActiveSession :exec
type DeleteUserActiveSessionParams (line 21) | type DeleteUserActiveSessionParams struct
method DeleteUserActiveSession (line 26) | func (q *Queries) DeleteUserActiveSession(ctx context.Context, arg Delet...
constant deleteUserActiveSessionBySession (line 31) | deleteUserActiveSessionBySession = `-- name: DeleteUserActiveSessionBySe...
type DeleteUserActiveSessionBySessionParams (line 36) | type DeleteUserActiveSessionBySessionParams struct
method DeleteUserActiveSessionBySession (line 41) | func (q *Queries) DeleteUserActiveSessionBySession(ctx context.Context, ...
constant getAllUserActiveSessions (line 46) | getAllUserActiveSessions = `-- name: GetAllUserActiveSessions :many
method GetAllUserActiveSessions (line 52) | func (q *Queries) GetAllUserActiveSessions(ctx context.Context, userID i...
constant getUserActiveSession (line 82) | getUserActiveSession = `-- name: GetUserActiveSession :one
type GetUserActiveSessionParams (line 90) | type GetUserActiveSessionParams struct
method GetUserActiveSession (line 95) | func (q *Queries) GetUserActiveSession(ctx context.Context, arg GetUserA...
constant upsertUserActiveSession (line 109) | upsertUserActiveSession = `-- name: UpsertUserActiveSession :one
type UpsertUserActiveSessionParams (line 120) | type UpsertUserActiveSessionParams struct
method UpsertUserActiveSession (line 127) | func (q *Queries) UpsertUserActiveSession(ctx context.Context, arg Upser...
FILE: api/sqlc_queries/user_chat_model_privilege.sql.go
constant createUserChatModelPrivilege (line 12) | createUserChatModelPrivilege = `-- name: CreateUserChatModelPrivilege :one
type CreateUserChatModelPrivilegeParams (line 18) | type CreateUserChatModelPrivilegeParams struct
method CreateUserChatModelPrivilege (line 26) | func (q *Queries) CreateUserChatModelPrivilege(ctx context.Context, arg ...
constant deleteUserChatModelPrivilege (line 48) | deleteUserChatModelPrivilege = `-- name: DeleteUserChatModelPrivilege :exec
method DeleteUserChatModelPrivilege (line 52) | func (q *Queries) DeleteUserChatModelPrivilege(ctx context.Context, id i...
constant listUserChatModelPrivileges (line 57) | listUserChatModelPrivileges = `-- name: ListUserChatModelPrivileges :many
method ListUserChatModelPrivileges (line 61) | func (q *Queries) ListUserChatModelPrivileges(ctx context.Context) ([]Us...
constant listUserChatModelPrivilegesByUserID (line 93) | listUserChatModelPrivilegesByUserID = `-- name: ListUserChatModelPrivile...
method ListUserChatModelPrivilegesByUserID (line 102) | func (q *Queries) ListUserChatModelPrivilegesByUserID(ctx context.Contex...
constant listUserChatModelPrivilegesRateLimit (line 134) | listUserChatModelPrivilegesRateLimit = `-- name: ListUserChatModelPrivil...
type ListUserChatModelPrivilegesRateLimitRow (line 142) | type ListUserChatModelPrivilegesRateLimitRow struct
method ListUserChatModelPrivilegesRateLimit (line 150) | func (q *Queries) ListUserChatModelPrivilegesRateLimit(ctx context.Conte...
constant rateLimiteByUserAndSessionUUID (line 179) | rateLimiteByUserAndSessionUUID = `-- name: RateLimiteByUserAndSessionUUI...
type RateLimiteByUserAndSessionUUIDParams (line 188) | type RateLimiteByUserAndSessionUUIDParams struct
type RateLimiteByUserAndSessionUUIDRow (line 193) | type RateLimiteByUserAndSessionUUIDRow struct
method RateLimiteByUserAndSessionUUID (line 198) | func (q *Queries) RateLimiteByUserAndSessionUUID(ctx context.Context, ar...
constant updateUserChatModelPrivilege (line 205) | updateUserChatModelPrivilege = `-- name: UpdateUserChatModelPrivilege :one
type UpdateUserChatModelPrivilegeParams (line 211) | type UpdateUserChatModelPrivilegeParams struct
method UpdateUserChatModelPrivilege (line 217) | func (q *Queries) UpdateUserChatModelPrivilege(ctx context.Context, arg ...
constant userChatModelPrivilegeByID (line 233) | userChatModelPrivilegeByID = `-- name: UserChatModelPrivilegeByID :one
method UserChatModelPrivilegeByID (line 237) | func (q *Queries) UserChatModelPrivilegeByID(ctx context.Context, id int...
constant userChatModelPrivilegeByUserAndModelID (line 253) | userChatModelPrivilegeByUserAndModelID = `-- name: UserChatModelPrivileg...
type UserChatModelPrivilegeByUserAndModelIDParams (line 257) | type UserChatModelPrivilegeByUserAndModelIDParams struct
method UserChatModelPrivilegeByUserAndModelID (line 262) | func (q *Queries) UserChatModelPrivilegeByUserAndModelID(ctx context.Con...
FILE: api/sqlc_queries/zz_custom_method.go
method Role (line 11) | func (user *AuthUser) Role() string {
method Authenticate (line 19) | func (m *ChatMessage) Authenticate(q Queries, userID int32) (bool, error) {
method Authenticate (line 26) | func (s *ChatSession) Authenticate(q Queries, userID int32) (bool, error) {
method Authenticate (line 33) | func (p *ChatPrompt) Authenticate(q Queries, userID int32) (bool, error) {
method ToRawMessage (line 41) | func (cs *ChatSession) ToRawMessage() *json.RawMessage {
type MessageWithRoleAndContent (line 51) | type MessageWithRoleAndContent interface
method GetRole (line 56) | func (m ChatMessage) GetRole() string {
method GetContent (line 60) | func (m ChatMessage) GetContent() string {
method GetRole (line 64) | func (m ChatPrompt) GetRole() string {
method GetContent (line 68) | func (m ChatPrompt) GetContent() string {
function SqlChatsToOpenAIMesages (line 72) | func SqlChatsToOpenAIMesages(messages []MessageWithRoleAndContent) []ope...
function SqlChatsToOpenAIMessagesGenerics (line 79) | func SqlChatsToOpenAIMessagesGenerics[T MessageWithRoleAndContent](messa...
FILE: api/sqlc_queries/zz_custom_query.go
type SimpleChatMessage (line 12) | type SimpleChatMessage struct
type Artifact (line 26) | type Artifact struct
method GetChatHistoryBySessionUUID (line 34) | func (q *Queries) GetChatHistoryBySessionUUID(ctx context.Context, uuid ...
FILE: api/streaming_helpers.go
function constructChatCompletionStreamResponse (line 18) | func constructChatCompletionStreamResponse(answerID string, content stri...
type StreamingResponse (line 34) | type StreamingResponse struct
function FlushResponse (line 41) | func FlushResponse(w http.ResponseWriter, flusher http.Flusher, response...
function ShouldFlushContent (line 58) | func ShouldFlushContent(content string, lastFlushLength int, isSmallCont...
function SetStreamingHeaders (line 65) | func SetStreamingHeaders(req *http.Request) {
function GenerateAnswerID (line 73) | func GenerateAnswerID(chatUuid string, regenerate bool) string {
function GetChatModel (line 81) | func GetChatModel(queries *sqlc_queries.Queries, modelName string) (*sql...
function GetChatFiles (line 90) | func GetChatFiles(queries *sqlc_queries.Queries, sessionUUID string) ([]...
FILE: api/text_buffer.go
type textBuffer (line 8) | type textBuffer struct
method appendByIndex (line 23) | func (tb *textBuffer) appendByIndex(index int, text string) {
method String (line 29) | func (tb *textBuffer) String(separator string) string {
function newTextBuffer (line 14) | func newTextBuffer(n int32, prefix, suffix string) *textBuffer {
FILE: api/tools/apply_a_similar_change/parse_diff2.py
function apply_diff_file (line 5) | def apply_diff_file(diff_file):
FILE: api/tools/fix_eris.py
function search_files (line 5) | def search_files(dir_name, pattern):
function replace_error_handling (line 14) | def replace_error_handling(file_path):
function main (line 31) | def main():
FILE: api/util.go
function NewUUID (line 18) | func NewUUID() string {
function getTokenCount (line 25) | func getTokenCount(content string) (int, error) {
function firstN (line 37) | func firstN(s string, n int) string {
function firstNWords (line 49) | func firstNWords(s string, n int) string {
function getUserID (line 62) | func getUserID(ctx context.Context) (int32, error) {
function getContextWithUser (line 76) | func getContextWithUser(userID int) context.Context {
function setSSEHeader (line 80) | func setSSEHeader(w http.ResponseWriter) {
function setupSSEStream (line 93) | func setupSSEStream(w http.ResponseWriter) (http.Flusher, error) {
function getPerWordStreamLimit (line 102) | func getPerWordStreamLimit() int {
function RespondWithJSON (line 118) | func RespondWithJSON(w http.ResponseWriter, status int, payload interfac...
function getPaginationParams (line 129) | func getPaginationParams(r *http.Request) (limit int32, offset int32) {
function getLimitParam (line 150) | func getLimitParam(r *http.Request, defaultLimit int32) int32 {
function DecodeJSON (line 164) | func DecodeJSON(r *http.Request, target interface{}) error {
FILE: api/util_test.go
function Test_firstN (line 5) | func Test_firstN(t *testing.T) {
FILE: api/util_words_test.go
function Test_firstNWords (line 7) | func Test_firstNWords(t *testing.T) {
FILE: e2e/lib/button-helpers.ts
function getClearConversationButton (line 12) | async function getClearConversationButton(page: Page) {
function getSnapshotButton (line 22) | async function getSnapshotButton(page: Page) {
function getVFSUploadButton (line 32) | async function getVFSUploadButton(page: Page) {
function getArtifactGalleryButton (line 42) | async function getArtifactGalleryButton(page: Page) {
constant FOOTER_BUTTON_POSITIONS (line 51) | const FOOTER_BUTTON_POSITIONS = {
FILE: e2e/lib/chat-test-setup.ts
constant DEFAULT_PASSWORD (line 4) | const DEFAULT_PASSWORD = '@ThisIsATestPass5'
function setupDebugChatSession (line 6) | async function setupDebugChatSession(page: Page, email: string) {
function sendMessageAndWaitAssistantCount (line 24) | async function sendMessageAndWaitAssistantCount(
FILE: e2e/lib/db/chat_message/index.ts
function selectChatMessagesBySessionUUID (line 1) | async function selectChatMessagesBySessionUUID(pool, sessionUUID: string) {
FILE: e2e/lib/db/chat_model/index.ts
function selectModels (line 1) | async function selectModels(pool) {
FILE: e2e/lib/db/chat_prompt/index.ts
function selectChatPromptsBySessionUUID (line 1) | async function selectChatPromptsBySessionUUID(pool, sessionUUID: string) {
FILE: e2e/lib/db/chat_session/index.ts
function selectChatSessionByUserId (line 1) | async function selectChatSessionByUserId(pool, userId: number) {
FILE: e2e/lib/db/chat_workspace/index.ts
type ChatWorkspace (line 3) | interface ChatWorkspace {
function selectWorkspacesByUserId (line 17) | async function selectWorkspacesByUserId(pool: Pool, userId: number): Pro...
function selectWorkspaceByUuid (line 30) | async function selectWorkspaceByUuid(pool: Pool, uuid: string): Promise<...
function insertWorkspace (line 43) | async function insertWorkspace(pool: Pool, workspace: Omit<ChatWorkspace...
function countSessionsInWorkspace (line 58) | async function countSessionsInWorkspace(pool: Pool, workspaceId: number)...
FILE: e2e/lib/db/user/index.ts
function selectUserByEmail (line 1) | async function selectUserByEmail(pool, email: string) {
FILE: e2e/lib/message-helpers.ts
class MessageHelpers (line 8) | class MessageHelpers {
method constructor (line 11) | constructor(page: Page) {
method getAllMessages (line 18) | async getAllMessages(): Promise<Locator[]> {
method getMessageByIndex (line 26) | async getMessageByIndex(index: number): Promise<Locator> {
method getMessageText (line 37) | async getMessageText(index: number): Promise<string> {
method waitForMessageTextContains (line 47) | async waitForMessageTextContains(index: number, expectedText: string, ...
method getAssistantMessages (line 64) | async getAssistantMessages(): Promise<Locator[]> {
method getAssistantMessageByContent (line 80) | async getAssistantMessageByContent(partialText: string): Promise<Locat...
method waitForAssistantMessageCount (line 96) | async waitForAssistantMessageCount(count: number, timeout: number = 15...
method waitForAssistantMessageTextContains (line 114) | async waitForAssistantMessageTextContains(index: number, expectedText:...
method waitForAssistantMessageWithText (line 135) | async waitForAssistantMessageWithText(expectedText: string, timeout: n...
method getAssistantMessageText (line 155) | async getAssistantMessageText(index: number): Promise<string> {
method clickAssistantRegenerate (line 168) | async clickAssistantRegenerate(index: number): Promise<void> {
method clickAssistantRegenerateByContent (line 181) | async clickAssistantRegenerateByContent(partialText: string): Promise<...
method isAssistantRegenerateButtonVisible (line 193) | async isAssistantRegenerateButtonVisible(index: number): Promise<boole...
method isAssistantRegenerateButtonVisibleByContent (line 207) | async isAssistantRegenerateButtonVisibleByContent(partialText: string)...
method getRegenerateButton (line 222) | async getRegenerateButton(index: number): Promise<Locator> {
method clickRegenerate (line 230) | async clickRegenerate(index: number): Promise<void> {
method waitForMessageWithText (line 239) | async waitForMessageWithText(text: string, timeout: number = 10000): P...
method getLastMessageText (line 253) | async getLastMessageText(): Promise<string> {
method waitForMessageCount (line 263) | async waitForMessageCount(count: number, timeout: number = 10000): Pro...
method isRegenerateButtonVisible (line 274) | async isRegenerateButtonVisible(index: number): Promise<boolean> {
method getMessageByContent (line 286) | async getMessageByContent(partialText: string): Promise<Locator | null> {
method getMessageIndexByContent (line 308) | async getMessageIndexByContent(partialText: string): Promise<number> {
class AuthHelpers (line 331) | class AuthHelpers {
method constructor (line 334) | constructor(page: Page) {
method signupAndWaitForAuth (line 341) | async signupAndWaitForAuth(email: string, password: string): Promise<v...
method waitForInterfaceReady (line 369) | async waitForInterfaceReady(): Promise<void> {
class InputHelpers (line 383) | class InputHelpers {
method constructor (line 386) | constructor(page: Page) {
method getInputArea (line 393) | async getInputArea(): Promise<Locator> {
method sendMessage (line 400) | async sendMessage(text: string, waitForResponse: boolean = true): Prom...
FILE: e2e/lib/sample.ts
function randomEmail (line 2) | function randomEmail() {
FILE: e2e/tests-examples/demo-todo-app.spec.ts
constant TODO_ITEMS (line 7) | const TODO_ITEMS = [
function createDefaultTodos (line 411) | async function createDefaultTodos(page: Page) {
function checkNumberOfTodosInLocalStorage (line 421) | async function checkNumberOfTodosInLocalStorage(page: Page, expected: nu...
function checkNumberOfCompletedTodosInLocalStorage (line 427) | async function checkNumberOfCompletedTodosInLocalStorage(page: Page, exp...
function checkTodosInLocalStorage (line 433) | async function checkTodosInLocalStorage(page: Page, title: string) {
FILE: e2e/tests/01_register.spec.ts
function randomEmail (line 4) | function randomEmail() {
FILE: e2e/tests/04_simpe_prompt_and_message.spec.ts
function waitForMessageCount (line 17) | async function waitForMessageCount(pool: Pool, sessionUuid: string, expe...
FILE: e2e/tests/06_clear_messages.spec.ts
function waitForMessageCount (line 15) | async function waitForMessageCount(pool: Pool, sessionUuid: string, expe...
FILE: e2e/tests/07_set_session_max_len.spec.ts
function randomEmail (line 4) | function randomEmail() {
FILE: mobile/lib/api/api_exception.dart
class ApiException (line 1) | class ApiException implements Exception {
method userMessage (line 16) | String userMessage({bool includeDetail = true})
method toString (line 24) | String toString()
FILE: mobile/lib/api/chat_api.dart
class ChatApi (line 15) | class ChatApi {
method login (line 28) | Future<AuthTokenResult> login({
method fetchWorkspaces (line 60) | Future<List<Workspace>> fetchWorkspaces()
method fetchSessions (line 75) | Future<List<ChatSession>> fetchSessions({
method fetchSessionById (line 92) | Future<ChatSession> fetchSessionById(String sessionId)
method fetchMessages (line 109) | Future<List<ChatMessage>> fetchMessages({
method createChatPrompt (line 132) | Future<ChatMessage> createChatPrompt({
method deleteChatPrompt (line 182) | Future<void> deleteChatPrompt(String promptId)
method streamChatResponse (line 193) | Future<void> streamChatResponse({
method fetchChatModels (line 237) | Future<List<ChatModel>> fetchChatModels()
method updateSession (line 252) | Future<void> updateSession({
method generateMoreSuggestions (line 293) | Future<SuggestionsResponse> generateMoreSuggestions({
method clearSessionMessages (line 313) | Future<void> clearSessionMessages(String sessionId)
method createChatSnapshot (line 324) | Future<String> createChatSnapshot(String sessionId)
method fetchSnapshots (line 341) | Future<List<ChatSnapshotMeta>> fetchSnapshots({
method fetchSnapshot (line 364) | Future<ChatSnapshotDetail> fetchSnapshot(String snapshotId)
method deleteSession (line 381) | Future<void> deleteSession(String sessionId)
method deleteMessage (line 392) | Future<void> deleteMessage(String messageId)
method updateMessage (line 403) | Future<void> updateMessage({
method createSession (line 423) | Future<ChatSession> createSession({
method _defaultHeaders (line 455) | Map<String, String> _defaultHeaders()
method _extractList (line 479) | List<Map<String, dynamic>> _extractList(dynamic payload)
method _parseApiError (line 499) | ApiException _parseApiError(int status, String body)
method _extractRefreshCookie (line 531) | String? _extractRefreshCookie(http.Response response)
method _asInt (line 557) | int? _asInt(dynamic value)
method _asString (line 573) | String? _asString(dynamic value)
method _asDateTime (line 583) | DateTime? _asDateTime(dynamic value)
method refreshToken (line 599) | Future<AuthTokenResult> refreshToken()
FILE: mobile/lib/constants/chat.dart
function defaultSystemPromptForLocale (line 14) | String defaultSystemPromptForLocale([Locale? locale])
FILE: mobile/lib/main.dart
function main (line 7) | void main()
class ChatMobileApp (line 11) | class ChatMobileApp extends StatelessWidget {
method build (line 15) | Widget build(BuildContext context)
FILE: mobile/lib/models/auth_token_result.dart
class AuthTokenResult (line 1) | class AuthTokenResult {
FILE: mobile/lib/models/chat_message.dart
type MessageRole (line 1) | enum MessageRole {
class ChatMessage (line 7) | class ChatMessage {
method copyWith (line 36) | ChatMessage copyWith({
function _asString (line 102) | String? _asString(dynamic value)
function _asBool (line 112) | bool _asBool(dynamic value)
function _asDateTime (line 128) | DateTime? _asDateTime(dynamic value)
function _asStringList (line 144) | List<String>? _asStringList(dynamic value)
FILE: mobile/lib/models/chat_model.dart
class ChatModel (line 1) | class ChatModel {
function _asString (line 37) | String? _asString(dynamic value)
function _asInt (line 47) | int? _asInt(dynamic value)
function _asBool (line 63) | bool _asBool(dynamic value)
FILE: mobile/lib/models/chat_session.dart
class ChatSession (line 1) | class ChatSession {
function _asString (line 87) | String? _asString(dynamic value)
function _asDateTime (line 97) | DateTime? _asDateTime(dynamic value)
function _asInt (line 113) | int? _asInt(dynamic value)
function _asDouble (line 129) | double? _asDouble(dynamic value)
function _asBool (line 145) | bool _asBool(dynamic value)
FILE: mobile/lib/models/chat_snapshot.dart
class ChatSnapshotMeta (line 3) | class ChatSnapshotMeta {
class ChatSnapshotDetail (line 26) | class ChatSnapshotDetail {
function _asString (line 68) | String? _asString(dynamic value)
function _asDateTime (line 78) | DateTime? _asDateTime(dynamic value)
FILE: mobile/lib/models/suggestions_response.dart
class SuggestionsResponse (line 1) | class SuggestionsResponse {
function _asStringList (line 18) | List<String>? _asStringList(dynamic value)
FILE: mobile/lib/models/workspace.dart
class Workspace (line 1) | class Workspace {
function _readString (line 32) | String _readString(
function _readBool (line 46) | bool _readBool(Map<String, dynamic> json, List<String> keys)
FILE: mobile/lib/screens/auth_gate.dart
class AuthGate (line 9) | class AuthGate extends HookConsumerWidget {
method build (line 13) | Widget build(BuildContext context, WidgetRef ref)
FILE: mobile/lib/screens/chat_screen.dart
class ChatScreen (line 16) | class ChatScreen extends HookConsumerWidget {
method build (line 22) | Widget build(BuildContext context, WidgetRef ref)
method _buildMessageList (line 153) | Widget _buildMessageList(
method _sendMessage (line 261) | Future<void> _sendMessage(
method _openModelSheet (line 287) | void _openModelSheet(
method _confirmClearConversation (line 361) | Future<void> _confirmClearConversation(
method _createSnapshot (line 395) | Future<void> _createSnapshot(
method _deleteMessage (line 431) | Future<void> _deleteMessage(
method _toggleMessagePin (line 451) | Future<void> _toggleMessagePin(
method _regenerateMessage (line 464) | Future<void> _regenerateMessage(
method _getDisplayTitle (line 479) | String _getDisplayTitle(String title)
method _showEditTitleDialog (line 486) | void _showEditTitleDialog(
FILE: mobile/lib/screens/home_screen.dart
class HomeScreen (line 16) | class HomeScreen extends HookConsumerWidget {
method build (line 20) | Widget build(BuildContext context, WidgetRef ref)
method _buildBody (line 96) | Widget _buildBody(
method _buildSessions (line 166) | Widget _buildSessions(
method _confirmDeleteSession (line 257) | Future<bool> _confirmDeleteSession(BuildContext context)
method _createSession (line 278) | Future<void> _createSession(BuildContext context, WidgetRef ref)
FILE: mobile/lib/screens/login_screen.dart
class LoginScreen (line 7) | class LoginScreen extends HookConsumerWidget {
method build (line 11) | Widget build(BuildContext context, WidgetRef ref)
method _submit (line 78) | void _submit(
FILE: mobile/lib/screens/snapshot_list_screen.dart
class SnapshotListScreen (line 10) | class SnapshotListScreen extends HookConsumerWidget {
method build (line 14) | Widget build(BuildContext context, WidgetRef ref)
method loadSnapshots (line 23) | Future<void> loadSnapshots({bool loadMore = false})
method _buildEmptyState (line 150) | Widget _buildEmptyState(
method _formatDate (line 174) | String _formatDate(DateTime dateTime)
method _two (line 182) | String _two(int value)
FILE: mobile/lib/screens/snapshot_screen.dart
class SnapshotScreen (line 10) | class SnapshotScreen extends HookConsumerWidget {
method build (line 16) | Widget build(BuildContext context, WidgetRef ref)
method loadSnapshot (line 21) | Future<void> loadSnapshot()
method _buildBody (line 61) | Widget _buildBody(
FILE: mobile/lib/state/auth_provider.dart
class AuthState (line 8) | class AuthState {
method copyWith (line 35) | AuthState copyWith({
class AuthNotifier (line 57) | class AuthNotifier extends StateNotifier<AuthState> {
method loadToken (line 71) | Future<void> loadToken()
method login (line 96) | Future<void> login({
method refreshToken (line 124) | Future<void> refreshToken()
method ensureFreshToken (line 151) | Future<bool> ensureFreshToken()
method logout (line 178) | Future<void> logout()
function _needsRefresh (line 196) | bool _needsRefresh(int? expiresIn)
FILE: mobile/lib/state/message_provider.dart
class MessageState (line 11) | class MessageState {
method copyWith (line 24) | MessageState copyWith({
class MessageNotifier (line 39) | class MessageNotifier extends StateNotifier<MessageState> {
method _ensureAuth (line 50) | Future<bool> _ensureAuth()
method loadMessages (line 61) | Future<void> loadMessages(String sessionId)
method sendMessage (line 102) | Future<String?> sendMessage({
method regenerateMessage (line 179) | Future<String?> regenerateMessage({
method addMessage (line 276) | void addMessage(ChatMessage message)
method _handleStreamChunk (line 280) | void _handleStreamChunk(String sessionId, String tempId, String chunk)
method _replaceMessageContent (line 362) | void _replaceMessageContent(String messageId, String content)
method _clearSuggestedQuestionsLoading (line 387) | void _clearSuggestedQuestionsLoading(String sessionId)
method generateMoreSuggestions (line 416) | Future<String?> generateMoreSuggestions(String messageId)
method setSuggestedQuestionBatch (line 505) | void setSuggestedQuestionBatch({
method clearSessionMessages (line 537) | Future<String?> clearSessionMessages(String sessionId)
method deleteMessage (line 556) | Future<String?> deleteMessage(String messageId)
method toggleMessagePin (line 588) | Future<String?> toggleMessagePin(String messageId)
method _setLatestAssistantLoading (line 622) | void _setLatestAssistantLoading(String sessionId, bool loading)
function _extractStreamingData (line 657) | String _extractStreamingData(String chunk)
function _mergeSessionMessages (line 668) | List<ChatMessage> _mergeSessionMessages({
FILE: mobile/lib/state/model_provider.dart
class ModelState (line 8) | class ModelState {
method copyWith (line 38) | ModelState copyWith({
class ModelNotifier (line 57) | class ModelNotifier extends StateNotifier<ModelState> {
method _ensureAuth (line 68) | Future<bool> _ensureAuth()
method loadModels (line 79) | Future<void> loadModels()
method setActiveModel (line 103) | void setActiveModel(String modelName)
method _resolveActiveModelName (line 107) | String? _resolveActiveModelName(List<ChatModel> models)
FILE: mobile/lib/state/session_provider.dart
class SessionState (line 8) | class SessionState {
method copyWith (line 19) | SessionState copyWith({
class SessionNotifier (line 32) | class SessionNotifier extends StateNotifier<SessionState> {
method _ensureAuth (line 42) | Future<bool> _ensureAuth()
method loadSessions (line 53) | Future<void> loadSessions(String? workspaceId)
method createSession (line 79) | Future<ChatSession?> createSession({
method addSession (line 113) | void addSession(ChatSession session)
method updateSession (line 117) | void updateSession(ChatSession updated)
method deleteSession (line 125) | Future<String?> deleteSession(String sessionId)
method updateSessionModel (line 148) | Future<String?> updateSessionModel({
method refreshSession (line 203) | Future<String?> refreshSession(String sessionId)
method updateSessionExploreMode (line 244) | Future<String?> updateSessionExploreMode({
method updateSessionTitle (line 299) | Future<String?> updateSessionTitle({
FILE: mobile/lib/state/workspace_provider.dart
class WorkspaceState (line 8) | class WorkspaceState {
method copyWith (line 34) | WorkspaceState copyWith({
class WorkspaceNotifier (line 53) | class WorkspaceNotifier extends StateNotifier<WorkspaceState> {
method _ensureAuth (line 64) | Future<bool> _ensureAuth()
method loadWorkspaces (line 75) | Future<void> loadWorkspaces()
method setActiveWorkspace (line 97) | void setActiveWorkspace(String workspaceId)
method addWorkspace (line 101) | void addWorkspace(Workspace workspace)
method _resolveActiveWorkspaceId (line 110) | String? _resolveActiveWorkspaceId(List<Workspace> workspaces)
FILE: mobile/lib/theme/app_theme.dart
class AppTheme (line 3) | class AppTheme {
method light (line 7) | ThemeData light()
FILE: mobile/lib/theme/color_utils.dart
function colorFromHex (line 3) | Color colorFromHex(String hex)
FILE: mobile/lib/utils/api_error.dart
function formatApiError (line 3) | String formatApiError(Object error)
FILE: mobile/lib/utils/thinking_parser.dart
class ThinkingParseResult (line 1) | class ThinkingParseResult {
function parseThinkingContent (line 15) | ThinkingParseResult parseThinkingContent(String text)
FILE: mobile/lib/widgets/icon_map.dart
function iconForName (line 3) | IconData iconForName(String iconName)
FILE: mobile/lib/widgets/message_bubble.dart
class MessageBubble (line 10) | class MessageBubble extends StatelessWidget {
method build (line 25) | Widget build(BuildContext context)
method _formatTimestamp (line 231) | String _formatTimestamp(DateTime timestamp)
method _showMessageMenu (line 248) | void _showMessageMenu(BuildContext context)
method _copyMessage (line 301) | void _copyMessage(BuildContext context)
method _confirmDelete (line 316) | void _confirmDelete(BuildContext context)
FILE: mobile/lib/widgets/message_composer.dart
class MessageComposer (line 4) | class MessageComposer extends HookWidget {
method build (line 15) | Widget build(BuildContext context)
FILE: mobile/lib/widgets/session_tile.dart
class SessionTile (line 5) | class SessionTile extends StatelessWidget {
method build (line 16) | Widget build(BuildContext context)
method _getDisplayTitle (line 30) | String _getDisplayTitle()
FILE: mobile/lib/widgets/suggested_questions.dart
class SuggestedQuestions (line 3) | class SuggestedQuestions extends StatelessWidget {
method build (line 28) | Widget build(BuildContext context)
FILE: mobile/lib/widgets/thinking_section.dart
class ThinkingSection (line 5) | class ThinkingSection extends StatefulWidget {
method createState (line 20) | State<ThinkingSection> createState()
class _ThinkingSectionState (line 23) | class _ThinkingSectionState extends State<ThinkingSection> {
method initState (line 28) | void initState()
method _copyContent (line 41) | Future<void> _copyContent()
method build (line 59) | Widget build(BuildContext context)
FILE: mobile/lib/widgets/workspace_selector.dart
class WorkspaceSelector (line 8) | class WorkspaceSelector extends HookConsumerWidget {
method build (line 12) | Widget build(BuildContext context, WidgetRef ref)
method _openWorkspaceSheet (line 61) | void _openWorkspaceSheet(BuildContext context, WidgetRef ref)
FILE: mobile/linux/flutter/generated_plugin_registrant.cc
function fl_register_plugins (line 10) | void fl_register_plugins(FlPluginRegistry* registry) {
FILE: mobile/linux/runner/main.cc
function main (line 3) | int main(int argc, char** argv) {
FILE: mobile/linux/runner/my_application.cc
type _MyApplication (line 10) | struct _MyApplication {
function first_frame_cb (line 18) | static void first_frame_cb(MyApplication* self, FlView *view)
function my_application_activate (line 24) | static void my_application_activate(GApplication* application) {
function gboolean (line 80) | static gboolean my_application_local_command_line(GApplication* applicat...
function my_application_startup (line 99) | static void my_application_startup(GApplication* application) {
function my_application_shutdown (line 108) | static void my_application_shutdown(GApplication* application) {
function my_application_dispose (line 117) | static void my_application_dispose(GObject* object) {
function my_application_class_init (line 123) | static void my_application_class_init(MyApplicationClass* klass) {
function my_application_init (line 131) | static void my_application_init(MyApplication* self) {}
function MyApplication (line 133) | MyApplication* my_application_new() {
FILE: mobile/test/widget_test.dart
function main (line 13) | void main()
FILE: mobile/windows/flutter/generated_plugin_registrant.cc
function RegisterPlugins (line 10) | void RegisterPlugins(flutter::PluginRegistry* registry) {
FILE: mobile/windows/runner/flutter_window.cpp
function LRESULT (line 50) | LRESULT
FILE: mobile/windows/runner/flutter_window.h
function class (line 12) | class FlutterWindow : public Win32Window {
FILE: mobile/windows/runner/main.cpp
function wWinMain (line 8) | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FILE: mobile/windows/runner/utils.cpp
function CreateAndAttachConsole (line 10) | void CreateAndAttachConsole() {
function GetCommandLineArguments (line 24) | std::vector<std::string> GetCommandLineArguments() {
function Utf8FromUtf16 (line 44) | std::string Utf8FromUtf16(const wchar_t* utf16_string) {
FILE: mobile/windows/runner/win32_window.cpp
function Scale (line 36) | int Scale(int source, double scale_factor) {
function EnableFullDpiSupportIfAvailable (line 42) | void EnableFullDpiSupportIfAvailable(HWND hwnd) {
class WindowClassRegistrar (line 59) | class WindowClassRegistrar {
method WindowClassRegistrar (line 64) | static WindowClassRegistrar* GetInstance() {
method WindowClassRegistrar (line 80) | WindowClassRegistrar() = default;
function wchar_t (line 89) | const wchar_t* WindowClassRegistrar::GetWindowClass() {
function LRESULT (line 157) | LRESULT CALLBACK Win32Window::WndProc(HWND const window,
function LRESULT (line 176) | LRESULT
function Win32Window (line 236) | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
function RECT (line 252) | RECT Win32Window::GetClientArea() {
function HWND (line 258) | HWND Win32Window::GetHandle() {
FILE: mobile/windows/runner/win32_window.h
type Size (line 21) | struct Size {
FILE: scripts/branch_clean.py
function get_local_branches (line 4) | def get_local_branches():
function get_branch_last_commit_date (line 12) | def get_branch_last_commit_date(branch):
function delete_branch (line 18) | def delete_branch(branch):
function confirm_deletion (line 22) | def confirm_deletion(branch):
function main (line 27) | def main():
FILE: scripts/locale_missing_key.py
function find_missing_keys (line 6) | def find_missing_keys(base_dict, other_dict):
function check_locales (line 19) | def check_locales(dir_name: str, base_locale: str = 'zh-CN'):
FILE: scripts/merge_keys.py
function merge_dicts (line 5) | def merge_dicts(d1, d2):
function merge_json_files (line 21) | def merge_json_files(file1, file2):
FILE: web/src/api/bot_answer_history.ts
function fetchBotAnswerHistory (line 3) | async function fetchBotAnswerHistory(botUuid: string, page: number, page...
function fetchBotRunCount (line 17) | async function fetchBotRunCount(botUuid: string) {
FILE: web/src/api/chat_file.ts
function getChatFilesList (line 9) | async function getChatFilesList(uuid: string) {
FILE: web/src/api/chat_instructions.ts
type ChatInstructions (line 3) | interface ChatInstructions {
FILE: web/src/api/chat_prompt.ts
type CreateChatPromptPayload (line 3) | interface CreateChatPromptPayload {
FILE: web/src/api/chat_workspace.ts
type CreateWorkspaceRequest (line 3) | interface CreateWorkspaceRequest {
type UpdateWorkspaceRequest (line 11) | interface UpdateWorkspaceRequest {
type CreateSessionInWorkspaceRequest (line 18) | interface CreateSessionInWorkspaceRequest {
FILE: web/src/api/export.ts
function format_chat_md (line 3) | function format_chat_md(chat: Chat.Message): string {
FILE: web/src/api/token.ts
function fetchAPIToken (line 3) | async function fetchAPIToken() {
FILE: web/src/api/user.ts
function fetchLogin (line 2) | async function fetchLogin(email: string, password: string) {
function fetchSignUp (line 13) | async function fetchSignUp(email: string, password: string) {
FILE: web/src/config/api.ts
type ApiConfig (line 1) | interface ApiConfig {
function getApiConfig (line 9) | function getApiConfig(): ApiConfig {
function getStreamingUrl (line 43) | function getStreamingUrl(endpoint: string): string {
function getApiUrl (line 51) | function getApiUrl(endpoint: string): string {
FILE: web/src/constants/apiTypes.ts
constant API_TYPES (line 1) | const API_TYPES = {
type ApiType (line 9) | type ApiType = typeof API_TYPES[keyof typeof API_TYPES]
constant API_TYPE_OPTIONS (line 11) | const API_TYPE_OPTIONS = [
constant API_TYPE_DISPLAY_NAMES (line 19) | const API_TYPE_DISPLAY_NAMES = {
FILE: web/src/hooks/useBasicLayout.ts
function useBasicLayout (line 3) | function useBasicLayout() {
FILE: web/src/hooks/useCopyCode.ts
function useCopyCode (line 4) | function useCopyCode() {
FILE: web/src/hooks/useIconRender.ts
type IconConfig (line 5) | interface IconConfig {
type IconStyle (line 11) | interface IconStyle {
FILE: web/src/hooks/useLanguage.ts
function useLanguage (line 6) | function useLanguage() {
FILE: web/src/hooks/useOnlineStatus.ts
function useOnlineStatus (line 4) | function useOnlineStatus() {
FILE: web/src/hooks/useTheme.ts
function useTheme (line 6) | function useTheme() {
FILE: web/src/hooks/useWorkspaceRouting.ts
function useWorkspaceRouting (line 5) | function useWorkspaceRouting() {
FILE: web/src/locales/index.ts
function t (line 20) | function t(key: string, values?: Record<string, string>) {
function setLocale (line 28) | function setLocale(locale: Language) {
function setupI18n (line 32) | function setupI18n(app: App) {
FILE: web/src/main.ts
function bootstrap (line 9) | async function bootstrap() {
FILE: web/src/plugins/assets.ts
function naiveStyleOverride (line 8) | function naiveStyleOverride() {
function setupAssets (line 14) | function setupAssets() {
FILE: web/src/router/index.ts
function setupRouter (line 130) | async function setupRouter(app: App) {
FILE: web/src/router/permission.ts
constant FIVE_MINUTES_IN_SECONDS (line 7) | const FIVE_MINUTES_IN_SECONDS = 5 * 60
function ensureFreshToken (line 10) | async function ensureFreshToken(authStore: any) {
function setupPageGuard (line 32) | function setupPageGuard(router: Router) {
FILE: web/src/service/snapshot.ts
function generateAPIHelper (line 7) | function generateAPIHelper(uuid: string, apiToken: string, origin: strin...
function getChatbotPosts (line 16) | function getChatbotPosts(posts: Snapshot.Snapshot[]) {
function getSnapshotPosts (line 26) | function getSnapshotPosts(posts: Snapshot.Snapshot[]) {
function postsByYearMonthTransform (line 36) | function postsByYearMonthTransform(posts: Snapshot.PostLink[]) {
function getSnapshotPostLinks (line 48) | function getSnapshotPostLinks(snapshots: Snapshot.Snapshot[]): Record<st...
function getBotPostLinks (line 53) | function getBotPostLinks(bots: Snapshot.Snapshot[]): Record<string, Snap...
FILE: web/src/services/codeTemplates.ts
type CodeTemplate (line 8) | interface CodeTemplate {
type TemplateCategory (line 32) | interface TemplateCategory {
class CodeTemplatesService (line 41) | class CodeTemplatesService {
method constructor (line 48) | private constructor() {
method getInstance (line 53) | static getInstance(): CodeTemplatesService {
method initializeBuiltInTemplates (line 63) | private initializeBuiltInTemplates(): void {
method initializeCategories (line 497) | private initializeCategories(): void {
method getTemplates (line 561) | getTemplates(): CodeTemplate[] {
method getTemplatesByCategory (line 568) | getTemplatesByCategory(categoryId: string): CodeTemplate[] {
method getTemplatesByLanguage (line 575) | getTemplatesByLanguage(language: string): CodeTemplate[] {
method getTemplate (line 582) | getTemplate(id: string): CodeTemplate | undefined {
method searchTemplates (line 589) | searchTemplates(query: string, filters?: {
method getCategories (line 635) | getCategories(): TemplateCategory[] {
method getPopularTemplates (line 646) | getPopularTemplates(limit = 10): CodeTemplate[] {
method getRecentTemplates (line 655) | getRecentTemplates(limit = 10): CodeTemplate[] {
method addTemplate (line 664) | addTemplate(template: Omit<CodeTemplate, 'id' | 'createdAt' | 'updated...
method updateTemplate (line 686) | updateTemplate(id: string, updates: Partial<CodeTemplate>): boolean {
method deleteTemplate (line 701) | deleteTemplate(id: string): boolean {
method incrementUsage (line 716) | incrementUsage(id: string): void {
method rateTemplate (line 727) | rateTemplate(id: string, rating: number): boolean {
method exportTemplates (line 742) | exportTemplates(): string {
method importTemplates (line 750) | importTemplates(jsonData: string): boolean {
method generateId (line 777) | private generateId(): string {
method saveUserTemplates (line 781) | private saveUserTemplates(): void {
method loadUserTemplates (line 790) | private loadUserTemplates(): void {
function useCodeTemplates (line 807) | function useCodeTemplates() {
FILE: web/src/store/index.ts
function setupStore (line 6) | function setupStore(app: App) {
FILE: web/src/store/modules/app/helper.ts
constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'appSetting'
type Theme (line 5) | type Theme = 'light' | 'dark' | 'auto'
type Language (line 7) | type Language = 'zh-CN' | 'zh-TW' | 'en-US'
type AppState (line 9) | interface AppState {
function defaultSetting (line 17) | function defaultSetting(): AppState {
function getLocalSetting (line 21) | function getLocalSetting(): AppState {
function setLocalSetting (line 26) | function setLocalSetting(setting: AppState): void {
FILE: web/src/store/modules/app/index.ts
method setSiderCollapsed (line 11) | setSiderCollapsed(collapsed: boolean) {
method setTheme (line 16) | setTheme(theme: Theme) {
method setLanguage (line 21) | setLanguage(language: Language) {
method setNextLanguage (line 27) | setNextLanguage() {
method recordState (line 34) | recordState() {
function useAppStoreWithOut (line 40) | function useAppStoreWithOut() {
FILE: web/src/store/modules/auth/helper.ts
function getToken (line 7) | function getToken(): string | null {
function setToken (line 11) | function setToken(token: string): void {
function removeToken (line 15) | function removeToken(): void {
constant EXPIRE_LOCAL_NAME (line 20) | const EXPIRE_LOCAL_NAME = 'expiresIn'
function getExpiresIn (line 22) | function getExpiresIn(): number | null {
function setExpiresIn (line 27) | function setExpiresIn(expiresIn: number): void {
function removeExpiresIn (line 31) | function removeExpiresIn(): void {
FILE: web/src/store/modules/auth/index.ts
type AuthState (line 7) | interface AuthState {
method isValid (line 25) | isValid(): boolean {
method getToken (line 28) | getToken(): string | null {
method getExpiresIn (line 31) | getExpiresIn(): number | null {
method needsRefresh (line 34) | needsRefresh(): boolean {
method needPermission (line 39) | needPermission(): boolean {
method initializeAuth (line 45) | async initializeAuth() {
method setToken (line 73) | setToken(token: string) {
method removeToken (line 77) | removeToken() {
method refreshToken (line 81) | async refreshToken() {
method setExpiresIn (line 125) | setExpiresIn(expiresIn: number) {
method removeExpiresIn (line 129) | removeExpiresIn() {
method waitForInitialization (line 133) | async waitForInitialization(timeoutMs = 10000) {
FILE: web/src/store/modules/message/index.ts
type MessageState (line 14) | interface MessageState {
method getChatSessionDataByUuid (line 26) | getChatSessionDataByUuid(state) {
method getIsLoadingBySession (line 35) | getIsLoadingBySession(state) {
method getLastMessageForSession (line 42) | getLastMessageForSession(state) {
method activeSessionMessages (line 50) | activeSessionMessages(state) {
method syncChatMessages (line 60) | async syncChatMessages(sessionUuid: string) {
method addMessage (line 155) | addMessage(sessionUuid: string, message: Chat.Message) {
method addMessages (line 162) | addMessages(sessionUuid: string, messages: Chat.Message[]) {
method updateMessage (line 169) | updateMessage(sessionUuid: string, messageUuid: string, updates: Partial...
method removeMessage (line 179) | async removeMessage(sessionUuid: string, messageUuid: string) {
method clearSessionMessages (line 199) | clearSessionMessages(sessionUuid: string) {
method updateLastMessage (line 215) | updateLastMessage(sessionUuid: string, updates: Partial<Chat.Message>) {
method setLoading (line 224) | setLoading(sessionUuid: string, isLoading: boolean) {
method getMessageCount (line 229) | getMessageCount(sessionUuid: string) {
method clearAllMessages (line 234) | clearAllMessages() {
method removeSessionData (line 240) | removeSessionData(sessionUuid: string) {
method hasMessages (line 246) | hasMessages(sessionUuid: string) {
method getMessagesByType (line 251) | getMessagesByType(sessionUuid: string, type: 'user' | 'assistant') {
method getPinnedMessages (line 262) | getPinnedMessages(sessionUuid: string) {
method getMessagesWithArtifacts (line 268) | getMessagesWithArtifacts(sessionUuid: string) {
method getMessagesByDateRange (line 274) | getMessagesByDateRange(sessionUuid: string, startDate: string, endDate: ...
method searchMessages (line 283) | searchMessages(sessionUuid: string, query: string) {
method getLoadingMessages (line 292) | getLoadingMessages(sessionUuid: string) {
method getErrorMessages (line 298) | getErrorMessages(sessionUuid: string) {
method getPromptMessages (line 304) | getPromptMessages(sessionUuid: string) {
method generateMoreSuggestedQuestions (line 310) | async generateMoreSuggestedQuestions(sessionUuid: string, messageUuid: s...
method previousSuggestedQuestionsBatch (line 355) | previousSuggestedQuestionsBatch(sessionUuid: string, messageUuid: string) {
method nextSuggestedQuestionsBatch (line 375) | nextSuggestedQuestionsBatch(sessionUuid: string, messageUuid: string) {
FILE: web/src/store/modules/prompt/helper.ts
constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'promptStore'
type Prompt (line 5) | type Prompt = {
type PromptList (line 9) | type PromptList = [] | Prompt[]
type PromptStore (line 11) | interface PromptStore {
function getLocalPromptList (line 164) | function getLocalPromptList(): PromptStore {
function setLocalPromptList (line 175) | function setLocalPromptList(promptStore: PromptStore): void {
FILE: web/src/store/modules/prompt/index.ts
method updatePromptList (line 9) | updatePromptList(promptList: []) {
method getPromptList (line 13) | getPromptList() {
FILE: web/src/store/modules/session/index.ts
type SessionState (line 14) | interface SessionState {
method getChatSessionByUuid (line 36) | getChatSessionByUuid(state) {
method getSessionsByWorkspace (line 49) | getSessionsByWorkspace(state) {
method activeSession (line 56) | activeSession(state) {
method getSessionUrl (line 68) | getSessionUrl() {
method syncWorkspaceSessions (line 83) | async syncWorkspaceSessions(workspaceUuid: string) {
method syncActiveWorkspaceSessions (line 105) | async syncActiveWorkspaceSessions() {
method syncAllWorkspaceSessions (line 136) | async syncAllWorkspaceSessions() {
method createSessionInWorkspace (line 161) | async createSessionInWorkspace(title: string, workspaceUuid?: string, mo...
method createLegacySession (line 219) | async createLegacySession(session: Chat.Session) {
method updateSession (line 237) | async updateSession(uuid: string, updates: Partial<Chat.Session>) {
method deleteSession (line 286) | async deleteSession(sessionUuid: string) {
method setActiveSession (line 324) | async setActiveSession(workspaceUuid: string | null, sessionUuid: string) {
method setActiveSessionWithoutNavigation (line 373) | setActiveSessionWithoutNavigation(workspaceUuid: string | null, sessionU...
method setNextActiveSession (line 383) | async setNextActiveSession(workspaceUuid: string | null) {
method navigateToSession (line 401) | async navigateToSession(sessionUuid: string) {
method clearWorkspaceSessions (line 445) | clearWorkspaceSessions(workspaceUuid: string) {
method getAllSessions (line 456) | getAllSessions() {
method addSession (line 465) | async addSession(session: Chat.Session) {
method createNewSession (line 470) | async createNewSession(title?: string, workspaceUuid?: string, model?: s...
FILE: web/src/store/modules/user/helper.ts
constant LOCAL_NAME (line 3) | const LOCAL_NAME = 'userStorage'
type UserInfo (line 5) | interface UserInfo {
type UserState (line 10) | interface UserState {
function defaultSetting (line 14) | function defaultSetting(): UserState {
function getLocalState (line 23) | function getLocalState(): UserState {
function setLocalState (line 28) | function setLocalState(setting: UserState): void {
FILE: web/src/store/modules/user/index.ts
method updateUserInfo (line 8) | updateUserInfo(userInfo: Partial<UserInfo>) {
method resetUserInfo (line 13) | resetUserInfo() {
method recordState (line 18) | recordState() {
FILE: web/src/store/modules/workspace/index.ts
type WorkspaceState (line 20) | interface WorkspaceState {
method getWorkspaceByUuid (line 38) | getWorkspaceByUuid(state) {
method getDefaultWorkspace (line 47) | getDefaultWorkspace(state) {
method activeWorkspace (line 51) | activeWorkspace(state) {
method getActiveSessionForWorkspace (line 59) | getActiveSessionForWorkspace(state) {
method initializeActiveWorkspace (line 68) | async initializeActiveWorkspace(targetWorkspaceUuid?: string) {
method initializeApplication (line 107) | async initializeApplication() {
method loadActiveWorkspace (line 165) | async loadActiveWorkspace(targetWorkspaceUuid?: string) {
method loadAllWorkspaces (line 205) | async loadAllWorkspaces() {
method syncWorkspaceActiveSessions (line 225) | async syncWorkspaceActiveSessions(urlWorkspaceUuid?: string, urlSessionU...
method ensureActiveWorkspace (line 275) | async ensureActiveWorkspace() {
method ensureUserHasSession (line 286) | async ensureUserHasSession() {
method handleInitialNavigation (line 311) | async handleInitialNavigation(urlWorkspaceUuid?: string, urlSessionUuid?...
method syncWorkspaces (line 340) | async syncWorkspaces() {
method ensureDefaultWorkspace (line 365) | async ensureDefaultWorkspace() {
method setActiveWorkspace (line 389) | async setActiveWorkspace(workspaceUuid: string) {
method restoreActiveSession (line 460) | restoreActiveSession() {
method createWorkspace (line 478) | async createWorkspace(name: string, description: string = '', color: str...
method updateWorkspace (line 506) | async updateWorkspace(workspaceUuid: string, updates: any) {
method deleteWorkspace (line 520) | async deleteWorkspace(workspaceUuid: string) {
method setDefaultWorkspace (line 545) | async setDefaultWorkspace(workspaceUuid: string) {
method updateWorkspaceOrder (line 558) | async updateWorkspaceOrder(workspaceUuids: string[]) {
method setActiveSessionForWorkspace (line 591) | setActiveSessionForWorkspace(workspaceUuid: string, sessionUuid: string) {
method clearActiveSessionForWorkspace (line 595) | clearActiveSessionForWorkspace(workspaceUuid: string) {
method navigateToWorkspace (line 599) | async navigateToWorkspace(workspaceUuid: string, sessionUuid?: string) {
FILE: web/src/types/chat-models.ts
type ChatModel (line 1) | interface ChatModel {
type CreateChatModelRequest (line 15) | interface CreateChatModelRequest {
type UpdateChatModelRequest (line 27) | interface UpdateChatModelRequest {
type ChatModelSelectOption (line 39) | interface ChatModelSelectOption {
type ChatModelsResponse (line 45) | interface ChatModelsResponse {
FILE: web/src/typings/chat.d.ts
type Artifact (line 3) | interface Artifact {
type Message (line 11) | interface Message {
type Session (line 29) | interface Session {
type Workspace (line 46) | interface Workspace {
type ActiveSession (line 59) | interface ActiveSession {
type ChatState (line 64) | interface ChatState {
type ConversationRequest (line 72) | interface ConversationRequest {
type ConversationResponse (line 78) | interface ConversationResponse {
type ChatModel (line 95) | interface ChatModel {
type ChatModelPrivilege (line 113) | interface ChatModelPrivilege {
type Comment (line 121) | interface Comment {
type Snapshot (line 135) | interface Snapshot {
type PostLink (line 144) | interface PostLink {
type BotAnswerHistory (line 152) | interface BotAnswerHistory {
FILE: web/src/typings/global.d.ts
type Window (line 1) | interface Window {
type SelectOption (line 8) | interface SelectOption {
FILE: web/src/utils/artifacts.ts
type Artifact (line 4) | type Artifact = Chat.Artifact
function generateUUID (line 7) | function generateUUID(): string {
function extractArtifacts (line 12) | function extractArtifacts(content: string): Artifact[] {
FILE: web/src/utils/crypto/index.ts
function enCrypto (line 6) | function enCrypto(data: any) {
function deCrypto (line 11) | function deCrypto(data: string) {
FILE: web/src/utils/date.ts
function nowISO (line 3) | function nowISO(): string {
function getCurrentDate (line 7) | function getCurrentDate() {
function displayLocaleDate (line 14) | function displayLocaleDate(ts: string) {
function formatYearMonth (line 23) | function formatYearMonth(date: Date): string {
FILE: web/src/utils/download.ts
function genTempDownloadLink (line 3) | function genTempDownloadLink(imgUrl: string) {
FILE: web/src/utils/errorHandler.ts
type ApiError (line 14) | interface ApiError {
type ErrorHandlerOptions (line 21) | interface ErrorHandlerOptions {
class ErrorHandler (line 29) | class ErrorHandler {
method isNetworkError (line 38) | private isNetworkError(error: any): boolean {
method isAuthError (line 47) | private isAuthError(error: any): boolean {
method isServerError (line 51) | private isServerError(error: any): boolean {
method isClientError (line 55) | private isClientError(error: any): boolean {
method extractErrorMessage (line 59) | private extractErrorMessage(error: any): string {
method logApiError (line 72) | private logApiError(method: string, url: string, error: any, options: ...
method handleAuthError (line 85) | private async handleAuthError(error: any): Promise<void> {
method retryRequest (line 101) | private async retryRequest(
method handleApiRequest (line 134) | async handleApiRequest<T>(
method get (line 209) | async get<T>(url: string, options?: ErrorHandlerOptions): Promise<T> {
method post (line 219) | async post<T>(url: string, data?: any, options?: ErrorHandlerOptions):...
method put (line 233) | async put<T>(url: string, data?: any, options?: ErrorHandlerOptions): ...
method delete (line 247) | async delete<T>(url: string, options?: ErrorHandlerOptions): Promise<T> {
method setupGlobalErrorHandler (line 257) | setupGlobalErrorHandler(): void {
FILE: web/src/utils/format/index.ts
function encodeHTML (line 5) | function encodeHTML(source: string) {
function includeCode (line 18) | function includeCode(text: string | null | undefined) {
function copyText (line 27) | function copyText(options: { text: string; origin?: boolean }) {
FILE: web/src/utils/is/index.ts
function isNumber (line 1) | function isNumber<T extends number>(value: T | unknown): value is number {
function isString (line 5) | function isString<T extends string>(value: T | unknown): value is string {
function isBoolean (line 9) | function isBoolean<T extends boolean>(value: T | unknown): value is bool...
function isNull (line 13) | function isNull<T extends null>(value: T | unknown): value is null {
function isUndefined (line 17) | function isUndefined<T extends undefined>(value: T | unknown): value is ...
function isObject (line 21) | function isObject<T extends object>(value: T | unknown): value is object {
function isArray (line 25) | function isArray<T extends any[]>(value: T | unknown): value is T {
function isFunction (line 29) | function isFunction<T extends (...args: any[]) => any | void | never>(va...
function isDate (line 33) | function isDate<T extends Date>(value: T | unknown): value is T {
function isRegExp (line 37) | function isRegExp<T extends RegExp>(value: T | unknown): value is T {
function isPromise (line 41) | function isPromise<T extends Promise<any>>(value: T | unknown): value is...
function isSet (line 45) | function isSet<T extends Set<any>>(value: T | unknown): value is T {
function isMap (line 49) | function isMap<T extends Map<any, any>>(value: T | unknown): value is T {
function isFile (line 53) | function isFile<T extends File>(value: T | unknown): value is T {
FILE: web/src/utils/jwt.ts
function isAdmin (line 3) | function isAdmin(token: string): boolean {
FILE: web/src/utils/logger.ts
type LogLevel (line 1) | enum LogLevel {
type LogEntry (line 8) | interface LogEntry {
class Logger (line 16) | class Logger {
method constructor (line 21) | constructor() {
method getLogLevelFromEnv (line 26) | private getLogLevelFromEnv(): LogLevel {
method shouldLog (line 37) | private shouldLog(level: LogLevel): boolean {
method createLogEntry (line 41) | private createLogEntry(level: LogLevel, message: string, context?: str...
method formatMessage (line 51) | private formatMessage(entry: LogEntry): string {
method log (line 58) | private log(level: LogLevel, message: string, context?: string, data?:...
method debug (line 88) | debug(message: string, context?: string, data?: any): void {
method info (line 92) | info(message: string, context?: string, data?: any): void {
method warn (line 96) | warn(message: string, context?: string, data?: any): void {
method error (line 100) | error(message: string, context?: string, data?: any): void {
method logApiCall (line 105) | logApiCall(method: string, url: string, status?: number, duration?: nu...
method logApiError (line 109) | logApiError(method: string, url: string, error: any, status?: number):...
method logStoreAction (line 113) | logStoreAction(action: string, store: string, data?: any): void {
method logPerformance (line 117) | logPerformance(metric: string, value: number, unit: string = 'ms'): vo...
method logUserAction (line 121) | logUserAction(action: string, details?: any): void {
method getLogs (line 126) | getLogs(level?: LogLevel): LogEntry[] {
method clearLogs (line 134) | clearLogs(): void {
method setLevel (line 139) | setLevel(level: LogLevel): void {
method exportLogs (line 144) | exportLogs(): string {
FILE: web/src/utils/notificationManager.ts
type NotificationOptions (line 5) | interface NotificationOptions {
type QueuedNotification (line 19) | interface QueuedNotification {
class NotificationManager (line 25) | class NotificationManager {
method setMessageInstance (line 32) | setMessageInstance(instance: any) {
method generateId (line 36) | private generateId(): string {
method canShowNotification (line 40) | private canShowNotification(): boolean {
method showNotification (line 44) | private showNotification(notification: QueuedNotification) {
method processQueue (line 94) | private processQueue() {
method show (line 103) | show(options: NotificationOptions): string {
method success (line 120) | success(message: string, options: Omit<NotificationOptions, 'message' ...
method error (line 124) | error(message: string, options: Omit<NotificationOptions, 'message' | ...
method warning (line 128) | warning(message: string, options: Omit<NotificationOptions, 'message' ...
method info (line 132) | info(message: string, options: Omit<NotificationOptions, 'message' | '...
method enhancedSuccess (line 137) | enhancedSuccess(title: string, message: string, options: Omit<Notifica...
method enhancedError (line 141) | enhancedError(title: string, message: string, options: Omit<Notificati...
method enhancedWarning (line 145) | enhancedWarning(title: string, message: string, options: Omit<Notifica...
method enhancedInfo (line 149) | enhancedInfo(title: string, message: string, options: Omit<Notificatio...
method persistent (line 153) | persistent(message: string, type: 'error' | 'warning' | 'info' = 'erro...
method remove (line 162) | remove(id: string): void {
method clear (line 167) | clear(): void {
method getStats (line 179) | getStats() {
method setMaxConcurrent (line 187) | setMaxConcurrent(max: number): void {
method enableQueue (line 192) | enableQueue(): void {
method disableQueue (line 197) | disableQueue(): void {
function useNotification (line 206) | function useNotification() {
function showNotification (line 231) | function showNotification(options: NotificationOptions): string {
function showSuccessNotification (line 235) | function showSuccessNotification(message: string, options?: Omit<Notific...
function showErrorNotification (line 239) | function showErrorNotification(message: string, options?: Omit<Notificat...
function showWarningNotification (line 243) | function showWarningNotification(message: string, options?: Omit<Notific...
function showInfoNotification (line 247) | function showInfoNotification(message: string, options?: Omit<Notificati...
function showPersistentNotification (line 251) | function showPersistentNotification(message: string, type: 'error' | 'wa...
function showEnhancedSuccessNotification (line 256) | function showEnhancedSuccessNotification(title: string, message: string,...
function showEnhancedErrorNotification (line 260) | function showEnhancedErrorNotification(title: string, message: string, o...
function showEnhancedWarningNotification (line 264) | function showEnhancedWarningNotification(title: string, message: string,...
function showEnhancedInfoNotification (line 268) | function showEnhancedInfoNotification(title: string, message: string, op...
function clearAllNotifications (line 272) | function clearAllNotifications(): void {
FILE: web/src/utils/rand.ts
function generateRandomString (line 5) | function generateRandomString(n: number): string {
FILE: web/src/utils/request/index.ts
type HttpOption (line 5) | interface HttpOption {
type Response (line 18) | interface Response<T> {
function http (line 24) | function http<T>(
function get (line 84) | function get<T>(
function post (line 98) | function post<T>(
FILE: web/src/utils/sanitize.ts
constant BLOCKED_TAGS (line 1) | const BLOCKED_TAGS = new Set(['script', 'iframe', 'object', 'embed'])
constant BLOCKED_SVG_TAGS (line 2) | const BLOCKED_SVG_TAGS = new Set(['script', 'foreignobject'])
FILE: web/src/utils/storage/local.ts
type StorageData (line 3) | interface StorageData<T> {
function createLocalStorage (line 8) | function createLocalStorage(options?: { expire?: number | null; crypto?:...
FILE: web/src/utils/string.ts
function extractStreamingData (line 1) | function extractStreamingData(streamResponse: string): string {
function escapeDollarNumber (line 21) | function escapeDollarNumber(text: string) {
function escapeBrackets (line 32) | function escapeBrackets(text: string) {
FILE: web/src/utils/tooling.ts
type ToolCall (line 1) | type ToolCall = {
FILE: web/src/utils/workspaceUrls.ts
function getBaseUrl (line 6) | function getBaseUrl(): string {
function generateSessionUrl (line 11) | function generateSessionUrl(sessionUuid: string, workspaceUuid?: string)...
function generateWorkspaceUrl (line 22) | function generateWorkspaceUrl(workspaceUuid: string): string {
function parseWorkspaceUrl (line 28) | function parseWorkspaceUrl(url: string): { workspaceUuid?: string; sessi...
function isValidWorkspaceUrl (line 58) | function isValidWorkspaceUrl(url: string): boolean {
function copyUrlToClipboard (line 64) | async function copyUrlToClipboard(url: string): Promise<boolean> {
function generateQRCodeUrl (line 91) | function generateQRCodeUrl(url: string): string {
function isValidWorkspaceUuid (line 98) | function isValidWorkspaceUuid(uuid: string): boolean {
function isValidSessionUuid (line 104) | function isValidSessionUuid(uuid: string): boolean {
function createWorkspaceSlug (line 109) | function createWorkspaceSlug(name: string): string {
FILE: web/src/views/chat/composables/useChatActions.ts
function useChatActions (line 12) | function useChatActions(sessionUuidRef: Ref<string>) {
FILE: web/src/views/chat/composables/useConversationFlow.ts
type ChatMessage (line 10) | interface ChatMessage {
function useConversationFlow (line 20) | function useConversationFlow(
FILE: web/src/views/chat/composables/useErrorHandling.ts
type AppError (line 7) | interface AppError {
type ErrorState (line 15) | interface ErrorState {
function useErrorHandling (line 21) | function useErrorHandling() {
FILE: web/src/views/chat/composables/usePerformanceOptimizations.ts
function useDebounce (line 6) | function useDebounce<T>(value: Ref<T> | T, delay: number) {
function useVirtualList (line 38) | function useVirtualList<T>(
function useThrottle (line 75) | function useThrottle<T extends (...args: any[]) => any>(
FILE: web/src/views/chat/composables/useRegenerate.ts
function useRegenerate (line 8) | function useRegenerate(sessionUuidRef: Ref<string>) {
FILE: web/src/views/chat/composables/useSearchAndPrompts.ts
type PromptItem (line 7) | interface PromptItem {
type ChatItem (line 12) | interface ChatItem {
type SearchOption (line 17) | interface SearchOption {
function useSearchAndPrompts (line 22) | function useSearchAndPrompts() {
FILE: web/src/views/chat/composables/useStreamHandling.ts
type ErrorResponse (line 11) | interface ErrorResponse {
type StreamChunkData (line 17) | interface StreamChunkData {
function useStreamHandling (line 27) | function useStreamHandling() {
FILE: web/src/views/chat/composables/useValidation.ts
type ValidationRule (line 4) | interface ValidationRule {
type ValidationResult (line 9) | interface ValidationResult {
function useValidation (line 14) | function useValidation() {
FILE: web/src/views/chat/hooks/useChat.ts
function useChat (line 5) | function useChat() {
FILE: web/src/views/chat/hooks/useCopyCode.ts
function useCopyCode (line 4) | function useCopyCode() {
FILE: web/src/views/chat/hooks/useScroll.ts
type ScrollElement (line 4) | type ScrollElement = HTMLDivElement | null
type ScrollReturn (line 6) | interface ScrollReturn {
function useScroll (line 14) | function useScroll(): ScrollReturn {
FILE: web/src/views/chat/hooks/useSlashToFocus.ts
function useSlashToFocus (line 9) | function useSlashToFocus(targetInputRef: Ref<HTMLInputElement | null>): ...
FILE: web/src/views/chat/hooks/useUsingContext.ts
function useUsingContext (line 5) | function useUsingContext() {
FILE: web/src/views/components/Message/Util.ts
type ThinkResult (line 1) | interface ThinkResult {
function parseText (line 5) | function parseText(text: string): ThinkResult {
FILE: web/src/views/components/Message/thinkingParser.ts
class ThinkingParser (line 3) | class ThinkingParser {
method constructor (line 7) | constructor(config: ThinkingParserConfig = {}) {
method parseText (line 17) | parseText(text: string): ThinkingParseResult {
method generateCacheKey (line 96) | private generateCacheKey(text: string): string {
method getFromCache (line 107) | private getFromCache(key: string): ThinkingParseResult | null {
method setToCache (line 121) | private setToCache(key: string, result: ThinkingParseResult): void {
method cleanupCache (line 134) | private cleanupCache(): void {
method clearCache (line 148) | clearCache(): void {
method getCacheStats (line 155) | getCacheStats(): { size: number; hitRate: number } {
FILE: web/src/views/components/Message/types/thinking.ts
type ThinkingContent (line 1) | interface ThinkingContent {
type ThinkingParseResult (line 9) | interface ThinkingParseResult {
type ThinkingRenderOptions (line 16) | interface ThinkingRenderOptions {
type ThinkingCacheEntry (line 26) | interface ThinkingCacheEntry {
type ThinkingParserConfig (line 32) | interface ThinkingParserConfig {
type ThinkingComposableReturn (line 39) | interface ThinkingComposableReturn {
type ThinkingRendererProps (line 50) | interface ThinkingRendererProps {
type ThinkingRendererEmits (line 57) | interface ThinkingRendererEmits {
type UseThinkingContentOptions (line 62) | interface UseThinkingContentOptions {
FILE: web/src/views/components/Message/useThinkingContent.ts
function useThinkingContent (line 10) | function useThinkingContent(
function useMultipleThinkingContent (line 85) | function useMultipleThinkingContent(
function useThinkingStats (line 123) | function useThinkingStats() {
Condensed preview — 547 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,402K chars).
[
{
"path": ".dockerignore",
"chars": 23,
"preview": "**/node_modules\n**/dist"
},
{
"path": ".github/workflows/docker-image.yml",
"chars": 1941,
"preview": "name: e2e test\n\non:\n push:\n branches: \"**\"\n pull_request:\n branches: \"**\"\nenv:\n PG_DB: postgres\n PG_USER: post"
},
{
"path": ".github/workflows/fly.yml",
"chars": 333,
"preview": "name: Fly Deploy\non:\n push:\n branches:\n - master\njobs:\n deploy:\n name: Deploy app\n runs-on: ubuntu-lates"
},
{
"path": ".github/workflows/mobile-build.yml",
"chars": 1657,
"preview": "name: Build Mobile Package\n\non:\n workflow_dispatch:\n push:\n paths:\n - \"mobile/**\"\n - \".github/workflows/m"
},
{
"path": ".github/workflows/publish.yml",
"chars": 1904,
"preview": "name: Publish\n\non:\n push:\n tags:\n - \"v*\"\n\njobs:\n build_api:\n runs-on: ubuntu-latest\n steps:\n - uses"
},
{
"path": ".gitignore",
"chars": 59,
"preview": ".DS_Store\n.env*\nenv.sh\nenv.ps\ndata\n.python-version\n.aider*\n"
},
{
"path": "AGENTS.md",
"chars": 7201,
"preview": "# Chat - Multi-LLM Chat Interface\n\nA full-stack chat application that provides a unified interface for interacting with "
},
{
"path": "CLAUDE.md",
"chars": 8101,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "Dockerfile",
"chars": 852,
"preview": "FROM node:16 as frontend_builder\n\n# Set the working directory to /app\nWORKDIR /app\n\n# Copy the package.json and package-"
},
{
"path": "README.md",
"chars": 4197,
"preview": "## Demo\n\n\n<img width=\"850\" alt=\"image\" src=\"https://github.com/user-attachments/assets/98940012-a1d9-41c0-b5c7-fc060e745"
},
{
"path": "api/.air.toml",
"chars": 769,
"preview": "root = \".\"\ntestdata_dir = \"testdata\"\ntmp_dir = \"tmp\"\n\n[build]\n args_bin = []\n #bin = \"./tmp/main\"\n #cmd = \"go build -"
},
{
"path": "api/.github/workflows/go.yml",
"chars": 576,
"preview": "# This workflow will build a golang project\n# For more information see: https://docs.github.com/en/actions/automating-bu"
},
{
"path": "api/.gitignore",
"chars": 38,
"preview": "tmp/\nchat_backend\nenv.sh\nstatic/static"
},
{
"path": "api/.vscode/settings.json",
"chars": 135,
"preview": "{\n \"editor.fontFamily\": \"Go Mono\",\n \"go.useLanguageServer\": true,\n \"files.watcherExclude\": {\n \"**/target"
},
{
"path": "api/LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2023-2024 Hao Wu\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "api/Makefile",
"chars": 365,
"preview": ".DEFAULT_GOAL:=build\n\nfmt:\n\tgo fmt ./...\n\n.PHONY: fmt\n\nlint: fmt\n\tgolint ./...\n\n.PHONY: lint\n\nvet: fmt\n\tgo vet ./...\n\n.P"
},
{
"path": "api/README.md",
"chars": 1922,
"preview": "# architecture\n\nrequest -> mux(router) -> sql generated code -> database -> sql\n\n## library used\n\n1. sqlc to connect go "
},
{
"path": "api/admin_handler.go",
"chars": 6701,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/swuecho/chat_back"
},
{
"path": "api/ai/model.go",
"chars": 1122,
"preview": "package ai\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n)\n\ntype Role int\n\nconst (\n\tSystem Role = iota\n\tUser\n\tAssistant\n)\n\nfunc (r R"
},
{
"path": "api/artifact_instruction.txt",
"chars": 4710,
"preview": "ARTIFACT CREATION GUIDELINES - MANDATORY COMPLIANCE REQUIRED:\n\n⚠️ CRITICAL: These formatting rules are REQUIRED for art"
},
{
"path": "api/auth/auth.go",
"chars": 1865,
"preview": "package auth\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/sha256\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.c"
},
{
"path": "api/auth/auth_test.go",
"chars": 1626,
"preview": "package auth\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestGeneratePasswordHash(t *testing.T) {\n\tpassword := \"mypas"
},
{
"path": "api/auth/token.go",
"chars": 3336,
"preview": "package auth\n\nimport (\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\tjwt \"github.com"
},
{
"path": "api/auth/token_test.go",
"chars": 694,
"preview": "package auth\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestGenerateToken(t *testing.T) {\n\tuser_id := int32(0)\n\tsecret "
},
{
"path": "api/bot_answer_history_handler.go",
"chars": 7285,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/swuecho/chat_back"
},
{
"path": "api/bot_answer_history_service.go",
"chars": 4542,
"preview": "package main\n\nimport (\n\t\"context\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"github.com/swuecho/chat_backend/sqlc_queries\"\n)\n\ntype"
},
{
"path": "api/chat_artifact.go",
"chars": 6058,
"preview": "package main\n\nimport (\n\t\"regexp\"\n\t\"strings\"\n)\n\n// extractArtifacts detects and extracts artifacts from message content\nf"
},
{
"path": "api/chat_auth_user_handler.go",
"chars": 21570,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gorilla"
},
{
"path": "api/chat_auth_user_service.go",
"chars": 9381,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"githu"
},
{
"path": "api/chat_comment_handler.go",
"chars": 2684,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/sw"
},
{
"path": "api/chat_comment_service.go",
"chars": 3108,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"github.com/swuecho/chat_backend/sqlc_queries\""
},
{
"path": "api/chat_main_handler.go",
"chars": 23916,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\tlog \"github.com/sirupsen/logrus\"\n\t\""
},
{
"path": "api/chat_main_service.go",
"chars": 17519,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t_ \"embe"
},
{
"path": "api/chat_message_handler.go",
"chars": 12312,
"preview": "package main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gorilla/m"
},
{
"path": "api/chat_message_service.go",
"chars": 5399,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"github.com/swuecho/chat_ba"
},
{
"path": "api/chat_message_service_test.go",
"chars": 5563,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"testing\"\n\n\t\"github.com/swuecho/chat_backe"
},
{
"path": "api/chat_model_handler.go",
"chars": 9434,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/samber/lo"
},
{
"path": "api/chat_model_handler_test.go",
"chars": 6264,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github"
},
{
"path": "api/chat_model_privilege_handler.go",
"chars": 9025,
"preview": "package main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gor"
},
{
"path": "api/chat_prompt_hander.go",
"chars": 6598,
"preview": "package main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"gi"
},
{
"path": "api/chat_prompt_service.go",
"chars": 3472,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"github.com/swuecho/chat_backend/sqlc_querie"
},
{
"path": "api/chat_prompt_service_test.go",
"chars": 8914,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t_ \"github.com/lib/pq\"\n\t\"github.com/swuec"
},
{
"path": "api/chat_session_handler.go",
"chars": 15108,
"preview": "package main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/google/uuid\"\n\n\t\"github.com/"
},
{
"path": "api/chat_session_service.go",
"chars": 7438,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"strings\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"github.com/sambe"
},
{
"path": "api/chat_session_service_test.go",
"chars": 5501,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\n\t_ \"github.com/lib/pq\"\n\t\"github.com/swuec"
},
{
"path": "api/chat_snapshot_handler.go",
"chars": 7426,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"strconv\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/swuecho/chat_back"
},
{
"path": "api/chat_snapshot_handler_test.go",
"chars": 2184,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gori"
},
{
"path": "api/chat_snapshot_service.go",
"chars": 3475,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"log\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/samber/lo\"\n\t\"github.co"
},
{
"path": "api/chat_user_active_chat_session_handler.go",
"chars": 9839,
"preview": "package main\n\nimport (\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"regexp\"\n\n\t\"github.com/gori"
},
{
"path": "api/chat_user_active_chat_session_sevice.go",
"chars": 2788,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"github.com/rotisserie/eris\"\n\tsqlc \"github.com/swuecho/chat_backend/s"
},
{
"path": "api/chat_workspace_handler.go",
"chars": 24262,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/google/uuid\""
},
{
"path": "api/chat_workspace_service.go",
"chars": 6266,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"errors\"\n\t\"log\"\n\n\t\"github.com/google/uuid\"\n\t\"github.com/rotisserie/er"
},
{
"path": "api/constants.go",
"chars": 1611,
"preview": "// Package main provides constants used throughout the chat application.\n// This file contains all magic numbers, timeou"
},
{
"path": "api/embed_debug_test.go",
"chars": 163,
"preview": "package main\n\nimport \"testing\"\n\nfunc TestEmbedInstructions(t *testing.T) {\n\tif artifactInstructionText == \"\" {\n\t\tt.Fatal"
},
{
"path": "api/errors.go",
"chars": 15585,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings"
},
{
"path": "api/file_upload_handler.go",
"chars": 6131,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"database/sql\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strconv\"\n\t\""
},
{
"path": "api/file_upload_service.go",
"chars": 2966,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\n\t\"github.com/swuecho/chat_backend/sqlc_queries\"\n)\n\n// ChatFileService handles "
},
{
"path": "api/go.mod",
"chars": 3777,
"preview": "module github.com/swuecho/chat_backend\n\ngo 1.19\n\nrequire (\n\tgithub.com/deckarep/golang-set/v2 v2.6.0\n\tgithub.com/golang-"
},
{
"path": "api/go.sum",
"chars": 28808,
"preview": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/Azure/go-ansiterm v0.0.0-2"
},
{
"path": "api/handle_tts.go",
"chars": 1559,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n)\n\nfunc handleTTSRequest(w http.ResponseWriter, r *http.Request) {"
},
{
"path": "api/jwt_secret_service.go",
"chars": 1582,
"preview": "package main\n\n// check if jwt_secret and jwt_aud available for 'chat' in database\n// if not, create them\n\nimport (\n\t\"con"
},
{
"path": "api/llm/claude/claude.go",
"chars": 1812,
"preview": "package claude\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\tmodels \"github.com/swuecho/chat_backend/models\"\n)\n\ntype De"
},
{
"path": "api/llm/gemini/gemini.go",
"chars": 7843,
"preview": "package gemini\n\nimport (\n\tb64 \"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\n\tmaps"
},
{
"path": "api/llm/gemini/gemini_test.go",
"chars": 934,
"preview": "package gemini\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestBuildAPIURL(t *testing.T) {\n\t// Set test environment variable\n\tos."
},
{
"path": "api/llm/openai/chat.go",
"chars": 14861,
"preview": "package openai\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n)\n\n// Chat message role defined by the OpenAI API.\nconst (\n\tChatMess"
},
{
"path": "api/llm/openai/client.go",
"chars": 215,
"preview": "package openai\n\nimport \"net/http\"\n\ntype httpHeader http.Header\n\nfunc (h *httpHeader) SetHeader(header http.Header) {\n\t*h"
},
{
"path": "api/llm/openai/common.go",
"chars": 939,
"preview": "package openai\n\n// common.go defines common types used throughout the OpenAI API.\n\n// Usage Represents the total token u"
},
{
"path": "api/llm/openai/openai.go",
"chars": 2734,
"preview": "package openai\n\n// code is copied from openai go to add reasoningContent field\ntype ChatCompletionStreamChoiceDelta stru"
},
{
"path": "api/llm_openai.go",
"chars": 4322,
"preview": "package main\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\tmapset \"github"
},
{
"path": "api/llm_summary.go",
"chars": 1311,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"log\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/tmc/langchaingo/chains\"\n\t\"github.com/tmc/langc"
},
{
"path": "api/main.go",
"chars": 9394,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t_ \"embed\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"stri"
},
{
"path": "api/main_test.go",
"chars": 2334,
"preview": "package main\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t_ \"github.com/lib/pq\"\n\t\"github.com/ory/do"
},
{
"path": "api/middleware_authenticate.go",
"chars": 8121,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\tjwt \"github.com/golang-jwt/jwt/v5\"\n\t\"github.com/swuech"
},
{
"path": "api/middleware_gzip.go",
"chars": 834,
"preview": "package main\n\nimport (\n\t\"compress/gzip\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n)\n\ntype gzipResponseWriter struct {\n\tio.Writer\n\thtt"
},
{
"path": "api/middleware_lastRequestTime.go",
"chars": 340,
"preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"time\"\n)\n\n// Middleware to update lastRequest time\nfunc UpdateLastRequestTime(next h"
},
{
"path": "api/middleware_rateLimit.go",
"chars": 1676,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/swuecho/chat_backend/sqlc_queries\"\n)\n\n// This functio"
},
{
"path": "api/middleware_validation.go",
"chars": 9418,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\t\"unicode/utf"
},
{
"path": "api/model_claude3_service.go",
"chars": 8775,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"t"
},
{
"path": "api/model_completion_service.go",
"chars": 5276,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/rotisseri"
},
{
"path": "api/model_custom_service.go",
"chars": 5113,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"s"
},
{
"path": "api/model_gemini_service.go",
"chars": 7759,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"s"
},
{
"path": "api/model_ollama_service.go",
"chars": 4967,
"preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"s"
},
{
"path": "api/model_openai_service.go",
"chars": 8787,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"githu"
},
{
"path": "api/model_test_service.go",
"chars": 2248,
"preview": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/swuecho/chat_backend/models\"\n\t\"github.com/swuec"
},
{
"path": "api/models/models.go",
"chars": 904,
"preview": "package models\n\nimport (\n\t\"log\"\n\n\t\"github.com/pkoukk/tiktoken-go\"\n)\n\nfunc getTokenCount(content string) (int, error) {\n\t"
},
{
"path": "api/models.go",
"chars": 3328,
"preview": "package main\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/swuecho/chat_backend/models\"\n\t\"github.com/swuecho/chat_backend/"
},
{
"path": "api/openai_test.go",
"chars": 1174,
"preview": "package main\n\nimport \"testing\"\n\nfunc Test_getModelBaseUrl(t *testing.T) {\n\n\ttestCases := []struct {\n\t\tname string\n\t\t"
},
{
"path": "api/pre-commit.sh",
"chars": 551,
"preview": "#!/bin/bash\n\necho \"Running 'go fmt' check...\"\n\nfiles=$(git diff --cached --name-only --diff-filter=ACM \"*.go\")\n\nif [ -z "
},
{
"path": "api/sqlc/README.txt",
"chars": 4684,
"preview": "you are a golang code assistant. Given table DDL, you will write all queies for sqlc in a crud applicaiton,\n\nplease do n"
},
{
"path": "api/sqlc/queries/auth_user.sql",
"chars": 6128,
"preview": "-- name: GetAllAuthUsers :many\nSELECT * FROM auth_user ORDER BY id;\n\n-- name: ListAuthUsers :many\nSELECT * FROM auth_use"
},
{
"path": "api/sqlc/queries/auth_user_management.sql",
"chars": 278,
"preview": "-- name: GetRateLimit :one\n-- GetRateLimit retrieves the rate limit for a user from the auth_user_management table.\n-- I"
},
{
"path": "api/sqlc/queries/bot_answer_history.sql",
"chars": 2273,
"preview": "-- Bot Answer History Queries --\n\n-- name: CreateBotAnswerHistory :one\nINSERT INTO bot_answer_history (\n bot_uuid,\n "
},
{
"path": "api/sqlc/queries/chat_comment.sql",
"chars": 814,
"preview": "-- name: CreateChatComment :one\nINSERT INTO chat_comment (\n uuid,\n chat_session_uuid,\n chat_message_uuid, \n "
},
{
"path": "api/sqlc/queries/chat_file.sql",
"chars": 617,
"preview": "-- name: CreateChatFile :one\nINSERT INTO chat_file (name, data, user_id, chat_session_uuid, mime_type)\nVALUES ($1, $2, $"
},
{
"path": "api/sqlc/queries/chat_log.sql",
"chars": 427,
"preview": "-- name: ListChatLogs :many\nSELECT * FROM chat_logs ORDER BY id;\n\n-- name: ChatLogByID :one\nSELECT * FROM chat_logs WHER"
},
{
"path": "api/sqlc/queries/chat_message.sql",
"chars": 5739,
"preview": "-- name: GetAllChatMessages :many\nSELECT * FROM chat_message \nWHERE is_deleted = false\nORDER BY id;\n\n-- name: GetChatMes"
},
{
"path": "api/sqlc/queries/chat_model.sql",
"chars": 1384,
"preview": "-- name: ListChatModels :many\nSELECT * FROM chat_model ORDER BY order_number;\n\n-- name: ListSystemChatModels :many\nSELEC"
},
{
"path": "api/sqlc/queries/chat_prompt.sql",
"chars": 1826,
"preview": "-- name: GetAllChatPrompts :many\nSELECT * FROM chat_prompt \nWHERE is_deleted = false\nORDER BY id;\n\n-- name: GetChatPromp"
},
{
"path": "api/sqlc/queries/chat_session.sql",
"chars": 4589,
"preview": "-- name: GetAllChatSessions :many\nSELECT * FROM chat_session \nwhere active = true\nORDER BY id;\n\n-- name: CreateChatSessi"
},
{
"path": "api/sqlc/queries/chat_snapshot.sql",
"chars": 1702,
"preview": "-- name: ListChatSnapshots :many\nSELECT * FROM chat_snapshot ORDER BY id;\n\n-- name: ChatSnapshotByID :one\nSELECT * FROM "
},
{
"path": "api/sqlc/queries/chat_workspace.sql",
"chars": 1834,
"preview": "-- name: CreateWorkspace :one\nINSERT INTO chat_workspace (uuid, user_id, name, description, color, icon, is_default, ord"
},
{
"path": "api/sqlc/queries/jwt_secrets.sql",
"chars": 248,
"preview": "-- name: CreateJwtSecret :one\nINSERT INTO jwt_secrets (name, secret, audience)\nVALUES ($1, $2, $3) RETURNING *;\n\n-- name"
},
{
"path": "api/sqlc/queries/user_active_chat_session.sql",
"chars": 986,
"preview": "-- Simplified unified queries for active sessions\n\n-- name: UpsertUserActiveSession :one\nINSERT INTO user_active_chat_se"
},
{
"path": "api/sqlc/queries/user_chat_model_privilege.sql",
"chars": 1665,
"preview": "-- name: ListUserChatModelPrivileges :many\nSELECT * FROM user_chat_model_privilege ORDER BY id;\n\n-- name: ListUserChatMo"
},
{
"path": "api/sqlc/schema.sql",
"chars": 20087,
"preview": "CREATE TABLE IF NOT EXISTS jwt_secrets (\n id SERIAL PRIMARY KEY,\n name TEXT NOT NULL,\n secret TEXT NOT NULL,\n "
},
{
"path": "api/sqlc.yaml",
"chars": 248,
"preview": "version: \"2\"\nsql:\n - engine: \"postgresql\"\n queries: \"sqlc/queries/\"\n schema: \"sqlc/schema.sql\"\n gen:\n go:"
},
{
"path": "api/sqlc_queries/auth_user.sql.go",
"chars": 18686,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: auth_user.sql\n\npackage sqlc_queries\n\ni"
},
{
"path": "api/sqlc_queries/auth_user_management.sql.go",
"chars": 671,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: auth_user_management.sql\n\npackage sqlc"
},
{
"path": "api/sqlc_queries/bot_answer_history.sql.go",
"chars": 10160,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: bot_answer_history.sql\n\npackage sqlc_q"
},
{
"path": "api/sqlc_queries/chat_comment.sql.go",
"chars": 3862,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_comment.sql\n\npackage sqlc_queries"
},
{
"path": "api/sqlc_queries/chat_file.sql.go",
"chars": 4182,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_file.sql\n\npackage sqlc_queries\n\ni"
},
{
"path": "api/sqlc_queries/chat_log.sql.go",
"chars": 2796,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_log.sql\n\npackage sqlc_queries\n\nim"
},
{
"path": "api/sqlc_queries/chat_message.sql.go",
"chars": 24163,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_message.sql\n\npackage sqlc_queries"
},
{
"path": "api/sqlc_queries/chat_model.sql.go",
"chars": 9644,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_model.sql\n\npackage sqlc_queries\n\n"
},
{
"path": "api/sqlc_queries/chat_prompt.sql.go",
"chars": 10899,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_prompt.sql\n\npackage sqlc_queries\n"
},
{
"path": "api/sqlc_queries/chat_session.sql.go",
"chars": 24333,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_session.sql\n\npackage sqlc_queries"
},
{
"path": "api/sqlc_queries/chat_snapshot.sql.go",
"chars": 11388,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_snapshot.sql\n\npackage sqlc_querie"
},
{
"path": "api/sqlc_queries/chat_workspace.sql.go",
"chars": 9659,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: chat_workspace.sql\n\npackage sqlc_queri"
},
{
"path": "api/sqlc_queries/db.go",
"chars": 605,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n\npackage sqlc_queries\n\nimport (\n\t\"context\"\n\t\"data"
},
{
"path": "api/sqlc_queries/jwt_secrets.sql.go",
"chars": 1451,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: jwt_secrets.sql\n\npackage sqlc_queries\n"
},
{
"path": "api/sqlc_queries/models.go",
"chars": 7692,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n\npackage sqlc_queries\n\nimport (\n\t\"database/sql\"\n\t"
},
{
"path": "api/sqlc_queries/user_active_chat_session.sql.go",
"chars": 3971,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: user_active_chat_session.sql\n\npackage "
},
{
"path": "api/sqlc_queries/user_chat_model_privilege.sql.go",
"chars": 8054,
"preview": "// Code generated by sqlc. DO NOT EDIT.\n// versions:\n// sqlc v1.30.0\n// source: user_chat_model_privilege.sql\n\npackage"
},
{
"path": "api/sqlc_queries/zz_custom_method.go",
"chars": 3514,
"preview": "package sqlc_queries\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\t\"github.com/samber/lo\"\n\t\"github.com/sashabaranov/go-openai\""
},
{
"path": "api/sqlc_queries/zz_custom_query.go",
"chars": 3061,
"preview": "package sqlc_queries\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/rotisserie/eris\"\n\t\"github.com/samber/lo"
},
{
"path": "api/static/awesome-chatgpt-prompts-en.json",
"chars": 6128,
"preview": "[\n {\n \"key\": \"Act As a UX/UI Designer\",\n \"value\": \"I want you to act as a UX/UI dev"
},
{
"path": "api/static/awesome-chatgpt-prompts-zh.json",
"chars": 25440,
"preview": "[\n {\n \"key\": \"充当 Linux 终端\",\n \"value\": \"我想让你充当 Linux 终端。我将输入命令,您将回复终端应显示的内容。我希望您只在一个"
},
{
"path": "api/static/static.go",
"chars": 70,
"preview": "package static\n\nimport \"embed\"\n\n//go:embed *\nvar StaticFiles embed.FS\n"
},
{
"path": "api/streaming_helpers.go",
"chars": 3130,
"preview": "// Package main provides streaming utilities for chat responses.\n// This file contains common streaming functionality to"
},
{
"path": "api/text_buffer.go",
"chars": 894,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype textBuffer struct {\n\tbuilders []strings.Builder\n\tprefix string\n\tsuffi"
},
{
"path": "api/tools/apply_a_similar_change/README.md",
"chars": 7137,
"preview": "\nThe need for a smart diff apply\n\nidea: sometime, a change is very similar to a previous change, such as another field t"
},
{
"path": "api/tools/apply_a_similar_change/apply_diff.py",
"chars": 3701,
"preview": "import fileinput\nimport sys\n\n# Define the changeset\nchangeset = \"\"\"commit 31c4f4b48ada4b3e8495abe7dcdc41ded550a598\nAutho"
},
{
"path": "api/tools/apply_a_similar_change/apply_diff_uselib.py",
"chars": 1484,
"preview": "import os\nimport difflib\n\n# specify the paths of the original file and the diff file\noriginal_file_path = 'path/to/origi"
},
{
"path": "api/tools/apply_a_similar_change/parse_diff.py",
"chars": 2495,
"preview": "import os\nimport difflib\nimport shutil\nfrom pathlib import Path\n\n\n# Initialize variables\ndiff = []\nfile_path = \"\"\nnew_fi"
},
{
"path": "api/tools/apply_a_similar_change/parse_diff2.py",
"chars": 997,
"preview": "import difflib\nimport os\nfrom pathlib import Path\n\ndef apply_diff_file(diff_file):\n with open(diff_file, 'r') as f:\n "
},
{
"path": "api/tools/apply_a_similar_change/parse_diff3.py",
"chars": 215,
"preview": "from unidiff import PatchSet\nfrom pathlib import Path\ncurrent_dir = Path(__file__).parent\n\ndata = (current_dir/ 'tools/"
},
{
"path": "api/tools/apply_a_similar_change/stream.diff",
"chars": 1670,
"preview": "commit 31c4f4b48ada4b3e8495abe7dcdc41ded550a598\nAuthor: Hao Wu <wuhaoecho@gmail.com>\nDate: Wed Mar 22 00:11:08 2023 +0"
},
{
"path": "api/tools/fix_eris.py",
"chars": 1635,
"preview": "import os\nimport re\nimport subprocess\n\ndef search_files(dir_name, pattern):\n \"\"\"\n Search for files in a directory "
},
{
"path": "api/util.go",
"chars": 3929,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.co"
},
{
"path": "api/util_test.go",
"chars": 600,
"preview": "package main\n\nimport \"testing\"\n\nfunc Test_firstN(t *testing.T) {\n\ttype args struct {\n\t\ts string\n\t\tn int\n\t}\n\ttests := []s"
},
{
"path": "api/util_words_test.go",
"chars": 1490,
"preview": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc Test_firstNWords(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\tinpu"
},
{
"path": "artifacts.md",
"chars": 6738,
"preview": "# Artifact Feature Implementation Plan\n\n## Overview\n\nThis document outlines the implementation plan for adding artifact "
},
{
"path": "chat.code-workspace",
"chars": 142,
"preview": "{\n\t\"folders\": [\n\t\t{\n\t\t\t\"path\": \"web\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"api\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"e2e\"\n\t\t},\n\t\t{\n\t\t\t\"path\": \"docs\"\n\t\t}\n"
},
{
"path": "docker-compose.yaml",
"chars": 1723,
"preview": "version: '3'\n\nservices:\n chat:\n container_name: chat\n image: ghcr.io/swuecho/chat:latest # or echowuhao/chat:lat"
},
{
"path": "docs/add_model_en.md",
"chars": 3792,
"preview": "# Adding a New Chat Model\n\nThis guide explains how to add a new chat model to the system.\n\n## Prerequisites\n- Admin acce"
},
{
"path": "docs/add_model_zh.md",
"chars": 3097,
"preview": "# 添加新聊天模型\n\n本指南介绍如何向系统添加新的聊天模型。\n\n## 先决条件\n- 系统管理员权限\n- 要添加模型的API凭证\n- 模型的API端点URL\n\n## 添加模型的步骤\n\n### 1. 访问管理员界面\n1. 以管理员用户登录\n2."
},
{
"path": "docs/artifact_gallery_en.md",
"chars": 10566,
"preview": "# Artifact Gallery\n\n## Overview\n\nThe Artifact Gallery is a comprehensive management interface for viewing, organizing, a"
},
{
"path": "docs/artifact_gallery_zh.md",
"chars": 4578,
"preview": "# 制品画廊\n\n## 概述\n\n制品画廊是一个综合管理界面,用于查看、组织和与聊天对话中生成的代码制品进行交互。它提供了一个集中的位置来浏览、执行和管理不同聊天会话中创建的所有制品。\n\n## 功能特性\n\n### 1. **制品管理**\n- *"
},
{
"path": "docs/code_runner_artifacts_tutorial.md",
"chars": 4170,
"preview": "# Code Runner and Artifacts Tutorial\n\nThis tutorial shows how to create artifacts in chat messages, run executable code,"
},
{
"path": "docs/code_runner_capabilities.md",
"chars": 1786,
"preview": "# Code Runner Capabilities\n\nThis document summarizes what the current chat + code runner can do today.\n\n## Execution\n\n- "
},
{
"path": "docs/code_runner_csv_tutorial.md",
"chars": 2269,
"preview": "# Tutorial: Chat + File Upload + Python Artifact (Iris CSV)\n\nThis walkthrough shows how to create a chat with a system p"
},
{
"path": "docs/custom_model_api_en.md",
"chars": 2852,
"preview": "# Custom Model API (Custom Provider)\n\nThis document describes the custom model API contract used by the backend when a c"
},
{
"path": "docs/deployment_en.md",
"chars": 973,
"preview": "## How to Deploy\n\nRefer to `docker-compose.yaml`\n\n[](https://railway"
},
{
"path": "docs/deployment_zh.md",
"chars": 620,
"preview": "## 如何部署\n\n参考 `docker-compose.yaml`\n\n[](https://railway.app/template/t"
},
{
"path": "docs/dev/ERROR_HANDLING_STANDARDS.md",
"chars": 1264,
"preview": "# Error Handling Standardization Guide\n\n## Current Issues\n- Inconsistent use of `http.Error()` vs `RespondWithAPIError()"
},
{
"path": "docs/dev/INTEGRATION_GUIDE.md",
"chars": 7160,
"preview": "# 🚀 Quick Integration Guide: Add File Upload to Chat\n\nThis guide shows you exactly how to add VFS file upload to your ch"
},
{
"path": "docs/dev/code_runner_manual.md",
"chars": 13104,
"preview": "# Code Runner User Manual\n\n## Overview\n\nThe Code Runner is an interactive JavaScript execution environment built into th"
},
{
"path": "docs/dev/conversation_patch_example.js",
"chars": 4136,
"preview": "/**\n * Example patch for Conversation.vue to add VFS file upload\n * \n * This shows the minimal changes needed to add VFS"
},
{
"path": "docs/dev/conversation_vfs_integration.md",
"chars": 7920,
"preview": "# Chat Session VFS Integration\n\nThis guide shows how to add VFS file upload functionality to chat sessions so users can "
},
{
"path": "docs/dev/python_async_execution.md",
"chars": 4471,
"preview": "# Running Async Python Code in Pyodide\n\n## Overview\n\nThis document outlines the challenges and solutions for executing a"
},
{
"path": "docs/dev/sse_processing_logic.md",
"chars": 6203,
"preview": "# Server-Sent Events (SSE) Processing Logic\n\nThis document explains how the chat application handles Server-Sent Events "
},
{
"path": "docs/dev/vfs_integration_example.md",
"chars": 7441,
"preview": "# VFS Integration Example\n\nThis document shows how to integrate the Virtual File System upload functionality into the ex"
},
{
"path": "docs/dev/virtual_file_system_plan.md",
"chars": 30379,
"preview": "# Virtual File System (VFS) Implementation Plan\n\n## Overview\n\nThis document outlines the implementation plan for a Virtu"
},
{
"path": "docs/dev/virtual_file_system_usage.md",
"chars": 14530,
"preview": "# Virtual File System (VFS) User Guide\n\n## Overview\n\nThe Virtual File System (VFS) provides file I/O capabilities for bo"
},
{
"path": "docs/dev_locally_en.md",
"chars": 739,
"preview": "## Local Development Guide\n\n1. Clone the repository\n2. Golang development\n\n```bash\ncd chat; cd api\ngo install github.com"
},
{
"path": "docs/dev_locally_zh.md",
"chars": 580,
"preview": "## 本地开发指南\n\n1. 克隆仓库\n2. Golang 开发环境\n\n```bash\ncd chat; cd api\ngo install github.com/cosmtrek/air@latest\ngo mod tidy\n\n# 根据你的"
},
{
"path": "docs/ollama_en.md",
"chars": 920,
"preview": "## Using Local Ollama Models\n\n1. Install Ollama and download a model\n \n```bash\ncurl -fsSL https://ollama.com/install.s"
},
{
"path": "docs/ollama_zh.md",
"chars": 605,
"preview": "## 使用本地Ollama 模型\n\n1. 安装ollama 并下载模型\n \n```bash\ncurl -fsSL https://ollama.com/install.sh | sh\nollama pull mistral\n```\n\nl"
},
{
"path": "docs/prompts.md",
"chars": 1155,
"preview": "backend: \n\nbased on api call,\n\nschema and sqlc:\n add sqlc query in new file api/sqlc/queries/chat_comment.sql based o"
},
{
"path": "docs/snapshots_vs_chatbots_en.md",
"chars": 2269,
"preview": "# Snapshots vs ChatBots\n\n## Snapshots (Chat Records)\n\nSnapshots are static records of chat conversations. They are usefu"
},
{
"path": "docs/snapshots_vs_chatbots_zh.md",
"chars": 1422,
"preview": "# 快照(Chat Records) vs 聊天机器人(ChatBots)\n\n## 快照 (聊天记录)\n\n快照是聊天对话的静态记录。它们适用于:\n\n- 存档重要对话\n- 与他人分享聊天记录\n- 回顾和参考过去的讨论\n- 以多种格式导出对话("
},
{
"path": "docs/tool_use_code_runner.md",
"chars": 2897,
"preview": "# Tool Use with Code Runner (Lightweight Mode)\n\nThis guide explains how tool use works when **Code Runner** is enabled p"
},
{
"path": "docs/tool_use_showcase.md",
"chars": 3067,
"preview": "# Tool-Use Showcase: Chat + Code Runner (Lightweight Mode)\n\nThis doc demonstrates the current tool‑use capabilities in t"
},
{
"path": "e2e/.gitignore",
"chars": 74,
"preview": "node_modules/\n/test-results/\n/playwright-report/\n/playwright/.cache/\n.env\n"
},
{
"path": "e2e/LICENSE",
"chars": 1063,
"preview": "MIT License\n\nCopyright (c) 2023 Hao Wu\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
},
{
"path": "e2e/Makefile",
"chars": 296,
"preview": ".DEFAULT_GOAL:=test\n\n\nexport OPENAI_API_KEY=sk-KltHM7dsS8x2oL0KGJ69T3XXXX\nexport PG_HOST=192.168.0.135\nexport PG_DB=hwu\n"
},
{
"path": "e2e/lib/button-helpers.ts",
"chars": 1992,
"preview": "import { Page } from '@playwright/test';\n\n/**\n * Helper functions for interacting with footer buttons in the chat interf"
},
{
"path": "e2e/lib/chat-test-setup.ts",
"chars": 1089,
"preview": "import type { Page } from '@playwright/test'\nimport { AuthHelpers, InputHelpers, MessageHelpers } from './message-helper"
},
{
"path": "e2e/lib/db/chat_message/index.ts",
"chars": 437,
"preview": "export async function selectChatMessagesBySessionUUID(pool, sessionUUID: string) {\n const query = {\n "
},
{
"path": "e2e/lib/db/chat_model/index.ts",
"chars": 276,
"preview": "export async function selectModels(pool) {\n const query = {\n text: 'SELECT name, label, is_default"
},
{
"path": "e2e/lib/db/chat_prompt/index.ts",
"chars": 437,
"preview": "export async function selectChatPromptsBySessionUUID(pool, sessionUUID: string) {\n const query = {\n "
},
{
"path": "e2e/lib/db/chat_session/index.ts",
"chars": 405,
"preview": "export async function selectChatSessionByUserId(pool, userId: number) {\n const query = {\n text: 'S"
},
{
"path": "e2e/lib/db/chat_workspace/index.ts",
"chars": 2171,
"preview": "import { Pool } from 'pg';\n\nexport interface ChatWorkspace {\n id: number;\n uuid: string;\n user_id: number;\n "
},
{
"path": "e2e/lib/db/config.ts",
"chars": 221,
"preview": "export const db_config = {\n user: process.env.PG_USER,\n host: process.env.PG_HOST,\n database: proce"
},
{
"path": "e2e/lib/db/user/index.ts",
"chars": 472,
"preview": "export async function selectUserByEmail(pool, email: string) {\n const query = {\n text: 'SELECT id,"
},
{
"path": "e2e/lib/message-helpers.ts",
"chars": 13840,
"preview": "import { Page, Locator } from '@playwright/test';\n\n/**\n * Helper functions for interacting with chat messages in E2E tes"
},
{
"path": "e2e/lib/sample.ts",
"chars": 218,
"preview": "//generate a random email address\nexport function randomEmail() {\n const random = Math.random().toString(36).subs"
},
{
"path": "e2e/package.json",
"chars": 327,
"preview": "{\n \"name\": \"e2e\",\n \"version\": \"1.0.0\",\n \"description\": \"\",\n \"main\": \"index.js\",\n \"scripts\": {\n \"test\": \"playwrig"
},
{
"path": "e2e/playwright.config.ts",
"chars": 2770,
"preview": "import { defineConfig, devices } from '@playwright/test';\n\n/**\n * Read environment variables from file.\n * https://githu"
},
{
"path": "e2e/tests/00_chat_gpt_web.spec.ts",
"chars": 370,
"preview": "import { expect, test } from '@playwright/test'\n\ntest('redirect to /static', async ({ page }) => {\n await page.goto('/'"
},
{
"path": "e2e/tests/01_register.spec.ts",
"chars": 861,
"preview": "import { test, expect } from '@playwright/test';\n\n//generate a random email address\nfunction randomEmail() {\n const ran"
},
{
"path": "e2e/tests/02_simpe_prompt.spec.ts",
"chars": 3625,
"preview": "import { test, expect } from '@playwright/test';\nimport { Pool } from 'pg';\nimport { selectUserByEmail } from '../lib/db"
},
{
"path": "e2e/tests/03_chat_session.spec.ts",
"chars": 1548,
"preview": "import { test, expect } from '@playwright/test';\nimport { randomEmail } from '../lib/sample';\nimport { Pool } from 'pg';"
},
{
"path": "e2e/tests/04_simpe_prompt_and_message.spec.ts",
"chars": 5156,
"preview": "import { test, expect } from '@playwright/test';\nimport { Pool } from 'pg';\nimport { selectUserByEmail } from '../lib/db"
},
{
"path": "e2e/tests/05_chat_session.spec.ts",
"chars": 3214,
"preview": "import { test, expect } from '@playwright/test';\nimport { Pool } from 'pg';\nimport { selectUserByEmail } from '../lib/db"
},
{
"path": "e2e/tests/06_clear_messages.spec.ts",
"chars": 4411,
"preview": "import { test, expect } from '@playwright/test';\nimport { Pool } from 'pg';\nimport { selectUserByEmail } from '../lib/db"
},
{
"path": "e2e/tests/07_set_session_max_len.spec.ts",
"chars": 1822,
"preview": "import { test, expect } from '@playwright/test';\n\n//generate a random email address\nfunction randomEmail() {\n con"
},
{
"path": "e2e/tests/08_session_config.spec.ts",
"chars": 2169,
"preview": "import { test, expect } from '@playwright/test';\nimport { selectUserByEmail } from '../lib/db/user';\nimport { Pool } fro"
},
{
"path": "e2e/tests/09_session_answer.spec.ts",
"chars": 772,
"preview": "import { test, expect } from '@playwright/test';\nimport { randomEmail } from '../lib/sample';\nimport { setupDebugChatSes"
},
{
"path": "e2e/tests/10_session_answer_regenerate.spec.ts",
"chars": 949,
"preview": "import { test, expect } from '@playwright/test';\nimport { randomEmail } from '../lib/sample';\nimport { setupDebugChatSes"
}
]
// ... and 347 more files (download for full content)
About this extraction
This page contains the full source code of the swuecho/chat GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 547 files (23.7 MB), approximately 568.8k tokens, and a symbol index with 1780 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.