diff --git a/.devcontainer/Dockerfile.base b/.devcontainer/Dockerfile.base index 2b74602b8..dc7d4eb8c 100644 --- a/.devcontainer/Dockerfile.base +++ b/.devcontainer/Dockerfile.base @@ -77,8 +77,8 @@ RUN curl -L https://github.com/DarthSim/overmind/releases/download/v2.1.0/overmi WORKDIR /workspace RUN chown vscode:vscode /workspace -# set up node js and pnpm in single layer -RUN npm install -g pnpm@${PNPM_VERSION} \ +# set up node js, pnpm and claude code in single layer +RUN npm install -g pnpm@${PNPM_VERSION} @anthropic-ai/claude-code \ && npm cache clean --force # Switch to vscode user diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c51338b51..2e237bbcb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,17 +4,26 @@ "dockerComposeFile": "docker-compose.yml", "settings": { - "terminal.integrated.shell.linux": "/bin/zsh" + "terminal.integrated.shell.linux": "/bin/zsh", + "extensions.showRecommendationsOnlyOnDemand": true, + "editor.formatOnSave": true, + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + "search.exclude": { + "**/node_modules": true, + "**/tmp": true, + "**/log": true, + "**/coverage": true, + "**/public/packs": true + } }, // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "rebornix.Ruby", + "Shopify.ruby-lsp", "misogi.ruby-rubocop", - "wingrunr21.vscode-ruby", "davidpallinder.rails-test-runner", - "eamodio.gitlens", "github.copilot", "mrmlnc.vscode-duplicate" ], diff --git a/.devcontainer/scripts/setup.sh b/.devcontainer/scripts/setup.sh index 6beb2ff57..36db5cfd9 100755 --- a/.devcontainer/scripts/setup.sh +++ b/.devcontainer/scripts/setup.sh @@ -4,5 +4,13 @@ sed -i -e '/POSTGRES_HOST/ s/=.*/=localhost/' .env sed -i -e '/SMTP_ADDRESS/ s/=.*/=localhost/' .env sed -i -e "/FRONTEND_URL/ s/=.*/=https:\/\/$CODESPACE_NAME-3000.app.github.dev/" .env +# Setup Claude Code API key if available +if [ -n "$CLAUDE_CODE_API_KEY" ]; then + mkdir -p ~/.claude + echo '{"apiKeyHelper": "~/.claude/anthropic_key.sh"}' > ~/.claude/settings.json + echo "echo \"$CLAUDE_CODE_API_KEY\"" > ~/.claude/anthropic_key.sh + chmod +x ~/.claude/anthropic_key.sh +fi + # codespaces make the ports public gh codespace ports visibility 3000:public 3036:public 8025:public -c $CODESPACE_NAME diff --git a/Makefile b/Makefile index 1c5ce297c..552ebe659 100644 --- a/Makefile +++ b/Makefile @@ -41,8 +41,15 @@ run: force_run: rm -f ./.overmind.sock + rm -f tmp/pids/*.pid overmind start -f Procfile.dev +force_run_tunnel: + lsof -ti:3000 | xargs kill -9 2>/dev/null || true + rm -f ./.overmind.sock + rm -f tmp/pids/*.pid + overmind start -f Procfile.tunnel + debug: overmind connect backend @@ -52,4 +59,4 @@ debug_worker: docker: docker build -t $(APP_NAME) -f ./docker/Dockerfile . -.PHONY: setup db_create db_migrate db_seed db_reset db console server burn docker run force_run debug debug_worker +.PHONY: setup db_create db_migrate db_seed db_reset db console server burn docker run force_run force_run_tunnel debug debug_worker diff --git a/Procfile.tunnel b/Procfile.tunnel new file mode 100644 index 000000000..bf3541dd0 --- /dev/null +++ b/Procfile.tunnel @@ -0,0 +1,4 @@ +backend: DISABLE_MINI_PROFILER=true bin/rails s -p 3000 +# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695 +worker: dotenv bundle exec sidekiq -C config/sidekiq.yml +vite: bin/vite build --watch diff --git a/app/controllers/api/v1/accounts/contacts_controller.rb b/app/controllers/api/v1/accounts/contacts_controller.rb index b4d5e3fc1..4fbe50902 100644 --- a/app/controllers/api/v1/accounts/contacts_controller.rb +++ b/app/controllers/api/v1/accounts/contacts_controller.rb @@ -14,7 +14,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController before_action :check_authorization before_action :set_current_page, only: [:index, :active, :search, :filter] before_action :fetch_contact, only: [:show, :update, :destroy, :avatar, :contactable_inboxes, :destroy_custom_attributes] - before_action :set_include_contact_inboxes, only: [:index, :search, :filter, :show, :update] + before_action :set_include_contact_inboxes, only: [:index, :active, :search, :filter, :show, :update] def index @contacts_count = resolved_contacts.count @@ -56,7 +56,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController contacts = Current.account.contacts.where(id: ::OnlineStatusTracker .get_available_contact_ids(Current.account.id)) @contacts_count = contacts.count - @contacts = contacts.page(@current_page) + @contacts = fetch_contacts(contacts) end def show; end diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 8753918fc..e27869d82 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -124,6 +124,12 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro @conversation.save! end + def destroy + authorize @conversation, :destroy? + ::DeleteObjectJob.perform_later(@conversation, Current.user, request.ip) + head :ok + end + private def permitted_update_params diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index c610dc846..773126755 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -92,7 +92,7 @@ class Api::V1::AccountsController < Api::BaseController end def settings_params - params.permit(:auto_resolve_after, :auto_resolve_message, :auto_resolve_ignore_waiting) + params.permit(:auto_resolve_after, :auto_resolve_message, :auto_resolve_ignore_waiting, :audio_transcriptions, :auto_resolve_label) end def check_signup_enabled diff --git a/app/controllers/super_admin/account_users_controller.rb b/app/controllers/super_admin/account_users_controller.rb index b210dea19..d665b5684 100644 --- a/app/controllers/super_admin/account_users_controller.rb +++ b/app/controllers/super_admin/account_users_controller.rb @@ -2,6 +2,13 @@ class SuperAdmin::AccountUsersController < SuperAdmin::ApplicationController # Overwrite any of the RESTful controller actions to implement custom behavior # For example, you may want to send an email after a foo is updated. # + + # Since account/user page - account user role attribute links to the show page + # Handle with a redirect to the user show page + def show + redirect_to super_admin_user_path(requested_resource.user) + end + def create resource = resource_class.new(resource_params) authorize_resource(resource) diff --git a/app/javascript/dashboard/api/contacts.js b/app/javascript/dashboard/api/contacts.js index 2eee3f484..025df2122 100644 --- a/app/javascript/dashboard/api/contacts.js +++ b/app/javascript/dashboard/api/contacts.js @@ -61,6 +61,11 @@ class ContactAPI extends ApiClient { return axios.get(requestURL); } + active(page = 1, sortAttr = 'name') { + let requestURL = `${this.url}/active?${buildContactParams(page, sortAttr)}`; + return axios.get(requestURL); + } + // eslint-disable-next-line default-param-last filter(page = 1, sortAttr = 'name', queryPayload) { let requestURL = `${this.url}/filter?${buildContactParams(page, sortAttr)}`; diff --git a/app/javascript/dashboard/api/inbox/conversation.js b/app/javascript/dashboard/api/inbox/conversation.js index 39546096f..0f539bfa9 100644 --- a/app/javascript/dashboard/api/inbox/conversation.js +++ b/app/javascript/dashboard/api/inbox/conversation.js @@ -134,13 +134,13 @@ class ConversationApi extends ApiClient { return axios.get(`${this.url}/${conversationId}/attachments`); } - requestCopilot(conversationId, body) { - return axios.post(`${this.url}/${conversationId}/copilot`, body); - } - getInboxAssistant(conversationId) { return axios.get(`${this.url}/${conversationId}/inbox_assistant`); } + + delete(conversationId) { + return axios.delete(`${this.url}/${conversationId}`); + } } export default new ConversationApi(); diff --git a/app/javascript/dashboard/assets/scss/widgets/_base.scss b/app/javascript/dashboard/assets/scss/widgets/_base.scss index 9c4ccf126..afaaf91c8 100644 --- a/app/javascript/dashboard/assets/scss/widgets/_base.scss +++ b/app/javascript/dashboard/assets/scss/widgets/_base.scss @@ -101,7 +101,7 @@ select { background-image: url("data:image/svg+xml;utf8,"); background-size: 9px 6px; - @apply field-base h-10 bg-origin-content focus-visible:outline-none bg-no-repeat py-2 ltr:bg-[right_-1rem_center] rtl:bg-[left_-1rem_center] ltr:pr-6 rtl:pl-6 rtl:pr-3 ltr:pl-3; + @apply field-base h-10 bg-origin-content bg-no-repeat py-2 ltr:bg-[right_-1rem_center] rtl:bg-[left_-1rem_center] ltr:pr-6 rtl:pl-6 rtl:pr-3 ltr:pl-3; &[disabled] { @apply field-disabled; diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue index b2b0dbfa0..0932a79c7 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsCard/ContactsCard.vue @@ -17,6 +17,7 @@ const props = defineProps({ additionalAttributes: { type: Object, default: () => ({}) }, phoneNumber: { type: String, default: '' }, thumbnail: { type: String, default: '' }, + availabilityStatus: { type: String, default: null }, isExpanded: { type: Boolean, default: false }, isUpdating: { type: Boolean, default: false }, }); @@ -92,7 +93,13 @@ const onClickViewDetails = () => emit('showContact', props.id); diff --git a/app/javascript/dashboard/components/buttons/ResolveAction.vue b/app/javascript/dashboard/components/buttons/ResolveAction.vue index 4c537aaac..0866b9a7f 100644 --- a/app/javascript/dashboard/components/buttons/ResolveAction.vue +++ b/app/javascript/dashboard/components/buttons/ResolveAction.vue @@ -134,7 +134,7 @@ useEmitter(CMD_RESOLVE_CONVERSATION, onCmdResolveConversation);