Full Code of swuecho/chat for AI

master cf9af2ce0a19 cached
547 files
23.7 MB
568.8k tokens
1780 symbols
1 requests
Download .txt
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" />


![image](https://github.com/user-attachments/assets/ad38194e-dd13-4eb0-b946-81c29a37955d)


<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" />


![image](https://github.com/user-attachments/assets/5b3751e4-eaa1-4a79-b47a-9b073c63eb04)

<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(&params); 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(&params); 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(&params); 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"
Download .txt
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
Download .txt
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[![Deploy on Railway](https://railway.app/button.svg)](https://railway"
  },
  {
    "path": "docs/deployment_zh.md",
    "chars": 620,
    "preview": "## 如何部署\n\n参考 `docker-compose.yaml`\n\n[![Deploy on Railway](https://railway.app/button.svg)](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.

Copied to clipboard!