Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement translate function #41

Merged
merged 30 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
fafa25e
add translate text command
heikkivihersalo Oct 23, 2024
317bf03
add translate
heikkivihersalo Oct 23, 2024
5b4a28f
add translation functions
heikkivihersalo Oct 23, 2024
3adb16c
add initial languages
heikkivihersalo Oct 23, 2024
fec5155
expand language options
heikkivihersalo Oct 23, 2024
6a2f7cf
expand language options
heikkivihersalo Oct 23, 2024
70ec63a
add translate mode
heikkivihersalo Oct 23, 2024
b796f11
add setter and getter for translation languages
heikkivihersalo Oct 23, 2024
138dd5f
added language to the store
heikkivihersalo Oct 23, 2024
b8eb120
change value to full language name
heikkivihersalo Oct 23, 2024
883e168
initial commit
heikkivihersalo Oct 23, 2024
f7c8909
initial commit
heikkivihersalo Oct 23, 2024
e948b78
added translation
heikkivihersalo Oct 23, 2024
49c0159
added translate
heikkivihersalo Oct 23, 2024
1873ffd
add closeModal helper function
heikkivihersalo Oct 23, 2024
39a2028
fix defaults to actual values
heikkivihersalo Oct 23, 2024
c5cca37
fix to use correct data store
heikkivihersalo Oct 23, 2024
3055566
add translate modal controls
heikkivihersalo Oct 23, 2024
8bb8440
initial styles for translate mode
heikkivihersalo Oct 23, 2024
b15a6b7
get button text based on mode
heikkivihersalo Oct 23, 2024
bb91b0e
add mode and wrapper around buttons
heikkivihersalo Oct 23, 2024
6926ccd
jsdoc fixes
heikkivihersalo Oct 23, 2024
7d7dc6a
added icon
heikkivihersalo Oct 23, 2024
e1cd7c9
added warning styles
heikkivihersalo Oct 24, 2024
9c4235e
remove translate from quick commands
heikkivihersalo Oct 24, 2024
67fd273
disable translate when no text is selected
heikkivihersalo Oct 24, 2024
397e5dc
add disabled state for button
heikkivihersalo Oct 24, 2024
43341bb
add selection
heikkivihersalo Oct 24, 2024
ef64c52
added disabled state
heikkivihersalo Oct 24, 2024
498978e
added missing jsdoc
heikkivihersalo Oct 24, 2024
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
17 changes: 17 additions & 0 deletions includes/api/class-callbacks.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ public static function generate_text( \WP_REST_Request $request ): \WP_REST_Resp
return new \WP_REST_Response( $result, 200 );
}

/**
* Translate text content through Open AI
*
* @param \WP_REST_Request $request Request object
* @return \WP_REST_Response|\WP_Error Response object
* @throws \Exception If user does not have sufficient permissions.
*/
public static function translate_text( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error {
try {
$result = Utils::translate_open_ai_text_content( $request );
} catch ( \Exception $e ) {
return new \WP_Error( 'error_' . $e->getCode(), $e->getMessage() );
}

return new \WP_REST_Response( $result, 200 );
}

/**
* Generate content through Open AI
*
Expand Down
15 changes: 15 additions & 0 deletions includes/api/class-routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,21 @@ public function register_endpoints(): void {
)
);

/**
* Route for translating text content
*
* @method POST
*/
register_rest_route(
$this->name . '/v' . $this->version,
'/text/translate',
array(
'methods' => \WP_REST_Server::EDITABLE, // Alias for GET transport method.
'callback' => array( Callbacks::class, 'translate_text' ),
'permission_callback' => Permissions::ADMIN->get_callback(),
)
);

/**
* Route for generating image content
*
Expand Down
75 changes: 75 additions & 0 deletions includes/api/class-utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,81 @@ public static function get_open_ai_text_content( \WP_REST_Request $request ): ar
}
}

/**
* Get translation prompt
*
* @since 0.3.0
* @access private
* @param string $languageFrom The language to translate from.
* @param string $languageTo The language to translate to.
* @return string
*/
private static function get_translation_prompt( string $languageFrom, string $languageTo ): string {
$prompt = 'You are a helpful assistant that returns text in markdown format.';
$prompt .= ' ';
$prompt .= 'You will be provided with a text in ' . $languageFrom . ', and your task is to translate it into ' . $languageTo . '.';
$prompt .= ' ';

if ( 'emoji' === $languageTo ) {
$prompt .= 'Do not use any regular text. Do your best with emojis only. Return only the result.';
} else {
$prompt .= 'Return only the result.';
}

return $prompt;
}

/**
* Translate content from Open AI
*
* @since 0.3.0
* @access public
* @param \WP_REST_Request $request Request object.
* @return Object
* @throws \Exception If failed to update contact information.
*/
public static function translate_open_ai_text_content( \WP_REST_Request $request ): array {
$body = json_decode( $request->get_body(), true );
$api = self::get_chatgpt_settings();
$languageFrom = $body['languageFrom'] ?? '';
$languageTo = $body['languageTo'] ?? '';
$data = array(
'model' => $api['model_text'] ?? 'gpt-4o-mini',
'messages' => array(
(object) array(
'role' => 'system',
'content' => self::get_translation_prompt( $languageFrom, $languageTo ),
),
(object) array(
'role' => 'user',
'content' => $body['text'],
),
),
);

$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
array(
'method' => 'POST',
'body' => wp_json_encode( $data ),
'headers' => array(
'Content-Type' => 'application/json',
'Authorization' => 'Bearer ' . $api['api_key'],
),
'sslverify' => false,
'timeout' => 60,
),
);

if ( ! is_wp_error( $response ) ) {
$body = json_decode( wp_remote_retrieve_body( $response ), true );
return $body;
} else {
$error_message = $response->get_error_message();
throw new \Exception( esc_html( $error_message ) );
}
}

