Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,15 @@
"@emotion/css": "^11.13.5",
"@hcaptcha/react-hcaptcha": "^2.0.2",
"@mdx-js/react": "^3.0.0",
"@xyflow/react": "^12.11.2",
"@yang1666204/docusaurus-search-local": "0.0.7",
"antd": "^5.12.2",
"autoprefixer": "^10.4.16",
"clsx": "^1.1.1",
"copy-to-clipboard": "^3.3.3",
"docusaurus-plugin-matomo": "^0.0.8",
"docusaurus-plugin-sass": "^0.2.3",
"elkjs": "^0.12.0",
"postcss": "^8.4.32",
"prism-react-renderer": "^2.1.0",
"react": "^18.2.0",
Expand Down
161 changes: 161 additions & 0 deletions src/components/profile-analysis/AiAnalysisForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import HCaptcha from '@hcaptcha/react-hcaptcha';
import React, { JSX, useCallback, useRef, useState } from 'react';
import type { ResponseLanguage } from './profile-analysis.types';

interface AiAnalysisFormProps {
file: File | null;
language: ResponseLanguage;
disabled: boolean;
hcaptchaSiteKey: string;
onLanguageChange: (language: ResponseLanguage) => void;
onAnalyze: (hcaptchaToken: string, resetCaptcha: () => void) => void;
}

export function AiAnalysisForm({
file,
language,
disabled,
hcaptchaSiteKey,
onLanguageChange,
onAnalyze,
}: AiAnalysisFormProps): JSX.Element {
const [consentAccepted, setConsentAccepted] = useState(false);
const [hcaptchaToken, setHCaptchaToken] = useState<string | null>(null);
const [hcaptchaError, setHCaptchaError] = useState<string | null>(null);
const hcaptchaRef = useRef<HCaptcha>(null);

const resetCaptcha = useCallback(() => {
hcaptchaRef.current?.resetCaptcha();
setHCaptchaToken(null);
setHCaptchaError(null);
}, []);

return (
<div className="profile-analysis__panel">
<p className="profile-analysis__panel-intro">
The prepared Profile is uploaded only when you start this action.
</p>

<fieldset className="profile-analysis__language" disabled={disabled}>
<legend>Response language</legend>
<label>
<input
type="radio"
name="profile-analysis-language"
value="en"
checked={language === 'en'}
onChange={() => onLanguageChange('en')}
/>
English
</label>
<label>
<input
type="radio"
name="profile-analysis-language"
value="zh-CN"
checked={language === 'zh-CN'}
onChange={() => onLanguageChange('zh-CN')}
/>
Simplified Chinese
</label>
</fieldset>

<div className="profile-analysis__privacy-notice" id="profile-analysis-privacy-notice" role="note">
<div className="profile-analysis__privacy-notice-title">
<span className="profile-analysis__privacy-notice-icon" aria-hidden="true">
!
</span>
<h3>Privacy and AI processing notice</h3>
</div>
<p>
This feature is provided by VeloDB and third-party large language model service providers. It
is not an official Apache Doris project feature, so please use it at your discretion.
</p>
<p>
Do not upload passwords, keys, access tokens, personal information, customer-confidential
data, or any other sensitive content that you are not authorized to disclose. All uploaded
information will be automatically and permanently deleted within one hour.
</p>
<label className="profile-analysis__consent">
<input
type="checkbox"
checked={consentAccepted}
disabled={disabled}
aria-describedby="profile-analysis-privacy-notice"
onChange={event => {
const accepted = event.currentTarget.checked;
setConsentAccepted(accepted);
if (!accepted) resetCaptcha();
}}
/>
I have read the notice, am authorized to upload this profile, and consent to third-party AI
processing.
</label>
</div>

{consentAccepted && (
<div className="profile-analysis__captcha">
<p id="profile-analysis-captcha-help" className="profile-analysis__captcha-label">
Complete the human verification before starting the analysis.
</p>
{hcaptchaSiteKey ? (
<HCaptcha
ref={hcaptchaRef}
sitekey={hcaptchaSiteKey}
reCaptchaCompat={false}
sentry={false}
onVerify={token => {
setHCaptchaToken(token);
setHCaptchaError(null);
}}
onExpire={() => {
setHCaptchaToken(null);
setHCaptchaError('Verification expired. Complete it again.');
}}
onChalExpired={() => {
setHCaptchaToken(null);
setHCaptchaError('Verification expired. Complete it again.');
}}
onError={() => {
setHCaptchaToken(null);
setHCaptchaError(
'Human verification could not load. Check your connection and try again.',
);
}}
/>
) : (
<div className="profile-analysis__validation-error" role="alert">
Human verification is not configured. Contact the site administrator.
</div>
)}
<small>
This site is protected by hCaptcha and its{' '}
<a href="https://www.hcaptcha.com/privacy" target="_blank" rel="noopener noreferrer">
Privacy Policy
</a>{' '}
and{' '}
<a href="https://www.hcaptcha.com/terms" target="_blank" rel="noopener noreferrer">
Terms of Service
</a>{' '}
apply.
</small>
{hcaptchaError && (
<div className="profile-analysis__validation-error" role="alert">
{hcaptchaError}
</div>
)}
</div>
)}
<button
className="button button--primary profile-analysis__action-button"
type="button"
disabled={!file || disabled || !consentAccepted || !hcaptchaToken}
onClick={() => {
if (hcaptchaToken) onAnalyze(hcaptchaToken, resetCaptcha);
}}
>
{disabled ? 'Processing…' : 'Analyze with AI'}
</button>
</div>
);
}
91 changes: 89 additions & 2 deletions src/components/profile-analysis/ProfileAnalysis.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
}

