code: def vocab_phrase_selected_callback(attr, old_selected, new_selected):
      logger.info("vocab selected")
      if grouping:
          # show group info
          if len(new_selected) == 1:
              res = send_request_to_server(
                  ["get_group", new_selected[0]]
              )
              if res is not None:
                  group_info_box.text = str(res)
      global clear_flag
      if not clear_flag:
          global all_selected_phrases, search_flag
          if search_flag:
              return
          logger.info(
              "selected_vocab was updated: old= %s, new= %s",
              str(old_selected),
              str(new_selected),
          )
          # sync expand table:
          # phrase was de-selected from vocab list:
          expand_selected = [
              expand_table_source.data["res"][p]
              for p in expand_table_source.selected.indices
          ]
          for o in old_selected:
              full_o = cut_vocab_dict[o]
              if o not in new_selected and full_o in expand_selected:
                  logger.info(
                      "%s removed from vocab selected and exists in expand selected",
                      full_o,
                  )
                  logger.info(
                      "removing %s from expand selected indices. index=%s",
                      full_o,
                      str(
                          expand_table_source.data["res"].index(full_o)
                      ),
                  )
                  logger.info(
                      "current expand indices: %s",
                      str(expand_table_source.selected.indices),
                  )
                  expand_table_source.selected.indices.remove(
                      expand_table_source.data["res"].index(full_o)
                  )
                  logger.info(
                      "new expand indices: %s",
                      str(expand_table_source.selected.indices),
                  )
                  break
          # new phrase was selected from vocab list:
          for n in new_selected:
              full_n = cut_vocab_dict[n]
              logger.info(
                  "selected phrase=" + n + ", full phrase=" + full_n
              )
              if (
                  n not in old_selected
                  and full_n in expand_table_source.data["res"]
                  and full_n not in expand_selected
              ):
                  expand_table_source.selected.indices.append(
                      expand_table_source.data["res"].index(full_n)
                  )
                  break
          update_all_selected_phrases()
          seed_input_box.value = get_selected_phrases_for_seed()
  
imports: import socket
  import pickle
  import logging
  import re
  from os.path import dirname, join
  from bokeh.layouts import column, layout
  from bokeh.models import ColumnDataSource, Div, Row, CustomJS
  from bokeh.models.widgets import Button, DataTable, TableColumn, CheckboxGroup, MultiSelect
  from bokeh.models.widgets.inputs import TextInput
  from bokeh.io import curdoc
  from settings import grouping, expand_host, expand_port
label_desc: Use image node_id 8 for calling a global function with example usage: vocab_phrase_selected_callback(attr, old_selected, new_selected) without return types