/**
* Get content from Open AI
*
Expand Down
1 change: 1 addition & 0 deletions src/constants/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export const API_PATH = {
GET_SETTINGS: 'gutenberg-native-ai/v1/settings',
SET_SETTINGS: 'gutenberg-native-ai/v1/settings',
GENERATE_TEXT: 'gutenberg-native-ai/v1/text/generate',
TRANSLATE_TEXT: 'gutenberg-native-ai/v1/text/translate',
GENERATE_IMAGE: 'gutenberg-native-ai/v1/image/generate',
SAVE_IMAGE: 'gutenberg-native-ai/v1/image/save',
};
1 change: 1 addition & 0 deletions src/constants/modal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const MODAL_STATUS = {
export const MODAL_MODE = {
TEXT: 'text',
IMAGE: 'image',
TRANSLATE: 'translate',
};

export const ALLOWED_TEXT_BLOCKS = [
Expand Down
22 changes: 22 additions & 0 deletions src/constants/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,25 @@ export const TONE_OF_VOICE = [
label: __('Authoritative and Expert', 'gutenberg-native-ai'),
},
];

export const AVAILABLE_LANGUAGES = [
{ value: 'chinese', label: __('Chinese', 'gutenberg-native-ai') },
{ value: 'dutch', label: __('Dutch', 'gutenberg-native-ai') },
{ value: 'english', label: __('English', 'gutenberg-native-ai') },
{ value: 'finnish', label: __('Finnish', 'gutenberg-native-ai') },
{ value: 'french', label: __('French', 'gutenberg-native-ai') },
{ value: 'german', label: __('German', 'gutenberg-native-ai') },
{ value: 'italian', label: __('Italian', 'gutenberg-native-ai') },
{ value: 'japanese', label: __('Japanese', 'gutenberg-native-ai') },
{ value: 'korean', label: __('Korean', 'gutenberg-native-ai') },
{ value: 'norwegian', label: __('Norwegian', 'gutenberg-native-ai') },
{ value: 'polish', label: __('Polish', 'gutenberg-native-ai') },
{ value: 'portugese', label: __('Portuguese', 'gutenberg-native-ai') },
{ value: 'romanian', label: __('Romanian', 'gutenberg-native-ai') },
{ value: 'russian', label: __('Russian', 'gutenberg-native-ai') },
{ value: 'spanish', label: __('Spanish', 'gutenberg-native-ai') },
{ value: 'swedish', label: __('Swedish', 'gutenberg-native-ai') },
{ value: 'turkish', label: __('Turkish', 'gutenberg-native-ai') },
{ value: 'vietnamese', label: __('Vietnamese', 'gutenberg-native-ai') },
{ value: 'emoji', label: __('Emoji', 'gutenberg-native-ai') },
];
2 changes: 2 additions & 0 deletions src/constants/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const ACTIONS = {
SET_SELECTION: 'SET_SELECTION',
GET_MODE: 'GET_MODE',
SET_MODE: 'SET_MODE',
GET_TRANSLATION_LANGUAGES: 'GET_TRANSLATION_LANGUAGES',
SET_TRANSLATION_LANGUAGES: 'SET_TRANSLATION_LANGUAGES',
GET_SETTINGS: 'GET_SETTINGS',
SET_SETTINGS: 'SET_SETTINGS',
SET_STATUS: 'SET_STATUS',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,50 @@ import { __ } from '@wordpress/i18n';
* Internal dependencies
*/
import { MODAL_STATUS } from '@constants/modal';
import type { ModalStatus } from 'types/modal';
import type { ModalMode, ModalStatus } from 'types/modal';

import styles from '../../index.module.css';

/**
* Get button text
* @param {string} mode - Modal mode
* @return {string} Button text
*/
const getButtonText = (mode: ModalMode): string => {
switch (mode) {
case 'text':
return __('Generate', 'gutenberg-native-ai');
case 'image':
return __('Generate', 'gutenberg-native-ai');
case 'translate':
return __('Translate', 'gutenberg-native-ai');
default:
return __('Generate', 'gutenberg-native-ai');
}
};