&__uploader,
&__result {
&__result,
&__workspace {
margin-bottom: 1.5rem;
padding: 1.5rem;
border: 1px solid var(--brand-border-soft);
Expand All @@ -47,6 +48,75 @@
}
}

&__tabs {
display: flex;
gap: 0.25rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--brand-border-soft);

button {
position: relative;
padding: 0.7rem 1rem;
border: 0;
color: var(--ifm-color-emphasis-700);
background: transparent;
cursor: pointer;
font: inherit;
font-weight: 700;
}

button::after {
position: absolute;
right: 0.5rem;
bottom: -1px;
left: 0.5rem;
height: 3px;
border-radius: 3px 3px 0 0;
background: transparent;
content: '';
}

button[aria-selected='true'] {
color: var(--brand-primary);
}

button[aria-selected='true']::after {
background: var(--brand-primary);
}

button:focus-visible {
border-radius: 6px;
outline: 2px solid var(--brand-primary-glow);
outline-offset: 2px;
}
}

&__tab-panel[hidden] {
display: none;
}

&__tab-panel > &__status,
&__tab-panel > &__error,
&__tab-panel > &__result {
margin-top: 1.25rem;
margin-bottom: 0;
}

&__tab-panel > &__result {
padding: 0;
border: 0;
box-shadow: none;
}

&__panel-intro {
margin-bottom: 1rem;
color: var(--ifm-color-emphasis-700);
}

&__panel-intro + &__action-button {
margin-top: 0;
}

&__help {
margin-bottom: 1rem;
color: var(--ifm-color-emphasis-700);
Expand Down Expand Up @@ -222,6 +292,16 @@
font-size: 0.9rem;
}

&__panel {
min-width: 0;
margin-bottom: 1.25rem;
}

&__action-button {
width: 100%;
margin-top: 1rem;
}

&__captcha {
max-width: 100%;
margin-top: 1.25rem;
Expand Down Expand Up @@ -354,10 +434,17 @@
padding-top: 1rem;

&__uploader,
&__result {
&__result,
&__workspace {
padding: 1rem;
}

&__tabs button {
flex: 1 1 0;
padding-right: 0.5rem;
padding-left: 0.5rem;
}

&__drop-zone {
min-height: 150px;
padding: 1rem;
Expand Down
Loading
Loading