Merge from docusealco/wip
This commit is contained in:
@@ -48,6 +48,9 @@ Style/MultipleComparison:
|
|||||||
Style/NumericPredicate:
|
Style/NumericPredicate:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Style/MinMaxComparison:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Naming/PredicateMethod:
|
Naming/PredicateMethod:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -9,7 +9,7 @@ RUN apk --no-cache add fontforge wget && \
|
|||||||
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
|
wget https://cdn.jsdelivr.net/gh/notofonts/notofonts.github.io/fonts/NotoSansSymbols2/hinted/ttf/NotoSansSymbols2-Regular.ttf && \
|
||||||
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
|
wget https://github.com/Maxattax97/gnu-freefont/raw/master/ttf/FreeSans.ttf && \
|
||||||
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
|
wget https://github.com/impallari/DancingScript/raw/master/OFL.txt && \
|
||||||
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.0.0/model_704_int8.onnx" && \
|
wget -O /model.onnx "https://github.com/docusealco/fields-detection/releases/download/2.1.0/model_704_int8.onnx" && \
|
||||||
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
|
wget -O pdfium-linux.tgz "https://github.com/docusealco/pdfium-binaries/releases/latest/download/pdfium-linux-$(uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/').tgz" && \
|
||||||
mkdir -p /pdfium-linux && \
|
mkdir -p /pdfium-linux && \
|
||||||
tar -xzf pdfium-linux.tgz -C /pdfium-linux
|
tar -xzf pdfium-linux.tgz -C /pdfium-linux
|
||||||
@@ -53,6 +53,8 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
RUN apk add --no-cache sqlite-dev libpq-dev mariadb-dev vips-dev yaml-dev redis libheif vips-heif gcompat ttf-freefont && mkdir /fonts && rm /usr/share/fonts/freefont/FreeSans.otf
|
||||||
|
|
||||||
|
RUN addgroup -g 2000 docuseal && adduser -u 2000 -G docuseal -s /bin/sh -D -h /home/docuseal docuseal
|
||||||
|
|
||||||
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
RUN echo $'.include = /etc/ssl/openssl.cnf\n\
|
||||||
\n\
|
\n\
|
||||||
[provider_sect]\n\
|
[provider_sect]\n\
|
||||||
@@ -92,7 +94,10 @@ COPY --from=webpack /app/public/packs ./public/packs
|
|||||||
RUN ln -s /fonts /app/public/fonts
|
RUN ln -s /fonts /app/public/fonts
|
||||||
RUN bundle exec bootsnap precompile -j 1 --gemfile app/ lib/
|
RUN bundle exec bootsnap precompile -j 1 --gemfile app/ lib/
|
||||||
|
|
||||||
|
RUN chown -R docuseal:docuseal /app
|
||||||
|
|
||||||
WORKDIR /data/docuseal
|
WORKDIR /data/docuseal
|
||||||
|
ENV HOME=/home/docuseal
|
||||||
ENV WORKDIR=/data/docuseal
|
ENV WORKDIR=/data/docuseal
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -5,34 +5,17 @@ module Api
|
|||||||
load_and_authorize_resource :submission
|
load_and_authorize_resource :submission
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
is_merge = params[:merge] == 'true' &&
|
||||||
|
(@submission.schema_documents || @submission.template.schema_documents).size > 1
|
||||||
|
|
||||||
documents =
|
documents =
|
||||||
if @submission.submitters.all?(&:completed_at?)
|
if @submission.submitters.all?(&:completed_at?)
|
||||||
last_submitter = @submission.submitters.max_by(&:completed_at)
|
build_completed_documents(@submission, merge: is_merge)
|
||||||
|
|
||||||
if last_submitter.documents_attachments.blank?
|
|
||||||
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(last_submitter)
|
|
||||||
end
|
|
||||||
|
|
||||||
last_submitter.documents_attachments
|
|
||||||
else
|
else
|
||||||
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(@submission)
|
build_preview_documents(@submission, merge: is_merge)
|
||||||
|
|
||||||
if @submission.preview_documents.present? &&
|
|
||||||
@submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
|
|
||||||
@submission.preview_documents
|
|
||||||
else
|
|
||||||
ApplicationRecord.no_touching do
|
|
||||||
@submission.preview_documents.each(&:destroy)
|
|
||||||
end
|
|
||||||
|
|
||||||
Submissions::GeneratePreviewAttachments.call(@submission, values_hash:)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
ActiveRecord::Associations::Preloader.new(
|
ActiveRecord::Associations::Preloader.new(records: documents, associations: [:blob]).call
|
||||||
records: documents,
|
|
||||||
associations: [:blob]
|
|
||||||
).call
|
|
||||||
|
|
||||||
expires_at = Accounts.link_expires_at(current_account)
|
expires_at = Accounts.link_expires_at(current_account)
|
||||||
|
|
||||||
@@ -43,5 +26,50 @@ module Api
|
|||||||
end
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def build_completed_documents(submission, merge: false)
|
||||||
|
last_submitter = submission.submitters.max_by(&:completed_at)
|
||||||
|
|
||||||
|
if merge
|
||||||
|
if submission.merged_document_attachment.blank?
|
||||||
|
submission.merged_document_attachment =
|
||||||
|
Submissions::GenerateCombinedAttachment.call(last_submitter, with_audit: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
[submission.merged_document_attachment]
|
||||||
|
else
|
||||||
|
if last_submitter.documents_attachments.blank?
|
||||||
|
last_submitter.documents_attachments = Submissions::EnsureResultGenerated.call(last_submitter)
|
||||||
|
end
|
||||||
|
|
||||||
|
last_submitter.documents_attachments
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_preview_documents(submission, merge: false)
|
||||||
|
values_hash = Submissions::GeneratePreviewAttachments.build_values_hash(submission)
|
||||||
|
|
||||||
|
if merge
|
||||||
|
if submission.preview_merged_document_attachment.present? &&
|
||||||
|
submission.preview_merged_document_attachment.metadata['values_hash'] == values_hash
|
||||||
|
[submission.preview_merged_document_attachment]
|
||||||
|
else
|
||||||
|
ApplicationRecord.no_touching { submission.preview_merged_document_attachment&.destroy }
|
||||||
|
|
||||||
|
Submissions::GeneratePreviewAttachments.call(submission, values_hash:, merge: true)
|
||||||
|
end
|
||||||
|
elsif submission.preview_documents.present? &&
|
||||||
|
submission.preview_documents.all? { |s| s.metadata['values_hash'] == values_hash }
|
||||||
|
submission.preview_documents
|
||||||
|
else
|
||||||
|
ApplicationRecord.no_touching do
|
||||||
|
submission.preview_documents.each(&:destroy)
|
||||||
|
end
|
||||||
|
|
||||||
|
Submissions::GeneratePreviewAttachments.call(submission, values_hash:)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class ApplicationController < ActionController::Base
|
|||||||
|
|
||||||
helper_method :button_title,
|
helper_method :button_title,
|
||||||
:current_account,
|
:current_account,
|
||||||
|
:true_ability,
|
||||||
:form_link_host,
|
:form_link_host,
|
||||||
:svg_icon
|
:svg_icon
|
||||||
|
|
||||||
@@ -102,6 +103,10 @@ class ApplicationController < ActionController::Base
|
|||||||
current_user&.account
|
current_user&.account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def true_ability
|
||||||
|
@true_ability ||= Ability.new(true_user)
|
||||||
|
end
|
||||||
|
|
||||||
def maybe_redirect_to_setup
|
def maybe_redirect_to_setup
|
||||||
redirect_to setup_index_path unless User.exists?
|
redirect_to setup_index_path unless User.exists?
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TemplatesCloneController < ApplicationController
|
||||||
|
load_and_authorize_resource :template, instance_name: :base_template
|
||||||
|
|
||||||
|
def new
|
||||||
|
authorize!(:create, Template)
|
||||||
|
|
||||||
|
@template = Template.new(name: "#{@base_template.name} (#{I18n.t('clone')})")
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
ActiveRecord::Associations::Preloader.new(
|
||||||
|
records: [@base_template],
|
||||||
|
associations: [schema_documents: :preview_images_attachments]
|
||||||
|
).call
|
||||||
|
|
||||||
|
@template = Templates::Clone.call(@base_template, author: current_user,
|
||||||
|
name: params.dig(:template, :name),
|
||||||
|
folder_name: params[:folder_name])
|
||||||
|
|
||||||
|
authorize!(:create, @template)
|
||||||
|
|
||||||
|
if params[:account_id].present? && true_ability.authorize!(:manage, Account.find(params[:account_id]))
|
||||||
|
@template.account_id = params[:account_id]
|
||||||
|
@template.author = true_user if true_user.account_id == @template.account_id
|
||||||
|
@template.folder = @template.account.default_template_folder if @template.account_id != current_account.id
|
||||||
|
else
|
||||||
|
@template.account = current_account
|
||||||
|
end
|
||||||
|
|
||||||
|
Templates.maybe_assign_access(@template)
|
||||||
|
|
||||||
|
if @template.save
|
||||||
|
Templates::CloneAttachments.call(template: @template, original_template: @base_template)
|
||||||
|
|
||||||
|
SearchEntries.enqueue_reindex(@template)
|
||||||
|
|
||||||
|
WebhookUrls.enqueue_events(@template, 'template.created')
|
||||||
|
|
||||||
|
maybe_redirect_to_template(@template)
|
||||||
|
else
|
||||||
|
render turbo_stream: turbo_stream.replace(:modal, partial: 'templates_clone/form'), status: :unprocessable_content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def maybe_redirect_to_template(template)
|
||||||
|
if template.account == current_account
|
||||||
|
redirect_to(edit_template_path(template))
|
||||||
|
else
|
||||||
|
redirect_back(fallback_location: root_path, notice: I18n.t('template_has_been_cloned'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,8 +3,6 @@
|
|||||||
class TemplatesController < ApplicationController
|
class TemplatesController < ApplicationController
|
||||||
load_and_authorize_resource :template
|
load_and_authorize_resource :template
|
||||||
|
|
||||||
before_action :load_base_template, only: %i[new create]
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
submissions = @template.submissions.accessible_by(current_ability)
|
submissions = @template.submissions.accessible_by(current_ability)
|
||||||
submissions = submissions.active if @template.archived_at.blank?
|
submissions = submissions.active if @template.archived_at.blank?
|
||||||
@@ -26,9 +24,7 @@ class TemplatesController < ApplicationController
|
|||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new; end
|
||||||
@template.name = "#{@base_template.name} (#{I18n.t('clone')})" if @base_template
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
ActiveRecord::Associations::Preloader.new(
|
ActiveRecord::Associations::Preloader.new(
|
||||||
@@ -48,37 +44,18 @@ class TemplatesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
if @base_template
|
@template.author = current_user
|
||||||
ActiveRecord::Associations::Preloader.new(
|
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
||||||
records: [@base_template],
|
@template.account = current_account
|
||||||
associations: [schema_documents: :preview_images_attachments]
|
|
||||||
).call
|
|
||||||
|
|
||||||
@template = Templates::Clone.call(@base_template, author: current_user,
|
|
||||||
name: params.dig(:template, :name),
|
|
||||||
folder_name: params[:folder_name])
|
|
||||||
else
|
|
||||||
@template.author = current_user
|
|
||||||
@template.folder = TemplateFolders.find_or_create_by_name(current_user, params[:folder_name])
|
|
||||||
end
|
|
||||||
|
|
||||||
if params[:account_id].present? && authorized_clone_account_id?(params[:account_id])
|
|
||||||
@template.account_id = params[:account_id]
|
|
||||||
@template.folder = @template.account.default_template_folder if @template.account_id != current_account.id
|
|
||||||
else
|
|
||||||
@template.account = current_account
|
|
||||||
end
|
|
||||||
|
|
||||||
Templates.maybe_assign_access(@template)
|
Templates.maybe_assign_access(@template)
|
||||||
|
|
||||||
if @template.save
|
if @template.save
|
||||||
Templates::CloneAttachments.call(template: @template, original_template: @base_template) if @base_template
|
|
||||||
|
|
||||||
SearchEntries.enqueue_reindex(@template)
|
SearchEntries.enqueue_reindex(@template)
|
||||||
|
|
||||||
WebhookUrls.enqueue_events(@template, 'template.created')
|
WebhookUrls.enqueue_events(@template, 'template.created')
|
||||||
|
|
||||||
maybe_redirect_to_template(@template)
|
redirect_to(edit_template_path(@template))
|
||||||
else
|
else
|
||||||
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_content
|
render turbo_stream: turbo_stream.replace(:modal, template: 'templates/new'), status: :unprocessable_content
|
||||||
end
|
end
|
||||||
@@ -132,23 +109,4 @@ class TemplatesController < ApplicationController
|
|||||||
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
areas: [%i[x y w h cell_w attachment_uuid option_uuid page]] }]] }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized_clone_account_id?(account_id)
|
|
||||||
true_user.account_id.to_s == account_id.to_s ||
|
|
||||||
true_user.account.linked_accounts.accessible_by(current_ability).exists?(id: account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def maybe_redirect_to_template(template)
|
|
||||||
if template.account == current_account
|
|
||||||
redirect_to(edit_template_path(@template))
|
|
||||||
else
|
|
||||||
redirect_back(fallback_location: root_path, notice: I18n.t('template_has_been_cloned'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_base_template
|
|
||||||
return if params[:base_template_id].blank?
|
|
||||||
|
|
||||||
@base_template = Template.accessible_by(current_ability).find_by(id: params[:base_template_id])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -461,7 +461,7 @@ export default {
|
|||||||
phone: IconPhoneCheck,
|
phone: IconPhoneCheck,
|
||||||
payment: IconCreditCard,
|
payment: IconCreditCard,
|
||||||
verification: IconId,
|
verification: IconId,
|
||||||
kba: IconUserScan,
|
kba: IconUserScan
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
image () {
|
image () {
|
||||||
|
|||||||
@@ -10,9 +10,15 @@
|
|||||||
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
|
<template v-else>{{ field.name || 'Knowledge Based Authentication' }}</template>
|
||||||
<span
|
<span
|
||||||
v-if="questions"
|
v-if="questions"
|
||||||
class="float-right text-base font-normal text-neutral-500 mt-1 whitespace-nowrap"
|
class="float-right font-normal text-neutral-500 mt-1 whitespace-nowrap text-right text-sm"
|
||||||
>
|
>
|
||||||
Question {{ currentQuestionIndex + 1 }} / {{ questions.length }}
|
<span>Question {{ currentQuestionIndex + 1 }} / {{ questions.length }}</span>
|
||||||
|
<span
|
||||||
|
v-if="timeLeftSeconds !== null"
|
||||||
|
class="block"
|
||||||
|
>
|
||||||
|
Time left: {{ formattedTimeLeft }}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
@@ -54,7 +60,9 @@
|
|||||||
<div v-else-if="questions && !error">
|
<div v-else-if="questions && !error">
|
||||||
<form @submit.prevent="nextQuestion">
|
<form @submit.prevent="nextQuestion">
|
||||||
<div class="mb-6 px-1">
|
<div class="mb-6 px-1">
|
||||||
<p class="font-semibold mb-4 text-lg">{{ currentQuestion.prompt }}</p>
|
<p class="font-semibold mb-4 text-lg">
|
||||||
|
{{ currentQuestion.prompt }}
|
||||||
|
</p>
|
||||||
<div class="space-y-3.5 mx-auto">
|
<div class="space-y-3.5 mx-auto">
|
||||||
<div
|
<div
|
||||||
v-for="(answer, index) in currentQuestion.answers"
|
v-for="(answer, index) in currentQuestion.answers"
|
||||||
@@ -316,6 +324,8 @@ export default {
|
|||||||
reference: null,
|
reference: null,
|
||||||
answers: {},
|
answers: {},
|
||||||
error: null,
|
error: null,
|
||||||
|
timeLeftSeconds: null,
|
||||||
|
countdownIntervalId: null,
|
||||||
form: {
|
form: {
|
||||||
fn: '',
|
fn: '',
|
||||||
ln: '',
|
ln: '',
|
||||||
@@ -337,6 +347,9 @@ export default {
|
|||||||
isRequiredFieldEmpty () {
|
isRequiredFieldEmpty () {
|
||||||
return this.emptyValueRequiredStep && this.emptyValueRequiredStep[0] !== this.field
|
return this.emptyValueRequiredStep && this.emptyValueRequiredStep[0] !== this.field
|
||||||
},
|
},
|
||||||
|
kbaTimeLimitSeconds () {
|
||||||
|
return 90
|
||||||
|
},
|
||||||
states () {
|
states () {
|
||||||
return [
|
return [
|
||||||
{ code: 'AL', name: 'Alabama' },
|
{ code: 'AL', name: 'Alabama' },
|
||||||
@@ -394,9 +407,53 @@ export default {
|
|||||||
},
|
},
|
||||||
isLastQuestion () {
|
isLastQuestion () {
|
||||||
return this.questions && this.currentQuestionIndex === this.questions.length - 1
|
return this.questions && this.currentQuestionIndex === this.questions.length - 1
|
||||||
|
},
|
||||||
|
formattedTimeLeft () {
|
||||||
|
if (this.timeLeftSeconds === null) return ''
|
||||||
|
|
||||||
|
const minutes = Math.floor(this.timeLeftSeconds / 60)
|
||||||
|
const seconds = this.timeLeftSeconds % 60
|
||||||
|
|
||||||
|
return `${minutes}:${String(seconds).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
beforeUnmount () {
|
||||||
|
this.clearCountdown()
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
clearCountdown () {
|
||||||
|
if (this.countdownIntervalId) {
|
||||||
|
clearInterval(this.countdownIntervalId)
|
||||||
|
this.countdownIntervalId = null
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeLeftSeconds = null
|
||||||
|
},
|
||||||
|
startCountdown () {
|
||||||
|
this.clearCountdown()
|
||||||
|
|
||||||
|
this.timeLeftSeconds = this.kbaTimeLimitSeconds
|
||||||
|
|
||||||
|
this.countdownIntervalId = setInterval(() => {
|
||||||
|
if (this.timeLeftSeconds === null) return
|
||||||
|
|
||||||
|
this.timeLeftSeconds -= 1
|
||||||
|
|
||||||
|
if (this.timeLeftSeconds <= 0) {
|
||||||
|
this.handleTimeout()
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
},
|
||||||
|
handleTimeout () {
|
||||||
|
this.clearCountdown()
|
||||||
|
|
||||||
|
this.questions = null
|
||||||
|
this.token = null
|
||||||
|
this.reference = null
|
||||||
|
this.answers = {}
|
||||||
|
this.currentQuestionIndex = 0
|
||||||
|
this.error = `Knowledge Based Authentication timed out. You only have ${this.kbaTimeLimitSeconds} seconds to complete the verification. Please retry.`
|
||||||
|
},
|
||||||
nextQuestion () {
|
nextQuestion () {
|
||||||
if (this.isLastQuestion) {
|
if (this.isLastQuestion) {
|
||||||
this.$emit('submit')
|
this.$emit('submit')
|
||||||
@@ -405,6 +462,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
restartKba () {
|
restartKba () {
|
||||||
|
this.clearCountdown()
|
||||||
|
|
||||||
this.questions = null
|
this.questions = null
|
||||||
this.token = null
|
this.token = null
|
||||||
this.reference = null
|
this.reference = null
|
||||||
@@ -415,6 +474,7 @@ export default {
|
|||||||
async startKba () {
|
async startKba () {
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
this.error = null
|
this.error = null
|
||||||
|
this.clearCountdown()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const payload = { ...this.form, submitter_slug: this.submitterSlug }
|
const payload = { ...this.form, submitter_slug: this.submitterSlug }
|
||||||
@@ -457,6 +517,8 @@ export default {
|
|||||||
this.questions.forEach(q => {
|
this.questions.forEach(q => {
|
||||||
this.answers[q.id] = null
|
this.answers[q.id] = null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.startCountdown()
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid KBA response')
|
throw new Error('Invalid KBA response')
|
||||||
}
|
}
|
||||||
@@ -467,6 +529,8 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async submit () {
|
async submit () {
|
||||||
|
this.clearCountdown()
|
||||||
|
|
||||||
this.isSubmitting = true
|
this.isSubmitting = true
|
||||||
this.error = null
|
this.error = null
|
||||||
|
|
||||||
|
|||||||
@@ -1116,14 +1116,17 @@ export default {
|
|||||||
const sortArea = (aArea, bArea) => {
|
const sortArea = (aArea, bArea) => {
|
||||||
if (aArea.attachment_uuid === bArea.attachment_uuid) {
|
if (aArea.attachment_uuid === bArea.attachment_uuid) {
|
||||||
if (aArea.page === bArea.page) {
|
if (aArea.page === bArea.page) {
|
||||||
if (Math.abs(aArea.y - bArea.y) < 0.01) {
|
const aY = aArea.y + aArea.h
|
||||||
|
const bY = bArea.y + bArea.h
|
||||||
|
|
||||||
|
if (Math.abs(aY - bY) < 0.01 || (aArea.h < bArea.h ? (aArea.y >= bArea.y && aY <= bY) : (bArea.y >= aArea.y && bY <= aY))) {
|
||||||
if (aArea.x === bArea.x) {
|
if (aArea.x === bArea.x) {
|
||||||
return 0
|
return 0
|
||||||
} else {
|
} else {
|
||||||
return aArea.x - bArea.x
|
return aArea.x - bArea.x
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return aArea.y - bArea.y
|
return (aArea.y + aArea.h) - (bArea.y + bArea.h)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return aArea.page - bArea.page
|
return aArea.page - bArea.page
|
||||||
@@ -1173,6 +1176,72 @@ export default {
|
|||||||
this.save()
|
this.save()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
findFieldInsertIndex (field) {
|
||||||
|
if (!field.areas?.length) return -1
|
||||||
|
|
||||||
|
const area = field.areas[0]
|
||||||
|
|
||||||
|
const attachmentUuidsIndex = this.template.schema.reduce((acc, e, index) => {
|
||||||
|
acc[e.attachment_uuid] = index
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
const compareAreas = (a, b) => {
|
||||||
|
const aAttIdx = attachmentUuidsIndex[a.attachment_uuid]
|
||||||
|
const bAttIdx = attachmentUuidsIndex[b.attachment_uuid]
|
||||||
|
|
||||||
|
if (aAttIdx !== bAttIdx) return aAttIdx - bAttIdx
|
||||||
|
if (a.page !== b.page) return a.page - b.page
|
||||||
|
|
||||||
|
const aY = a.y + a.h
|
||||||
|
const bY = b.y + b.h
|
||||||
|
|
||||||
|
if (Math.abs(aY - bY) < 0.01) return a.x - b.x
|
||||||
|
if (a.h < b.h ? a.y >= b.y && aY <= bY : b.y >= a.y && bY <= aY) return a.x - b.x
|
||||||
|
|
||||||
|
return aY - bY
|
||||||
|
}
|
||||||
|
|
||||||
|
let closestBeforeIndex = -1
|
||||||
|
let closestBeforeArea = null
|
||||||
|
let closestAfterIndex = -1
|
||||||
|
let closestAfterArea = null
|
||||||
|
|
||||||
|
this.template.fields.forEach((f, index) => {
|
||||||
|
if (f.submitter_uuid === field.submitter_uuid) {
|
||||||
|
(f.areas || []).forEach((a) => {
|
||||||
|
const cmp = compareAreas(a, area)
|
||||||
|
|
||||||
|
if (cmp < 0) {
|
||||||
|
if (!closestBeforeArea || (compareAreas(a, closestBeforeArea) > 0 && closestBeforeIndex < index)) {
|
||||||
|
closestBeforeIndex = index
|
||||||
|
closestBeforeArea = a
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!closestAfterArea || (compareAreas(a, closestAfterArea) < 0 && closestAfterIndex > index)) {
|
||||||
|
closestAfterIndex = index
|
||||||
|
closestAfterArea = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (closestBeforeIndex !== -1) return closestBeforeIndex + 1
|
||||||
|
if (closestAfterIndex !== -1) return closestAfterIndex
|
||||||
|
|
||||||
|
return -1
|
||||||
|
},
|
||||||
|
insertField (field) {
|
||||||
|
const insertIndex = this.findFieldInsertIndex(field)
|
||||||
|
|
||||||
|
if (insertIndex !== -1) {
|
||||||
|
this.template.fields.splice(insertIndex, 0, field)
|
||||||
|
} else {
|
||||||
|
this.template.fields.push(field)
|
||||||
|
}
|
||||||
|
},
|
||||||
closeDropdown () {
|
closeDropdown () {
|
||||||
document.activeElement.blur()
|
document.activeElement.blur()
|
||||||
},
|
},
|
||||||
@@ -1226,7 +1295,7 @@ export default {
|
|||||||
field.preferences.with_signature_id = this.withSignatureId
|
field.preferences.with_signature_id = this.withSignatureId
|
||||||
}
|
}
|
||||||
|
|
||||||
this.template.fields.push(field)
|
this.insertField(field)
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
},
|
},
|
||||||
@@ -1455,11 +1524,13 @@ export default {
|
|||||||
|
|
||||||
field.areas.push(area)
|
field.areas.push(area)
|
||||||
} else {
|
} else {
|
||||||
this.template.fields.push({
|
const newField = {
|
||||||
...JSON.parse(JSON.stringify(field)),
|
...JSON.parse(JSON.stringify(field)),
|
||||||
uuid: v4(),
|
uuid: v4(),
|
||||||
areas: [area]
|
areas: [area]
|
||||||
})
|
}
|
||||||
|
|
||||||
|
this.insertField(newField)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.selectedAreaRef.value = area
|
this.selectedAreaRef.value = area
|
||||||
@@ -1482,7 +1553,7 @@ export default {
|
|||||||
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
const documentRef = this.documentRefs.find((e) => e.document.uuid === area.attachment_uuid)
|
||||||
const pageMask = documentRef.pageRefs[area.page].$refs.mask
|
const pageMask = documentRef.pageRefs[area.page].$refs.mask
|
||||||
|
|
||||||
if (type === 'checkbox') {
|
if (type === 'checkbox' || type === 'radio' || type === 'multiple') {
|
||||||
area.w = pageMask.clientWidth / 30 / pageMask.clientWidth
|
area.w = pageMask.clientWidth / 30 / pageMask.clientWidth
|
||||||
area.h = (pageMask.clientWidth / 30 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
|
area.h = (pageMask.clientWidth / 30 / pageMask.clientWidth) * (pageMask.clientWidth / pageMask.clientHeight)
|
||||||
} else if (type === 'image') {
|
} else if (type === 'image') {
|
||||||
@@ -1544,7 +1615,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.template.fields.indexOf(this.drawField) === -1) {
|
if (this.template.fields.indexOf(this.drawField) === -1) {
|
||||||
this.template.fields.push(this.drawField)
|
this.insertField(this.drawField)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.drawField = null
|
this.drawField = null
|
||||||
@@ -1572,8 +1643,8 @@ export default {
|
|||||||
const previousArea = previousField?.areas?.[previousField.areas.length - 1]
|
const previousArea = previousField?.areas?.[previousField.areas.length - 1]
|
||||||
|
|
||||||
if (previousArea || area.w) {
|
if (previousArea || area.w) {
|
||||||
const areaW = previousArea?.w || (30 / pageMask.clientWidth)
|
const areaW = previousArea?.w || area.w || (30 / pageMask.clientWidth)
|
||||||
const areaH = previousArea?.h || (30 / pageMask.clientHeight)
|
const areaH = previousArea?.h || area.h || (30 / pageMask.clientHeight)
|
||||||
|
|
||||||
if ((pageMask.clientWidth * area.w) < 5) {
|
if ((pageMask.clientWidth * area.w) < 5) {
|
||||||
area.x = area.x - (areaW / 2)
|
area.x = area.x - (areaW / 2)
|
||||||
@@ -1683,7 +1754,7 @@ export default {
|
|||||||
this.selectedAreaRef.value = fieldArea
|
this.selectedAreaRef.value = fieldArea
|
||||||
|
|
||||||
if (this.template.fields.indexOf(field) === -1) {
|
if (this.template.fields.indexOf(field) === -1) {
|
||||||
this.template.fields.push(field)
|
this.insertField(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.save()
|
this.save()
|
||||||
@@ -1713,7 +1784,7 @@ export default {
|
|||||||
} else if (previousField?.areas?.length) {
|
} else if (previousField?.areas?.length) {
|
||||||
baseArea = previousField.areas[previousField.areas.length - 1]
|
baseArea = previousField.areas[previousField.areas.length - 1]
|
||||||
} else {
|
} else {
|
||||||
if (['checkbox'].includes(fieldType)) {
|
if (['checkbox', 'radio', 'multiple'].includes(fieldType)) {
|
||||||
baseArea = {
|
baseArea = {
|
||||||
w: area.maskW / 30 / area.maskW,
|
w: area.maskW / 30 / area.maskW,
|
||||||
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
|
h: area.maskW / 30 / area.maskW * (area.maskW / area.maskH)
|
||||||
@@ -1845,7 +1916,7 @@ export default {
|
|||||||
attachment.metadata.pdf.fields.forEach((field) => {
|
attachment.metadata.pdf.fields.forEach((field) => {
|
||||||
field.submitter_uuid = this.selectedSubmitter.uuid
|
field.submitter_uuid = this.selectedSubmitter.uuid
|
||||||
|
|
||||||
this.template.fields.push(field)
|
this.insertField(field)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export default {
|
|||||||
payment: IconCreditCard,
|
payment: IconCreditCard,
|
||||||
phone: IconPhoneCheck,
|
phone: IconPhoneCheck,
|
||||||
verification: IconId,
|
verification: IconId,
|
||||||
kba: IconUserScan,
|
kba: IconUserScan
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
skipTypes () {
|
skipTypes () {
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ class Submission < ApplicationRecord
|
|||||||
|
|
||||||
has_one_attached :audit_trail
|
has_one_attached :audit_trail
|
||||||
has_one_attached :combined_document
|
has_one_attached :combined_document
|
||||||
|
has_one_attached :merged_document
|
||||||
|
has_one_attached :preview_merged_document
|
||||||
|
|
||||||
has_many_attached :preview_documents
|
has_many_attached :preview_documents
|
||||||
has_many_attached :documents
|
has_many_attached :documents
|
||||||
|
|||||||
@@ -235,7 +235,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:manage, :personalization_advanced) %>
|
<% if !Docuseal.multitenant? || can?(:manage, :personalization_advanced) %>
|
||||||
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::WITH_FILE_LINKS_KEY) %>
|
<% account_config = AccountConfig.find_or_initialize_by(account: current_account, key: AccountConfig::WITH_FILE_LINKS_KEY) %>
|
||||||
<% if can?(:manage, account_config) %>
|
<% if can?(:manage, account_config) %>
|
||||||
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
<%= form_for account_config, url: account_configs_path, method: :post do |f| %>
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
<%= form_for @template, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
<%= form_for @template, data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||||
<% if @base_template %>
|
|
||||||
<%= hidden_field_tag :base_template_id, @base_template.id %>
|
|
||||||
<% end %>
|
|
||||||
<% if @base_template && (can?(:manage, :tenants) || true_user != current_user) && true_user.account.linked_accounts.active.accessible_by(current_ability).exists? %>
|
|
||||||
<div class="form-control -mb-2 mt-2">
|
|
||||||
<%= select_tag :account_id, options_for_select([true_user.account, *true_user.account.linked_accounts.active.accessible_by(current_ability)].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="form-control mt-4">
|
<div class="form-control mt-4">
|
||||||
<%= f.text_field :name, required: true, placeholder: t('document_name'), class: 'base-input', dir: 'auto' %>
|
<%= f.text_field :name, required: true, placeholder: t('document_name'), class: 'base-input', dir: 'auto' %>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,7 +8,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<folder-autocomplete class="flex justify-between w-full">
|
<folder-autocomplete class="flex justify-between w-full">
|
||||||
<set-value data-on="blur" data-value="<%= TemplateFolder::DEFAULT_NAME %>" data-empty-only="true" class="peer w-full whitespace-nowrap">
|
<set-value data-on="blur" data-value="<%= TemplateFolder::DEFAULT_NAME %>" data-empty-only="true" class="peer w-full whitespace-nowrap">
|
||||||
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.full_name || TemplateFolder::DEFAULT_NAME %>" autocomplete="off">
|
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1" name="folder_name" value="<%= params[:folder_name].presence || TemplateFolder::DEFAULT_NAME %>" autocomplete="off">
|
||||||
</set-value>
|
</set-value>
|
||||||
<set-value data-on="click" data-value="" data-input-id="folder_name" class="peer-focus-within:hidden whitespace-nowrap">
|
<set-value data-on="click" data-value="" data-input-id="folder_name" class="peer-focus-within:hidden whitespace-nowrap">
|
||||||
<label for="folder_name" data-clear-on-focus="true" class="shrink-0 link mr-1.5 cursor-pointer">
|
<label for="folder_name" data-clear-on-focus="true" class="shrink-0 link mr-1.5 cursor-pointer">
|
||||||
@@ -26,6 +18,6 @@
|
|||||||
</folder-autocomplete>
|
</folder-autocomplete>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-control">
|
<div class="form-control">
|
||||||
<%= f.button button_title(title: @base_template ? t('submit') : t('create'), disabled_with: t('creating')), class: 'base-button' %>
|
<%= f.button button_title(title: t('create'), disabled_with: t('creating')), class: 'base-button' %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, template) %>
|
<% if can?(:create, template) %>
|
||||||
<span class="tooltip tooltip-left" data-tip="<%= t('clone') %>">
|
<span class="tooltip tooltip-left" data-tip="<%= t('clone') %>">
|
||||||
<a href="<%= new_template_path(base_template_id: template.id) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
<a href="<%= new_template_clone_path(template) %>" data-turbo-frame="modal" class="btn btn-xs hover:btn-outline bg-base-200 btn-circle">
|
||||||
<%= svg_icon('copy', class: 'w-4 h-4') %>
|
<%= svg_icon('copy', class: 'w-4 h-4') %>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving'), title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete, data: { turbo_confirm: t('are_you_sure_') } %>
|
<%= button_to button_title(title: t('archive'), disabled_with: t('archiving'), title_class: 'inline', icon: svg_icon('archive', class: 'w-6 h-6')), template_path(template), class: 'btn btn-outline btn-sm w-full', form_class: 'flex-1', method: :delete, data: { turbo_confirm: t('are_you_sure_') } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
<% if can?(:create, current_account.templates.new(author: current_user)) %>
|
||||||
<%= link_to new_template_path(base_template_id: template.id), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
<%= link_to new_template_clone_path(template), class: 'btn btn-outline btn-sm flex-1', data: { turbo_frame: :modal } do %>
|
||||||
<span class="flex items-center justify-center space-x-2">
|
<span class="flex items-center justify-center space-x-2">
|
||||||
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
<%= svg_icon('copy', class: 'w-6 h-6') %>
|
||||||
<span class="inline"><%= t('clone') %></span>
|
<span class="inline"><%= t('clone') %></span>
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<%= form_for @template, url: template_clone_index_path(@base_template), data: { turbo_frame: :_top }, html: { autocomplete: :off } do |f| %>
|
||||||
|
<% accounts = Account.accessible_by(true_ability).where.not(id: true_user.account.testing_accounts).where(User.where(User.arel_table[:account_id].eq(Account.arel_table[:id])).arel.exists).active %>
|
||||||
|
<% if (can?(:manage, :tenants) || true_user != current_user) && accounts.where.not(id: current_account.id).exists? %>
|
||||||
|
<div class="form-control -mb-2 mt-2">
|
||||||
|
<%= select_tag :account_id, options_for_select([current_account, *accounts.order(:name)].uniq.map { |e| [e.name, e.id] }, current_account.id), required: true, class: 'base-select' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="form-control mt-4">
|
||||||
|
<%= f.text_field :name, required: true, placeholder: t('document_name'), class: 'base-input', dir: 'auto' %>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 mb-4 flex items-center justify-between">
|
||||||
|
<label for="folder_name" class="cursor-pointer">
|
||||||
|
<%= svg_icon('folder', class: 'w-6 h-6') %>
|
||||||
|
</label>
|
||||||
|
<folder-autocomplete class="flex justify-between w-full">
|
||||||
|
<set-value data-on="blur" data-value="<%= TemplateFolder::DEFAULT_NAME %>" data-empty-only="true" class="peer w-full whitespace-nowrap">
|
||||||
|
<input id="folder_name" placeholder="<%= t('folder_name') %>" type="text" class="w-full outline-none border-transparent focus:border-transparent focus:ring-0 bg-base-100 px-1" name="folder_name" value="<%= params[:folder_name].presence || @base_template&.folder&.full_name || TemplateFolder::DEFAULT_NAME %>" autocomplete="off">
|
||||||
|
</set-value>
|
||||||
|
<set-value data-on="click" data-value="" data-input-id="folder_name" class="peer-focus-within:hidden whitespace-nowrap">
|
||||||
|
<label for="folder_name" data-clear-on-focus="true" class="shrink-0 link mr-1.5 cursor-pointer">
|
||||||
|
<%= t('change_folder') %>
|
||||||
|
</label>
|
||||||
|
</set-value>
|
||||||
|
</folder-autocomplete>
|
||||||
|
</div>
|
||||||
|
<div class="form-control">
|
||||||
|
<%= f.button button_title(title: t('submit'), disabled_with: t('creating')), class: 'base-button' %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<%= render 'shared/turbo_modal', title: t('clone_template') do %>
|
||||||
|
<%= render 'templates_clone/form' %>
|
||||||
|
<% end %>
|
||||||
@@ -1,6 +1,20 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
if ENV['RAILS_ENV'] == 'production'
|
if ENV['RAILS_ENV'] == 'production'
|
||||||
|
if Process.uid.zero?
|
||||||
|
begin
|
||||||
|
workdir = ENV.fetch('WORKDIR', '.')
|
||||||
|
|
||||||
|
if File.exist?(workdir) && File.stat(workdir).uid != 2000
|
||||||
|
puts 'Changing the owner of the docuseal directory...' unless Dir.empty?(workdir)
|
||||||
|
|
||||||
|
FileUtils.chown_R(2000, 2000, workdir)
|
||||||
|
end
|
||||||
|
rescue StandardError
|
||||||
|
puts 'Unable to change docuseal directory owner'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if !ENV['AWS_SECRET_MANAGER_ID'].to_s.empty?
|
if !ENV['AWS_SECRET_MANAGER_ID'].to_s.empty?
|
||||||
require 'aws-sdk-secretsmanager'
|
require 'aws-sdk-secretsmanager'
|
||||||
|
|
||||||
@@ -30,12 +44,30 @@ if ENV['RAILS_ENV'] == 'production'
|
|||||||
File.write(dotenv_path, default_env)
|
File.write(dotenv_path, default_env)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if Process.uid.zero?
|
||||||
|
begin
|
||||||
|
File.chown(0, 0, dotenv_path)
|
||||||
|
File.chmod(0o600, dotenv_path)
|
||||||
|
rescue StandardError
|
||||||
|
puts 'Unable to set dotenv mod'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
database_url = ENV.fetch('DATABASE_URL', nil)
|
database_url = ENV.fetch('DATABASE_URL', nil)
|
||||||
|
|
||||||
Dotenv.load(dotenv_path)
|
Dotenv.load(dotenv_path)
|
||||||
|
|
||||||
ENV['DATABASE_URL'] = ENV['DATABASE_URL'].to_s.empty? ? database_url : ENV.fetch('DATABASE_URL', nil)
|
ENV['DATABASE_URL'] = ENV['DATABASE_URL'].to_s.empty? ? database_url : ENV.fetch('DATABASE_URL', nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
unless Process.uid == 2000
|
||||||
|
begin
|
||||||
|
Process::Sys.setgid(2000)
|
||||||
|
Process::Sys.setuid(2000)
|
||||||
|
rescue StandardError
|
||||||
|
puts 'Unable to run as 2000:2000'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ENV['DATABASE_URL'].to_s.split('@').last.to_s.split('/').first.to_s.include?('_')
|
if ENV['DATABASE_URL'].to_s.split('@').last.to_s.split('/').first.to_s.include?('_')
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ Rails.application.routes.draw do
|
|||||||
resources :templates, only: %i[index], controller: 'templates_dashboard'
|
resources :templates, only: %i[index], controller: 'templates_dashboard'
|
||||||
resources :submissions_filters, only: %i[show], param: 'name'
|
resources :submissions_filters, only: %i[show], param: 'name'
|
||||||
resources :templates, only: %i[new create edit update show destroy] do
|
resources :templates, only: %i[new create edit update show destroy] do
|
||||||
|
resources :clone, only: %i[new create], controller: 'templates_clone'
|
||||||
resource :debug, only: %i[show], controller: 'templates_debug' if Rails.env.development?
|
resource :debug, only: %i[show], controller: 'templates_debug' if Rails.env.development?
|
||||||
resources :documents, only: %i[index create], controller: 'template_documents'
|
resources :documents, only: %i[index create], controller: 'template_documents'
|
||||||
resources :clone_and_replace, only: %i[create], controller: 'templates_clone_and_replace'
|
resources :clone_and_replace, only: %i[create], controller: 'templates_clone_and_replace'
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ module Submissions
|
|||||||
module GenerateCombinedAttachment
|
module GenerateCombinedAttachment
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
def call(submitter)
|
def call(submitter, with_audit: true)
|
||||||
pdf = build_combined_pdf(submitter)
|
pdf = build_combined_pdf(submitter, with_audit:)
|
||||||
|
|
||||||
submission = submitter.submission
|
submission = submitter.submission
|
||||||
account = submission.account
|
account = submission.account
|
||||||
@@ -39,7 +39,7 @@ module Submissions
|
|||||||
blob: ActiveStorage::Blob.create_and_upload!(
|
blob: ActiveStorage::Blob.create_and_upload!(
|
||||||
io: io.tap(&:rewind), filename: "#{submission.name || submission.template.name}.pdf"
|
io: io.tap(&:rewind), filename: "#{submission.name || submission.template.name}.pdf"
|
||||||
),
|
),
|
||||||
name: 'combined_document',
|
name: with_audit ? 'combined_document' : 'merged_document',
|
||||||
record: submission
|
record: submission
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@@ -58,14 +58,16 @@ module Submissions
|
|||||||
pdf.sign(io, write_options: { validate: false }, **sign_params)
|
pdf.sign(io, write_options: { validate: false }, **sign_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_combined_pdf(submitter)
|
def build_combined_pdf(submitter, with_audit:)
|
||||||
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
|
pdfs_index = Submissions::GenerateResultAttachments.generate_pdfs(submitter)
|
||||||
|
|
||||||
audit_trail = I18n.with_locale(submitter.account.locale) do
|
if with_audit
|
||||||
Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission)
|
audit_trail = I18n.with_locale(submitter.account.locale) do
|
||||||
end
|
Submissions::GenerateAuditTrail.build_audit_trail(submitter.submission)
|
||||||
|
end
|
||||||
|
|
||||||
audit_trail.dispatch_message(:complete_objects)
|
audit_trail.dispatch_message(:complete_objects)
|
||||||
|
end
|
||||||
|
|
||||||
result = HexaPDF::Document.new
|
result = HexaPDF::Document.new
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@ module Submissions
|
|||||||
pdf.pages.each { |page| result.pages << result.import(page) }
|
pdf.pages.each { |page| result.pages << result.import(page) }
|
||||||
end
|
end
|
||||||
|
|
||||||
audit_trail.pages.each { |page| result.pages << result.import(page) }
|
audit_trail&.pages&.each { |page| result.pages << result.import(page) }
|
||||||
|
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module Submissions
|
|||||||
module_function
|
module_function
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(submission, values_hash: nil, submitter: nil)
|
def call(submission, values_hash: nil, submitter: nil, merge: false)
|
||||||
values_hash ||= if submitter
|
values_hash ||= if submitter
|
||||||
build_submitter_values_hash(submitter)
|
build_submitter_values_hash(submitter)
|
||||||
else
|
else
|
||||||
@@ -42,48 +42,74 @@ module Submissions
|
|||||||
|
|
||||||
template = submission.template
|
template = submission.template
|
||||||
|
|
||||||
image_pdfs = []
|
if merge
|
||||||
original_documents = submission.schema_documents.preload(:blob)
|
result = HexaPDF::Document.new
|
||||||
|
|
||||||
result_attachments =
|
(submission.template_schema || template.schema).each do |item|
|
||||||
(submission.template_schema || template.schema).filter_map do |item|
|
|
||||||
pdf = pdfs_index[item['attachment_uuid']]
|
pdf = pdfs_index[item['attachment_uuid']]
|
||||||
|
|
||||||
next if pdf.nil?
|
next unless pdf
|
||||||
|
|
||||||
if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
|
pdf.dispatch_message(:complete_objects)
|
||||||
pdf = GenerateResultAttachments.normalize_image_pdf(pdf)
|
|
||||||
|
|
||||||
image_pdfs << pdf
|
pdf.pages.each { |page| result.pages << result.import(page) }
|
||||||
end
|
|
||||||
|
|
||||||
build_pdf_attachment(pdf:, submission:, submitter:,
|
|
||||||
uuid: item['attachment_uuid'],
|
|
||||||
values_hash:,
|
|
||||||
name: item['name'])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return ApplicationRecord.no_touching { result_attachments.map { |e| e.tap(&:save!) } } if image_pdfs.size < 2
|
attachment = build_pdf_attachment(
|
||||||
|
pdf: result,
|
||||||
images_pdf =
|
|
||||||
image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
|
|
||||||
pdf.pages.each { |page| doc.pages << doc.import(page) }
|
|
||||||
end
|
|
||||||
|
|
||||||
images_pdf = GenerateResultAttachments.normalize_image_pdf(images_pdf)
|
|
||||||
|
|
||||||
images_pdf_attachment =
|
|
||||||
build_pdf_attachment(
|
|
||||||
pdf: images_pdf,
|
|
||||||
submission:,
|
submission:,
|
||||||
submitter:,
|
|
||||||
uuid: GenerateResultAttachments.images_pdf_uuid(original_documents.select(&:image?)),
|
|
||||||
values_hash:,
|
values_hash:,
|
||||||
name: submission.name || template.name
|
name: 'preview_merged_document',
|
||||||
|
filename: "#{submission.name || template.name}.pdf"
|
||||||
)
|
)
|
||||||
|
|
||||||
ApplicationRecord.no_touching do
|
ApplicationRecord.no_touching { attachment.save! }
|
||||||
(result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) }
|
|
||||||
|
[attachment]
|
||||||
|
else
|
||||||
|
image_pdfs = []
|
||||||
|
original_documents = submission.schema_documents.preload(:blob)
|
||||||
|
|
||||||
|
result_attachments =
|
||||||
|
(submission.template_schema || template.schema).filter_map do |item|
|
||||||
|
pdf = pdfs_index[item['attachment_uuid']]
|
||||||
|
|
||||||
|
next if pdf.nil?
|
||||||
|
|
||||||
|
if original_documents.find { |a| a.uuid == item['attachment_uuid'] }.image?
|
||||||
|
pdf = GenerateResultAttachments.normalize_image_pdf(pdf)
|
||||||
|
|
||||||
|
image_pdfs << pdf
|
||||||
|
end
|
||||||
|
|
||||||
|
build_pdf_attachment(pdf:, submission:, submitter:,
|
||||||
|
uuid: item['attachment_uuid'],
|
||||||
|
values_hash:,
|
||||||
|
filename: "#{item['name']}.pdf")
|
||||||
|
end
|
||||||
|
|
||||||
|
return ApplicationRecord.no_touching { result_attachments.map { |e| e.tap(&:save!) } } if image_pdfs.size < 2
|
||||||
|
|
||||||
|
images_pdf =
|
||||||
|
image_pdfs.each_with_object(HexaPDF::Document.new) do |pdf, doc|
|
||||||
|
pdf.pages.each { |page| doc.pages << doc.import(page) }
|
||||||
|
end
|
||||||
|
|
||||||
|
images_pdf = GenerateResultAttachments.normalize_image_pdf(images_pdf)
|
||||||
|
|
||||||
|
images_pdf_attachment =
|
||||||
|
build_pdf_attachment(
|
||||||
|
pdf: images_pdf,
|
||||||
|
submission:,
|
||||||
|
submitter:,
|
||||||
|
uuid: GenerateResultAttachments.images_pdf_uuid(original_documents.select(&:image?)),
|
||||||
|
values_hash:,
|
||||||
|
filename: "#{submission.name || template.name}.pdf"
|
||||||
|
)
|
||||||
|
|
||||||
|
ApplicationRecord.no_touching do
|
||||||
|
(result_attachments + [images_pdf_attachment]).map { |e| e.tap(&:save!) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,7 +128,8 @@ module Submissions
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_pdf_attachment(pdf:, submission:, submitter:, uuid:, name:, values_hash:)
|
def build_pdf_attachment(pdf:, submission:, filename:, values_hash:, submitter: nil, uuid: nil,
|
||||||
|
name: 'preview_documents')
|
||||||
io = StringIO.new
|
io = StringIO.new
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -114,13 +141,13 @@ module Submissions
|
|||||||
end
|
end
|
||||||
|
|
||||||
ActiveStorage::Attachment.new(
|
ActiveStorage::Attachment.new(
|
||||||
blob: ActiveStorage::Blob.create_and_upload!(io: io.tap(&:rewind), filename: "#{name}.pdf"),
|
blob: ActiveStorage::Blob.create_and_upload!(io: io.tap(&:rewind), filename:),
|
||||||
io_data: io.string,
|
io_data: io.string,
|
||||||
metadata: { original_uuid: uuid,
|
metadata: { original_uuid: uuid,
|
||||||
values_hash:,
|
values_hash:,
|
||||||
analyzed: true,
|
analyzed: true,
|
||||||
sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(io.string)) },
|
sha256: Base64.urlsafe_encode64(Digest::SHA256.digest(io.string)) }.compact,
|
||||||
name: 'preview_documents',
|
name: name,
|
||||||
record: submitter || submission
|
record: submitter || submission
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -33,14 +33,18 @@ module Submissions
|
|||||||
return submitter_params if default_values.blank?
|
return submitter_params if default_values.blank?
|
||||||
|
|
||||||
values, new_attachments, new_fields =
|
values, new_attachments, new_fields =
|
||||||
Submitters::NormalizeValues.call(template,
|
Submitters::NormalizeValues.call(
|
||||||
default_values,
|
template,
|
||||||
submitter_name: submitter_params[:role] ||
|
default_values,
|
||||||
template.submitters.dig(index, 'name'),
|
submitter_name: submitter_params[:role] ||
|
||||||
role_names: submitter_params[:roles],
|
(submitter_params[:uuid] &&
|
||||||
for_submitter:,
|
template.submitters.find { |s| s['uuid'] == submitter_params[:uuid] }&.dig('name')) ||
|
||||||
add_fields:,
|
template.submitters.dig(index, 'name'),
|
||||||
throw_errors: !with_values)
|
role_names: submitter_params[:roles],
|
||||||
|
for_submitter:,
|
||||||
|
add_fields:,
|
||||||
|
throw_errors: !with_values
|
||||||
|
)
|
||||||
|
|
||||||
submitter_params[:values] = values
|
submitter_params[:values] = values
|
||||||
|
|
||||||
|
|||||||
@@ -60,24 +60,24 @@ module Templates
|
|||||||
|
|
||||||
# rubocop:disable Metrics, Style
|
# rubocop:disable Metrics, Style
|
||||||
def call(io, attachment: nil, confidence: 0.3, temperature: 1, inference: Templates::ImageToFields, nms: 0.1,
|
def call(io, attachment: nil, confidence: 0.3, temperature: 1, inference: Templates::ImageToFields, nms: 0.1,
|
||||||
split_page: false, aspect_ratio: true, padding: 20, regexp_type: true, &)
|
nmm: 0.5, split_page: false, aspect_ratio: true, padding: 20, regexp_type: true, &)
|
||||||
fields, head_node =
|
fields, head_node =
|
||||||
if attachment&.image?
|
if attachment&.image?
|
||||||
process_image_attachment(io, attachment:, confidence:, nms:, split_page:, inference:,
|
process_image_attachment(io, attachment:, confidence:, nms:, nmm:, split_page:, inference:,
|
||||||
temperature:, aspect_ratio:, padding:, &)
|
temperature:, aspect_ratio:, padding:, &)
|
||||||
else
|
else
|
||||||
process_pdf_attachment(io, attachment:, confidence:, nms:, split_page:, inference:,
|
process_pdf_attachment(io, attachment:, confidence:, nms:, nmm:, split_page:, inference:,
|
||||||
temperature:, aspect_ratio:, regexp_type:, padding:, &)
|
temperature:, aspect_ratio:, regexp_type:, padding:, &)
|
||||||
end
|
end
|
||||||
|
|
||||||
[fields, head_node]
|
[fields, head_node]
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_image_attachment(io, attachment:, confidence:, nms:, temperature:, inference:,
|
def process_image_attachment(io, attachment:, confidence:, nms:, nmm:, temperature:, inference:,
|
||||||
split_page: false, aspect_ratio: false, padding: nil)
|
split_page: false, aspect_ratio: false, padding: nil)
|
||||||
image = Vips::Image.new_from_buffer(io.read, '')
|
image = Vips::Image.new_from_buffer(io.read, '')
|
||||||
|
|
||||||
fields = inference.call(image, confidence:, nms:, split_page:,
|
fields = inference.call(image, confidence:, nms:, nmm:, split_page:,
|
||||||
temperature:, aspect_ratio:, padding:)
|
temperature:, aspect_ratio:, padding:)
|
||||||
|
|
||||||
fields = sort_fields(fields, y_threshold: 10.0 / image.height)
|
fields = sort_fields(fields, y_threshold: 10.0 / image.height)
|
||||||
@@ -104,7 +104,7 @@ module Templates
|
|||||||
[fields, nil]
|
[fields, nil]
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_pdf_attachment(io, attachment:, confidence:, nms:, temperature:, inference:,
|
def process_pdf_attachment(io, attachment:, confidence:, nms:, nmm:, temperature:, inference:,
|
||||||
split_page: false, aspect_ratio: false, padding: nil, regexp_type: false)
|
split_page: false, aspect_ratio: false, padding: nil, regexp_type: false)
|
||||||
doc = Pdfium::Document.open_bytes(io.read)
|
doc = Pdfium::Document.open_bytes(io.read)
|
||||||
|
|
||||||
@@ -121,7 +121,7 @@ module Templates
|
|||||||
|
|
||||||
image = Vips::Image.new_from_memory(data, width, height, 4, :uchar)
|
image = Vips::Image.new_from_memory(data, width, height, 4, :uchar)
|
||||||
|
|
||||||
fields = inference.call(image, confidence: confidence / 3.0, nms:, split_page:,
|
fields = inference.call(image, confidence: confidence / 3.0, nms:, nmm:, split_page:,
|
||||||
temperature:, aspect_ratio:, padding:)
|
temperature:, aspect_ratio:, padding:)
|
||||||
|
|
||||||
text_fields = extract_text_fields_from_page(page)
|
text_fields = extract_text_fields_from_page(page)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ module Templates
|
|||||||
CPU_THREADS = Etc.nprocessors
|
CPU_THREADS = Etc.nprocessors
|
||||||
|
|
||||||
# rubocop:disable Metrics
|
# rubocop:disable Metrics
|
||||||
def call(image, confidence: 0.3, nms: 0.1, temperature: 1,
|
def call(image, confidence: 0.3, nms: 0.1, nmm: 0.9, temperature: 1,
|
||||||
split_page: false, aspect_ratio: true, padding: nil, resolution: self.resolution)
|
split_page: false, aspect_ratio: true, padding: nil, resolution: self.resolution)
|
||||||
image = image.extract_band(0, n: 3) if image.bands > 3
|
image = image.extract_band(0, n: 3) if image.bands > 3
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ module Templates
|
|||||||
detections = postprocess_outputs(boxes, logits, transform_info, confidence:, temperature:, resolution:)
|
detections = postprocess_outputs(boxes, logits, transform_info, confidence:, temperature:, resolution:)
|
||||||
end
|
end
|
||||||
|
|
||||||
detections = apply_nms(detections, nms)
|
detections = apply_nms_nmm(detections, nms_threshold: nms, nmm_threshold: nmm)
|
||||||
|
|
||||||
build_fields_from_detections(detections, image)
|
build_fields_from_detections(detections, image)
|
||||||
end
|
end
|
||||||
@@ -245,7 +245,7 @@ module Templates
|
|||||||
|
|
||||||
left, top, trim_width, trim_height = image.find_trim(threshold: 10, background: [255, 255, 255])
|
left, top, trim_width, trim_height = image.find_trim(threshold: 10, background: [255, 255, 255])
|
||||||
|
|
||||||
trim_width = [trim_width, image.width * 0.7].max
|
trim_width = [trim_width, image.width - (left * 1.9)].max
|
||||||
|
|
||||||
padded_left = [left - padding, 0].max
|
padded_left = [left - padding, 0].max
|
||||||
padded_top = [top - padding, 0].max
|
padded_top = [top - padding, 0].max
|
||||||
@@ -297,7 +297,10 @@ module Templates
|
|||||||
[img_array.reshape(1, 3, resolution, resolution), transform_info]
|
[img_array.reshape(1, 3, resolution, resolution), transform_info]
|
||||||
end
|
end
|
||||||
|
|
||||||
def nms(boxes, scores, iou_threshold = 0.5)
|
def nms(detections, iou_threshold = 0.5)
|
||||||
|
boxes = detections[:xyxy]
|
||||||
|
scores = detections[:confidence]
|
||||||
|
|
||||||
return Numo::Int32[] if boxes.shape[0].zero?
|
return Numo::Int32[] if boxes.shape[0].zero?
|
||||||
|
|
||||||
x1 = boxes[true, 0]
|
x1 = boxes[true, 0]
|
||||||
@@ -333,7 +336,73 @@ module Templates
|
|||||||
order = order[inds + 1]
|
order = order[inds + 1]
|
||||||
end
|
end
|
||||||
|
|
||||||
Numo::Int32.cast(keep)
|
{
|
||||||
|
xyxy: detections[:xyxy][keep, true],
|
||||||
|
confidence: detections[:confidence][keep],
|
||||||
|
class_id: detections[:class_id][keep]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def nmm(detections, overlap_threshold = 0.9, confidence: 0.3)
|
||||||
|
boxes = detections[:xyxy]
|
||||||
|
scores = detections[:confidence]
|
||||||
|
classes = detections[:class_id]
|
||||||
|
|
||||||
|
return detections if boxes.shape[0].zero?
|
||||||
|
|
||||||
|
x1 = boxes[true, 0]
|
||||||
|
y1 = boxes[true, 1]
|
||||||
|
x2 = boxes[true, 2]
|
||||||
|
y2 = boxes[true, 3]
|
||||||
|
|
||||||
|
areas = (x2 - x1) * (y2 - y1)
|
||||||
|
order = areas.sort_index.reverse
|
||||||
|
|
||||||
|
keep = []
|
||||||
|
|
||||||
|
while order.size.positive?
|
||||||
|
i = order[0]
|
||||||
|
keep << i
|
||||||
|
break if order.size == 1
|
||||||
|
|
||||||
|
xx1 = Numo::SFloat.maximum(x1[i], x1[order[1..]])
|
||||||
|
yy1 = Numo::SFloat.maximum(y1[i], y1[order[1..]])
|
||||||
|
xx2 = Numo::SFloat.minimum(x2[i], x2[order[1..]])
|
||||||
|
yy2 = Numo::SFloat.minimum(y2[i], y2[order[1..]])
|
||||||
|
|
||||||
|
w = Numo::SFloat.maximum(0.0, xx2 - xx1)
|
||||||
|
h = Numo::SFloat.maximum(0.0, yy2 - yy1)
|
||||||
|
|
||||||
|
intersection = w * h
|
||||||
|
|
||||||
|
overlap = intersection / areas[order[1..]]
|
||||||
|
|
||||||
|
merge_mask = scores[i] > confidence ? (overlap.gt(overlap_threshold) & classes[order[1..]].eq(classes[i])) : nil
|
||||||
|
|
||||||
|
if merge_mask && (merge_inds = merge_mask.where).size.positive?
|
||||||
|
candidates = order[merge_inds + 1]
|
||||||
|
|
||||||
|
scores[i] = [scores[i], scores[candidates].max].max
|
||||||
|
|
||||||
|
x1[i] = [x1[i], x1[candidates].min].min
|
||||||
|
y1[i] = [y1[i], y1[candidates].min].min
|
||||||
|
x2[i] = [x2[i], x2[candidates].max].max
|
||||||
|
y2[i] = [y2[i], y2[candidates].max].max
|
||||||
|
end
|
||||||
|
|
||||||
|
if merge_mask
|
||||||
|
inds = (~merge_mask).where
|
||||||
|
order = order[inds + 1]
|
||||||
|
else
|
||||||
|
order = order[1..]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{
|
||||||
|
xyxy: detections[:xyxy][keep, true],
|
||||||
|
confidence: detections[:confidence][keep],
|
||||||
|
class_id: detections[:class_id][keep]
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def postprocess_outputs(boxes, logits, transform_info, detections = nil, confidence: 0.3, temperature: 1,
|
def postprocess_outputs(boxes, logits, transform_info, detections = nil, confidence: 0.3, temperature: 1,
|
||||||
@@ -429,25 +498,21 @@ module Templates
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def apply_nms(detections, threshold = 0.5)
|
def apply_nms_nmm(detections, nms_threshold: 0.5, nmm_threshold: 0.7, confidence: 0.3)
|
||||||
return detections if detections[:xyxy].shape[0].zero?
|
return detections if detections[:xyxy].shape[0].zero?
|
||||||
|
|
||||||
keep_indices = nms(detections[:xyxy], detections[:confidence], threshold)
|
nms_result = nms(detections, nms_threshold)
|
||||||
|
|
||||||
{
|
nmm(nms_result, nmm_threshold, confidence:)
|
||||||
xyxy: detections[:xyxy][keep_indices, true],
|
|
||||||
confidence: detections[:confidence][keep_indices],
|
|
||||||
class_id: detections[:class_id][keep_indices]
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def model
|
def model
|
||||||
@model ||= OnnxRuntime::Model.new(
|
@model ||= OnnxRuntime::Model.new(
|
||||||
MODEL_PATH.to_s,
|
MODEL_PATH.to_s,
|
||||||
inter_op_num_threads: CPU_THREADS,
|
inter_op_num_threads: 1,
|
||||||
intra_op_num_threads: CPU_THREADS,
|
intra_op_num_threads: CPU_THREADS,
|
||||||
enable_mem_pattern: false,
|
enable_mem_pattern: false,
|
||||||
enable_cpu_mem_arena: false,
|
enable_cpu_mem_arena: Docuseal.multitenant? || Rails.env.development?,
|
||||||
providers: ['CPUExecutionProvider']
|
providers: ['CPUExecutionProvider']
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user