/**
* SubmitButton component
* @param {Object} props - Component props
* @param {string} props.status - Popover status
* @param {string} props.mode - Popover mode
* @param {boolean} props.disabled - Button disabled state
* @return {JSX.Element} SubmitButton component
*/
const FormSubmitButton = ({ status }: { status: ModalStatus }): JSX.Element => {
const FormSubmitButton = ({
status,
mode,
disabled = false,
}: {
status: ModalStatus;
mode: ModalMode;
disabled?: boolean;
}): JSX.Element => {
return (
<button
type="submit"
className={styles.formButtonSubmit}
disabled={status === MODAL_STATUS.LOADING}
disabled={status === MODAL_STATUS.LOADING || disabled}
>
{status === MODAL_STATUS.LOADING ? (
<svg xmlns="http://www.w3.org/2000/svg" width={24} height={24}>
Expand All @@ -40,7 +68,7 @@ const FormSubmitButton = ({ status }: { status: ModalStatus }): JSX.Element => {
/>
</svg>
) : (
__('Generate', 'gutenberg-native-ai')
getButtonText(mode)
)}
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import ImagePreview from './ImagePreview';
import { MODAL_STATUS } from '@constants/modal';
import { DATA_STORE } from '@constants/stores';

import type { ModalSettings, ModalStatus } from 'types/modal';
import type {
ModalSettings,
ModalStatus,
ModalMode,
ModalSelection,
} from 'types/modal';

import styles from '../../index.module.css';

Expand All @@ -31,10 +36,12 @@ import styles from '../../index.module.css';
*/
const ModalImage = (): JSX.Element | null => {
const [preview, setPreview] = useState<ChatGPTImage[] | null>(null);
const { status, settings } = useSelect((select: WPAny) => {
const { status, settings, mode } = useSelect((select: WPAny) => {
return {
status: select(DATA_STORE).getStatus() as ModalStatus,
settings: select(DATA_STORE).getSettings() as ModalSettings,
selection: select(DATA_STORE).getSelection() as ModalSelection,
mode: select(DATA_STORE).getMode() as ModalMode,
};
}, []);

Expand Down Expand Up @@ -94,9 +101,11 @@ const ModalImage = (): JSX.Element | null => {
'gutenberg-native-ai'
)}
/>
<FormSubmitButton status={status} />
<CloseButton closeCallback={handleCLose} />
<Settings />
<div className={styles.controlContainerButtons}>
<FormSubmitButton status={status} mode={mode} />
<CloseButton closeCallback={handleCLose} />
<Settings />
</div>
</div>
<ImagePreview preview={preview} />
<WarningText />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ import Settings from '../../../settings';

import {
addBlocksToEditor,
closeModal,
parseMarkdownToBlocks,
replateSelectedText,
} from '../../../../utils';

import { MODAL_STATUS } from '@constants/modal';
import { DATA_STORE } from '@constants/stores';

import type { ModalStatus, ModalSelection, ModalSettings } from 'types/modal';
import type {
ModalStatus,
ModalSelection,
ModalSettings,
ModalMode,
} from 'types/modal';

import styles from '../../index.module.css';

Expand All @@ -34,27 +40,18 @@ import styles from '../../index.module.css';
* @return {JSX.Element} Popover component
*/
const ModalControlsText = (): JSX.Element | null => {
const { status, selection, settings } = useSelect((select: WPAny) => {
const { status, selection, settings, mode } = useSelect((select: WPAny) => {
return {
status: select(DATA_STORE).getStatus() as ModalStatus,
selection: select(DATA_STORE).getSelection() as ModalSelection,
settings: select(DATA_STORE).getSettings() as ModalSettings,
mode: select(DATA_STORE).getMode() as ModalMode,
};
}, []);

const { setStatus, setSelection } = useDispatch(DATA_STORE);
const { setStatus } = useDispatch(DATA_STORE);
const { getText } = useChatGPT();

const handleCLose = () => {
setStatus(MODAL_STATUS.INITIAL);
setSelection({
block: null,
text: '',
start: 0,
end: 0,
});
};

/**
* Handle generate content
* @param event - Form event
Expand Down Expand Up @@ -109,7 +106,7 @@ const ModalControlsText = (): JSX.Element | null => {
}
}

handleCLose();
closeModal();
};

return (
Expand All @@ -121,9 +118,11 @@ const ModalControlsText = (): JSX.Element | null => {
'gutenberg-native-ai'
)}
/>
<FormSubmitButton status={status} />
<CloseButton closeCallback={handleCLose} />
<Settings />
<div className={styles.controlContainerButtons}>
<FormSubmitButton status={status} mode={mode} />
<CloseButton closeCallback={closeModal} />
<Settings />
</div>
</div>
<WarningText />
</Form>
Expand Down
Loading
Loading