Plugin Directory

Changeset 3765411


Ignore:
Timestamp:
10/26/2026 01:31:50 AM (67 minutes ago)
Author:
ournest
Message:

发布 v1.3.0:新增 ODS 智能客服助手,并清理无用订单/物流 AJAX

Location:
ods-ournest-dropship
Files:
4 added
6 edited
23 copied

Legend:

Unmodified
Added
Removed
  • ods-ournest-dropship/tags/1.3.0/assets/style.css

    r3760832 r3765411  
    35463546    color: #6b7280;
    35473547}
     3548
     3549/* ──────────────────────────────────────────
     3550   Support assistant (floating help)
     3551   ────────────────────────────────────────── */
     3552.ods-support-assistant {
     3553    position: fixed;
     3554    right: 24px;
     3555    bottom: 24px;
     3556    z-index: 100000;
     3557    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
     3558}
     3559
     3560.ods-support-fab {
     3561    width: 52px;
     3562    height: 52px;
     3563    border-radius: 999px;
     3564    border: none;
     3565    background: var(--ali-primary, #2563eb);
     3566    color: #fff;
     3567    box-shadow: var(--ali-shadow-lg);
     3568    cursor: pointer;
     3569    display: flex;
     3570    align-items: center;
     3571    justify-content: center;
     3572    transition: transform 0.15s ease, background 0.15s ease;
     3573}
     3574
     3575.ods-support-fab:hover {
     3576    background: var(--ali-primary-dark, #1d4ed8);
     3577    transform: translateY(-1px);
     3578}
     3579
     3580.ods-support-fab-icon {
     3581    font-size: 22px;
     3582    font-weight: 700;
     3583    line-height: 1;
     3584}
     3585
     3586.ods-support-panel {
     3587    display: none;
     3588    position: absolute;
     3589    right: 0;
     3590    bottom: 64px;
     3591    width: min(380px, calc(100vw - 32px));
     3592    max-height: min(560px, calc(100vh - 120px));
     3593    background: #fff;
     3594    border: 1px solid var(--ali-border, #e2e8f0);
     3595    border-radius: 12px;
     3596    box-shadow: var(--ali-shadow-lg);
     3597    flex-direction: column;
     3598    overflow: hidden;
     3599}
     3600
     3601.ods-support-assistant.is-open .ods-support-panel {
     3602    display: flex;
     3603}
     3604
     3605.ods-support-assistant.is-open .ods-support-fab {
     3606    display: none;
     3607}
     3608
     3609.ods-support-body {
     3610    position: relative;
     3611    flex: 1 1 auto;
     3612    min-height: 220px;
     3613    display: flex;
     3614    flex-direction: column;
     3615    overflow: hidden;
     3616}
     3617
     3618.ods-support-header {
     3619    display: flex;
     3620    align-items: flex-start;
     3621    gap: 8px;
     3622    padding: 14px 14px 10px;
     3623    border-bottom: 1px solid var(--ali-border, #e2e8f0);
     3624    background: #f8fafc;
     3625}
     3626
     3627.ods-support-header-text {
     3628    flex: 1;
     3629    min-width: 0;
     3630    display: flex;
     3631    flex-direction: column;
     3632    gap: 2px;
     3633}
     3634
     3635.ods-support-header-text strong {
     3636    font-size: 14px;
     3637    color: #0f172a;
     3638}
     3639
     3640.ods-support-header-text span {
     3641    font-size: 12px;
     3642    color: var(--ali-text-muted, #64748b);
     3643}
     3644
     3645.ods-support-close {
     3646    border: none;
     3647    background: transparent;
     3648    color: #64748b;
     3649    font-size: 22px;
     3650    line-height: 1;
     3651    cursor: pointer;
     3652    padding: 0 4px;
     3653}
     3654
     3655.ods-support-messages {
     3656    flex: 1 1 auto;
     3657    overflow-y: auto;
     3658    padding: 12px 14px;
     3659    display: flex;
     3660    flex-direction: column;
     3661    gap: 10px;
     3662    background: #fff;
     3663}
     3664
     3665.ods-support-msg {
     3666    display: flex;
     3667    flex-direction: column;
     3668    gap: 4px;
     3669    max-width: 95%;
     3670}
     3671
     3672.ods-support-msg--user {
     3673    align-self: flex-end;
     3674}
     3675
     3676.ods-support-msg--assistant {
     3677    align-self: flex-start;
     3678}
     3679
     3680.ods-support-bubble {
     3681    padding: 10px 12px;
     3682    border-radius: 10px;
     3683    font-size: 13px;
     3684    line-height: 1.45;
     3685    color: #1e293b;
     3686    background: #f1f5f9;
     3687    white-space: normal;
     3688    word-break: break-word;
     3689}
     3690
     3691.ods-support-msg--user .ods-support-bubble {
     3692    background: #2563eb;
     3693    color: #fff;
     3694}
     3695
     3696.ods-support-bubble--thinking {
     3697    color: #64748b;
     3698    font-style: italic;
     3699}
     3700
     3701.ods-support-meta {
     3702    font-size: 11px;
     3703    color: #64748b;
     3704    padding: 0 2px;
     3705}
     3706
     3707.ods-support-cites {
     3708    margin-top: 2px;
     3709}
     3710
     3711.ods-support-cite {
     3712    display: inline-block;
     3713    background: #eff6ff;
     3714    color: #1d4ed8;
     3715    border-radius: 4px;
     3716    padding: 1px 6px;
     3717    margin: 2px 2px 0 0;
     3718}
     3719
     3720.ods-support-fallback a,
     3721.ods-support-footer a,
     3722.ods-support-docs-link {
     3723    color: #2563eb;
     3724}
     3725
     3726.ods-support-docs-link {
     3727    text-decoration: underline;
     3728    font-weight: 600;
     3729}
     3730
     3731.ods-support-error {
     3732    color: #b91c1c;
     3733}
     3734
     3735/* Quick prompts: float above messages inside the panel; stagger in/out from the left. */
     3736.ods-support-quick {
     3737    position: absolute;
     3738    left: 10px;
     3739    right: 10px;
     3740    bottom: 10px;
     3741    z-index: 2;
     3742    display: flex;
     3743    flex-direction: column;
     3744    align-items: flex-start;
     3745    gap: 8px;
     3746    pointer-events: none;
     3747}
     3748
     3749.ods-support-chip {
     3750    border: 1px solid #cbd5e1;
     3751    background: rgba(255, 255, 255, 0.96);
     3752    color: #334155;
     3753    border-radius: 999px;
     3754    padding: 7px 12px;
     3755    font-size: 11px;
     3756    cursor: pointer;
     3757    line-height: 1.35;
     3758    text-align: left;
     3759    box-shadow: var(--ali-shadow);
     3760    max-width: 92%;
     3761    white-space: normal;
     3762    opacity: 0;
     3763    transform: translateX(-120%);
     3764    pointer-events: none;
     3765    transition:
     3766        transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
     3767        opacity 0.28s ease;
     3768    transition-delay: 0ms;
     3769}
     3770
     3771.ods-support-quick.is-visible .ods-support-chip {
     3772    opacity: 1;
     3773    transform: translateX(0);
     3774    pointer-events: auto;
     3775    transition-delay: calc(var(--ods-quick-i, 0) * 70ms);
     3776}
     3777
     3778.ods-support-quick.is-hiding .ods-support-chip {
     3779    opacity: 0;
     3780    transform: translateX(-120%);
     3781    pointer-events: none;
     3782    transition-delay: calc((2 - var(--ods-quick-i, 0)) * 70ms);
     3783}
     3784
     3785.ods-support-chip:hover {
     3786    border-color: #2563eb;
     3787    color: #1d4ed8;
     3788}
     3789
     3790.ods-support-form {
     3791    display: flex;
     3792    gap: 8px;
     3793    padding: 0 14px 10px;
     3794    align-items: flex-end;
     3795}
     3796
     3797.ods-support-input {
     3798    flex: 1;
     3799    min-height: 44px;
     3800    max-height: 96px;
     3801    resize: vertical;
     3802    border: 1px solid #cbd5e1;
     3803    border-radius: 8px;
     3804    padding: 8px 10px;
     3805    font-size: 13px;
     3806    line-height: 1.4;
     3807}
     3808
     3809.ods-support-input:focus {
     3810    outline: none;
     3811    border-color: #2563eb;
     3812    box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15);
     3813}
     3814
     3815.ods-support-send {
     3816    border: none;
     3817    background: #2563eb;
     3818    color: #fff;
     3819    border-radius: 8px;
     3820    padding: 10px 14px;
     3821    font-size: 13px;
     3822    font-weight: 600;
     3823    cursor: pointer;
     3824    white-space: nowrap;
     3825}
     3826
     3827.ods-support-send:disabled {
     3828    opacity: 0.55;
     3829    cursor: not-allowed;
     3830}
     3831
     3832.ods-support-footer {
     3833    padding: 8px 14px 12px;
     3834    border-top: 1px solid var(--ali-border, #e2e8f0);
     3835    font-size: 11px;
     3836    color: #64748b;
     3837    background: #f8fafc;
     3838}
     3839
     3840@media (max-width: 782px) {
     3841    .ods-support-assistant {
     3842        right: 16px;
     3843        bottom: 16px;
     3844    }
     3845}
  • ods-ournest-dropship/tags/1.3.0/includes/ajax/ajax-mapping.php

    r3757662 r3765411  
    827827
    828828// ──────────────────────────────────────────
    829 // Save default logistics for a mapped variation
    830 // ──────────────────────────────────────────
    831 add_action(
    832         'wp_ajax_ournest_dropship_save_mapping_logistics',
    833         function (): void {
    834                 Settings::verify_request();
    835 
    836                 $product_id   = ournest_dropship_post_int( 'product_id', 0 );
    837                 $variation_id = ournest_dropship_post_int( 'variation_id', 0 );
    838                 $service      = ournest_dropship_post_text( 'logistics_service', '' );
    839                 $company      = ournest_dropship_post_text( 'logistics_company', '' );
    840                 $ae_product_id = ournest_dropship_post_text( 'ae_product_id', '' );
    841                 $ae_sku_id     = ournest_dropship_post_text( 'ae_sku_id', '' );
    842                 $ae_sku_title  = ournest_dropship_post_text( 'ae_sku_title', '' );
    843                 $ae_sku_img    = esc_url_raw( ournest_dropship_post_unslash_string( 'ae_sku_img' ) );
    844                 $ae_sku_path   = ournest_dropship_post_text( 'ae_sku_path', '' );
    845 
    846                 if ( ! $variation_id ) {
    847                         wp_send_json_error( array( 'message' => __( 'Variation ID is required.', 'ods-ournest-dropship' ) ) );
    848                 }
    849                 if ( ! $product_id ) {
    850                         wp_send_json_error( array( 'message' => __( 'Product ID is required.', 'ods-ournest-dropship' ) ) );
    851                 }
    852 
    853                 $product = wc_get_product( $product_id );
    854                 if ( ! $product ) {
    855                         wp_send_json_error( array( 'message' => __( 'Product not found.', 'ods-ournest-dropship' ) ) );
    856                 }
    857                 if ( $product->is_type( 'variable' ) ) {
    858                         $children = array_map( 'intval', $product->get_children() );
    859                         if ( ! in_array( $variation_id, $children, true ) ) {
    860                                 wp_send_json_error( array( 'message' => __( 'Variation does not belong to this product.', 'ods-ournest-dropship' ) ) );
    861                         }
    862                 } elseif ( $variation_id !== $product_id ) {
    863                         wp_send_json_error( array( 'message' => __( 'Variation does not belong to this product.', 'ods-ournest-dropship' ) ) );
    864                 }
    865 
    866                 $existing = Product_Mapper::get_variation_mapping( $variation_id );
    867                 if ( ! $existing ) {
    868                         if ( $ae_product_id === '' || $ae_sku_id === '' ) {
    869                                 wp_send_json_error( array( 'message' => __( 'Map an AliExpress SKU before saving logistics.', 'ods-ournest-dropship' ) ) );
    870                         }
    871                         Product_Mapper::map_variation(
    872                                 $product_id,
    873                                 $variation_id,
    874                                 $ae_product_id,
    875                                 $ae_sku_id,
    876                                 $ae_sku_title,
    877                                 $ae_sku_img,
    878                                 $ae_sku_path
    879                         );
    880                         Product_Mapper::auto_maintain_suppliers( $product_id );
    881                 } elseif ( $ae_sku_id !== '' && (string) ( $existing['ae_sku_id'] ?? '' ) !== $ae_sku_id ) {
    882                         // Draft SKU changed before Save Mapping — sync then clear stale logistics via map.
    883                         Product_Mapper::map_variation(
    884                                 $product_id,
    885                                 $variation_id,
    886                                 $ae_product_id !== '' ? $ae_product_id : (string) ( $existing['ae_product_id'] ?? '' ),
    887                                 $ae_sku_id,
    888                                 $ae_sku_title !== '' ? $ae_sku_title : (string) ( $existing['ae_sku_title'] ?? '' ),
    889                                 $ae_sku_img !== '' ? $ae_sku_img : (string) ( $existing['ae_sku_img'] ?? '' ),
    890                                 $ae_sku_path !== '' ? $ae_sku_path : (string) ( $existing['ae_sku_path'] ?? '' )
    891                         );
    892                 }
    893 
    894                 $result = Product_Mapper::save_mapping_logistics( $variation_id, $service, $company );
    895                 if ( is_wp_error( $result ) ) {
    896                         wp_send_json_error( array( 'message' => $result->get_error_message() ) );
    897                 }
    898 
    899                 wp_send_json_success(
    900                         array(
    901                                 'message'           => __( 'Logistics saved.', 'ods-ournest-dropship' ),
    902                                 'logistics_service' => $service,
    903                                 'logistics_company' => $company,
    904                         )
    905                 );
    906         }
    907 );
    908 
    909 // ──────────────────────────────────────────
    910829// Add supplier to a WC product
    911830// ──────────────────────────────────────────
  • ods-ournest-dropship/tags/1.3.0/includes/class-i18n.php

    r3757662 r3765411  
    352352                        'completedProgress'       => __( '%1$d / %2$d completed', 'ods-ournest-dropship' ),
    353353                        'dismissFailed'           => __( 'Failed to dismiss', 'ods-ournest-dropship' ),
     354
     355                        // Support assistant
     356                        'supportOpen'                => __( 'Help', 'ods-ournest-dropship' ),
     357                        'supportClose'               => __( 'Close', 'ods-ournest-dropship' ),
     358                        'supportTitle'               => __( 'ODS Assistant', 'ods-ournest-dropship' ),
     359                        'supportSubtitle'            => __( 'Operator help from the ODS manual', 'ods-ournest-dropship' ),
     360                        'supportPlaceholder'         => __( 'Ask how to connect, map, or place orders…', 'ods-ournest-dropship' ),
     361                        'supportSend'                => __( 'Send', 'ods-ournest-dropship' ),
     362                        'supportHuman'               => __( 'Need a human?', 'ods-ournest-dropship' ),
     363                        'supportThinking'            => __( 'Looking up the manual…', 'ods-ournest-dropship' ),
     364                        'supportEmptyAnswer'         => __( 'No answer returned.', 'ods-ournest-dropship' ),
     365                        'supportError'               => __( 'Something went wrong. Please try again.', 'ods-ournest-dropship' ),
     366                        'supportCitations'           => __( 'From the manual:', 'ods-ournest-dropship' ),
     367                        'supportFallbackHint'        => __( 'If this does not help, email', 'ods-ournest-dropship' ),
     368                        /* translators: %s: HTML link labeled "operator manual". */
     369                        'supportWelcome'             => __( 'Ask an ODS how-to question. Answers follow the %s.', 'ods-ournest-dropship' ),
     370                        'supportManualLink'          => __( 'operator manual', 'ods-ournest-dropship' ),
     371                        'supportWelcomeDisconnected' => __( 'You can ask how to connect AliExpress even before authorization. Connect for fulfillment details.', 'ods-ournest-dropship' ),
     372                        'supportQuickConnect'        => __( 'How do I connect AliExpress?', 'ods-ournest-dropship' ),
     373                        'supportQuickMapping'        => __( 'How do I map product SKUs?', 'ods-ournest-dropship' ),
     374                        'supportQuickPlace'          => __( 'Place order failed — what should I check?', 'ods-ournest-dropship' ),
    354375                );
    355376        }
  • ods-ournest-dropship/tags/1.3.0/includes/class-order-handler.php

    r3760832 r3765411  
    4949                $list['counts'] = self::compute_tab_counts( $search_ids, $buckets );
    5050                return $list;
    51         }
    52 
    53         /**
    54          * @return array{orders: array, total: int, pages: int}
    55          */
    56         public static function get_wc_orders( string $tab = 'paid', string $keyword = '', int $page = 1, int $per_page = 20 ): array {
    57                 $page     = max( 1, $page );
    58                 $per_page = max( 1, min( 100, $per_page ) );
    59 
    60                 $search_ids = null;
    61                 if ( $keyword !== '' ) {
    62                         $search_ids = array_map( 'absint', wc_order_search( $keyword ) );
    63                         if ( empty( $search_ids ) ) {
    64                                 return array(
    65                                         'orders' => array(),
    66                                         'total'  => 0,
    67                                         'pages'  => 0,
    68                                 );
    69                         }
    70                 }
    71 
    72                 return self::query_orders_for_tab( $tab, $search_ids, $page, $per_page, self::place_order_display_buckets() );
    73         }
    74 
    75         /**
    76          * Order counts per display-status tab (same filter rules as get_wc_orders).
    77          *
    78          * @return array<string,int>
    79          */
    80         public static function get_tab_counts( string $keyword = '' ): array {
    81                 $search_ids = null;
    82                 if ( $keyword !== '' ) {
    83                         $search_ids = array_map( 'absint', wc_order_search( $keyword ) );
    84                         if ( empty( $search_ids ) ) {
    85                                 return array_fill_keys(
    86                                         array( 'pending', 'paid', 'processing', 'shipping', 'fulfilled', 'cancelled', 'failed' ),
    87                                         0
    88                                 );
    89                         }
    90                 }
    91 
    92                 return self::compute_tab_counts( $search_ids, self::place_order_display_buckets() );
    9351        }
    9452
  • ods-ournest-dropship/tags/1.3.0/ournest-dropship.php

    r3760832 r3765411  
    1111 * Plugin URI:        https://anest.top/ods/
    1212 * Description:       AliExpress dropshipping for WooCommerce by OurNest (ODS). Browse, import, map SKUs, and fulfill orders from your WordPress store.
    13  * Version:           1.2.0
     13 * Version:           1.3.0
    1414 * Requires at least: 6.0
    1515 * Requires PHP:      8.0
    … …  
    3030// Constants
    3131// ──────────────────────────────────────────────
    32 define( 'OURNEST_DROPSHIP_VERSION', '1.2.0' );
     32define( 'OURNEST_DROPSHIP_VERSION', '1.3.0' );
    3333define( 'OURNEST_DROPSHIP_PATH', plugin_dir_path( __FILE__ ) );
    3434define( 'OURNEST_DROPSHIP_URL', plugin_dir_url( __FILE__ ) );
    … …  
    4747if ( ! defined( 'OURNEST_DROPSHIP_SERVER_URL' ) ) {
    4848        define( 'OURNEST_DROPSHIP_SERVER_URL', 'https://api.anest.top' );
     49}
     50// Shared with ae-server [support].service_key — PHP only, never localize to JS.
     51if ( ! defined( 'OURNEST_DROPSHIP_SUPPORT_KEY' ) ) {
     52        define( 'OURNEST_DROPSHIP_SUPPORT_KEY', 'd7c8cc25fc2d63f637a9371cf2c247fbd363795912cb7c50cb427f097608e798' );
    4953}
    5054
    … …  
    375379                                        ? \Ournest\Dropship\Settings::is_api_key_valid()
    376380                                        : false,
    377                                 'countries'     => $needs_countries && function_exists( 'ournest_dropship_countries' )
     381                                'current_page'   => $page,
     382                                'support_email'  => 'support@anest.top',
     383                                'docs_url'       => 'https://anest.top/ods/help',
     384                                'countries'      => $needs_countries && function_exists( 'ournest_dropship_countries' )
    378385                                        ? ournest_dropship_countries()
    379386                                        : array(),
    380387                                'i18n'          => class_exists( '\\Ournest\Dropship\\I18n' ) ? \Ournest\Dropship\I18n::js_strings() : array(),
    381388                        )
     389                );
     390                wp_enqueue_script(
     391                        'ournest-dropship-support-assistant',
     392                        OURNEST_DROPSHIP_URL . 'assets/js/support-assistant.js',
     393                        array( $common_handle ),
     394                        ournest_dropship_asset_ver( 'assets/js/support-assistant.js' ),
     395                        true
    382396                );
    383397
    … …  
    490504        require_once $ournest_dropship_onboarding_ajax;
    491505}
     506
     507$ournest_dropship_support_ajax = OURNEST_DROPSHIP_PATH . 'includes/ajax/ajax-support.php';
     508if ( file_exists( $ournest_dropship_support_ajax ) ) {
     509        require_once $ournest_dropship_support_ajax;
     510}
  • ods-ournest-dropship/tags/1.3.0/readme.txt

    r3760832 r3765411  
    99WC requires at least: 8.0
    1010WC tested up to: 9.8
    11 Stable tag: 1.2.0
     11Stable tag: 1.3.0
    1212License: GPLv2 or later
    1313License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    3838* Apply optional USD exchange rates in pricing rules
    3939* Optional daily AliExpress stock sync with per-product exclusion
     40* Floating smart assistant for operator how-to questions (manual-based)
    4041
    4142= How It Works =
    … …  
    140141
    141142== Changelog ==
     143
     144= 1.3.0 =
     145
     146* Smart assistant: floating help on all ODS admin pages for operator how-to questions.
     147* Smart assistant: quick prompts with staggered show/hide; answers cite the operator manual.
     148* Smart assistant: “operator manual” links to https://anest.top/ods/help .
     149* Smart assistant: works before AliExpress is connected (how-to connect still available).
     150* Removed unused order-list helpers and an unused mapping-logistics AJAX endpoint.
    142151
    143152= 1.2.0 =
    … …  
    246255== Upgrade Notice ==
    247256
     257= 1.3.0 =
     258Adds a floating smart assistant for ODS operator how-to questions, with manual citations and docs link. Recommended update for all stores.
     259
    248260= 1.2.0 =
    249261Improves My Orders compatibility with WooCommerce 11, links Order # to Woo edit, and shows AE SKU image/title in place-order and AE drawers. Recommended update for all stores.
  • ods-ournest-dropship/trunk/assets/style.css

    r3760832 r3765411  
    35463546    color: #6b7280;
    35473547}
     3548
     3549/* ──────────────────────────────────────────
     3550   Support assistant (floating help)
     3551   ────────────────────────────────────────── */
     3552.ods-support-assistant {
     3553    position: fixed;
     3554    right: 24px;
     3555    bottom: 24px;
     3556    z-index: 100000;
     3557    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
     3558}
     3559
     3560.ods-support-fab {
     3561    width: 52px;
     3562    height: 52px;
     3563    border-radius: 999px;
     3564    border: none;
     3565    background: var(--ali-primary, #2563eb);
     3566    color: #fff;
     3567    box-shadow: var(--ali-shadow-lg);
     3568    cursor: pointer;
     3569    display: flex;
     3570    align-items: center;
     3571    justify-content: center;
     3572    transition: transform 0.15s ease, background 0.15s ease;
     3573}
     3574
     3575.ods-support-fab:hover {
     3576    background: var(--ali-primary-dark, #1d4ed8);
     3577    transform: translateY(-1px);
     3578}
     3579
     3580.ods-support-fab-icon {
     3581    font-size: 22px;
     3582    font-weight: 700;
     3583    line-height: 1;
     3584}
     3585
     3586.ods-support-panel {
     3587    display: none;
     3588    position: absolute;
     3589    right: 0;
     3590    bottom: 64px;
     3591    width: min(380px, calc(100vw - 32px));
     3592    max-height: min(560px, calc(100vh - 120px));
     3593    background: #fff;
     3594    border: 1px solid var(--ali-border, #e2e8f0);
     3595    border-radius: 12px;
     3596    box-shadow: var(--ali-shadow-lg);
     3597    flex-direction: column;
     3598    overflow: hidden;
     3599}
     3600
     3601.ods-support-assistant.is-open .ods-support-panel {
     3602    display: flex;
     3603}
     3604
     3605.ods-support-assistant.is-open .ods-support-fab {
     3606    display: none;
     3607}
     3608
     3609.ods-support-body {
     3610    position: relative;
     3611    flex: 1 1 auto;
     3612    min-height: 220px;
     3613    display: flex;
     3614    flex-direction: column;
     3615    overflow: hidden;
     3616}
     3617
     3618.ods-support-header {
     3619    display: flex;
     3620    align-items: flex-start;
     3621    gap: 8px;
     3622    padding: 14px 14px 10px;
     3623    border-bottom: 1px solid var(--ali-border, #e2e8f0);
     3624    background: #f8fafc;
     3625}
     3626
     3627.ods-support-header-text {
     3628    flex: 1;
     3629    min-width: 0;
     3630    display: flex;
     3631    flex-direction: column;
     3632    gap: 2px;
     3633}
     3634
     3635.ods-support-header-text strong {
     3636    font-size: 14px;
     3637    color: #0f172a;
     3638}
     3639
     3640.ods-support-header-text span {
     3641    font-size: 12px;
     3642    color: var(--ali-text-muted, #64748b);
     3643}
     3644
     3645.ods-support-close {
     3646    border: none;
     3647    background: transparent;
     3648    color: #64748b;
     3649    font-size: 22px;
     3650    line-height: 1;
     3651    cursor: pointer;
     3652    padding: 0 4px;
     3653}
     3654
     3655.ods-support-messages {
     3656    flex: 1 1 auto;
     3657    overflow-y: auto;
     3658    padding: 12px 14px;
     3659    display: flex;
     3660    flex-direction: column;
     3661    gap: 10px;
     3662    background: #fff;
     3663}
     3664
     3665.ods-support-msg {
     3666    display: flex;
     3667    flex-direction: column;
     3668    gap: 4px;
     3669    max-width: 95%;
     3670}
     3671
     3672.ods-support-msg--user {
     3673    align-self: flex-end;
     3674}
     3675
     3676.ods-support-msg--assistant {
     3677    align-self: flex-start;
     3678}
     3679
     3680.ods-support-bubble {
     3681    padding: 10px 12px;
     3682    border-radius: 10px;
     3683    font-size: 13px;
     3684    line-height: 1.45;
     3685    color: #1e293b;
     3686    background: #f1f5f9;
     3687    white-space: normal;
     3688    word-break: break-word;
     3689}
     3690
     3691.ods-support-msg--user .ods-support-bubble {
     3692    background: #2563eb;
     3693    color: #fff;
     3694}
     3695
     3696.ods-support-bubble--thinking {
     3697    color: #64748b;
     3698    font-style: italic;
     3699}
     3700
     3701.ods-support-meta {
     3702    font-size: 11px;
     3703    color: #64748b;
     3704    padding: 0 2px;
     3705}
     3706
     3707.ods-support-cites {
     3708    margin-top: 2px;
     3709}
     3710
     3711.ods-support-cite {
     3712    display: inline-block;
     3713    background: #eff6ff;
     3714    color: #1d4ed8;
     3715    border-radius: 4px;
     3716    padding: 1px 6px;
     3717    margin: 2px 2px 0 0;
     3718}
     3719
     3720.ods-support-fallback a,
     3721.ods-support-footer a,
     3722.ods-support-docs-link {
     3723    color: #2563eb;
     3724}
     3725
     3726.ods-support-docs-link {
     3727    text-decoration: underline;
     3728    font-weight: 600;
     3729}
     3730
     3731.ods-support-error {
     3732    color: #b91c1c;
     3733}
     3734
     3735/* Quick prompts: float above messages inside the panel; stagger in/out from the left. */
     3736.ods-support-quick {
     3737    position: absolute;
     3738    left: 10px;
     3739    right: 10px;
     3740    bottom: 10px;
     3741    z-index: 2;
     3742    display: flex;
     3743    flex-direction: column;
     3744    align-items: flex-start;
     3745    gap: 8px;
     3746    pointer-events: none;
     3747}
     3748
     3749.ods-support-chip {
     3750    border: 1px solid #cbd5e1;
     3751    background: rgba(255, 255, 255, 0.96);
     3752    color: #334155;
     3753    border-radius: 999px;
     3754    padding: 7px 12px;
     3755    font-size: 11px;
     3756    cursor: pointer;
     3757    line-height: 1.35;
     3758    text-align: left;
     3759    box-shadow: var(--ali-shadow);
     3760    max-width: 92%;
     3761    white-space: normal;
     3762    opacity: 0;
     3763    transform: translateX(-120%);
     3764    pointer-events: none;
     3765    transition:
     3766        transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
     3767        opacity 0.28s ease;
     3768    transition-delay: 0ms;
     3769}
     3770
     3771.ods-support-quick.is-visible .ods-support-chip {
     3772    opacity: 1;
     3773    transform: translateX(0);
     3774    pointer-events: auto;
     3775    transition-delay: calc(var(--ods-quick-i, 0) * 70ms);
     3776}
     3777
     3778.ods-support-quick.is-hiding .ods-support-chip {
     3779    opacity: 0;
     3780    transform: translateX(-120%);
     3781    pointer-events: none;
     3782    transition-delay: calc((2 - var(--ods-quick-i, 0)) * 70ms);
     3783}
     3784
     3785.ods-support-chip:hover {
     3786    border-color: #2563eb;
     3787    color: #1d4ed8;
     3788}
     3789
     3790.ods-support-form {
     3791    display: flex;
     3792    gap: 8px;
     3793    padding: 0 14px 10px;
     3794    align-items: flex-end;
     3795}
     3796
     3797.ods-support-input {
     3798    flex: 1;
     3799    min-height: 44px;
     3800    max-height: 96px;
     3801    resize: vertical;
     3802    border: 1px solid #cbd5e1;
     3803    border-radius: 8px;
     3804    padding: 8px 10px;
     3805    font-size: 13px;
     3806    line-height: 1.4;
     3807}
     3808
     3809.ods-support-input:focus {
     3810    outline: none;
     3811    border-color: #2563eb;
     3812    box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15);
     3813}
     3814
     3815.ods-support-send {
     3816    border: none;
     3817    background: #2563eb;
     3818    color: #fff;
     3819    border-radius: 8px;
     3820    padding: 10px 14px;
     3821    font-size: 13px;
     3822    font-weight: 600;
     3823    cursor: pointer;
     3824    white-space: nowrap;
     3825}
     3826
     3827.ods-support-send:disabled {
     3828    opacity: 0.55;
     3829    cursor: not-allowed;
     3830}
     3831
     3832.ods-support-footer {
     3833    padding: 8px 14px 12px;
     3834    border-top: 1px solid var(--ali-border, #e2e8f0);
     3835    font-size: 11px;
     3836    color: #64748b;
     3837    background: #f8fafc;
     3838}
     3839
     3840@media (max-width: 782px) {
     3841    .ods-support-assistant {
     3842        right: 16px;
     3843        bottom: 16px;
     3844    }
     3845}
  • ods-ournest-dropship/trunk/includes/ajax/ajax-mapping.php

    r3757662 r3765411  
    827827
    828828// ──────────────────────────────────────────
    829 // Save default logistics for a mapped variation
    830 // ──────────────────────────────────────────
    831 add_action(
    832         'wp_ajax_ournest_dropship_save_mapping_logistics',
    833         function (): void {
    834                 Settings::verify_request();
    835 
    836                 $product_id   = ournest_dropship_post_int( 'product_id', 0 );
    837                 $variation_id = ournest_dropship_post_int( 'variation_id', 0 );
    838                 $service      = ournest_dropship_post_text( 'logistics_service', '' );
    839                 $company      = ournest_dropship_post_text( 'logistics_company', '' );
    840                 $ae_product_id = ournest_dropship_post_text( 'ae_product_id', '' );
    841                 $ae_sku_id     = ournest_dropship_post_text( 'ae_sku_id', '' );
    842                 $ae_sku_title  = ournest_dropship_post_text( 'ae_sku_title', '' );
    843                 $ae_sku_img    = esc_url_raw( ournest_dropship_post_unslash_string( 'ae_sku_img' ) );
    844                 $ae_sku_path   = ournest_dropship_post_text( 'ae_sku_path', '' );
    845 
    846                 if ( ! $variation_id ) {
    847                         wp_send_json_error( array( 'message' => __( 'Variation ID is required.', 'ods-ournest-dropship' ) ) );
    848                 }
    849                 if ( ! $product_id ) {
    850                         wp_send_json_error( array( 'message' => __( 'Product ID is required.', 'ods-ournest-dropship' ) ) );
    851                 }
    852 
    853                 $product = wc_get_product( $product_id );
    854                 if ( ! $product ) {
    855                         wp_send_json_error( array( 'message' => __( 'Product not found.', 'ods-ournest-dropship' ) ) );
    856                 }
    857                 if ( $product->is_type( 'variable' ) ) {
    858                         $children = array_map( 'intval', $product->get_children() );
    859                         if ( ! in_array( $variation_id, $children, true ) ) {
    860                                 wp_send_json_error( array( 'message' => __( 'Variation does not belong to this product.', 'ods-ournest-dropship' ) ) );
    861                         }
    862                 } elseif ( $variation_id !== $product_id ) {
    863                         wp_send_json_error( array( 'message' => __( 'Variation does not belong to this product.', 'ods-ournest-dropship' ) ) );
    864                 }
    865 
    866                 $existing = Product_Mapper::get_variation_mapping( $variation_id );
    867                 if ( ! $existing ) {
    868                         if ( $ae_product_id === '' || $ae_sku_id === '' ) {
    869                                 wp_send_json_error( array( 'message' => __( 'Map an AliExpress SKU before saving logistics.', 'ods-ournest-dropship' ) ) );
    870                         }
    871                         Product_Mapper::map_variation(
    872                                 $product_id,
    873                                 $variation_id,
    874                                 $ae_product_id,
    875                                 $ae_sku_id,
    876                                 $ae_sku_title,
    877                                 $ae_sku_img,
    878                                 $ae_sku_path
    879                         );
    880                         Product_Mapper::auto_maintain_suppliers( $product_id );
    881                 } elseif ( $ae_sku_id !== '' && (string) ( $existing['ae_sku_id'] ?? '' ) !== $ae_sku_id ) {
    882                         // Draft SKU changed before Save Mapping — sync then clear stale logistics via map.
    883                         Product_Mapper::map_variation(
    884                                 $product_id,
    885                                 $variation_id,
    886                                 $ae_product_id !== '' ? $ae_product_id : (string) ( $existing['ae_product_id'] ?? '' ),
    887                                 $ae_sku_id,
    888                                 $ae_sku_title !== '' ? $ae_sku_title : (string) ( $existing['ae_sku_title'] ?? '' ),
    889                                 $ae_sku_img !== '' ? $ae_sku_img : (string) ( $existing['ae_sku_img'] ?? '' ),
    890                                 $ae_sku_path !== '' ? $ae_sku_path : (string) ( $existing['ae_sku_path'] ?? '' )
    891                         );
    892                 }
    893 
    894                 $result = Product_Mapper::save_mapping_logistics( $variation_id, $service, $company );
    895                 if ( is_wp_error( $result ) ) {
    896                         wp_send_json_error( array( 'message' => $result->get_error_message() ) );
    897                 }
    898 
    899                 wp_send_json_success(
    900                         array(
    901                                 'message'           => __( 'Logistics saved.', 'ods-ournest-dropship' ),
    902                                 'logistics_service' => $service,
    903                                 'logistics_company' => $company,
    904                         )
    905                 );
    906         }
    907 );
    908 
    909 // ──────────────────────────────────────────
    910829// Add supplier to a WC product
    911830// ──────────────────────────────────────────
  • ods-ournest-dropship/trunk/includes/class-i18n.php

    r3757662 r3765411  
    352352                        'completedProgress'       => __( '%1$d / %2$d completed', 'ods-ournest-dropship' ),
    353353                        'dismissFailed'           => __( 'Failed to dismiss', 'ods-ournest-dropship' ),
     354
     355                        // Support assistant
     356                        'supportOpen'                => __( 'Help', 'ods-ournest-dropship' ),
     357                        'supportClose'               => __( 'Close', 'ods-ournest-dropship' ),
     358                        'supportTitle'               => __( 'ODS Assistant', 'ods-ournest-dropship' ),
     359                        'supportSubtitle'            => __( 'Operator help from the ODS manual', 'ods-ournest-dropship' ),
     360                        'supportPlaceholder'         => __( 'Ask how to connect, map, or place orders…', 'ods-ournest-dropship' ),
     361                        'supportSend'                => __( 'Send', 'ods-ournest-dropship' ),
     362                        'supportHuman'               => __( 'Need a human?', 'ods-ournest-dropship' ),
     363                        'supportThinking'            => __( 'Looking up the manual…', 'ods-ournest-dropship' ),
     364                        'supportEmptyAnswer'         => __( 'No answer returned.', 'ods-ournest-dropship' ),
     365                        'supportError'               => __( 'Something went wrong. Please try again.', 'ods-ournest-dropship' ),
     366                        'supportCitations'           => __( 'From the manual:', 'ods-ournest-dropship' ),
     367                        'supportFallbackHint'        => __( 'If this does not help, email', 'ods-ournest-dropship' ),
     368                        /* translators: %s: HTML link labeled "operator manual". */
     369                        'supportWelcome'             => __( 'Ask an ODS how-to question. Answers follow the %s.', 'ods-ournest-dropship' ),
     370                        'supportManualLink'          => __( 'operator manual', 'ods-ournest-dropship' ),
     371                        'supportWelcomeDisconnected' => __( 'You can ask how to connect AliExpress even before authorization. Connect for fulfillment details.', 'ods-ournest-dropship' ),
     372                        'supportQuickConnect'        => __( 'How do I connect AliExpress?', 'ods-ournest-dropship' ),
     373                        'supportQuickMapping'        => __( 'How do I map product SKUs?', 'ods-ournest-dropship' ),
     374                        'supportQuickPlace'          => __( 'Place order failed — what should I check?', 'ods-ournest-dropship' ),
    354375                );
    355376        }
  • ods-ournest-dropship/trunk/includes/class-order-handler.php

    r3760832 r3765411  
    4949                $list['counts'] = self::compute_tab_counts( $search_ids, $buckets );
    5050                return $list;
    51         }
    52 
    53         /**
    54          * @return array{orders: array, total: int, pages: int}
    55          */
    56         public static function get_wc_orders( string $tab = 'paid', string $keyword = '', int $page = 1, int $per_page = 20 ): array {
    57                 $page     = max( 1, $page );
    58                 $per_page = max( 1, min( 100, $per_page ) );
    59 
    60                 $search_ids = null;
    61                 if ( $keyword !== '' ) {
    62                         $search_ids = array_map( 'absint', wc_order_search( $keyword ) );
    63                         if ( empty( $search_ids ) ) {
    64                                 return array(
    65                                         'orders' => array(),
    66                                         'total'  => 0,
    67                                         'pages'  => 0,
    68                                 );
    69                         }
    70                 }
    71 
    72                 return self::query_orders_for_tab( $tab, $search_ids, $page, $per_page, self::place_order_display_buckets() );
    73         }
    74 
    75         /**
    76          * Order counts per display-status tab (same filter rules as get_wc_orders).
    77          *
    78          * @return array<string,int>
    79          */
    80         public static function get_tab_counts( string $keyword = '' ): array {
    81                 $search_ids = null;
    82                 if ( $keyword !== '' ) {
    83                         $search_ids = array_map( 'absint', wc_order_search( $keyword ) );
    84                         if ( empty( $search_ids ) ) {
    85                                 return array_fill_keys(
    86                                         array( 'pending', 'paid', 'processing', 'shipping', 'fulfilled', 'cancelled', 'failed' ),
    87                                         0
    88                                 );
    89                         }
    90                 }
    91 
    92                 return self::compute_tab_counts( $search_ids, self::place_order_display_buckets() );
    9351        }
    9452
  • ods-ournest-dropship/trunk/ournest-dropship.php

    r3760832 r3765411  
    1111 * Plugin URI:        https://anest.top/ods/
    1212 * Description:       AliExpress dropshipping for WooCommerce by OurNest (ODS). Browse, import, map SKUs, and fulfill orders from your WordPress store.
    13  * Version:           1.2.0
     13 * Version:           1.3.0
    1414 * Requires at least: 6.0
    1515 * Requires PHP:      8.0
    … …  
    3030// Constants
    3131// ──────────────────────────────────────────────
    32 define( 'OURNEST_DROPSHIP_VERSION', '1.2.0' );
     32define( 'OURNEST_DROPSHIP_VERSION', '1.3.0' );
    3333define( 'OURNEST_DROPSHIP_PATH', plugin_dir_path( __FILE__ ) );
    3434define( 'OURNEST_DROPSHIP_URL', plugin_dir_url( __FILE__ ) );
    … …  
    4747if ( ! defined( 'OURNEST_DROPSHIP_SERVER_URL' ) ) {
    4848        define( 'OURNEST_DROPSHIP_SERVER_URL', 'https://api.anest.top' );
     49}
     50// Shared with ae-server [support].service_key — PHP only, never localize to JS.
     51if ( ! defined( 'OURNEST_DROPSHIP_SUPPORT_KEY' ) ) {
     52        define( 'OURNEST_DROPSHIP_SUPPORT_KEY', 'd7c8cc25fc2d63f637a9371cf2c247fbd363795912cb7c50cb427f097608e798' );
    4953}
    5054
    … …  
    375379                                        ? \Ournest\Dropship\Settings::is_api_key_valid()
    376380                                        : false,
    377                                 'countries'     => $needs_countries && function_exists( 'ournest_dropship_countries' )
     381                                'current_page'   => $page,
     382                                'support_email'  => 'support@anest.top',
     383                                'docs_url'       => 'https://anest.top/ods/help',
     384                                'countries'      => $needs_countries && function_exists( 'ournest_dropship_countries' )
    378385                                        ? ournest_dropship_countries()
    379386                                        : array(),
    380387                                'i18n'          => class_exists( '\\Ournest\Dropship\\I18n' ) ? \Ournest\Dropship\I18n::js_strings() : array(),
    381388                        )
     389                );
     390                wp_enqueue_script(
     391                        'ournest-dropship-support-assistant',
     392                        OURNEST_DROPSHIP_URL . 'assets/js/support-assistant.js',
     393                        array( $common_handle ),
     394                        ournest_dropship_asset_ver( 'assets/js/support-assistant.js' ),
     395                        true
    382396                );
    383397
    … …  
    490504        require_once $ournest_dropship_onboarding_ajax;
    491505}
     506
     507$ournest_dropship_support_ajax = OURNEST_DROPSHIP_PATH . 'includes/ajax/ajax-support.php';
     508if ( file_exists( $ournest_dropship_support_ajax ) ) {
     509        require_once $ournest_dropship_support_ajax;
     510}
  • ods-ournest-dropship/trunk/readme.txt

    r3760832 r3765411  
    99WC requires at least: 8.0
    1010WC tested up to: 9.8
    11 Stable tag: 1.2.0
     11Stable tag: 1.3.0
    1212License: GPLv2 or later
    1313License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    3838* Apply optional USD exchange rates in pricing rules
    3939* Optional daily AliExpress stock sync with per-product exclusion
     40* Floating smart assistant for operator how-to questions (manual-based)
    4041
    4142= How It Works =
    … …  
    140141
    141142== Changelog ==
     143
     144= 1.3.0 =
     145
     146* Smart assistant: floating help on all ODS admin pages for operator how-to questions.
     147* Smart assistant: quick prompts with staggered show/hide; answers cite the operator manual.
     148* Smart assistant: “operator manual” links to https://anest.top/ods/help .
     149* Smart assistant: works before AliExpress is connected (how-to connect still available).
     150* Removed unused order-list helpers and an unused mapping-logistics AJAX endpoint.
    142151
    143152= 1.2.0 =
    … …  
    246255== Upgrade Notice ==
    247256
     257= 1.3.0 =
     258Adds a floating smart assistant for ODS operator how-to questions, with manual citations and docs link. Recommended update for all stores.
     259
    248260= 1.2.0 =
    249261Improves My Orders compatibility with WooCommerce 11, links Order # to Woo edit, and shows AE SKU image/title in place-order and AE drawers. Recommended update for all stores.
Note: See TracChangeset for help on using the changeset viewer.